2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161 Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165 /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176 char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178 int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
221 extern void ConsoleCreate();
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
239 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
249 /* States for ics_getting_history */
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
257 /* whosays values for GameEnds */
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
269 /* Different types of move when calling RegisterMove */
271 #define CMAIL_RESIGN 1
273 #define CMAIL_ACCEPT 3
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
280 /* Telnet protocol constants */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
293 assert( dst != NULL );
294 assert( src != NULL );
297 strncpy( dst, src, count );
298 dst[ count-1 ] = '\0';
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
308 assert( dst != NULL );
309 assert( src != NULL );
312 dst_len = strlen(dst);
314 assert( count > dst_len ); /* Buffer size must be greater than current length */
316 safeStrCpy( dst + dst_len, src, count - dst_len );
322 /* Some compiler can't cast u64 to double
323 * This function do the job for us:
325 * We use the highest bit for cast, this only
326 * works if the highest bit is not
327 * in use (This should not happen)
329 * We used this for all compiler
332 u64ToDouble(u64 value)
335 u64 tmp = value & u64Const(0x7fffffffffffffff);
336 r = (double)(s64)tmp;
337 if (value & u64Const(0x8000000000000000))
338 r += 9.2233720368547758080e18; /* 2^63 */
342 /* Fake up flags for now, as we aren't keeping track of castling
343 availability yet. [HGM] Change of logic: the flag now only
344 indicates the type of castlings allowed by the rule of the game.
345 The actual rights themselves are maintained in the array
346 castlingRights, as part of the game history, and are not probed
352 int flags = F_ALL_CASTLE_OK;
353 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354 switch (gameInfo.variant) {
356 flags &= ~F_ALL_CASTLE_OK;
357 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358 flags |= F_IGNORE_CHECK;
360 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
363 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
365 case VariantKriegspiel:
366 flags |= F_KRIEGSPIEL_CAPTURE;
368 case VariantCapaRandom:
369 case VariantFischeRandom:
370 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371 case VariantNoCastle:
372 case VariantShatranj:
374 flags &= ~F_ALL_CASTLE_OK;
382 FILE *gameFileFP, *debugFP;
385 [AS] Note: sometimes, the sscanf() function is used to parse the input
386 into a fixed-size buffer. Because of this, we must be prepared to
387 receive strings as long as the size of the input buffer, which is currently
388 set to 4K for Windows and 8K for the rest.
389 So, we must either allocate sufficiently large buffers here, or
390 reduce the size of the input buffer in the input reading part.
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
397 ChessProgramState first, second;
399 /* premove variables */
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
449 /* animateTraining preserves the state of appData.animate
450 * when Training mode is activated. This allows the
451 * response to be animated when appData.animate == TRUE and
452 * appData.animateDragging == TRUE.
454 Boolean animateTraining;
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char epStatus[MAX_MOVES];
463 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int initialRulePlies, FENrulePlies;
469 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
473 ChessSquare FIDEArray[2][BOARD_SIZE] = {
474 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
475 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
476 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
477 BlackKing, BlackBishop, BlackKnight, BlackRook }
480 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
481 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
483 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484 BlackKing, BlackKing, BlackKnight, BlackRook }
487 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
488 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
489 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
490 { BlackRook, BlackMan, BlackBishop, BlackQueen,
491 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
494 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
495 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
496 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
497 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
498 BlackKing, BlackBishop, BlackKnight, BlackRook }
501 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
502 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
503 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
505 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
510 ChessSquare ShogiArray[2][BOARD_SIZE] = {
511 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
512 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
513 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
514 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
517 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
518 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
519 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
521 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
524 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
525 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
526 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
527 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
528 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
531 ChessSquare GreatArray[2][BOARD_SIZE] = {
532 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
533 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
534 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
535 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
538 ChessSquare JanusArray[2][BOARD_SIZE] = {
539 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
540 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
541 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
542 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
546 ChessSquare GothicArray[2][BOARD_SIZE] = {
547 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
548 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
549 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
550 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
553 #define GothicArray CapablancaArray
557 ChessSquare FalconArray[2][BOARD_SIZE] = {
558 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
559 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
560 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
561 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
564 #define FalconArray CapablancaArray
567 #else // !(BOARD_SIZE>=10)
568 #define XiangqiPosition FIDEArray
569 #define CapablancaArray FIDEArray
570 #define GothicArray FIDEArray
571 #define GreatArray FIDEArray
572 #endif // !(BOARD_SIZE>=10)
575 ChessSquare CourierArray[2][BOARD_SIZE] = {
576 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
577 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
579 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
581 #else // !(BOARD_SIZE>=12)
582 #define CourierArray CapablancaArray
583 #endif // !(BOARD_SIZE>=12)
586 Board initialPosition;
589 /* Convert str to a rating. Checks for special cases of "----",
591 "++++", etc. Also strips ()'s */
593 string_to_rating(str)
596 while(*str && !isdigit(*str)) ++str;
598 return 0; /* One of the special "no rating" cases */
606 /* Init programStats */
607 programStats.movelist[0] = 0;
608 programStats.depth = 0;
609 programStats.nr_moves = 0;
610 programStats.moves_left = 0;
611 programStats.nodes = 0;
612 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
613 programStats.score = 0;
614 programStats.got_only_move = 0;
615 programStats.got_fail = 0;
616 programStats.line_is_book = 0;
622 int matched, min, sec;
624 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
626 GetTimeMark(&programStartTime);
627 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
630 programStats.ok_to_send = 1;
631 programStats.seen_stat = 0;
634 * Initialize game list
640 * Internet chess server status
642 if (appData.icsActive) {
643 appData.matchMode = FALSE;
644 appData.matchGames = 0;
646 appData.noChessProgram = !appData.zippyPlay;
648 appData.zippyPlay = FALSE;
649 appData.zippyTalk = FALSE;
650 appData.noChessProgram = TRUE;
652 if (*appData.icsHelper != NULLCHAR) {
653 appData.useTelnet = TRUE;
654 appData.telnetProgram = appData.icsHelper;
657 appData.zippyTalk = appData.zippyPlay = FALSE;
660 /* [AS] Initialize pv info list [HGM] and game state */
664 for( i=0; i<MAX_MOVES; i++ ) {
665 pvInfoList[i].depth = -1;
667 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
672 * Parse timeControl resource
674 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
675 appData.movesPerSession)) {
677 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
678 DisplayFatalError(buf, 0, 2);
682 * Parse searchTime resource
684 if (*appData.searchTime != NULLCHAR) {
685 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
687 searchTime = min * 60;
688 } else if (matched == 2) {
689 searchTime = min * 60 + sec;
692 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
693 DisplayFatalError(buf, 0, 2);
697 /* [AS] Adjudication threshold */
698 adjudicateLossThreshold = appData.adjudicateLossThreshold;
700 first.which = "first";
701 second.which = "second";
702 first.maybeThinking = second.maybeThinking = FALSE;
703 first.pr = second.pr = NoProc;
704 first.isr = second.isr = NULL;
705 first.sendTime = second.sendTime = 2;
706 first.sendDrawOffers = 1;
707 if (appData.firstPlaysBlack) {
708 first.twoMachinesColor = "black\n";
709 second.twoMachinesColor = "white\n";
711 first.twoMachinesColor = "white\n";
712 second.twoMachinesColor = "black\n";
714 first.program = appData.firstChessProgram;
715 second.program = appData.secondChessProgram;
716 first.host = appData.firstHost;
717 second.host = appData.secondHost;
718 first.dir = appData.firstDirectory;
719 second.dir = appData.secondDirectory;
720 first.other = &second;
721 second.other = &first;
722 first.initString = appData.initString;
723 second.initString = appData.secondInitString;
724 first.computerString = appData.firstComputerString;
725 second.computerString = appData.secondComputerString;
726 first.useSigint = second.useSigint = TRUE;
727 first.useSigterm = second.useSigterm = TRUE;
728 first.reuse = appData.reuseFirst;
729 second.reuse = appData.reuseSecond;
730 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
731 second.nps = appData.secondNPS;
732 first.useSetboard = second.useSetboard = FALSE;
733 first.useSAN = second.useSAN = FALSE;
734 first.usePing = second.usePing = FALSE;
735 first.lastPing = second.lastPing = 0;
736 first.lastPong = second.lastPong = 0;
737 first.usePlayother = second.usePlayother = FALSE;
738 first.useColors = second.useColors = TRUE;
739 first.useUsermove = second.useUsermove = FALSE;
740 first.sendICS = second.sendICS = FALSE;
741 first.sendName = second.sendName = appData.icsActive;
742 first.sdKludge = second.sdKludge = FALSE;
743 first.stKludge = second.stKludge = FALSE;
744 TidyProgramName(first.program, first.host, first.tidy);
745 TidyProgramName(second.program, second.host, second.tidy);
746 first.matchWins = second.matchWins = 0;
747 strcpy(first.variants, appData.variant);
748 strcpy(second.variants, appData.variant);
749 first.analysisSupport = second.analysisSupport = 2; /* detect */
750 first.analyzing = second.analyzing = FALSE;
751 first.initDone = second.initDone = FALSE;
753 /* New features added by Tord: */
754 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
755 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
756 /* End of new features added by Tord. */
757 first.fenOverride = appData.fenOverride1;
758 second.fenOverride = appData.fenOverride2;
760 /* [HGM] time odds: set factor for each machine */
761 first.timeOdds = appData.firstTimeOdds;
762 second.timeOdds = appData.secondTimeOdds;
764 if(appData.timeOddsMode) {
765 norm = first.timeOdds;
766 if(norm > second.timeOdds) norm = second.timeOdds;
768 first.timeOdds /= norm;
769 second.timeOdds /= norm;
772 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
773 first.accumulateTC = appData.firstAccumulateTC;
774 second.accumulateTC = appData.secondAccumulateTC;
775 first.maxNrOfSessions = second.maxNrOfSessions = 1;
778 first.debug = second.debug = FALSE;
779 first.supportsNPS = second.supportsNPS = UNKNOWN;
782 first.optionSettings = appData.firstOptions;
783 second.optionSettings = appData.secondOptions;
785 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
786 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
787 first.isUCI = appData.firstIsUCI; /* [AS] */
788 second.isUCI = appData.secondIsUCI; /* [AS] */
789 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
790 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
792 if (appData.firstProtocolVersion > PROTOVER ||
793 appData.firstProtocolVersion < 1) {
795 sprintf(buf, _("protocol version %d not supported"),
796 appData.firstProtocolVersion);
797 DisplayFatalError(buf, 0, 2);
799 first.protocolVersion = appData.firstProtocolVersion;
802 if (appData.secondProtocolVersion > PROTOVER ||
803 appData.secondProtocolVersion < 1) {
805 sprintf(buf, _("protocol version %d not supported"),
806 appData.secondProtocolVersion);
807 DisplayFatalError(buf, 0, 2);
809 second.protocolVersion = appData.secondProtocolVersion;
812 if (appData.icsActive) {
813 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
814 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
815 appData.clockMode = FALSE;
816 first.sendTime = second.sendTime = 0;
820 /* Override some settings from environment variables, for backward
821 compatibility. Unfortunately it's not feasible to have the env
822 vars just set defaults, at least in xboard. Ugh.
824 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
829 if (appData.noChessProgram) {
830 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
831 sprintf(programVersion, "%s", PACKAGE_STRING);
836 while (*q != ' ' && *q != NULLCHAR) q++;
838 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
839 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
840 sprintf(programVersion, "%s + ", PACKAGE_STRING);
841 strncat(programVersion, p, q - p);
843 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
844 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
845 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
849 if (!appData.icsActive) {
851 /* Check for variants that are supported only in ICS mode,
852 or not at all. Some that are accepted here nevertheless
853 have bugs; see comments below.
855 VariantClass variant = StringToVariant(appData.variant);
857 case VariantBughouse: /* need four players and two boards */
858 case VariantKriegspiel: /* need to hide pieces and move details */
859 /* case VariantFischeRandom: (Fabien: moved below) */
860 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861 DisplayFatalError(buf, 0, 2);
865 case VariantLoadable:
875 sprintf(buf, _("Unknown variant name %s"), appData.variant);
876 DisplayFatalError(buf, 0, 2);
879 case VariantXiangqi: /* [HGM] repetition rules not implemented */
880 case VariantFairy: /* [HGM] TestLegality definitely off! */
881 case VariantGothic: /* [HGM] should work */
882 case VariantCapablanca: /* [HGM] should work */
883 case VariantCourier: /* [HGM] initial forced moves not implemented */
884 case VariantShogi: /* [HGM] drops not tested for legality */
885 case VariantKnightmate: /* [HGM] should work */
886 case VariantCylinder: /* [HGM] untested */
887 case VariantFalcon: /* [HGM] untested */
888 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889 offboard interposition not understood */
890 case VariantNormal: /* definitely works! */
891 case VariantWildCastle: /* pieces not automatically shuffled */
892 case VariantNoCastle: /* pieces not automatically shuffled */
893 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894 case VariantLosers: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantSuicide: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantGiveaway: /* should work except for win condition,
899 and doesn't know captures are mandatory */
900 case VariantTwoKings: /* should work */
901 case VariantAtomic: /* should work except for win condition */
902 case Variant3Check: /* should work except for win condition */
903 case VariantShatranj: /* should work except for all win conditions */
904 case VariantBerolina: /* might work if TestLegality is off */
905 case VariantCapaRandom: /* should work */
906 case VariantJanus: /* should work */
907 case VariantSuper: /* experimental */
908 case VariantGreat: /* experimental, requires legality testing to be off */
913 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
914 InitEngineUCI( installDir, &second );
917 int NextIntegerFromString( char ** str, long * value )
922 while( *s == ' ' || *s == '\t' ) {
928 if( *s >= '0' && *s <= '9' ) {
929 while( *s >= '0' && *s <= '9' ) {
930 *value = *value * 10 + (*s - '0');
942 int NextTimeControlFromString( char ** str, long * value )
945 int result = NextIntegerFromString( str, &temp );
948 *value = temp * 60; /* Minutes */
951 result = NextIntegerFromString( str, &temp );
952 *value += temp; /* Seconds */
959 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
960 { /* [HGM] routine added to read '+moves/time' for secondary time control */
961 int result = -1; long temp, temp2;
963 if(**str != '+') return -1; // old params remain in force!
965 if( NextTimeControlFromString( str, &temp ) ) return -1;
968 /* time only: incremental or sudden-death time control */
969 if(**str == '+') { /* increment follows; read it */
971 if(result = NextIntegerFromString( str, &temp2)) return -1;
974 *moves = 0; *tc = temp * 1000;
976 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
978 (*str)++; /* classical time control */
979 result = NextTimeControlFromString( str, &temp2);
988 int GetTimeQuota(int movenr)
989 { /* [HGM] get time to add from the multi-session time-control string */
990 int moves=1; /* kludge to force reading of first session */
991 long time, increment;
992 char *s = fullTimeControlString;
994 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
997 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
998 if(movenr == -1) return time; /* last move before new session */
999 if(!moves) return increment; /* current session is incremental */
1000 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1001 } while(movenr >= -1); /* try again for next session */
1003 return 0; // no new time quota on this move
1007 ParseTimeControl(tc, ti, mps)
1013 int matched, min, sec;
1015 matched = sscanf(tc, "%d:%d", &min, &sec);
1017 timeControl = min * 60 * 1000;
1018 } else if (matched == 2) {
1019 timeControl = (min * 60 + sec) * 1000;
1028 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1031 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1032 else sprintf(buf, "+%s+%d", tc, ti);
1035 sprintf(buf, "+%d/%s", mps, tc);
1036 else sprintf(buf, "+%s", tc);
1038 fullTimeControlString = StrSave(buf);
1040 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1045 /* Parse second time control */
1048 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1056 timeControl_2 = tc2 * 1000;
1066 timeControl = tc1 * 1000;
1070 timeIncrement = ti * 1000; /* convert to ms */
1071 movesPerSession = 0;
1074 movesPerSession = mps;
1082 if (appData.debugMode) {
1083 fprintf(debugFP, "%s\n", programVersion);
1086 if (appData.matchGames > 0) {
1087 appData.matchMode = TRUE;
1088 } else if (appData.matchMode) {
1089 appData.matchGames = 1;
1091 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1092 appData.matchGames = appData.sameColorGames;
1093 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1094 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1095 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1098 if (appData.noChessProgram || first.protocolVersion == 1) {
1101 /* kludge: allow timeout for initial "feature" commands */
1103 DisplayMessage("", _("Starting chess program"));
1104 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1109 InitBackEnd3 P((void))
1111 GameMode initialMode;
1115 InitChessProgram(&first, startedFromSetupPosition);
1118 if (appData.icsActive) {
1120 /* [DM] Make a console window if needed [HGM] merged ifs */
1125 if (*appData.icsCommPort != NULLCHAR) {
1126 sprintf(buf, _("Could not open comm port %s"),
1127 appData.icsCommPort);
1129 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1130 appData.icsHost, appData.icsPort);
1132 DisplayFatalError(buf, err, 1);
1137 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1139 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1140 } else if (appData.noChessProgram) {
1146 if (*appData.cmailGameName != NULLCHAR) {
1148 OpenLoopback(&cmailPR);
1150 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1154 DisplayMessage("", "");
1155 if (StrCaseCmp(appData.initialMode, "") == 0) {
1156 initialMode = BeginningOfGame;
1157 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1158 initialMode = TwoMachinesPlay;
1159 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1160 initialMode = AnalyzeFile;
1161 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1162 initialMode = AnalyzeMode;
1163 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1164 initialMode = MachinePlaysWhite;
1165 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1166 initialMode = MachinePlaysBlack;
1167 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1168 initialMode = EditGame;
1169 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1170 initialMode = EditPosition;
1171 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1172 initialMode = Training;
1174 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1175 DisplayFatalError(buf, 0, 2);
1179 if (appData.matchMode) {
1180 /* Set up machine vs. machine match */
1181 if (appData.noChessProgram) {
1182 DisplayFatalError(_("Can't have a match with no chess programs"),
1188 if (*appData.loadGameFile != NULLCHAR) {
1189 int index = appData.loadGameIndex; // [HGM] autoinc
1190 if(index<0) lastIndex = index = 1;
1191 if (!LoadGameFromFile(appData.loadGameFile,
1193 appData.loadGameFile, FALSE)) {
1194 DisplayFatalError(_("Bad game file"), 0, 1);
1197 } else if (*appData.loadPositionFile != NULLCHAR) {
1198 int index = appData.loadPositionIndex; // [HGM] autoinc
1199 if(index<0) lastIndex = index = 1;
1200 if (!LoadPositionFromFile(appData.loadPositionFile,
1202 appData.loadPositionFile)) {
1203 DisplayFatalError(_("Bad position file"), 0, 1);
1208 } else if (*appData.cmailGameName != NULLCHAR) {
1209 /* Set up cmail mode */
1210 ReloadCmailMsgEvent(TRUE);
1212 /* Set up other modes */
1213 if (initialMode == AnalyzeFile) {
1214 if (*appData.loadGameFile == NULLCHAR) {
1215 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1219 if (*appData.loadGameFile != NULLCHAR) {
1220 (void) LoadGameFromFile(appData.loadGameFile,
1221 appData.loadGameIndex,
1222 appData.loadGameFile, TRUE);
1223 } else if (*appData.loadPositionFile != NULLCHAR) {
1224 (void) LoadPositionFromFile(appData.loadPositionFile,
1225 appData.loadPositionIndex,
1226 appData.loadPositionFile);
1227 /* [HGM] try to make self-starting even after FEN load */
1228 /* to allow automatic setup of fairy variants with wtm */
1229 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1230 gameMode = BeginningOfGame;
1231 setboardSpoiledMachineBlack = 1;
1233 /* [HGM] loadPos: make that every new game uses the setup */
1234 /* from file as long as we do not switch variant */
1235 if(!blackPlaysFirst) { int i;
1236 startedFromPositionFile = TRUE;
1237 CopyBoard(filePosition, boards[0]);
1238 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1241 if (initialMode == AnalyzeMode) {
1242 if (appData.noChessProgram) {
1243 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1251 } else if (initialMode == AnalyzeFile) {
1252 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1253 ShowThinkingEvent();
1255 AnalysisPeriodicEvent(1);
1256 } else if (initialMode == MachinePlaysWhite) {
1257 if (appData.noChessProgram) {
1258 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1262 if (appData.icsActive) {
1263 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1267 MachineWhiteEvent();
1268 } else if (initialMode == MachinePlaysBlack) {
1269 if (appData.noChessProgram) {
1270 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1274 if (appData.icsActive) {
1275 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1279 MachineBlackEvent();
1280 } else if (initialMode == TwoMachinesPlay) {
1281 if (appData.noChessProgram) {
1282 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1286 if (appData.icsActive) {
1287 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1292 } else if (initialMode == EditGame) {
1294 } else if (initialMode == EditPosition) {
1295 EditPositionEvent();
1296 } else if (initialMode == Training) {
1297 if (*appData.loadGameFile == NULLCHAR) {
1298 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1307 * Establish will establish a contact to a remote host.port.
1308 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1309 * used to talk to the host.
1310 * Returns 0 if okay, error code if not.
1317 if (*appData.icsCommPort != NULLCHAR) {
1318 /* Talk to the host through a serial comm port */
1319 return OpenCommPort(appData.icsCommPort, &icsPR);
1321 } else if (*appData.gateway != NULLCHAR) {
1322 if (*appData.remoteShell == NULLCHAR) {
1323 /* Use the rcmd protocol to run telnet program on a gateway host */
1324 snprintf(buf, sizeof(buf), "%s %s %s",
1325 appData.telnetProgram, appData.icsHost, appData.icsPort);
1326 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1329 /* Use the rsh program to run telnet program on a gateway host */
1330 if (*appData.remoteUser == NULLCHAR) {
1331 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1332 appData.gateway, appData.telnetProgram,
1333 appData.icsHost, appData.icsPort);
1335 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1336 appData.remoteShell, appData.gateway,
1337 appData.remoteUser, appData.telnetProgram,
1338 appData.icsHost, appData.icsPort);
1340 return StartChildProcess(buf, "", &icsPR);
1343 } else if (appData.useTelnet) {
1344 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1347 /* TCP socket interface differs somewhat between
1348 Unix and NT; handle details in the front end.
1350 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1355 show_bytes(fp, buf, count)
1361 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1362 fprintf(fp, "\\%03o", *buf & 0xff);
1371 /* Returns an errno value */
1373 OutputMaybeTelnet(pr, message, count, outError)
1379 char buf[8192], *p, *q, *buflim;
1380 int left, newcount, outcount;
1382 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1383 *appData.gateway != NULLCHAR) {
1384 if (appData.debugMode) {
1385 fprintf(debugFP, ">ICS: ");
1386 show_bytes(debugFP, message, count);
1387 fprintf(debugFP, "\n");
1389 return OutputToProcess(pr, message, count, outError);
1392 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1399 if (appData.debugMode) {
1400 fprintf(debugFP, ">ICS: ");
1401 show_bytes(debugFP, buf, newcount);
1402 fprintf(debugFP, "\n");
1404 outcount = OutputToProcess(pr, buf, newcount, outError);
1405 if (outcount < newcount) return -1; /* to be sure */
1412 } else if (((unsigned char) *p) == TN_IAC) {
1413 *q++ = (char) TN_IAC;
1420 if (appData.debugMode) {
1421 fprintf(debugFP, ">ICS: ");
1422 show_bytes(debugFP, buf, newcount);
1423 fprintf(debugFP, "\n");
1425 outcount = OutputToProcess(pr, buf, newcount, outError);
1426 if (outcount < newcount) return -1; /* to be sure */
1431 read_from_player(isr, closure, message, count, error)
1438 int outError, outCount;
1439 static int gotEof = 0;
1441 /* Pass data read from player on to ICS */
1444 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1445 if (outCount < count) {
1446 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448 } else if (count < 0) {
1449 RemoveInputSource(isr);
1450 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1451 } else if (gotEof++ > 0) {
1452 RemoveInputSource(isr);
1453 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1459 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1460 SendToICS("date\n");
1461 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1468 int count, outCount, outError;
1470 if (icsPR == NULL) return;
1473 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1474 if (outCount < count) {
1475 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479 /* This is used for sending logon scripts to the ICS. Sending
1480 without a delay causes problems when using timestamp on ICC
1481 (at least on my machine). */
1483 SendToICSDelayed(s,msdelay)
1487 int count, outCount, outError;
1489 if (icsPR == NULL) return;
1492 if (appData.debugMode) {
1493 fprintf(debugFP, ">ICS: ");
1494 show_bytes(debugFP, s, count);
1495 fprintf(debugFP, "\n");
1497 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1499 if (outCount < count) {
1500 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1505 /* Remove all highlighting escape sequences in s
1506 Also deletes any suffix starting with '('
1509 StripHighlightAndTitle(s)
1512 static char retbuf[MSG_SIZ];
1515 while (*s != NULLCHAR) {
1516 while (*s == '\033') {
1517 while (*s != NULLCHAR && !isalpha(*s)) s++;
1518 if (*s != NULLCHAR) s++;
1520 while (*s != NULLCHAR && *s != '\033') {
1521 if (*s == '(' || *s == '[') {
1532 /* Remove all highlighting escape sequences in s */
1537 static char retbuf[MSG_SIZ];
1540 while (*s != NULLCHAR) {
1541 while (*s == '\033') {
1542 while (*s != NULLCHAR && !isalpha(*s)) s++;
1543 if (*s != NULLCHAR) s++;
1545 while (*s != NULLCHAR && *s != '\033') {
1553 char *variantNames[] = VARIANT_NAMES;
1558 return variantNames[v];
1562 /* Identify a variant from the strings the chess servers use or the
1563 PGN Variant tag names we use. */
1570 VariantClass v = VariantNormal;
1571 int i, found = FALSE;
1576 /* [HGM] skip over optional board-size prefixes */
1577 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1578 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1579 while( *e++ != '_');
1582 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1583 if (StrCaseStr(e, variantNames[i])) {
1584 v = (VariantClass) i;
1591 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1592 || StrCaseStr(e, "wild/fr")
1593 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1594 v = VariantFischeRandom;
1595 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1596 (i = 1, p = StrCaseStr(e, "w"))) {
1598 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1605 case 0: /* FICS only, actually */
1607 /* Castling legal even if K starts on d-file */
1608 v = VariantWildCastle;
1613 /* Castling illegal even if K & R happen to start in
1614 normal positions. */
1615 v = VariantNoCastle;
1628 /* Castling legal iff K & R start in normal positions */
1634 /* Special wilds for position setup; unclear what to do here */
1635 v = VariantLoadable;
1638 /* Bizarre ICC game */
1639 v = VariantTwoKings;
1642 v = VariantKriegspiel;
1648 v = VariantFischeRandom;
1651 v = VariantCrazyhouse;
1654 v = VariantBughouse;
1660 /* Not quite the same as FICS suicide! */
1661 v = VariantGiveaway;
1667 v = VariantShatranj;
1670 /* Temporary names for future ICC types. The name *will* change in
1671 the next xboard/WinBoard release after ICC defines it. */
1709 v = VariantCapablanca;
1712 v = VariantKnightmate;
1718 v = VariantCylinder;
1724 v = VariantCapaRandom;
1727 v = VariantBerolina;
1739 /* Found "wild" or "w" in the string but no number;
1740 must assume it's normal chess. */
1744 sprintf(buf, _("Unknown wild type %d"), wnum);
1745 DisplayError(buf, 0);
1751 if (appData.debugMode) {
1752 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1753 e, wnum, VariantName(v));
1758 static int leftover_start = 0, leftover_len = 0;
1759 char star_match[STAR_MATCH_N][MSG_SIZ];
1761 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1762 advance *index beyond it, and set leftover_start to the new value of
1763 *index; else return FALSE. If pattern contains the character '*', it
1764 matches any sequence of characters not containing '\r', '\n', or the
1765 character following the '*' (if any), and the matched sequence(s) are
1766 copied into star_match.
1769 looking_at(buf, index, pattern)
1774 char *bufp = &buf[*index], *patternp = pattern;
1776 char *matchp = star_match[0];
1779 if (*patternp == NULLCHAR) {
1780 *index = leftover_start = bufp - buf;
1784 if (*bufp == NULLCHAR) return FALSE;
1785 if (*patternp == '*') {
1786 if (*bufp == *(patternp + 1)) {
1788 matchp = star_match[++star_count];
1792 } else if (*bufp == '\n' || *bufp == '\r') {
1794 if (*patternp == NULLCHAR)
1799 *matchp++ = *bufp++;
1803 if (*patternp != *bufp) return FALSE;
1810 SendToPlayer(data, length)
1814 int error, outCount;
1815 outCount = OutputToProcess(NoProc, data, length, &error);
1816 if (outCount < length) {
1817 DisplayFatalError(_("Error writing to display"), error, 1);
1822 PackHolding(packed, holding)
1834 switch (runlength) {
1845 sprintf(q, "%d", runlength);
1857 /* Telnet protocol requests from the front end */
1859 TelnetRequest(ddww, option)
1860 unsigned char ddww, option;
1862 unsigned char msg[3];
1863 int outCount, outError;
1865 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1867 if (appData.debugMode) {
1868 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1884 sprintf(buf1, "%d", ddww);
1893 sprintf(buf2, "%d", option);
1896 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1901 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1903 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1910 if (!appData.icsActive) return;
1911 TelnetRequest(TN_DO, TN_ECHO);
1917 if (!appData.icsActive) return;
1918 TelnetRequest(TN_DONT, TN_ECHO);
1922 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1924 /* put the holdings sent to us by the server on the board holdings area */
1925 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1929 if(gameInfo.holdingsWidth < 2) return;
1931 if( (int)lowestPiece >= BlackPawn ) {
1934 holdingsStartRow = BOARD_HEIGHT-1;
1937 holdingsColumn = BOARD_WIDTH-1;
1938 countsColumn = BOARD_WIDTH-2;
1939 holdingsStartRow = 0;
1943 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1944 board[i][holdingsColumn] = EmptySquare;
1945 board[i][countsColumn] = (ChessSquare) 0;
1947 while( (p=*holdings++) != NULLCHAR ) {
1948 piece = CharToPiece( ToUpper(p) );
1949 if(piece == EmptySquare) continue;
1950 /*j = (int) piece - (int) WhitePawn;*/
1951 j = PieceToNumber(piece);
1952 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1953 if(j < 0) continue; /* should not happen */
1954 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1955 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1956 board[holdingsStartRow+j*direction][countsColumn]++;
1963 VariantSwitch(Board board, VariantClass newVariant)
1965 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1966 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1967 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1969 startedFromPositionFile = FALSE;
1970 if(gameInfo.variant == newVariant) return;
1972 /* [HGM] This routine is called each time an assignment is made to
1973 * gameInfo.variant during a game, to make sure the board sizes
1974 * are set to match the new variant. If that means adding or deleting
1975 * holdings, we shift the playing board accordingly
1976 * This kludge is needed because in ICS observe mode, we get boards
1977 * of an ongoing game without knowing the variant, and learn about the
1978 * latter only later. This can be because of the move list we requested,
1979 * in which case the game history is refilled from the beginning anyway,
1980 * but also when receiving holdings of a crazyhouse game. In the latter
1981 * case we want to add those holdings to the already received position.
1985 if (appData.debugMode) {
1986 fprintf(debugFP, "Switch board from %s to %s\n",
1987 VariantName(gameInfo.variant), VariantName(newVariant));
1988 setbuf(debugFP, NULL);
1990 shuffleOpenings = 0; /* [HGM] shuffle */
1991 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1992 switch(newVariant) {
1994 newWidth = 9; newHeight = 9;
1995 gameInfo.holdingsSize = 7;
1996 case VariantBughouse:
1997 case VariantCrazyhouse:
1998 newHoldingsWidth = 2; break;
2000 newHoldingsWidth = gameInfo.holdingsSize = 0;
2003 if(newWidth != gameInfo.boardWidth ||
2004 newHeight != gameInfo.boardHeight ||
2005 newHoldingsWidth != gameInfo.holdingsWidth ) {
2007 /* shift position to new playing area, if needed */
2008 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2009 for(i=0; i<BOARD_HEIGHT; i++)
2010 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013 for(i=0; i<newHeight; i++) {
2014 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2015 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2018 for(i=0; i<BOARD_HEIGHT; i++)
2019 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2020 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2024 gameInfo.boardWidth = newWidth;
2025 gameInfo.boardHeight = newHeight;
2026 gameInfo.holdingsWidth = newHoldingsWidth;
2027 gameInfo.variant = newVariant;
2028 InitDrawingSizes(-2, 0);
2030 /* [HGM] The following should definitely be solved in a better way */
2032 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2033 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2034 saveEP = epStatus[0];
2036 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2038 epStatus[0] = saveEP;
2039 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2040 CopyBoard(tempBoard, board); /* restore position received from ICS */
2042 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2044 forwardMostMove = oldForwardMostMove;
2045 backwardMostMove = oldBackwardMostMove;
2046 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2049 static int loggedOn = FALSE;
2051 /*-- Game start info cache: --*/
2053 char gs_kind[MSG_SIZ];
2054 static char player1Name[128] = "";
2055 static char player2Name[128] = "";
2056 static int player1Rating = -1;
2057 static int player2Rating = -1;
2058 /*----------------------------*/
2060 ColorClass curColor = ColorNormal;
2061 int suppressKibitz = 0;
2064 read_from_ics(isr, closure, data, count, error)
2071 #define BUF_SIZE 8192
2072 #define STARTED_NONE 0
2073 #define STARTED_MOVES 1
2074 #define STARTED_BOARD 2
2075 #define STARTED_OBSERVE 3
2076 #define STARTED_HOLDINGS 4
2077 #define STARTED_CHATTER 5
2078 #define STARTED_COMMENT 6
2079 #define STARTED_MOVES_NOHIDE 7
2081 static int started = STARTED_NONE;
2082 static char parse[20000];
2083 static int parse_pos = 0;
2084 static char buf[BUF_SIZE + 1];
2085 static int firstTime = TRUE, intfSet = FALSE;
2086 static ColorClass prevColor = ColorNormal;
2087 static int savingComment = FALSE;
2093 int backup; /* [DM] For zippy color lines */
2095 char talker[MSG_SIZ]; // [HGM] chat
2097 if (appData.debugMode) {
2099 fprintf(debugFP, "<ICS: ");
2100 show_bytes(debugFP, data, count);
2101 fprintf(debugFP, "\n");
2105 if (appData.debugMode) { int f = forwardMostMove;
2106 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2107 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2110 /* If last read ended with a partial line that we couldn't parse,
2111 prepend it to the new read and try again. */
2112 if (leftover_len > 0) {
2113 for (i=0; i<leftover_len; i++)
2114 buf[i] = buf[leftover_start + i];
2117 /* Copy in new characters, removing nulls and \r's */
2118 buf_len = leftover_len;
2119 for (i = 0; i < count; i++) {
2120 if (data[i] != NULLCHAR && data[i] != '\r')
2121 buf[buf_len++] = data[i];
2122 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2123 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2124 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2125 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2129 buf[buf_len] = NULLCHAR;
2130 next_out = leftover_len;
2134 while (i < buf_len) {
2135 /* Deal with part of the TELNET option negotiation
2136 protocol. We refuse to do anything beyond the
2137 defaults, except that we allow the WILL ECHO option,
2138 which ICS uses to turn off password echoing when we are
2139 directly connected to it. We reject this option
2140 if localLineEditing mode is on (always on in xboard)
2141 and we are talking to port 23, which might be a real
2142 telnet server that will try to keep WILL ECHO on permanently.
2144 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2145 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2146 unsigned char option;
2148 switch ((unsigned char) buf[++i]) {
2150 if (appData.debugMode)
2151 fprintf(debugFP, "\n<WILL ");
2152 switch (option = (unsigned char) buf[++i]) {
2154 if (appData.debugMode)
2155 fprintf(debugFP, "ECHO ");
2156 /* Reply only if this is a change, according
2157 to the protocol rules. */
2158 if (remoteEchoOption) break;
2159 if (appData.localLineEditing &&
2160 atoi(appData.icsPort) == TN_PORT) {
2161 TelnetRequest(TN_DONT, TN_ECHO);
2164 TelnetRequest(TN_DO, TN_ECHO);
2165 remoteEchoOption = TRUE;
2169 if (appData.debugMode)
2170 fprintf(debugFP, "%d ", option);
2171 /* Whatever this is, we don't want it. */
2172 TelnetRequest(TN_DONT, option);
2177 if (appData.debugMode)
2178 fprintf(debugFP, "\n<WONT ");
2179 switch (option = (unsigned char) buf[++i]) {
2181 if (appData.debugMode)
2182 fprintf(debugFP, "ECHO ");
2183 /* Reply only if this is a change, according
2184 to the protocol rules. */
2185 if (!remoteEchoOption) break;
2187 TelnetRequest(TN_DONT, TN_ECHO);
2188 remoteEchoOption = FALSE;
2191 if (appData.debugMode)
2192 fprintf(debugFP, "%d ", (unsigned char) option);
2193 /* Whatever this is, it must already be turned
2194 off, because we never agree to turn on
2195 anything non-default, so according to the
2196 protocol rules, we don't reply. */
2201 if (appData.debugMode)
2202 fprintf(debugFP, "\n<DO ");
2203 switch (option = (unsigned char) buf[++i]) {
2205 /* Whatever this is, we refuse to do it. */
2206 if (appData.debugMode)
2207 fprintf(debugFP, "%d ", option);
2208 TelnetRequest(TN_WONT, option);
2213 if (appData.debugMode)
2214 fprintf(debugFP, "\n<DONT ");
2215 switch (option = (unsigned char) buf[++i]) {
2217 if (appData.debugMode)
2218 fprintf(debugFP, "%d ", option);
2219 /* Whatever this is, we are already not doing
2220 it, because we never agree to do anything
2221 non-default, so according to the protocol
2222 rules, we don't reply. */
2227 if (appData.debugMode)
2228 fprintf(debugFP, "\n<IAC ");
2229 /* Doubled IAC; pass it through */
2233 if (appData.debugMode)
2234 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2235 /* Drop all other telnet commands on the floor */
2238 if (oldi > next_out)
2239 SendToPlayer(&buf[next_out], oldi - next_out);
2245 /* OK, this at least will *usually* work */
2246 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2250 if (loggedOn && !intfSet) {
2251 if (ics_type == ICS_ICC) {
2253 "/set-quietly interface %s\n/set-quietly style 12\n",
2256 } else if (ics_type == ICS_CHESSNET) {
2257 sprintf(str, "/style 12\n");
2259 strcpy(str, "alias $ @\n$set interface ");
2260 strcat(str, programVersion);
2261 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2263 strcat(str, "$iset nohighlight 1\n");
2265 strcat(str, "$iset lock 1\n$style 12\n");
2271 if (started == STARTED_COMMENT) {
2272 /* Accumulate characters in comment */
2273 parse[parse_pos++] = buf[i];
2274 if (buf[i] == '\n') {
2275 parse[parse_pos] = NULLCHAR;
2276 if(chattingPartner>=0) {
2278 sprintf(mess, "%s%s", talker, parse);
2279 OutputChatMessage(chattingPartner, mess);
2280 chattingPartner = -1;
2282 if(!suppressKibitz) // [HGM] kibitz
2283 AppendComment(forwardMostMove, StripHighlight(parse));
2284 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2285 int nrDigit = 0, nrAlph = 0, i;
2286 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2287 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2288 parse[parse_pos] = NULLCHAR;
2289 // try to be smart: if it does not look like search info, it should go to
2290 // ICS interaction window after all, not to engine-output window.
2291 for(i=0; i<parse_pos; i++) { // count letters and digits
2292 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2293 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2294 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2296 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2297 int depth=0; float score;
2298 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2299 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2300 pvInfoList[forwardMostMove-1].depth = depth;
2301 pvInfoList[forwardMostMove-1].score = 100*score;
2303 OutputKibitz(suppressKibitz, parse);
2306 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2307 SendToPlayer(tmp, strlen(tmp));
2310 started = STARTED_NONE;
2312 /* Don't match patterns against characters in chatter */
2317 if (started == STARTED_CHATTER) {
2318 if (buf[i] != '\n') {
2319 /* Don't match patterns against characters in chatter */
2323 started = STARTED_NONE;
2326 /* Kludge to deal with rcmd protocol */
2327 if (firstTime && looking_at(buf, &i, "\001*")) {
2328 DisplayFatalError(&buf[1], 0, 1);
2334 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2337 if (appData.debugMode)
2338 fprintf(debugFP, "ics_type %d\n", ics_type);
2341 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2342 ics_type = ICS_FICS;
2344 if (appData.debugMode)
2345 fprintf(debugFP, "ics_type %d\n", ics_type);
2348 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2349 ics_type = ICS_CHESSNET;
2351 if (appData.debugMode)
2352 fprintf(debugFP, "ics_type %d\n", ics_type);
2357 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2358 looking_at(buf, &i, "Logging you in as \"*\"") ||
2359 looking_at(buf, &i, "will be \"*\""))) {
2360 strcpy(ics_handle, star_match[0]);
2364 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2366 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2367 DisplayIcsInteractionTitle(buf);
2368 have_set_title = TRUE;
2371 /* skip finger notes */
2372 if (started == STARTED_NONE &&
2373 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2374 (buf[i] == '1' && buf[i+1] == '0')) &&
2375 buf[i+2] == ':' && buf[i+3] == ' ') {
2376 started = STARTED_CHATTER;
2381 /* skip formula vars */
2382 if (started == STARTED_NONE &&
2383 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2384 started = STARTED_CHATTER;
2390 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2391 if (appData.autoKibitz && started == STARTED_NONE &&
2392 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2393 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2394 if(looking_at(buf, &i, "* kibitzes: ") &&
2395 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2396 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2397 suppressKibitz = TRUE;
2398 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2399 && (gameMode == IcsPlayingWhite)) ||
2400 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2401 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2402 started = STARTED_CHATTER; // own kibitz we simply discard
2404 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2405 parse_pos = 0; parse[0] = NULLCHAR;
2406 savingComment = TRUE;
2407 suppressKibitz = gameMode != IcsObserving ? 2 :
2408 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2412 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2413 started = STARTED_CHATTER;
2414 suppressKibitz = TRUE;
2416 } // [HGM] kibitz: end of patch
2418 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2420 // [HGM] chat: intercept tells by users for which we have an open chat window
2421 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2422 looking_at(buf, &i, "* whispers:"))) {
2424 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2425 chattingPartner = -1;
2426 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2427 for(p=0; p<MAX_CHAT; p++) if(!strcmp("WHISPER", chatPartner[p])) {
2428 talker[0] = '['; strcat(talker, "]");
2429 chattingPartner = p; break;
2431 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2432 for(p=0; p<MAX_CHAT; p++) if(!strcasecmp(talker+1, chatPartner[p])) {
2434 chattingPartner = p; break;
2436 if(chattingPartner<0) i = oldi; else {
2437 started = STARTED_COMMENT;
2438 parse_pos = 0; parse[0] = NULLCHAR;
2439 savingComment = TRUE;
2440 suppressKibitz = TRUE;
2442 } // [HGM] chat: end of patch
2444 if (appData.zippyTalk || appData.zippyPlay) {
2445 /* [DM] Backup address for color zippy lines */
2449 if (loggedOn == TRUE)
2450 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2451 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2453 if (ZippyControl(buf, &i) ||
2454 ZippyConverse(buf, &i) ||
2455 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2457 if (!appData.colorize) continue;
2461 } // [DM] 'else { ' deleted
2463 /* Regular tells and says */
2464 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2465 looking_at(buf, &i, "* (your partner) tells you: ") ||
2466 looking_at(buf, &i, "* says: ") ||
2467 /* Don't color "message" or "messages" output */
2468 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2469 looking_at(buf, &i, "*. * at *:*: ") ||
2470 looking_at(buf, &i, "--* (*:*): ") ||
2471 /* Message notifications (same color as tells) */
2472 looking_at(buf, &i, "* has left a message ") ||
2473 looking_at(buf, &i, "* just sent you a message:\n") ||
2474 /* Whispers and kibitzes */
2475 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2476 looking_at(buf, &i, "* kibitzes: ") ||
2478 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2480 if (tkind == 1 && strchr(star_match[0], ':')) {
2481 /* Avoid "tells you:" spoofs in channels */
2484 if (star_match[0][0] == NULLCHAR ||
2485 strchr(star_match[0], ' ') ||
2486 (tkind == 3 && strchr(star_match[1], ' '))) {
2487 /* Reject bogus matches */
2490 if (appData.colorize) {
2491 if (oldi > next_out) {
2492 SendToPlayer(&buf[next_out], oldi - next_out);
2497 Colorize(ColorTell, FALSE);
2498 curColor = ColorTell;
2501 Colorize(ColorKibitz, FALSE);
2502 curColor = ColorKibitz;
2505 p = strrchr(star_match[1], '(');
2512 Colorize(ColorChannel1, FALSE);
2513 curColor = ColorChannel1;
2515 Colorize(ColorChannel, FALSE);
2516 curColor = ColorChannel;
2520 curColor = ColorNormal;
2524 if (started == STARTED_NONE && appData.autoComment &&
2525 (gameMode == IcsObserving ||
2526 gameMode == IcsPlayingWhite ||
2527 gameMode == IcsPlayingBlack)) {
2528 parse_pos = i - oldi;
2529 memcpy(parse, &buf[oldi], parse_pos);
2530 parse[parse_pos] = NULLCHAR;
2531 started = STARTED_COMMENT;
2532 savingComment = TRUE;
2534 started = STARTED_CHATTER;
2535 savingComment = FALSE;
2542 if (looking_at(buf, &i, "* s-shouts: ") ||
2543 looking_at(buf, &i, "* c-shouts: ")) {
2544 if (appData.colorize) {
2545 if (oldi > next_out) {
2546 SendToPlayer(&buf[next_out], oldi - next_out);
2549 Colorize(ColorSShout, FALSE);
2550 curColor = ColorSShout;
2553 started = STARTED_CHATTER;
2557 if (looking_at(buf, &i, "--->")) {
2562 if (looking_at(buf, &i, "* shouts: ") ||
2563 looking_at(buf, &i, "--> ")) {
2564 if (appData.colorize) {
2565 if (oldi > next_out) {
2566 SendToPlayer(&buf[next_out], oldi - next_out);
2569 Colorize(ColorShout, FALSE);
2570 curColor = ColorShout;
2573 started = STARTED_CHATTER;
2577 if (looking_at( buf, &i, "Challenge:")) {
2578 if (appData.colorize) {
2579 if (oldi > next_out) {
2580 SendToPlayer(&buf[next_out], oldi - next_out);
2583 Colorize(ColorChallenge, FALSE);
2584 curColor = ColorChallenge;
2590 if (looking_at(buf, &i, "* offers you") ||
2591 looking_at(buf, &i, "* offers to be") ||
2592 looking_at(buf, &i, "* would like to") ||
2593 looking_at(buf, &i, "* requests to") ||
2594 looking_at(buf, &i, "Your opponent offers") ||
2595 looking_at(buf, &i, "Your opponent requests")) {
2597 if (appData.colorize) {
2598 if (oldi > next_out) {
2599 SendToPlayer(&buf[next_out], oldi - next_out);
2602 Colorize(ColorRequest, FALSE);
2603 curColor = ColorRequest;
2608 if (looking_at(buf, &i, "* (*) seeking")) {
2609 if (appData.colorize) {
2610 if (oldi > next_out) {
2611 SendToPlayer(&buf[next_out], oldi - next_out);
2614 Colorize(ColorSeek, FALSE);
2615 curColor = ColorSeek;
2620 if (looking_at(buf, &i, "\\ ")) {
2621 if (prevColor != ColorNormal) {
2622 if (oldi > next_out) {
2623 SendToPlayer(&buf[next_out], oldi - next_out);
2626 Colorize(prevColor, TRUE);
2627 curColor = prevColor;
2629 if (savingComment) {
2630 parse_pos = i - oldi;
2631 memcpy(parse, &buf[oldi], parse_pos);
2632 parse[parse_pos] = NULLCHAR;
2633 started = STARTED_COMMENT;
2635 started = STARTED_CHATTER;
2640 if (looking_at(buf, &i, "Black Strength :") ||
2641 looking_at(buf, &i, "<<< style 10 board >>>") ||
2642 looking_at(buf, &i, "<10>") ||
2643 looking_at(buf, &i, "#@#")) {
2644 /* Wrong board style */
2646 SendToICS(ics_prefix);
2647 SendToICS("set style 12\n");
2648 SendToICS(ics_prefix);
2649 SendToICS("refresh\n");
2653 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2655 have_sent_ICS_logon = 1;
2659 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2660 (looking_at(buf, &i, "\n<12> ") ||
2661 looking_at(buf, &i, "<12> "))) {
2663 if (oldi > next_out) {
2664 SendToPlayer(&buf[next_out], oldi - next_out);
2667 started = STARTED_BOARD;
2672 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2673 looking_at(buf, &i, "<b1> ")) {
2674 if (oldi > next_out) {
2675 SendToPlayer(&buf[next_out], oldi - next_out);
2678 started = STARTED_HOLDINGS;
2683 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2685 /* Header for a move list -- first line */
2687 switch (ics_getting_history) {
2691 case BeginningOfGame:
2692 /* User typed "moves" or "oldmoves" while we
2693 were idle. Pretend we asked for these
2694 moves and soak them up so user can step
2695 through them and/or save them.
2698 gameMode = IcsObserving;
2701 ics_getting_history = H_GOT_UNREQ_HEADER;
2703 case EditGame: /*?*/
2704 case EditPosition: /*?*/
2705 /* Should above feature work in these modes too? */
2706 /* For now it doesn't */
2707 ics_getting_history = H_GOT_UNWANTED_HEADER;
2710 ics_getting_history = H_GOT_UNWANTED_HEADER;
2715 /* Is this the right one? */
2716 if (gameInfo.white && gameInfo.black &&
2717 strcmp(gameInfo.white, star_match[0]) == 0 &&
2718 strcmp(gameInfo.black, star_match[2]) == 0) {
2720 ics_getting_history = H_GOT_REQ_HEADER;
2723 case H_GOT_REQ_HEADER:
2724 case H_GOT_UNREQ_HEADER:
2725 case H_GOT_UNWANTED_HEADER:
2726 case H_GETTING_MOVES:
2727 /* Should not happen */
2728 DisplayError(_("Error gathering move list: two headers"), 0);
2729 ics_getting_history = H_FALSE;
2733 /* Save player ratings into gameInfo if needed */
2734 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2735 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2736 (gameInfo.whiteRating == -1 ||
2737 gameInfo.blackRating == -1)) {
2739 gameInfo.whiteRating = string_to_rating(star_match[1]);
2740 gameInfo.blackRating = string_to_rating(star_match[3]);
2741 if (appData.debugMode)
2742 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2743 gameInfo.whiteRating, gameInfo.blackRating);
2748 if (looking_at(buf, &i,
2749 "* * match, initial time: * minute*, increment: * second")) {
2750 /* Header for a move list -- second line */
2751 /* Initial board will follow if this is a wild game */
2752 if (gameInfo.event != NULL) free(gameInfo.event);
2753 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2754 gameInfo.event = StrSave(str);
2755 /* [HGM] we switched variant. Translate boards if needed. */
2756 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2760 if (looking_at(buf, &i, "Move ")) {
2761 /* Beginning of a move list */
2762 switch (ics_getting_history) {
2764 /* Normally should not happen */
2765 /* Maybe user hit reset while we were parsing */
2768 /* Happens if we are ignoring a move list that is not
2769 * the one we just requested. Common if the user
2770 * tries to observe two games without turning off
2773 case H_GETTING_MOVES:
2774 /* Should not happen */
2775 DisplayError(_("Error gathering move list: nested"), 0);
2776 ics_getting_history = H_FALSE;
2778 case H_GOT_REQ_HEADER:
2779 ics_getting_history = H_GETTING_MOVES;
2780 started = STARTED_MOVES;
2782 if (oldi > next_out) {
2783 SendToPlayer(&buf[next_out], oldi - next_out);
2786 case H_GOT_UNREQ_HEADER:
2787 ics_getting_history = H_GETTING_MOVES;
2788 started = STARTED_MOVES_NOHIDE;
2791 case H_GOT_UNWANTED_HEADER:
2792 ics_getting_history = H_FALSE;
2798 if (looking_at(buf, &i, "% ") ||
2799 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2800 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2801 savingComment = FALSE;
2804 case STARTED_MOVES_NOHIDE:
2805 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2806 parse[parse_pos + i - oldi] = NULLCHAR;
2807 ParseGameHistory(parse);
2809 if (appData.zippyPlay && first.initDone) {
2810 FeedMovesToProgram(&first, forwardMostMove);
2811 if (gameMode == IcsPlayingWhite) {
2812 if (WhiteOnMove(forwardMostMove)) {
2813 if (first.sendTime) {
2814 if (first.useColors) {
2815 SendToProgram("black\n", &first);
2817 SendTimeRemaining(&first, TRUE);
2820 if (first.useColors) {
2821 SendToProgram("white\ngo\n", &first);
2823 SendToProgram("go\n", &first);
2826 if (first.useColors) {
2827 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2829 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2831 first.maybeThinking = TRUE;
2833 if (first.usePlayother) {
2834 if (first.sendTime) {
2835 SendTimeRemaining(&first, TRUE);
2837 SendToProgram("playother\n", &first);
2843 } else if (gameMode == IcsPlayingBlack) {
2844 if (!WhiteOnMove(forwardMostMove)) {
2845 if (first.sendTime) {
2846 if (first.useColors) {
2847 SendToProgram("white\n", &first);
2849 SendTimeRemaining(&first, FALSE);
2852 if (first.useColors) {
2853 SendToProgram("black\ngo\n", &first);
2855 SendToProgram("go\n", &first);
2858 if (first.useColors) {
2859 SendToProgram("black\n", &first);
2861 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2863 first.maybeThinking = TRUE;
2865 if (first.usePlayother) {
2866 if (first.sendTime) {
2867 SendTimeRemaining(&first, FALSE);
2869 SendToProgram("playother\n", &first);
2878 if (gameMode == IcsObserving && ics_gamenum == -1) {
2879 /* Moves came from oldmoves or moves command
2880 while we weren't doing anything else.
2882 currentMove = forwardMostMove;
2883 ClearHighlights();/*!!could figure this out*/
2884 flipView = appData.flipView;
2885 DrawPosition(FALSE, boards[currentMove]);
2886 DisplayBothClocks();
2887 sprintf(str, "%s vs. %s",
2888 gameInfo.white, gameInfo.black);
2892 /* Moves were history of an active game */
2893 if (gameInfo.resultDetails != NULL) {
2894 free(gameInfo.resultDetails);
2895 gameInfo.resultDetails = NULL;
2898 HistorySet(parseList, backwardMostMove,
2899 forwardMostMove, currentMove-1);
2900 DisplayMove(currentMove - 1);
2901 if (started == STARTED_MOVES) next_out = i;
2902 started = STARTED_NONE;
2903 ics_getting_history = H_FALSE;
2906 case STARTED_OBSERVE:
2907 started = STARTED_NONE;
2908 SendToICS(ics_prefix);
2909 SendToICS("refresh\n");
2915 if(bookHit) { // [HGM] book: simulate book reply
2916 static char bookMove[MSG_SIZ]; // a bit generous?
2918 programStats.nodes = programStats.depth = programStats.time =
2919 programStats.score = programStats.got_only_move = 0;
2920 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2922 strcpy(bookMove, "move ");
2923 strcat(bookMove, bookHit);
2924 HandleMachineMove(bookMove, &first);
2929 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2930 started == STARTED_HOLDINGS ||
2931 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2932 /* Accumulate characters in move list or board */
2933 parse[parse_pos++] = buf[i];
2936 /* Start of game messages. Mostly we detect start of game
2937 when the first board image arrives. On some versions
2938 of the ICS, though, we need to do a "refresh" after starting
2939 to observe in order to get the current board right away. */
2940 if (looking_at(buf, &i, "Adding game * to observation list")) {
2941 started = STARTED_OBSERVE;
2945 /* Handle auto-observe */
2946 if (appData.autoObserve &&
2947 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2948 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2950 /* Choose the player that was highlighted, if any. */
2951 if (star_match[0][0] == '\033' ||
2952 star_match[1][0] != '\033') {
2953 player = star_match[0];
2955 player = star_match[2];
2957 sprintf(str, "%sobserve %s\n",
2958 ics_prefix, StripHighlightAndTitle(player));
2961 /* Save ratings from notify string */
2962 strcpy(player1Name, star_match[0]);
2963 player1Rating = string_to_rating(star_match[1]);
2964 strcpy(player2Name, star_match[2]);
2965 player2Rating = string_to_rating(star_match[3]);
2967 if (appData.debugMode)
2969 "Ratings from 'Game notification:' %s %d, %s %d\n",
2970 player1Name, player1Rating,
2971 player2Name, player2Rating);
2976 /* Deal with automatic examine mode after a game,
2977 and with IcsObserving -> IcsExamining transition */
2978 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2979 looking_at(buf, &i, "has made you an examiner of game *")) {
2981 int gamenum = atoi(star_match[0]);
2982 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2983 gamenum == ics_gamenum) {
2984 /* We were already playing or observing this game;
2985 no need to refetch history */
2986 gameMode = IcsExamining;
2988 pauseExamForwardMostMove = forwardMostMove;
2989 } else if (currentMove < forwardMostMove) {
2990 ForwardInner(forwardMostMove);
2993 /* I don't think this case really can happen */
2994 SendToICS(ics_prefix);
2995 SendToICS("refresh\n");
3000 /* Error messages */
3001 // if (ics_user_moved) {
3002 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3003 if (looking_at(buf, &i, "Illegal move") ||
3004 looking_at(buf, &i, "Not a legal move") ||
3005 looking_at(buf, &i, "Your king is in check") ||
3006 looking_at(buf, &i, "It isn't your turn") ||
3007 looking_at(buf, &i, "It is not your move")) {
3009 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3010 currentMove = --forwardMostMove;
3011 DisplayMove(currentMove - 1); /* before DMError */
3012 DrawPosition(FALSE, boards[currentMove]);
3014 DisplayBothClocks();
3016 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3022 if (looking_at(buf, &i, "still have time") ||
3023 looking_at(buf, &i, "not out of time") ||
3024 looking_at(buf, &i, "either player is out of time") ||
3025 looking_at(buf, &i, "has timeseal; checking")) {
3026 /* We must have called his flag a little too soon */
3027 whiteFlag = blackFlag = FALSE;
3031 if (looking_at(buf, &i, "added * seconds to") ||
3032 looking_at(buf, &i, "seconds were added to")) {
3033 /* Update the clocks */
3034 SendToICS(ics_prefix);
3035 SendToICS("refresh\n");
3039 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3040 ics_clock_paused = TRUE;
3045 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3046 ics_clock_paused = FALSE;
3051 /* Grab player ratings from the Creating: message.
3052 Note we have to check for the special case when
3053 the ICS inserts things like [white] or [black]. */
3054 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3055 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3057 0 player 1 name (not necessarily white)
3059 2 empty, white, or black (IGNORED)
3060 3 player 2 name (not necessarily black)
3063 The names/ratings are sorted out when the game
3064 actually starts (below).
3066 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3067 player1Rating = string_to_rating(star_match[1]);
3068 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3069 player2Rating = string_to_rating(star_match[4]);
3071 if (appData.debugMode)
3073 "Ratings from 'Creating:' %s %d, %s %d\n",
3074 player1Name, player1Rating,
3075 player2Name, player2Rating);
3080 /* Improved generic start/end-of-game messages */
3081 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3082 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3083 /* If tkind == 0: */
3084 /* star_match[0] is the game number */
3085 /* [1] is the white player's name */
3086 /* [2] is the black player's name */
3087 /* For end-of-game: */
3088 /* [3] is the reason for the game end */
3089 /* [4] is a PGN end game-token, preceded by " " */
3090 /* For start-of-game: */
3091 /* [3] begins with "Creating" or "Continuing" */
3092 /* [4] is " *" or empty (don't care). */
3093 int gamenum = atoi(star_match[0]);
3094 char *whitename, *blackname, *why, *endtoken;
3095 ChessMove endtype = (ChessMove) 0;
3098 whitename = star_match[1];
3099 blackname = star_match[2];
3100 why = star_match[3];
3101 endtoken = star_match[4];
3103 whitename = star_match[1];
3104 blackname = star_match[3];
3105 why = star_match[5];
3106 endtoken = star_match[6];
3109 /* Game start messages */
3110 if (strncmp(why, "Creating ", 9) == 0 ||
3111 strncmp(why, "Continuing ", 11) == 0) {
3112 gs_gamenum = gamenum;
3113 strcpy(gs_kind, strchr(why, ' ') + 1);
3115 if (appData.zippyPlay) {
3116 ZippyGameStart(whitename, blackname);
3122 /* Game end messages */
3123 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3124 ics_gamenum != gamenum) {
3127 while (endtoken[0] == ' ') endtoken++;
3128 switch (endtoken[0]) {
3131 endtype = GameUnfinished;
3134 endtype = BlackWins;
3137 if (endtoken[1] == '/')
3138 endtype = GameIsDrawn;
3140 endtype = WhiteWins;
3143 GameEnds(endtype, why, GE_ICS);
3145 if (appData.zippyPlay && first.initDone) {
3146 ZippyGameEnd(endtype, why);
3147 if (first.pr == NULL) {
3148 /* Start the next process early so that we'll
3149 be ready for the next challenge */
3150 StartChessProgram(&first);
3152 /* Send "new" early, in case this command takes
3153 a long time to finish, so that we'll be ready
3154 for the next challenge. */
3155 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3162 if (looking_at(buf, &i, "Removing game * from observation") ||
3163 looking_at(buf, &i, "no longer observing game *") ||
3164 looking_at(buf, &i, "Game * (*) has no examiners")) {
3165 if (gameMode == IcsObserving &&
3166 atoi(star_match[0]) == ics_gamenum)
3168 /* icsEngineAnalyze */
3169 if (appData.icsEngineAnalyze) {
3176 ics_user_moved = FALSE;
3181 if (looking_at(buf, &i, "no longer examining game *")) {
3182 if (gameMode == IcsExamining &&
3183 atoi(star_match[0]) == ics_gamenum)
3187 ics_user_moved = FALSE;
3192 /* Advance leftover_start past any newlines we find,
3193 so only partial lines can get reparsed */
3194 if (looking_at(buf, &i, "\n")) {
3195 prevColor = curColor;
3196 if (curColor != ColorNormal) {
3197 if (oldi > next_out) {
3198 SendToPlayer(&buf[next_out], oldi - next_out);
3201 Colorize(ColorNormal, FALSE);
3202 curColor = ColorNormal;
3204 if (started == STARTED_BOARD) {
3205 started = STARTED_NONE;
3206 parse[parse_pos] = NULLCHAR;
3207 ParseBoard12(parse);
3210 /* Send premove here */
3211 if (appData.premove) {
3213 if (currentMove == 0 &&
3214 gameMode == IcsPlayingWhite &&
3215 appData.premoveWhite) {
3216 sprintf(str, "%s%s\n", ics_prefix,
3217 appData.premoveWhiteText);
3218 if (appData.debugMode)
3219 fprintf(debugFP, "Sending premove:\n");
3221 } else if (currentMove == 1 &&
3222 gameMode == IcsPlayingBlack &&
3223 appData.premoveBlack) {
3224 sprintf(str, "%s%s\n", ics_prefix,
3225 appData.premoveBlackText);
3226 if (appData.debugMode)
3227 fprintf(debugFP, "Sending premove:\n");
3229 } else if (gotPremove) {
3231 ClearPremoveHighlights();
3232 if (appData.debugMode)
3233 fprintf(debugFP, "Sending premove:\n");
3234 UserMoveEvent(premoveFromX, premoveFromY,
3235 premoveToX, premoveToY,
3240 /* Usually suppress following prompt */
3241 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3242 if (looking_at(buf, &i, "*% ")) {
3243 savingComment = FALSE;
3247 } else if (started == STARTED_HOLDINGS) {
3249 char new_piece[MSG_SIZ];
3250 started = STARTED_NONE;
3251 parse[parse_pos] = NULLCHAR;
3252 if (appData.debugMode)
3253 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3254 parse, currentMove);
3255 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3256 gamenum == ics_gamenum) {
3257 if (gameInfo.variant == VariantNormal) {
3258 /* [HGM] We seem to switch variant during a game!
3259 * Presumably no holdings were displayed, so we have
3260 * to move the position two files to the right to
3261 * create room for them!
3263 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3264 /* Get a move list just to see the header, which
3265 will tell us whether this is really bug or zh */
3266 if (ics_getting_history == H_FALSE) {
3267 ics_getting_history = H_REQUESTED;
3268 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3272 new_piece[0] = NULLCHAR;
3273 sscanf(parse, "game %d white [%s black [%s <- %s",
3274 &gamenum, white_holding, black_holding,
3276 white_holding[strlen(white_holding)-1] = NULLCHAR;
3277 black_holding[strlen(black_holding)-1] = NULLCHAR;
3278 /* [HGM] copy holdings to board holdings area */
3279 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3280 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3282 if (appData.zippyPlay && first.initDone) {
3283 ZippyHoldings(white_holding, black_holding,
3287 if (tinyLayout || smallLayout) {
3288 char wh[16], bh[16];
3289 PackHolding(wh, white_holding);
3290 PackHolding(bh, black_holding);
3291 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3292 gameInfo.white, gameInfo.black);
3294 sprintf(str, "%s [%s] vs. %s [%s]",
3295 gameInfo.white, white_holding,
3296 gameInfo.black, black_holding);
3299 DrawPosition(FALSE, boards[currentMove]);
3302 /* Suppress following prompt */
3303 if (looking_at(buf, &i, "*% ")) {
3304 savingComment = FALSE;
3311 i++; /* skip unparsed character and loop back */
3314 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3315 started != STARTED_HOLDINGS && i > next_out) {
3316 SendToPlayer(&buf[next_out], i - next_out);
3319 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3321 leftover_len = buf_len - leftover_start;
3322 /* if buffer ends with something we couldn't parse,
3323 reparse it after appending the next read */
3325 } else if (count == 0) {
3326 RemoveInputSource(isr);
3327 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3329 DisplayFatalError(_("Error reading from ICS"), error, 1);
3334 /* Board style 12 looks like this:
3336 <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
3338 * The "<12> " is stripped before it gets to this routine. The two
3339 * trailing 0's (flip state and clock ticking) are later addition, and
3340 * some chess servers may not have them, or may have only the first.
3341 * Additional trailing fields may be added in the future.
3344 #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"
3346 #define RELATION_OBSERVING_PLAYED 0
3347 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3348 #define RELATION_PLAYING_MYMOVE 1
3349 #define RELATION_PLAYING_NOTMYMOVE -1
3350 #define RELATION_EXAMINING 2
3351 #define RELATION_ISOLATED_BOARD -3
3352 #define RELATION_STARTING_POSITION -4 /* FICS only */
3355 ParseBoard12(string)
3358 GameMode newGameMode;
3359 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3360 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3361 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3362 char to_play, board_chars[200];
3363 char move_str[500], str[500], elapsed_time[500];
3364 char black[32], white[32];
3366 int prevMove = currentMove;
3369 int fromX, fromY, toX, toY;
3371 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3372 char *bookHit = NULL; // [HGM] book
3374 fromX = fromY = toX = toY = -1;
3378 if (appData.debugMode)
3379 fprintf(debugFP, _("Parsing board: %s\n"), string);
3381 move_str[0] = NULLCHAR;
3382 elapsed_time[0] = NULLCHAR;
3383 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3385 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3386 if(string[i] == ' ') { ranks++; files = 0; }
3390 for(j = 0; j <i; j++) board_chars[j] = string[j];
3391 board_chars[i] = '\0';
3394 n = sscanf(string, PATTERN, &to_play, &double_push,
3395 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3396 &gamenum, white, black, &relation, &basetime, &increment,
3397 &white_stren, &black_stren, &white_time, &black_time,
3398 &moveNum, str, elapsed_time, move_str, &ics_flip,
3402 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3403 DisplayError(str, 0);
3407 /* Convert the move number to internal form */
3408 moveNum = (moveNum - 1) * 2;
3409 if (to_play == 'B') moveNum++;
3410 if (moveNum >= MAX_MOVES) {
3411 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3417 case RELATION_OBSERVING_PLAYED:
3418 case RELATION_OBSERVING_STATIC:
3419 if (gamenum == -1) {
3420 /* Old ICC buglet */
3421 relation = RELATION_OBSERVING_STATIC;
3423 newGameMode = IcsObserving;
3425 case RELATION_PLAYING_MYMOVE:
3426 case RELATION_PLAYING_NOTMYMOVE:
3428 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3429 IcsPlayingWhite : IcsPlayingBlack;
3431 case RELATION_EXAMINING:
3432 newGameMode = IcsExamining;
3434 case RELATION_ISOLATED_BOARD:
3436 /* Just display this board. If user was doing something else,
3437 we will forget about it until the next board comes. */
3438 newGameMode = IcsIdle;
3440 case RELATION_STARTING_POSITION:
3441 newGameMode = gameMode;
3445 /* Modify behavior for initial board display on move listing
3448 switch (ics_getting_history) {
3452 case H_GOT_REQ_HEADER:
3453 case H_GOT_UNREQ_HEADER:
3454 /* This is the initial position of the current game */
3455 gamenum = ics_gamenum;
3456 moveNum = 0; /* old ICS bug workaround */
3457 if (to_play == 'B') {
3458 startedFromSetupPosition = TRUE;
3459 blackPlaysFirst = TRUE;
3461 if (forwardMostMove == 0) forwardMostMove = 1;
3462 if (backwardMostMove == 0) backwardMostMove = 1;
3463 if (currentMove == 0) currentMove = 1;
3465 newGameMode = gameMode;
3466 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3468 case H_GOT_UNWANTED_HEADER:
3469 /* This is an initial board that we don't want */
3471 case H_GETTING_MOVES:
3472 /* Should not happen */
3473 DisplayError(_("Error gathering move list: extra board"), 0);
3474 ics_getting_history = H_FALSE;
3478 /* Take action if this is the first board of a new game, or of a
3479 different game than is currently being displayed. */
3480 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3481 relation == RELATION_ISOLATED_BOARD) {
3483 /* Forget the old game and get the history (if any) of the new one */
3484 if (gameMode != BeginningOfGame) {
3488 if (appData.autoRaiseBoard) BoardToTop();
3490 if (gamenum == -1) {
3491 newGameMode = IcsIdle;
3492 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3493 appData.getMoveList) {
3494 /* Need to get game history */
3495 ics_getting_history = H_REQUESTED;
3496 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3500 /* Initially flip the board to have black on the bottom if playing
3501 black or if the ICS flip flag is set, but let the user change
3502 it with the Flip View button. */
3503 flipView = appData.autoFlipView ?
3504 (newGameMode == IcsPlayingBlack) || ics_flip :
3507 /* Done with values from previous mode; copy in new ones */
3508 gameMode = newGameMode;
3510 ics_gamenum = gamenum;
3511 if (gamenum == gs_gamenum) {
3512 int klen = strlen(gs_kind);
3513 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3514 sprintf(str, "ICS %s", gs_kind);
3515 gameInfo.event = StrSave(str);
3517 gameInfo.event = StrSave("ICS game");
3519 gameInfo.site = StrSave(appData.icsHost);
3520 gameInfo.date = PGNDate();
3521 gameInfo.round = StrSave("-");
3522 gameInfo.white = StrSave(white);
3523 gameInfo.black = StrSave(black);
3524 timeControl = basetime * 60 * 1000;
3526 timeIncrement = increment * 1000;
3527 movesPerSession = 0;
3528 gameInfo.timeControl = TimeControlTagValue();
3529 VariantSwitch(board, StringToVariant(gameInfo.event) );
3530 if (appData.debugMode) {
3531 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3532 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3533 setbuf(debugFP, NULL);
3536 gameInfo.outOfBook = NULL;
3538 /* Do we have the ratings? */
3539 if (strcmp(player1Name, white) == 0 &&
3540 strcmp(player2Name, black) == 0) {
3541 if (appData.debugMode)
3542 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3543 player1Rating, player2Rating);
3544 gameInfo.whiteRating = player1Rating;
3545 gameInfo.blackRating = player2Rating;
3546 } else if (strcmp(player2Name, white) == 0 &&
3547 strcmp(player1Name, black) == 0) {
3548 if (appData.debugMode)
3549 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3550 player2Rating, player1Rating);
3551 gameInfo.whiteRating = player2Rating;
3552 gameInfo.blackRating = player1Rating;
3554 player1Name[0] = player2Name[0] = NULLCHAR;
3556 /* Silence shouts if requested */
3557 if (appData.quietPlay &&
3558 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3559 SendToICS(ics_prefix);
3560 SendToICS("set shout 0\n");
3564 /* Deal with midgame name changes */
3566 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3567 if (gameInfo.white) free(gameInfo.white);
3568 gameInfo.white = StrSave(white);
3570 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3571 if (gameInfo.black) free(gameInfo.black);
3572 gameInfo.black = StrSave(black);
3576 /* Throw away game result if anything actually changes in examine mode */
3577 if (gameMode == IcsExamining && !newGame) {
3578 gameInfo.result = GameUnfinished;
3579 if (gameInfo.resultDetails != NULL) {
3580 free(gameInfo.resultDetails);
3581 gameInfo.resultDetails = NULL;
3585 /* In pausing && IcsExamining mode, we ignore boards coming
3586 in if they are in a different variation than we are. */
3587 if (pauseExamInvalid) return;
3588 if (pausing && gameMode == IcsExamining) {
3589 if (moveNum <= pauseExamForwardMostMove) {
3590 pauseExamInvalid = TRUE;
3591 forwardMostMove = pauseExamForwardMostMove;
3596 if (appData.debugMode) {
3597 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3599 /* Parse the board */
3600 for (k = 0; k < ranks; k++) {
3601 for (j = 0; j < files; j++)
3602 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3603 if(gameInfo.holdingsWidth > 1) {
3604 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3605 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3608 CopyBoard(boards[moveNum], board);
3610 startedFromSetupPosition =
3611 !CompareBoards(board, initialPosition);
3612 if(startedFromSetupPosition)
3613 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3616 /* [HGM] Set castling rights. Take the outermost Rooks,
3617 to make it also work for FRC opening positions. Note that board12
3618 is really defective for later FRC positions, as it has no way to
3619 indicate which Rook can castle if they are on the same side of King.
3620 For the initial position we grant rights to the outermost Rooks,
3621 and remember thos rights, and we then copy them on positions
3622 later in an FRC game. This means WB might not recognize castlings with
3623 Rooks that have moved back to their original position as illegal,
3624 but in ICS mode that is not its job anyway.
3626 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3627 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3629 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3630 if(board[0][i] == WhiteRook) j = i;
3631 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3632 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3633 if(board[0][i] == WhiteRook) j = i;
3634 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3635 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3636 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3637 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3638 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3639 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3640 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3642 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3643 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3644 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3645 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3646 if(board[BOARD_HEIGHT-1][k] == bKing)
3647 initialRights[5] = castlingRights[moveNum][5] = k;
3649 r = castlingRights[moveNum][0] = initialRights[0];
3650 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3651 r = castlingRights[moveNum][1] = initialRights[1];
3652 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3653 r = castlingRights[moveNum][3] = initialRights[3];
3654 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3655 r = castlingRights[moveNum][4] = initialRights[4];
3656 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3657 /* wildcastle kludge: always assume King has rights */
3658 r = castlingRights[moveNum][2] = initialRights[2];
3659 r = castlingRights[moveNum][5] = initialRights[5];
3661 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3662 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3665 if (ics_getting_history == H_GOT_REQ_HEADER ||
3666 ics_getting_history == H_GOT_UNREQ_HEADER) {
3667 /* This was an initial position from a move list, not
3668 the current position */
3672 /* Update currentMove and known move number limits */
3673 newMove = newGame || moveNum > forwardMostMove;
3675 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3676 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3677 takeback = forwardMostMove - moveNum;
3678 for (i = 0; i < takeback; i++) {
3679 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3680 SendToProgram("undo\n", &first);
3685 forwardMostMove = backwardMostMove = currentMove = moveNum;
3686 if (gameMode == IcsExamining && moveNum == 0) {
3687 /* Workaround for ICS limitation: we are not told the wild
3688 type when starting to examine a game. But if we ask for
3689 the move list, the move list header will tell us */
3690 ics_getting_history = H_REQUESTED;
3691 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3694 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3695 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3696 forwardMostMove = moveNum;
3697 if (!pausing || currentMove > forwardMostMove)
3698 currentMove = forwardMostMove;
3700 /* New part of history that is not contiguous with old part */
3701 if (pausing && gameMode == IcsExamining) {
3702 pauseExamInvalid = TRUE;
3703 forwardMostMove = pauseExamForwardMostMove;
3706 forwardMostMove = backwardMostMove = currentMove = moveNum;
3707 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3708 ics_getting_history = H_REQUESTED;
3709 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3714 /* Update the clocks */
3715 if (strchr(elapsed_time, '.')) {
3717 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3718 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3720 /* Time is in seconds */
3721 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3722 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3727 if (appData.zippyPlay && newGame &&
3728 gameMode != IcsObserving && gameMode != IcsIdle &&
3729 gameMode != IcsExamining)
3730 ZippyFirstBoard(moveNum, basetime, increment);
3733 /* Put the move on the move list, first converting
3734 to canonical algebraic form. */
3736 if (appData.debugMode) {
3737 if (appData.debugMode) { int f = forwardMostMove;
3738 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3739 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3741 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3742 fprintf(debugFP, "moveNum = %d\n", moveNum);
3743 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3744 setbuf(debugFP, NULL);
3746 if (moveNum <= backwardMostMove) {
3747 /* We don't know what the board looked like before
3749 strcpy(parseList[moveNum - 1], move_str);
3750 strcat(parseList[moveNum - 1], " ");
3751 strcat(parseList[moveNum - 1], elapsed_time);
3752 moveList[moveNum - 1][0] = NULLCHAR;
3753 } else if (strcmp(move_str, "none") == 0) {
3754 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3755 /* Again, we don't know what the board looked like;
3756 this is really the start of the game. */
3757 parseList[moveNum - 1][0] = NULLCHAR;
3758 moveList[moveNum - 1][0] = NULLCHAR;
3759 backwardMostMove = moveNum;
3760 startedFromSetupPosition = TRUE;
3761 fromX = fromY = toX = toY = -1;
3763 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3764 // So we parse the long-algebraic move string in stead of the SAN move
3765 int valid; char buf[MSG_SIZ], *prom;
3767 // str looks something like "Q/a1-a2"; kill the slash
3769 sprintf(buf, "%c%s", str[0], str+2);
3770 else strcpy(buf, str); // might be castling
3771 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3772 strcat(buf, prom); // long move lacks promo specification!
3773 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3774 if(appData.debugMode)
3775 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3776 strcpy(move_str, buf);
3778 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3779 &fromX, &fromY, &toX, &toY, &promoChar)
3780 || ParseOneMove(buf, moveNum - 1, &moveType,
3781 &fromX, &fromY, &toX, &toY, &promoChar);
3782 // end of long SAN patch
3784 (void) CoordsToAlgebraic(boards[moveNum - 1],
3785 PosFlags(moveNum - 1), EP_UNKNOWN,
3786 fromY, fromX, toY, toX, promoChar,
3787 parseList[moveNum-1]);
3788 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3789 castlingRights[moveNum]) ) {
3795 if(gameInfo.variant != VariantShogi)
3796 strcat(parseList[moveNum - 1], "+");
3799 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3800 strcat(parseList[moveNum - 1], "#");
3803 strcat(parseList[moveNum - 1], " ");
3804 strcat(parseList[moveNum - 1], elapsed_time);
3805 /* currentMoveString is set as a side-effect of ParseOneMove */
3806 strcpy(moveList[moveNum - 1], currentMoveString);
3807 strcat(moveList[moveNum - 1], "\n");
3809 /* Move from ICS was illegal!? Punt. */
3810 if (appData.debugMode) {
3811 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3812 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3815 if (appData.testLegality && appData.debugMode) {
3816 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3817 DisplayError(str, 0);
3820 strcpy(parseList[moveNum - 1], move_str);
3821 strcat(parseList[moveNum - 1], " ");
3822 strcat(parseList[moveNum - 1], elapsed_time);
3823 moveList[moveNum - 1][0] = NULLCHAR;
3824 fromX = fromY = toX = toY = -1;
3827 if (appData.debugMode) {
3828 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3829 setbuf(debugFP, NULL);
3833 /* Send move to chess program (BEFORE animating it). */
3834 if (appData.zippyPlay && !newGame && newMove &&
3835 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3837 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3838 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3839 if (moveList[moveNum - 1][0] == NULLCHAR) {
3840 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3842 DisplayError(str, 0);
3844 if (first.sendTime) {
3845 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3847 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3848 if (firstMove && !bookHit) {
3850 if (first.useColors) {
3851 SendToProgram(gameMode == IcsPlayingWhite ?
3853 "black\ngo\n", &first);
3855 SendToProgram("go\n", &first);
3857 first.maybeThinking = TRUE;
3860 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3861 if (moveList[moveNum - 1][0] == NULLCHAR) {
3862 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3863 DisplayError(str, 0);
3865 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3866 SendMoveToProgram(moveNum - 1, &first);
3873 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3874 /* If move comes from a remote source, animate it. If it
3875 isn't remote, it will have already been animated. */
3876 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3877 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3879 if (!pausing && appData.highlightLastMove) {
3880 SetHighlights(fromX, fromY, toX, toY);
3884 /* Start the clocks */
3885 whiteFlag = blackFlag = FALSE;
3886 appData.clockMode = !(basetime == 0 && increment == 0);
3888 ics_clock_paused = TRUE;
3890 } else if (ticking == 1) {
3891 ics_clock_paused = FALSE;
3893 if (gameMode == IcsIdle ||
3894 relation == RELATION_OBSERVING_STATIC ||
3895 relation == RELATION_EXAMINING ||
3897 DisplayBothClocks();
3901 /* Display opponents and material strengths */
3902 if (gameInfo.variant != VariantBughouse &&
3903 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3904 if (tinyLayout || smallLayout) {
3905 if(gameInfo.variant == VariantNormal)
3906 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3907 gameInfo.white, white_stren, gameInfo.black, black_stren,
3908 basetime, increment);
3910 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3911 gameInfo.white, white_stren, gameInfo.black, black_stren,
3912 basetime, increment, (int) gameInfo.variant);
3914 if(gameInfo.variant == VariantNormal)
3915 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3916 gameInfo.white, white_stren, gameInfo.black, black_stren,
3917 basetime, increment);
3919 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3920 gameInfo.white, white_stren, gameInfo.black, black_stren,
3921 basetime, increment, VariantName(gameInfo.variant));
3924 if (appData.debugMode) {
3925 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3930 /* Display the board */
3931 if (!pausing && !appData.noGUI) {
3933 if (appData.premove)
3935 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3936 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3937 ClearPremoveHighlights();
3939 DrawPosition(FALSE, boards[currentMove]);
3940 DisplayMove(moveNum - 1);
3941 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3942 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3943 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3944 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3948 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3950 if(bookHit) { // [HGM] book: simulate book reply
3951 static char bookMove[MSG_SIZ]; // a bit generous?
3953 programStats.nodes = programStats.depth = programStats.time =
3954 programStats.score = programStats.got_only_move = 0;
3955 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3957 strcpy(bookMove, "move ");
3958 strcat(bookMove, bookHit);
3959 HandleMachineMove(bookMove, &first);
3968 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3969 ics_getting_history = H_REQUESTED;
3970 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3976 AnalysisPeriodicEvent(force)
3979 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3980 && !force) || !appData.periodicUpdates)
3983 /* Send . command to Crafty to collect stats */
3984 SendToProgram(".\n", &first);
3986 /* Don't send another until we get a response (this makes
3987 us stop sending to old Crafty's which don't understand
3988 the "." command (sending illegal cmds resets node count & time,
3989 which looks bad)) */
3990 programStats.ok_to_send = 0;
3994 SendMoveToProgram(moveNum, cps)
3996 ChessProgramState *cps;
4000 if (cps->useUsermove) {
4001 SendToProgram("usermove ", cps);
4005 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4006 int len = space - parseList[moveNum];
4007 memcpy(buf, parseList[moveNum], len);
4009 buf[len] = NULLCHAR;
4011 sprintf(buf, "%s\n", parseList[moveNum]);
4013 SendToProgram(buf, cps);
4015 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4016 AlphaRank(moveList[moveNum], 4);
4017 SendToProgram(moveList[moveNum], cps);
4018 AlphaRank(moveList[moveNum], 4); // and back
4020 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4021 * the engine. It would be nice to have a better way to identify castle
4023 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4024 && cps->useOOCastle) {
4025 int fromX = moveList[moveNum][0] - AAA;
4026 int fromY = moveList[moveNum][1] - ONE;
4027 int toX = moveList[moveNum][2] - AAA;
4028 int toY = moveList[moveNum][3] - ONE;
4029 if((boards[moveNum][fromY][fromX] == WhiteKing
4030 && boards[moveNum][toY][toX] == WhiteRook)
4031 || (boards[moveNum][fromY][fromX] == BlackKing
4032 && boards[moveNum][toY][toX] == BlackRook)) {
4033 if(toX > fromX) SendToProgram("O-O\n", cps);
4034 else SendToProgram("O-O-O\n", cps);
4036 else SendToProgram(moveList[moveNum], cps);
4038 else SendToProgram(moveList[moveNum], cps);
4039 /* End of additions by Tord */
4042 /* [HGM] setting up the opening has brought engine in force mode! */
4043 /* Send 'go' if we are in a mode where machine should play. */
4044 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4045 (gameMode == TwoMachinesPlay ||
4047 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4049 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4050 SendToProgram("go\n", cps);
4051 if (appData.debugMode) {
4052 fprintf(debugFP, "(extra)\n");
4055 setboardSpoiledMachineBlack = 0;
4059 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4061 int fromX, fromY, toX, toY;
4063 char user_move[MSG_SIZ];
4067 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4068 (int)moveType, fromX, fromY, toX, toY);
4069 DisplayError(user_move + strlen("say "), 0);
4071 case WhiteKingSideCastle:
4072 case BlackKingSideCastle:
4073 case WhiteQueenSideCastleWild:
4074 case BlackQueenSideCastleWild:
4076 case WhiteHSideCastleFR:
4077 case BlackHSideCastleFR:
4079 sprintf(user_move, "o-o\n");
4081 case WhiteQueenSideCastle:
4082 case BlackQueenSideCastle:
4083 case WhiteKingSideCastleWild:
4084 case BlackKingSideCastleWild:
4086 case WhiteASideCastleFR:
4087 case BlackASideCastleFR:
4089 sprintf(user_move, "o-o-o\n");
4091 case WhitePromotionQueen:
4092 case BlackPromotionQueen:
4093 case WhitePromotionRook:
4094 case BlackPromotionRook:
4095 case WhitePromotionBishop:
4096 case BlackPromotionBishop:
4097 case WhitePromotionKnight:
4098 case BlackPromotionKnight:
4099 case WhitePromotionKing:
4100 case BlackPromotionKing:
4101 case WhitePromotionChancellor:
4102 case BlackPromotionChancellor:
4103 case WhitePromotionArchbishop:
4104 case BlackPromotionArchbishop:
4105 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4106 sprintf(user_move, "%c%c%c%c=%c\n",
4107 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4108 PieceToChar(WhiteFerz));
4109 else if(gameInfo.variant == VariantGreat)
4110 sprintf(user_move, "%c%c%c%c=%c\n",
4111 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4112 PieceToChar(WhiteMan));
4114 sprintf(user_move, "%c%c%c%c=%c\n",
4115 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4116 PieceToChar(PromoPiece(moveType)));
4120 sprintf(user_move, "%c@%c%c\n",
4121 ToUpper(PieceToChar((ChessSquare) fromX)),
4122 AAA + toX, ONE + toY);
4125 case WhiteCapturesEnPassant:
4126 case BlackCapturesEnPassant:
4127 case IllegalMove: /* could be a variant we don't quite understand */
4128 sprintf(user_move, "%c%c%c%c\n",
4129 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4132 SendToICS(user_move);
4133 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4134 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4138 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4143 if (rf == DROP_RANK) {
4144 sprintf(move, "%c@%c%c\n",
4145 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4147 if (promoChar == 'x' || promoChar == NULLCHAR) {
4148 sprintf(move, "%c%c%c%c\n",
4149 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4151 sprintf(move, "%c%c%c%c%c\n",
4152 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4158 ProcessICSInitScript(f)
4163 while (fgets(buf, MSG_SIZ, f)) {
4164 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4171 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4173 AlphaRank(char *move, int n)
4175 // char *p = move, c; int x, y;
4177 if (appData.debugMode) {
4178 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4182 move[2]>='0' && move[2]<='9' &&
4183 move[3]>='a' && move[3]<='x' ) {
4185 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4186 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4188 if(move[0]>='0' && move[0]<='9' &&
4189 move[1]>='a' && move[1]<='x' &&
4190 move[2]>='0' && move[2]<='9' &&
4191 move[3]>='a' && move[3]<='x' ) {
4192 /* input move, Shogi -> normal */
4193 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4194 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4195 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4196 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4199 move[3]>='0' && move[3]<='9' &&
4200 move[2]>='a' && move[2]<='x' ) {
4202 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4203 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4206 move[0]>='a' && move[0]<='x' &&
4207 move[3]>='0' && move[3]<='9' &&
4208 move[2]>='a' && move[2]<='x' ) {
4209 /* output move, normal -> Shogi */
4210 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4211 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4212 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4213 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4214 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4216 if (appData.debugMode) {
4217 fprintf(debugFP, " out = '%s'\n", move);
4221 /* Parser for moves from gnuchess, ICS, or user typein box */
4223 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4226 ChessMove *moveType;
4227 int *fromX, *fromY, *toX, *toY;
4230 if (appData.debugMode) {
4231 fprintf(debugFP, "move to parse: %s\n", move);
4233 *moveType = yylexstr(moveNum, move);
4235 switch (*moveType) {
4236 case WhitePromotionChancellor:
4237 case BlackPromotionChancellor:
4238 case WhitePromotionArchbishop:
4239 case BlackPromotionArchbishop:
4240 case WhitePromotionQueen:
4241 case BlackPromotionQueen:
4242 case WhitePromotionRook:
4243 case BlackPromotionRook:
4244 case WhitePromotionBishop:
4245 case BlackPromotionBishop:
4246 case WhitePromotionKnight:
4247 case BlackPromotionKnight:
4248 case WhitePromotionKing:
4249 case BlackPromotionKing:
4251 case WhiteCapturesEnPassant:
4252 case BlackCapturesEnPassant:
4253 case WhiteKingSideCastle:
4254 case WhiteQueenSideCastle:
4255 case BlackKingSideCastle:
4256 case BlackQueenSideCastle:
4257 case WhiteKingSideCastleWild:
4258 case WhiteQueenSideCastleWild:
4259 case BlackKingSideCastleWild:
4260 case BlackQueenSideCastleWild:
4261 /* Code added by Tord: */
4262 case WhiteHSideCastleFR:
4263 case WhiteASideCastleFR:
4264 case BlackHSideCastleFR:
4265 case BlackASideCastleFR:
4266 /* End of code added by Tord */
4267 case IllegalMove: /* bug or odd chess variant */
4268 *fromX = currentMoveString[0] - AAA;
4269 *fromY = currentMoveString[1] - ONE;
4270 *toX = currentMoveString[2] - AAA;
4271 *toY = currentMoveString[3] - ONE;
4272 *promoChar = currentMoveString[4];
4273 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4274 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4275 if (appData.debugMode) {
4276 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4278 *fromX = *fromY = *toX = *toY = 0;
4281 if (appData.testLegality) {
4282 return (*moveType != IllegalMove);
4284 return !(fromX == fromY && toX == toY);
4289 *fromX = *moveType == WhiteDrop ?
4290 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4291 (int) CharToPiece(ToLower(currentMoveString[0]));
4293 *toX = currentMoveString[2] - AAA;
4294 *toY = currentMoveString[3] - ONE;
4295 *promoChar = NULLCHAR;
4299 case ImpossibleMove:
4300 case (ChessMove) 0: /* end of file */
4309 if (appData.debugMode) {
4310 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4313 *fromX = *fromY = *toX = *toY = 0;
4314 *promoChar = NULLCHAR;
4319 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4320 // All positions will have equal probability, but the current method will not provide a unique
4321 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4327 int piecesLeft[(int)BlackPawn];
4328 int seed, nrOfShuffles;
4330 void GetPositionNumber()
4331 { // sets global variable seed
4334 seed = appData.defaultFrcPosition;
4335 if(seed < 0) { // randomize based on time for negative FRC position numbers
4336 for(i=0; i<50; i++) seed += random();
4337 seed = random() ^ random() >> 8 ^ random() << 8;
4338 if(seed<0) seed = -seed;
4342 int put(Board board, int pieceType, int rank, int n, int shade)
4343 // put the piece on the (n-1)-th empty squares of the given shade
4347 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4348 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4349 board[rank][i] = (ChessSquare) pieceType;
4350 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4352 piecesLeft[pieceType]--;
4360 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4361 // calculate where the next piece goes, (any empty square), and put it there
4365 i = seed % squaresLeft[shade];
4366 nrOfShuffles *= squaresLeft[shade];
4367 seed /= squaresLeft[shade];
4368 put(board, pieceType, rank, i, shade);
4371 void AddTwoPieces(Board board, int pieceType, int rank)
4372 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4374 int i, n=squaresLeft[ANY], j=n-1, k;
4376 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4377 i = seed % k; // pick one
4380 while(i >= j) i -= j--;
4381 j = n - 1 - j; i += j;
4382 put(board, pieceType, rank, j, ANY);
4383 put(board, pieceType, rank, i, ANY);
4386 void SetUpShuffle(Board board, int number)
4390 GetPositionNumber(); nrOfShuffles = 1;
4392 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4393 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4394 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4396 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4398 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4399 p = (int) board[0][i];
4400 if(p < (int) BlackPawn) piecesLeft[p] ++;
4401 board[0][i] = EmptySquare;
4404 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4405 // shuffles restricted to allow normal castling put KRR first
4406 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4407 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4408 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4409 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4410 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4411 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4412 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4413 put(board, WhiteRook, 0, 0, ANY);
4414 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4417 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4418 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4419 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4420 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4421 while(piecesLeft[p] >= 2) {
4422 AddOnePiece(board, p, 0, LITE);
4423 AddOnePiece(board, p, 0, DARK);
4425 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4428 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4429 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4430 // but we leave King and Rooks for last, to possibly obey FRC restriction
4431 if(p == (int)WhiteRook) continue;
4432 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4433 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4436 // now everything is placed, except perhaps King (Unicorn) and Rooks
4438 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4439 // Last King gets castling rights
4440 while(piecesLeft[(int)WhiteUnicorn]) {
4441 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4442 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4445 while(piecesLeft[(int)WhiteKing]) {
4446 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4447 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4452 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4453 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4456 // Only Rooks can be left; simply place them all
4457 while(piecesLeft[(int)WhiteRook]) {
4458 i = put(board, WhiteRook, 0, 0, ANY);
4459 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4462 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4464 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4467 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4468 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4471 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4474 int SetCharTable( char *table, const char * map )
4475 /* [HGM] moved here from winboard.c because of its general usefulness */
4476 /* Basically a safe strcpy that uses the last character as King */
4478 int result = FALSE; int NrPieces;
4480 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4481 && NrPieces >= 12 && !(NrPieces&1)) {
4482 int i; /* [HGM] Accept even length from 12 to 34 */
4484 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4485 for( i=0; i<NrPieces/2-1; i++ ) {
4487 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4489 table[(int) WhiteKing] = map[NrPieces/2-1];
4490 table[(int) BlackKing] = map[NrPieces-1];
4498 void Prelude(Board board)
4499 { // [HGM] superchess: random selection of exo-pieces
4500 int i, j, k; ChessSquare p;
4501 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4503 GetPositionNumber(); // use FRC position number
4505 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4506 SetCharTable(pieceToChar, appData.pieceToCharTable);
4507 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4508 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4511 j = seed%4; seed /= 4;
4512 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4513 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4514 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4515 j = seed%3 + (seed%3 >= j); seed /= 3;
4516 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4517 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4518 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4519 j = seed%3; seed /= 3;
4520 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4521 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4522 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4523 j = seed%2 + (seed%2 >= j); seed /= 2;
4524 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4525 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4526 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4527 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4528 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4529 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4530 put(board, exoPieces[0], 0, 0, ANY);
4531 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4535 InitPosition(redraw)
4538 ChessSquare (* pieces)[BOARD_SIZE];
4539 int i, j, pawnRow, overrule,
4540 oldx = gameInfo.boardWidth,
4541 oldy = gameInfo.boardHeight,
4542 oldh = gameInfo.holdingsWidth,
4543 oldv = gameInfo.variant;
4545 currentMove = forwardMostMove = backwardMostMove = 0;
4546 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4548 /* [AS] Initialize pv info list [HGM] and game status */
4550 for( i=0; i<MAX_MOVES; i++ ) {
4551 pvInfoList[i].depth = 0;
4552 epStatus[i]=EP_NONE;
4553 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4556 initialRulePlies = 0; /* 50-move counter start */
4558 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4559 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4563 /* [HGM] logic here is completely changed. In stead of full positions */
4564 /* the initialized data only consist of the two backranks. The switch */
4565 /* selects which one we will use, which is than copied to the Board */
4566 /* initialPosition, which for the rest is initialized by Pawns and */
4567 /* empty squares. This initial position is then copied to boards[0], */
4568 /* possibly after shuffling, so that it remains available. */
4570 gameInfo.holdingsWidth = 0; /* default board sizes */
4571 gameInfo.boardWidth = 8;
4572 gameInfo.boardHeight = 8;
4573 gameInfo.holdingsSize = 0;
4574 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4575 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4576 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4578 switch (gameInfo.variant) {
4579 case VariantFischeRandom:
4580 shuffleOpenings = TRUE;
4584 case VariantShatranj:
4585 pieces = ShatranjArray;
4586 nrCastlingRights = 0;
4587 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4589 case VariantTwoKings:
4590 pieces = twoKingsArray;
4592 case VariantCapaRandom:
4593 shuffleOpenings = TRUE;
4594 case VariantCapablanca:
4595 pieces = CapablancaArray;
4596 gameInfo.boardWidth = 10;
4597 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4600 pieces = GothicArray;
4601 gameInfo.boardWidth = 10;
4602 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4605 pieces = JanusArray;
4606 gameInfo.boardWidth = 10;
4607 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4608 nrCastlingRights = 6;
4609 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4610 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4611 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4612 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4613 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4614 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4617 pieces = FalconArray;
4618 gameInfo.boardWidth = 10;
4619 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4621 case VariantXiangqi:
4622 pieces = XiangqiArray;
4623 gameInfo.boardWidth = 9;
4624 gameInfo.boardHeight = 10;
4625 nrCastlingRights = 0;
4626 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4629 pieces = ShogiArray;
4630 gameInfo.boardWidth = 9;
4631 gameInfo.boardHeight = 9;
4632 gameInfo.holdingsSize = 7;
4633 nrCastlingRights = 0;
4634 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4636 case VariantCourier:
4637 pieces = CourierArray;
4638 gameInfo.boardWidth = 12;
4639 nrCastlingRights = 0;
4640 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4641 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4643 case VariantKnightmate:
4644 pieces = KnightmateArray;
4645 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4648 pieces = fairyArray;
4649 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4652 pieces = GreatArray;
4653 gameInfo.boardWidth = 10;
4654 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4655 gameInfo.holdingsSize = 8;
4659 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4660 gameInfo.holdingsSize = 8;
4661 startedFromSetupPosition = TRUE;
4663 case VariantCrazyhouse:
4664 case VariantBughouse:
4666 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4667 gameInfo.holdingsSize = 5;
4669 case VariantWildCastle:
4671 /* !!?shuffle with kings guaranteed to be on d or e file */
4672 shuffleOpenings = 1;
4674 case VariantNoCastle:
4676 nrCastlingRights = 0;
4677 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4678 /* !!?unconstrained back-rank shuffle */
4679 shuffleOpenings = 1;
4684 if(appData.NrFiles >= 0) {
4685 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4686 gameInfo.boardWidth = appData.NrFiles;
4688 if(appData.NrRanks >= 0) {
4689 gameInfo.boardHeight = appData.NrRanks;
4691 if(appData.holdingsSize >= 0) {
4692 i = appData.holdingsSize;
4693 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4694 gameInfo.holdingsSize = i;
4696 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4697 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4698 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4700 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4701 if(pawnRow < 1) pawnRow = 1;
4703 /* User pieceToChar list overrules defaults */
4704 if(appData.pieceToCharTable != NULL)
4705 SetCharTable(pieceToChar, appData.pieceToCharTable);
4707 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4709 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4710 s = (ChessSquare) 0; /* account holding counts in guard band */
4711 for( i=0; i<BOARD_HEIGHT; i++ )
4712 initialPosition[i][j] = s;
4714 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4715 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4716 initialPosition[pawnRow][j] = WhitePawn;
4717 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4718 if(gameInfo.variant == VariantXiangqi) {
4720 initialPosition[pawnRow][j] =
4721 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4722 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4723 initialPosition[2][j] = WhiteCannon;
4724 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4728 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4730 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4733 initialPosition[1][j] = WhiteBishop;
4734 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4736 initialPosition[1][j] = WhiteRook;
4737 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4740 if( nrCastlingRights == -1) {
4741 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4742 /* This sets default castling rights from none to normal corners */
4743 /* Variants with other castling rights must set them themselves above */
4744 nrCastlingRights = 6;
4746 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4747 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4748 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4749 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4750 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4751 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4754 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4755 if(gameInfo.variant == VariantGreat) { // promotion commoners
4756 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4757 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4758 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4759 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4762 if(gameInfo.variant == VariantFischeRandom) {
4763 if( appData.defaultFrcPosition < 0 ) {
4764 ShuffleFRC( initialPosition );
4767 SetupFRC( initialPosition, appData.defaultFrcPosition );
4769 startedFromSetupPosition = TRUE;
4772 if (appData.debugMode) {
4773 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4775 if(shuffleOpenings) {
4776 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4777 startedFromSetupPosition = TRUE;
4780 if(startedFromPositionFile) {
4781 /* [HGM] loadPos: use PositionFile for every new game */
4782 CopyBoard(initialPosition, filePosition);
4783 for(i=0; i<nrCastlingRights; i++)
4784 castlingRights[0][i] = initialRights[i] = fileRights[i];
4785 startedFromSetupPosition = TRUE;
4788 CopyBoard(boards[0], initialPosition);
4790 if(oldx != gameInfo.boardWidth ||
4791 oldy != gameInfo.boardHeight ||
4792 oldh != gameInfo.holdingsWidth
4794 || oldv == VariantGothic || // For licensing popups
4795 gameInfo.variant == VariantGothic
4798 || oldv == VariantFalcon ||
4799 gameInfo.variant == VariantFalcon
4802 InitDrawingSizes(-2 ,0);
4805 DrawPosition(TRUE, boards[currentMove]);
4809 SendBoard(cps, moveNum)
4810 ChessProgramState *cps;
4813 char message[MSG_SIZ];
4815 if (cps->useSetboard) {
4816 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4817 sprintf(message, "setboard %s\n", fen);
4818 SendToProgram(message, cps);
4824 /* Kludge to set black to move, avoiding the troublesome and now
4825 * deprecated "black" command.
4827 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4829 SendToProgram("edit\n", cps);
4830 SendToProgram("#\n", cps);
4831 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4832 bp = &boards[moveNum][i][BOARD_LEFT];
4833 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4834 if ((int) *bp < (int) BlackPawn) {
4835 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4837 if(message[0] == '+' || message[0] == '~') {
4838 sprintf(message, "%c%c%c+\n",
4839 PieceToChar((ChessSquare)(DEMOTED *bp)),
4842 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4843 message[1] = BOARD_RGHT - 1 - j + '1';
4844 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4846 SendToProgram(message, cps);
4851 SendToProgram("c\n", cps);
4852 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4853 bp = &boards[moveNum][i][BOARD_LEFT];
4854 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4855 if (((int) *bp != (int) EmptySquare)
4856 && ((int) *bp >= (int) BlackPawn)) {
4857 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4859 if(message[0] == '+' || message[0] == '~') {
4860 sprintf(message, "%c%c%c+\n",
4861 PieceToChar((ChessSquare)(DEMOTED *bp)),
4864 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4865 message[1] = BOARD_RGHT - 1 - j + '1';
4866 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4868 SendToProgram(message, cps);
4873 SendToProgram(".\n", cps);
4875 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4879 IsPromotion(fromX, fromY, toX, toY)
4880 int fromX, fromY, toX, toY;
4882 /* [HGM] add Shogi promotions */
4883 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4886 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4887 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4888 /* [HGM] Note to self: line above also weeds out drops */
4889 piece = boards[currentMove][fromY][fromX];
4890 if(gameInfo.variant == VariantShogi) {
4891 promotionZoneSize = 3;
4892 highestPromotingPiece = (int)WhiteKing;
4893 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4894 and if in normal chess we then allow promotion to King, why not
4895 allow promotion of other piece in Shogi? */
4897 if((int)piece >= BlackPawn) {
4898 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4900 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4902 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4903 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4905 return ( (int)piece <= highestPromotingPiece );
4909 InPalace(row, column)
4911 { /* [HGM] for Xiangqi */
4912 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4913 column < (BOARD_WIDTH + 4)/2 &&
4914 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4919 PieceForSquare (x, y)
4923 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4926 return boards[currentMove][y][x];
4930 OKToStartUserMove(x, y)
4933 ChessSquare from_piece;
4936 if (matchMode) return FALSE;
4937 if (gameMode == EditPosition) return TRUE;
4939 if (x >= 0 && y >= 0)
4940 from_piece = boards[currentMove][y][x];
4942 from_piece = EmptySquare;
4944 if (from_piece == EmptySquare) return FALSE;
4946 white_piece = (int)from_piece >= (int)WhitePawn &&
4947 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4950 case PlayFromGameFile:
4952 case TwoMachinesPlay:
4960 case MachinePlaysWhite:
4961 case IcsPlayingBlack:
4962 if (appData.zippyPlay) return FALSE;
4964 DisplayMoveError(_("You are playing Black"));
4969 case MachinePlaysBlack:
4970 case IcsPlayingWhite:
4971 if (appData.zippyPlay) return FALSE;
4973 DisplayMoveError(_("You are playing White"));
4979 if (!white_piece && WhiteOnMove(currentMove)) {
4980 DisplayMoveError(_("It is White's turn"));
4983 if (white_piece && !WhiteOnMove(currentMove)) {
4984 DisplayMoveError(_("It is Black's turn"));
4987 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4988 /* Editing correspondence game history */
4989 /* Could disallow this or prompt for confirmation */
4992 if (currentMove < forwardMostMove) {
4993 /* Discarding moves */
4994 /* Could prompt for confirmation here,
4995 but I don't think that's such a good idea */
4996 forwardMostMove = currentMove;
5000 case BeginningOfGame:
5001 if (appData.icsActive) return FALSE;
5002 if (!appData.noChessProgram) {
5004 DisplayMoveError(_("You are playing White"));
5011 if (!white_piece && WhiteOnMove(currentMove)) {
5012 DisplayMoveError(_("It is White's turn"));
5015 if (white_piece && !WhiteOnMove(currentMove)) {
5016 DisplayMoveError(_("It is Black's turn"));
5025 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5026 && gameMode != AnalyzeFile && gameMode != Training) {
5027 DisplayMoveError(_("Displayed position is not current"));
5033 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5034 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5035 int lastLoadGameUseList = FALSE;
5036 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5037 ChessMove lastLoadGameStart = (ChessMove) 0;
5041 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5042 int fromX, fromY, toX, toY;
5046 ChessSquare pdown, pup;
5048 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5049 if ((fromX == toX) && (fromY == toY)) {
5050 return ImpossibleMove;
5053 /* [HGM] suppress all moves into holdings area and guard band */
5054 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5055 return ImpossibleMove;
5057 /* [HGM] <sameColor> moved to here from winboard.c */
5058 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5059 pdown = boards[currentMove][fromY][fromX];
5060 pup = boards[currentMove][toY][toX];
5061 if ( gameMode != EditPosition &&
5062 (WhitePawn <= pdown && pdown < BlackPawn &&
5063 WhitePawn <= pup && pup < BlackPawn ||
5064 BlackPawn <= pdown && pdown < EmptySquare &&
5065 BlackPawn <= pup && pup < EmptySquare
5066 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5067 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5068 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5070 return ImpossibleMove;
5072 /* Check if the user is playing in turn. This is complicated because we
5073 let the user "pick up" a piece before it is his turn. So the piece he
5074 tried to pick up may have been captured by the time he puts it down!
5075 Therefore we use the color the user is supposed to be playing in this
5076 test, not the color of the piece that is currently on the starting
5077 square---except in EditGame mode, where the user is playing both
5078 sides; fortunately there the capture race can't happen. (It can
5079 now happen in IcsExamining mode, but that's just too bad. The user
5080 will get a somewhat confusing message in that case.)
5084 case PlayFromGameFile:
5086 case TwoMachinesPlay:
5090 /* We switched into a game mode where moves are not accepted,
5091 perhaps while the mouse button was down. */
5092 return ImpossibleMove;
5094 case MachinePlaysWhite:
5095 /* User is moving for Black */
5096 if (WhiteOnMove(currentMove)) {
5097 DisplayMoveError(_("It is White's turn"));
5098 return ImpossibleMove;
5102 case MachinePlaysBlack:
5103 /* User is moving for White */
5104 if (!WhiteOnMove(currentMove)) {
5105 DisplayMoveError(_("It is Black's turn"));
5106 return ImpossibleMove;
5112 case BeginningOfGame:
5115 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5116 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5117 /* User is moving for Black */
5118 if (WhiteOnMove(currentMove)) {
5119 DisplayMoveError(_("It is White's turn"));
5120 return ImpossibleMove;
5123 /* User is moving for White */
5124 if (!WhiteOnMove(currentMove)) {
5125 DisplayMoveError(_("It is Black's turn"));
5126 return ImpossibleMove;
5131 case IcsPlayingBlack:
5132 /* User is moving for Black */
5133 if (WhiteOnMove(currentMove)) {
5134 if (!appData.premove) {
5135 DisplayMoveError(_("It is White's turn"));
5136 } else if (toX >= 0 && toY >= 0) {
5139 premoveFromX = fromX;
5140 premoveFromY = fromY;
5141 premovePromoChar = promoChar;
5143 if (appData.debugMode)
5144 fprintf(debugFP, "Got premove: fromX %d,"
5145 "fromY %d, toX %d, toY %d\n",
5146 fromX, fromY, toX, toY);
5148 return ImpossibleMove;
5152 case IcsPlayingWhite:
5153 /* User is moving for White */
5154 if (!WhiteOnMove(currentMove)) {
5155 if (!appData.premove) {
5156 DisplayMoveError(_("It is Black's turn"));
5157 } else if (toX >= 0 && toY >= 0) {
5160 premoveFromX = fromX;
5161 premoveFromY = fromY;
5162 premovePromoChar = promoChar;
5164 if (appData.debugMode)
5165 fprintf(debugFP, "Got premove: fromX %d,"
5166 "fromY %d, toX %d, toY %d\n",
5167 fromX, fromY, toX, toY);
5169 return ImpossibleMove;
5177 /* EditPosition, empty square, or different color piece;
5178 click-click move is possible */
5179 if (toX == -2 || toY == -2) {
5180 boards[0][fromY][fromX] = EmptySquare;
5181 return AmbiguousMove;
5182 } else if (toX >= 0 && toY >= 0) {
5183 boards[0][toY][toX] = boards[0][fromY][fromX];
5184 boards[0][fromY][fromX] = EmptySquare;
5185 return AmbiguousMove;
5187 return ImpossibleMove;
5190 /* [HGM] If move started in holdings, it means a drop */
5191 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5192 if( pup != EmptySquare ) return ImpossibleMove;
5193 if(appData.testLegality) {
5194 /* it would be more logical if LegalityTest() also figured out
5195 * which drops are legal. For now we forbid pawns on back rank.
5196 * Shogi is on its own here...
5198 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5199 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5200 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5202 return WhiteDrop; /* Not needed to specify white or black yet */
5205 userOfferedDraw = FALSE;
5207 /* [HGM] always test for legality, to get promotion info */
5208 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5209 epStatus[currentMove], castlingRights[currentMove],
5210 fromY, fromX, toY, toX, promoChar);
5212 /* [HGM] but possibly ignore an IllegalMove result */
5213 if (appData.testLegality) {
5214 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5215 DisplayMoveError(_("Illegal move"));
5216 return ImpossibleMove;
5219 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5221 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5222 function is made into one that returns an OK move type if FinishMove
5223 should be called. This to give the calling driver routine the
5224 opportunity to finish the userMove input with a promotion popup,
5225 without bothering the user with this for invalid or illegal moves */
5227 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5230 /* Common tail of UserMoveEvent and DropMenuEvent */
5232 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5234 int fromX, fromY, toX, toY;
5235 /*char*/int promoChar;
5238 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5239 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5240 // [HGM] superchess: suppress promotions to non-available piece
5241 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5242 if(WhiteOnMove(currentMove)) {
5243 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5245 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5249 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5250 move type in caller when we know the move is a legal promotion */
5251 if(moveType == NormalMove && promoChar)
5252 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5253 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5254 /* [HGM] convert drag-and-drop piece drops to standard form */
5255 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5256 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5257 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5258 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5259 // fromX = boards[currentMove][fromY][fromX];
5260 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5261 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5262 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5263 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5267 /* [HGM] <popupFix> The following if has been moved here from
5268 UserMoveEvent(). Because it seemed to belon here (why not allow
5269 piece drops in training games?), and because it can only be
5270 performed after it is known to what we promote. */
5271 if (gameMode == Training) {
5272 /* compare the move played on the board to the next move in the
5273 * game. If they match, display the move and the opponent's response.
5274 * If they don't match, display an error message.
5277 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5278 CopyBoard(testBoard, boards[currentMove]);
5279 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5281 if (CompareBoards(testBoard, boards[currentMove+1])) {
5282 ForwardInner(currentMove+1);
5284 /* Autoplay the opponent's response.
5285 * if appData.animate was TRUE when Training mode was entered,
5286 * the response will be animated.
5288 saveAnimate = appData.animate;
5289 appData.animate = animateTraining;
5290 ForwardInner(currentMove+1);
5291 appData.animate = saveAnimate;
5293 /* check for the end of the game */
5294 if (currentMove >= forwardMostMove) {
5295 gameMode = PlayFromGameFile;
5297 SetTrainingModeOff();
5298 DisplayInformation(_("End of game"));
5301 DisplayError(_("Incorrect move"), 0);
5306 /* Ok, now we know that the move is good, so we can kill
5307 the previous line in Analysis Mode */
5308 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5309 forwardMostMove = currentMove;
5312 /* If we need the chess program but it's dead, restart it */
5313 ResurrectChessProgram();
5315 /* A user move restarts a paused game*/
5319 thinkOutput[0] = NULLCHAR;
5321 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5323 if (gameMode == BeginningOfGame) {
5324 if (appData.noChessProgram) {
5325 gameMode = EditGame;
5329 gameMode = MachinePlaysBlack;
5332 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5334 if (first.sendName) {
5335 sprintf(buf, "name %s\n", gameInfo.white);
5336 SendToProgram(buf, &first);
5342 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5343 /* Relay move to ICS or chess engine */
5344 if (appData.icsActive) {
5345 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5346 gameMode == IcsExamining) {
5347 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5351 if (first.sendTime && (gameMode == BeginningOfGame ||
5352 gameMode == MachinePlaysWhite ||
5353 gameMode == MachinePlaysBlack)) {
5354 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5356 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5357 // [HGM] book: if program might be playing, let it use book
5358 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5359 first.maybeThinking = TRUE;
5360 } else SendMoveToProgram(forwardMostMove-1, &first);
5361 if (currentMove == cmailOldMove + 1) {
5362 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5366 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5370 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5371 EP_UNKNOWN, castlingRights[currentMove]) ) {
5377 if (WhiteOnMove(currentMove)) {
5378 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5380 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5384 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5389 case MachinePlaysBlack:
5390 case MachinePlaysWhite:
5391 /* disable certain menu options while machine is thinking */
5392 SetMachineThinkingEnables();
5399 if(bookHit) { // [HGM] book: simulate book reply
5400 static char bookMove[MSG_SIZ]; // a bit generous?
5402 programStats.nodes = programStats.depth = programStats.time =
5403 programStats.score = programStats.got_only_move = 0;
5404 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5406 strcpy(bookMove, "move ");
5407 strcat(bookMove, bookHit);
5408 HandleMachineMove(bookMove, &first);
5414 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5415 int fromX, fromY, toX, toY;
5418 /* [HGM] This routine was added to allow calling of its two logical
5419 parts from other modules in the old way. Before, UserMoveEvent()
5420 automatically called FinishMove() if the move was OK, and returned
5421 otherwise. I separated the two, in order to make it possible to
5422 slip a promotion popup in between. But that it always needs two
5423 calls, to the first part, (now called UserMoveTest() ), and to
5424 FinishMove if the first part succeeded. Calls that do not need
5425 to do anything in between, can call this routine the old way.
5427 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5428 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5429 if(moveType != ImpossibleMove)
5430 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5433 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5435 // char * hint = lastHint;
5436 FrontEndProgramStats stats;
5438 stats.which = cps == &first ? 0 : 1;
5439 stats.depth = cpstats->depth;
5440 stats.nodes = cpstats->nodes;
5441 stats.score = cpstats->score;
5442 stats.time = cpstats->time;
5443 stats.pv = cpstats->movelist;
5444 stats.hint = lastHint;
5445 stats.an_move_index = 0;
5446 stats.an_move_count = 0;
5448 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5449 stats.hint = cpstats->move_name;
5450 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5451 stats.an_move_count = cpstats->nr_moves;
5454 SetProgramStats( &stats );
5457 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5458 { // [HGM] book: this routine intercepts moves to simulate book replies
5459 char *bookHit = NULL;
5461 //first determine if the incoming move brings opponent into his book
5462 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5463 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5464 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5465 if(bookHit != NULL && !cps->bookSuspend) {
5466 // make sure opponent is not going to reply after receiving move to book position
5467 SendToProgram("force\n", cps);
5468 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5470 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5471 // now arrange restart after book miss
5473 // after a book hit we never send 'go', and the code after the call to this routine
5474 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5476 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5477 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5478 SendToProgram(buf, cps);
5479 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5480 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5481 SendToProgram("go\n", cps);
5482 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5483 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5484 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5485 SendToProgram("go\n", cps);
5486 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5488 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5492 ChessProgramState *savedState;
5493 void DeferredBookMove(void)
5495 if(savedState->lastPing != savedState->lastPong)
5496 ScheduleDelayedEvent(DeferredBookMove, 10);
5498 HandleMachineMove(savedMessage, savedState);
5502 HandleMachineMove(message, cps)
5504 ChessProgramState *cps;
5506 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5507 char realname[MSG_SIZ];
5508 int fromX, fromY, toX, toY;
5515 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5517 * Kludge to ignore BEL characters
5519 while (*message == '\007') message++;
5522 * [HGM] engine debug message: ignore lines starting with '#' character
5524 if(cps->debug && *message == '#') return;
5527 * Look for book output
5529 if (cps == &first && bookRequested) {
5530 if (message[0] == '\t' || message[0] == ' ') {
5531 /* Part of the book output is here; append it */
5532 strcat(bookOutput, message);
5533 strcat(bookOutput, " \n");
5535 } else if (bookOutput[0] != NULLCHAR) {
5536 /* All of book output has arrived; display it */
5537 char *p = bookOutput;
5538 while (*p != NULLCHAR) {
5539 if (*p == '\t') *p = ' ';
5542 DisplayInformation(bookOutput);
5543 bookRequested = FALSE;
5544 /* Fall through to parse the current output */
5549 * Look for machine move.
5551 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5552 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5554 /* This method is only useful on engines that support ping */
5555 if (cps->lastPing != cps->lastPong) {
5556 if (gameMode == BeginningOfGame) {
5557 /* Extra move from before last new; ignore */
5558 if (appData.debugMode) {
5559 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5562 if (appData.debugMode) {
5563 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5564 cps->which, gameMode);
5567 SendToProgram("undo\n", cps);
5573 case BeginningOfGame:
5574 /* Extra move from before last reset; ignore */
5575 if (appData.debugMode) {
5576 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5583 /* Extra move after we tried to stop. The mode test is
5584 not a reliable way of detecting this problem, but it's
5585 the best we can do on engines that don't support ping.
5587 if (appData.debugMode) {
5588 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5589 cps->which, gameMode);
5591 SendToProgram("undo\n", cps);
5594 case MachinePlaysWhite:
5595 case IcsPlayingWhite:
5596 machineWhite = TRUE;
5599 case MachinePlaysBlack:
5600 case IcsPlayingBlack:
5601 machineWhite = FALSE;
5604 case TwoMachinesPlay:
5605 machineWhite = (cps->twoMachinesColor[0] == 'w');
5608 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5609 if (appData.debugMode) {
5611 "Ignoring move out of turn by %s, gameMode %d"
5612 ", forwardMost %d\n",
5613 cps->which, gameMode, forwardMostMove);
5618 if (appData.debugMode) { int f = forwardMostMove;
5619 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5620 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5622 if(cps->alphaRank) AlphaRank(machineMove, 4);
5623 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5624 &fromX, &fromY, &toX, &toY, &promoChar)) {
5625 /* Machine move could not be parsed; ignore it. */
5626 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5627 machineMove, cps->which);
5628 DisplayError(buf1, 0);
5629 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5630 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5631 if (gameMode == TwoMachinesPlay) {
5632 GameEnds(machineWhite ? BlackWins : WhiteWins,
5638 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5639 /* So we have to redo legality test with true e.p. status here, */
5640 /* to make sure an illegal e.p. capture does not slip through, */
5641 /* to cause a forfeit on a justified illegal-move complaint */
5642 /* of the opponent. */
5643 if( gameMode==TwoMachinesPlay && appData.testLegality
5644 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5647 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5648 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5649 fromY, fromX, toY, toX, promoChar);
5650 if (appData.debugMode) {
5652 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5653 castlingRights[forwardMostMove][i], castlingRank[i]);
5654 fprintf(debugFP, "castling rights\n");
5656 if(moveType == IllegalMove) {
5657 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5658 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5659 GameEnds(machineWhite ? BlackWins : WhiteWins,
5662 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5663 /* [HGM] Kludge to handle engines that send FRC-style castling
5664 when they shouldn't (like TSCP-Gothic) */
5666 case WhiteASideCastleFR:
5667 case BlackASideCastleFR:
5669 currentMoveString[2]++;
5671 case WhiteHSideCastleFR:
5672 case BlackHSideCastleFR:
5674 currentMoveString[2]--;
5676 default: ; // nothing to do, but suppresses warning of pedantic compilers
5679 hintRequested = FALSE;
5680 lastHint[0] = NULLCHAR;
5681 bookRequested = FALSE;
5682 /* Program may be pondering now */
5683 cps->maybeThinking = TRUE;
5684 if (cps->sendTime == 2) cps->sendTime = 1;
5685 if (cps->offeredDraw) cps->offeredDraw--;
5688 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5690 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5692 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5693 char buf[3*MSG_SIZ];
5695 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5696 programStats.score / 100.,
5698 programStats.time / 100.,
5699 (unsigned int)programStats.nodes,
5700 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5701 programStats.movelist);
5703 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5707 /* currentMoveString is set as a side-effect of ParseOneMove */
5708 strcpy(machineMove, currentMoveString);
5709 strcat(machineMove, "\n");
5710 strcpy(moveList[forwardMostMove], machineMove);
5712 /* [AS] Save move info and clear stats for next move */
5713 pvInfoList[ forwardMostMove ].score = programStats.score;
5714 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5715 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5716 ClearProgramStats();
5717 thinkOutput[0] = NULLCHAR;
5718 hiddenThinkOutputState = 0;
5720 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5722 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5723 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5726 while( count < adjudicateLossPlies ) {
5727 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5730 score = -score; /* Flip score for winning side */
5733 if( score > adjudicateLossThreshold ) {
5740 if( count >= adjudicateLossPlies ) {
5741 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5743 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5744 "Xboard adjudication",
5751 if( gameMode == TwoMachinesPlay ) {
5752 // [HGM] some adjudications useful with buggy engines
5753 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5754 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5757 if( appData.testLegality )
5758 { /* [HGM] Some more adjudications for obstinate engines */
5759 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5760 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5761 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5762 static int moveCount = 6;
5764 char *reason = NULL;
5766 /* Count what is on board. */
5767 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5768 { ChessSquare p = boards[forwardMostMove][i][j];
5772 { /* count B,N,R and other of each side */
5775 NrK++; break; // [HGM] atomic: count Kings
5779 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5780 bishopsColor |= 1 << ((i^j)&1);
5785 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5786 bishopsColor |= 1 << ((i^j)&1);
5801 PawnAdvance += m; NrPawns++;
5803 NrPieces += (p != EmptySquare);
5804 NrW += ((int)p < (int)BlackPawn);
5805 if(gameInfo.variant == VariantXiangqi &&
5806 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5807 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5808 NrW -= ((int)p < (int)BlackPawn);
5812 /* Some material-based adjudications that have to be made before stalemate test */
5813 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5814 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5815 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5816 if(appData.checkMates) {
5817 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5818 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5819 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5820 "Xboard adjudication: King destroyed", GE_XBOARD );
5825 /* Bare King in Shatranj (loses) or Losers (wins) */
5826 if( NrW == 1 || NrPieces - NrW == 1) {
5827 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5828 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5829 if(appData.checkMates) {
5830 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5831 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5832 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5833 "Xboard adjudication: Bare king", GE_XBOARD );
5837 if( gameInfo.variant == VariantShatranj && --bare < 0)
5839 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5840 if(appData.checkMates) {
5841 /* but only adjudicate if adjudication enabled */
5842 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5843 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5844 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5845 "Xboard adjudication: Bare king", GE_XBOARD );
5852 // don't wait for engine to announce game end if we can judge ourselves
5853 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5854 castlingRights[forwardMostMove]) ) {
5856 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5857 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5858 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5859 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5862 reason = "Xboard adjudication: 3rd check";
5863 epStatus[forwardMostMove] = EP_CHECKMATE;
5873 reason = "Xboard adjudication: Stalemate";
5874 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5875 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5876 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5877 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5878 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5879 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5880 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5881 EP_CHECKMATE : EP_WINS);
5882 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5883 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5887 reason = "Xboard adjudication: Checkmate";
5888 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5892 switch(i = epStatus[forwardMostMove]) {
5894 result = GameIsDrawn; break;
5896 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5898 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5900 result = (ChessMove) 0;
5902 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5903 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5904 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5905 GameEnds( result, reason, GE_XBOARD );
5909 /* Next absolutely insufficient mating material. */
5910 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5911 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5912 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5913 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5914 { /* KBK, KNK, KK of KBKB with like Bishops */
5916 /* always flag draws, for judging claims */
5917 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5919 if(appData.materialDraws) {
5920 /* but only adjudicate them if adjudication enabled */
5921 SendToProgram("force\n", cps->other); // suppress reply
5922 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5923 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5924 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5929 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5931 ( NrWR == 1 && NrBR == 1 /* KRKR */
5932 || NrWQ==1 && NrBQ==1 /* KQKQ */
5933 || NrWN==2 || NrBN==2 /* KNNK */
5934 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5936 if(--moveCount < 0 && appData.trivialDraws)
5937 { /* if the first 3 moves do not show a tactical win, declare draw */
5938 SendToProgram("force\n", cps->other); // suppress reply
5939 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5940 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5941 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5944 } else moveCount = 6;
5948 if (appData.debugMode) { int i;
5949 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5950 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5951 appData.drawRepeats);
5952 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5953 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5957 /* Check for rep-draws */
5959 for(k = forwardMostMove-2;
5960 k>=backwardMostMove && k>=forwardMostMove-100 &&
5961 epStatus[k] < EP_UNKNOWN &&
5962 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5966 if (appData.debugMode) {
5967 fprintf(debugFP, " loop\n");
5970 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5972 if (appData.debugMode) {
5973 fprintf(debugFP, "match\n");
5976 /* compare castling rights */
5977 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5978 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5979 rights++; /* King lost rights, while rook still had them */
5980 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5981 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5982 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5983 rights++; /* but at least one rook lost them */
5985 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5986 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5988 if( castlingRights[forwardMostMove][5] >= 0 ) {
5989 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5990 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5994 if (appData.debugMode) {
5995 for(i=0; i<nrCastlingRights; i++)
5996 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5999 if (appData.debugMode) {
6000 fprintf(debugFP, " %d %d\n", rights, k);
6003 if( rights == 0 && ++count > appData.drawRepeats-2
6004 && appData.drawRepeats > 1) {
6005 /* adjudicate after user-specified nr of repeats */
6006 SendToProgram("force\n", cps->other); // suppress reply
6007 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6008 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6009 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6010 // [HGM] xiangqi: check for forbidden perpetuals
6011 int m, ourPerpetual = 1, hisPerpetual = 1;
6012 for(m=forwardMostMove; m>k; m-=2) {
6013 if(MateTest(boards[m], PosFlags(m),
6014 EP_NONE, castlingRights[m]) != MT_CHECK)
6015 ourPerpetual = 0; // the current mover did not always check
6016 if(MateTest(boards[m-1], PosFlags(m-1),
6017 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6018 hisPerpetual = 0; // the opponent did not always check
6020 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6021 ourPerpetual, hisPerpetual);
6022 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6023 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6024 "Xboard adjudication: perpetual checking", GE_XBOARD );
6027 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6028 break; // (or we would have caught him before). Abort repetition-checking loop.
6029 // Now check for perpetual chases
6030 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6031 hisPerpetual = PerpetualChase(k, forwardMostMove);
6032 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6033 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6034 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6035 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6038 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6039 break; // Abort repetition-checking loop.
6041 // if neither of us is checking or chasing all the time, or both are, it is draw
6043 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6046 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6047 epStatus[forwardMostMove] = EP_REP_DRAW;
6051 /* Now we test for 50-move draws. Determine ply count */
6052 count = forwardMostMove;
6053 /* look for last irreversble move */
6054 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6056 /* if we hit starting position, add initial plies */
6057 if( count == backwardMostMove )
6058 count -= initialRulePlies;
6059 count = forwardMostMove - count;
6061 epStatus[forwardMostMove] = EP_RULE_DRAW;
6062 /* this is used to judge if draw claims are legal */
6063 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6064 SendToProgram("force\n", cps->other); // suppress reply
6065 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6066 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6067 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6071 /* if draw offer is pending, treat it as a draw claim
6072 * when draw condition present, to allow engines a way to
6073 * claim draws before making their move to avoid a race
6074 * condition occurring after their move
6076 if( cps->other->offeredDraw || cps->offeredDraw ) {
6078 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6079 p = "Draw claim: 50-move rule";
6080 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6081 p = "Draw claim: 3-fold repetition";
6082 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6083 p = "Draw claim: insufficient mating material";
6085 SendToProgram("force\n", cps->other); // suppress reply
6086 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6087 GameEnds( GameIsDrawn, p, GE_XBOARD );
6088 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6094 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6095 SendToProgram("force\n", cps->other); // suppress reply
6096 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6097 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6099 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6106 if (gameMode == TwoMachinesPlay) {
6107 /* [HGM] relaying draw offers moved to after reception of move */
6108 /* and interpreting offer as claim if it brings draw condition */
6109 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6110 SendToProgram("draw\n", cps->other);
6112 if (cps->other->sendTime) {
6113 SendTimeRemaining(cps->other,
6114 cps->other->twoMachinesColor[0] == 'w');
6116 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6117 if (firstMove && !bookHit) {
6119 if (cps->other->useColors) {
6120 SendToProgram(cps->other->twoMachinesColor, cps->other);
6122 SendToProgram("go\n", cps->other);
6124 cps->other->maybeThinking = TRUE;
6127 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6129 if (!pausing && appData.ringBellAfterMoves) {
6134 * Reenable menu items that were disabled while
6135 * machine was thinking
6137 if (gameMode != TwoMachinesPlay)
6138 SetUserThinkingEnables();
6140 // [HGM] book: after book hit opponent has received move and is now in force mode
6141 // force the book reply into it, and then fake that it outputted this move by jumping
6142 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6144 static char bookMove[MSG_SIZ]; // a bit generous?
6146 strcpy(bookMove, "move ");
6147 strcat(bookMove, bookHit);
6150 programStats.nodes = programStats.depth = programStats.time =
6151 programStats.score = programStats.got_only_move = 0;
6152 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6154 if(cps->lastPing != cps->lastPong) {
6155 savedMessage = message; // args for deferred call
6157 ScheduleDelayedEvent(DeferredBookMove, 10);
6166 /* Set special modes for chess engines. Later something general
6167 * could be added here; for now there is just one kludge feature,
6168 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6169 * when "xboard" is given as an interactive command.
6171 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6172 cps->useSigint = FALSE;
6173 cps->useSigterm = FALSE;
6175 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6176 ParseFeatures(message+8, cps);
6177 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6180 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6181 * want this, I was asked to put it in, and obliged.
6183 if (!strncmp(message, "setboard ", 9)) {
6184 Board initial_position; int i;
6186 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6188 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6189 DisplayError(_("Bad FEN received from engine"), 0);
6192 Reset(FALSE, FALSE);
6193 CopyBoard(boards[0], initial_position);
6194 initialRulePlies = FENrulePlies;
6195 epStatus[0] = FENepStatus;
6196 for( i=0; i<nrCastlingRights; i++ )
6197 castlingRights[0][i] = FENcastlingRights[i];
6198 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6199 else gameMode = MachinePlaysBlack;
6200 DrawPosition(FALSE, boards[currentMove]);
6206 * Look for communication commands
6208 if (!strncmp(message, "telluser ", 9)) {
6209 DisplayNote(message + 9);
6212 if (!strncmp(message, "tellusererror ", 14)) {
6213 DisplayError(message + 14, 0);
6216 if (!strncmp(message, "tellopponent ", 13)) {
6217 if (appData.icsActive) {
6219 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6223 DisplayNote(message + 13);
6227 if (!strncmp(message, "tellothers ", 11)) {
6228 if (appData.icsActive) {
6230 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6236 if (!strncmp(message, "tellall ", 8)) {
6237 if (appData.icsActive) {
6239 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6243 DisplayNote(message + 8);
6247 if (strncmp(message, "warning", 7) == 0) {
6248 /* Undocumented feature, use tellusererror in new code */
6249 DisplayError(message, 0);
6252 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6253 strcpy(realname, cps->tidy);
6254 strcat(realname, " query");
6255 AskQuestion(realname, buf2, buf1, cps->pr);
6258 /* Commands from the engine directly to ICS. We don't allow these to be
6259 * sent until we are logged on. Crafty kibitzes have been known to
6260 * interfere with the login process.
6263 if (!strncmp(message, "tellics ", 8)) {
6264 SendToICS(message + 8);
6268 if (!strncmp(message, "tellicsnoalias ", 15)) {
6269 SendToICS(ics_prefix);
6270 SendToICS(message + 15);
6274 /* The following are for backward compatibility only */
6275 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6276 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6277 SendToICS(ics_prefix);
6283 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6287 * If the move is illegal, cancel it and redraw the board.
6288 * Also deal with other error cases. Matching is rather loose
6289 * here to accommodate engines written before the spec.
6291 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6292 strncmp(message, "Error", 5) == 0) {
6293 if (StrStr(message, "name") ||
6294 StrStr(message, "rating") || StrStr(message, "?") ||
6295 StrStr(message, "result") || StrStr(message, "board") ||
6296 StrStr(message, "bk") || StrStr(message, "computer") ||
6297 StrStr(message, "variant") || StrStr(message, "hint") ||
6298 StrStr(message, "random") || StrStr(message, "depth") ||
6299 StrStr(message, "accepted")) {
6302 if (StrStr(message, "protover")) {
6303 /* Program is responding to input, so it's apparently done
6304 initializing, and this error message indicates it is
6305 protocol version 1. So we don't need to wait any longer
6306 for it to initialize and send feature commands. */
6307 FeatureDone(cps, 1);
6308 cps->protocolVersion = 1;
6311 cps->maybeThinking = FALSE;
6313 if (StrStr(message, "draw")) {
6314 /* Program doesn't have "draw" command */
6315 cps->sendDrawOffers = 0;
6318 if (cps->sendTime != 1 &&
6319 (StrStr(message, "time") || StrStr(message, "otim"))) {
6320 /* Program apparently doesn't have "time" or "otim" command */
6324 if (StrStr(message, "analyze")) {
6325 cps->analysisSupport = FALSE;
6326 cps->analyzing = FALSE;
6328 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6329 DisplayError(buf2, 0);
6332 if (StrStr(message, "(no matching move)st")) {
6333 /* Special kludge for GNU Chess 4 only */
6334 cps->stKludge = TRUE;
6335 SendTimeControl(cps, movesPerSession, timeControl,
6336 timeIncrement, appData.searchDepth,
6340 if (StrStr(message, "(no matching move)sd")) {
6341 /* Special kludge for GNU Chess 4 only */
6342 cps->sdKludge = TRUE;
6343 SendTimeControl(cps, movesPerSession, timeControl,
6344 timeIncrement, appData.searchDepth,
6348 if (!StrStr(message, "llegal")) {
6351 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6352 gameMode == IcsIdle) return;
6353 if (forwardMostMove <= backwardMostMove) return;
6355 /* Following removed: it caused a bug where a real illegal move
6356 message in analyze mored would be ignored. */
6357 if (cps == &first && programStats.ok_to_send == 0) {
6358 /* Bogus message from Crafty responding to "." This filtering
6359 can miss some of the bad messages, but fortunately the bug
6360 is fixed in current Crafty versions, so it doesn't matter. */
6364 if (pausing) PauseEvent();
6365 if (gameMode == PlayFromGameFile) {
6366 /* Stop reading this game file */
6367 gameMode = EditGame;
6370 currentMove = --forwardMostMove;
6371 DisplayMove(currentMove-1); /* before DisplayMoveError */
6373 DisplayBothClocks();
6374 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6375 parseList[currentMove], cps->which);
6376 DisplayMoveError(buf1);
6377 DrawPosition(FALSE, boards[currentMove]);
6379 /* [HGM] illegal-move claim should forfeit game when Xboard */
6380 /* only passes fully legal moves */
6381 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6382 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6383 "False illegal-move claim", GE_XBOARD );
6387 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6388 /* Program has a broken "time" command that
6389 outputs a string not ending in newline.
6395 * If chess program startup fails, exit with an error message.
6396 * Attempts to recover here are futile.
6398 if ((StrStr(message, "unknown host") != NULL)
6399 || (StrStr(message, "No remote directory") != NULL)
6400 || (StrStr(message, "not found") != NULL)
6401 || (StrStr(message, "No such file") != NULL)
6402 || (StrStr(message, "can't alloc") != NULL)
6403 || (StrStr(message, "Permission denied") != NULL)) {
6405 cps->maybeThinking = FALSE;
6406 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6407 cps->which, cps->program, cps->host, message);
6408 RemoveInputSource(cps->isr);
6409 DisplayFatalError(buf1, 0, 1);
6414 * Look for hint output
6416 if (sscanf(message, "Hint: %s", buf1) == 1) {
6417 if (cps == &first && hintRequested) {
6418 hintRequested = FALSE;
6419 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6420 &fromX, &fromY, &toX, &toY, &promoChar)) {
6421 (void) CoordsToAlgebraic(boards[forwardMostMove],
6422 PosFlags(forwardMostMove), EP_UNKNOWN,
6423 fromY, fromX, toY, toX, promoChar, buf1);
6424 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6425 DisplayInformation(buf2);
6427 /* Hint move could not be parsed!? */
6428 snprintf(buf2, sizeof(buf2),
6429 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6431 DisplayError(buf2, 0);
6434 strcpy(lastHint, buf1);
6440 * Ignore other messages if game is not in progress
6442 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6443 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6446 * look for win, lose, draw, or draw offer
6448 if (strncmp(message, "1-0", 3) == 0) {
6449 char *p, *q, *r = "";
6450 p = strchr(message, '{');
6458 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6460 } else if (strncmp(message, "0-1", 3) == 0) {
6461 char *p, *q, *r = "";
6462 p = strchr(message, '{');
6470 /* Kludge for Arasan 4.1 bug */
6471 if (strcmp(r, "Black resigns") == 0) {
6472 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6475 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6477 } else if (strncmp(message, "1/2", 3) == 0) {
6478 char *p, *q, *r = "";
6479 p = strchr(message, '{');
6488 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6491 } else if (strncmp(message, "White resign", 12) == 0) {
6492 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6494 } else if (strncmp(message, "Black resign", 12) == 0) {
6495 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6497 } else if (strncmp(message, "White matches", 13) == 0 ||
6498 strncmp(message, "Black matches", 13) == 0 ) {
6499 /* [HGM] ignore GNUShogi noises */
6501 } else if (strncmp(message, "White", 5) == 0 &&
6502 message[5] != '(' &&
6503 StrStr(message, "Black") == NULL) {
6504 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6506 } else if (strncmp(message, "Black", 5) == 0 &&
6507 message[5] != '(') {
6508 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6510 } else if (strcmp(message, "resign") == 0 ||
6511 strcmp(message, "computer resigns") == 0) {
6513 case MachinePlaysBlack:
6514 case IcsPlayingBlack:
6515 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6517 case MachinePlaysWhite:
6518 case IcsPlayingWhite:
6519 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6521 case TwoMachinesPlay:
6522 if (cps->twoMachinesColor[0] == 'w')
6523 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6525 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6532 } else if (strncmp(message, "opponent mates", 14) == 0) {
6534 case MachinePlaysBlack:
6535 case IcsPlayingBlack:
6536 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6538 case MachinePlaysWhite:
6539 case IcsPlayingWhite:
6540 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6542 case TwoMachinesPlay:
6543 if (cps->twoMachinesColor[0] == 'w')
6544 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6546 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6553 } else if (strncmp(message, "computer mates", 14) == 0) {
6555 case MachinePlaysBlack:
6556 case IcsPlayingBlack:
6557 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6559 case MachinePlaysWhite:
6560 case IcsPlayingWhite:
6561 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6563 case TwoMachinesPlay:
6564 if (cps->twoMachinesColor[0] == 'w')
6565 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6567 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6574 } else if (strncmp(message, "checkmate", 9) == 0) {
6575 if (WhiteOnMove(forwardMostMove)) {
6576 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6578 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6581 } else if (strstr(message, "Draw") != NULL ||
6582 strstr(message, "game is a draw") != NULL) {
6583 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6585 } else if (strstr(message, "offer") != NULL &&
6586 strstr(message, "draw") != NULL) {
6588 if (appData.zippyPlay && first.initDone) {
6589 /* Relay offer to ICS */
6590 SendToICS(ics_prefix);
6591 SendToICS("draw\n");
6594 cps->offeredDraw = 2; /* valid until this engine moves twice */
6595 if (gameMode == TwoMachinesPlay) {
6596 if (cps->other->offeredDraw) {
6597 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6598 /* [HGM] in two-machine mode we delay relaying draw offer */
6599 /* until after we also have move, to see if it is really claim */
6603 if (cps->other->sendDrawOffers) {
6604 SendToProgram("draw\n", cps->other);
6608 } else if (gameMode == MachinePlaysWhite ||
6609 gameMode == MachinePlaysBlack) {
6610 if (userOfferedDraw) {
6611 DisplayInformation(_("Machine accepts your draw offer"));
6612 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6614 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6621 * Look for thinking output
6623 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6624 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6626 int plylev, mvleft, mvtot, curscore, time;
6627 char mvname[MOVE_LEN];
6631 int prefixHint = FALSE;
6632 mvname[0] = NULLCHAR;
6635 case MachinePlaysBlack:
6636 case IcsPlayingBlack:
6637 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6639 case MachinePlaysWhite:
6640 case IcsPlayingWhite:
6641 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6646 case IcsObserving: /* [DM] icsEngineAnalyze */
6647 if (!appData.icsEngineAnalyze) ignore = TRUE;
6649 case TwoMachinesPlay:
6650 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6661 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6662 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6664 if (plyext != ' ' && plyext != '\t') {
6668 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6669 if( cps->scoreIsAbsolute &&
6670 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6672 curscore = -curscore;
6676 programStats.depth = plylev;
6677 programStats.nodes = nodes;
6678 programStats.time = time;
6679 programStats.score = curscore;
6680 programStats.got_only_move = 0;
6682 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6685 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6686 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6687 if(WhiteOnMove(forwardMostMove))
6688 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6689 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6692 /* Buffer overflow protection */
6693 if (buf1[0] != NULLCHAR) {
6694 if (strlen(buf1) >= sizeof(programStats.movelist)
6695 && appData.debugMode) {
6697 "PV is too long; using the first %d bytes.\n",
6698 sizeof(programStats.movelist) - 1);
6701 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6703 sprintf(programStats.movelist, " no PV\n");
6706 if (programStats.seen_stat) {
6707 programStats.ok_to_send = 1;
6710 if (strchr(programStats.movelist, '(') != NULL) {
6711 programStats.line_is_book = 1;
6712 programStats.nr_moves = 0;
6713 programStats.moves_left = 0;
6715 programStats.line_is_book = 0;
6718 SendProgramStatsToFrontend( cps, &programStats );
6721 [AS] Protect the thinkOutput buffer from overflow... this
6722 is only useful if buf1 hasn't overflowed first!
6724 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6726 (gameMode == TwoMachinesPlay ?
6727 ToUpper(cps->twoMachinesColor[0]) : ' '),
6728 ((double) curscore) / 100.0,
6729 prefixHint ? lastHint : "",
6730 prefixHint ? " " : "" );
6732 if( buf1[0] != NULLCHAR ) {
6733 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6735 if( strlen(buf1) > max_len ) {
6736 if( appData.debugMode) {
6737 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6739 buf1[max_len+1] = '\0';
6742 strcat( thinkOutput, buf1 );
6745 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6746 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6747 DisplayMove(currentMove - 1);
6752 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6753 /* crafty (9.25+) says "(only move) <move>"
6754 * if there is only 1 legal move
6756 sscanf(p, "(only move) %s", buf1);
6757 sprintf(thinkOutput, "%s (only move)", buf1);
6758 sprintf(programStats.movelist, "%s (only move)", buf1);
6759 programStats.depth = 1;
6760 programStats.nr_moves = 1;
6761 programStats.moves_left = 1;
6762 programStats.nodes = 1;
6763 programStats.time = 1;
6764 programStats.got_only_move = 1;
6766 /* Not really, but we also use this member to
6767 mean "line isn't going to change" (Crafty
6768 isn't searching, so stats won't change) */
6769 programStats.line_is_book = 1;
6771 SendProgramStatsToFrontend( cps, &programStats );
6773 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6774 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6775 DisplayMove(currentMove - 1);
6779 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6780 &time, &nodes, &plylev, &mvleft,
6781 &mvtot, mvname) >= 5) {
6782 /* The stat01: line is from Crafty (9.29+) in response
6783 to the "." command */
6784 programStats.seen_stat = 1;
6785 cps->maybeThinking = TRUE;
6787 if (programStats.got_only_move || !appData.periodicUpdates)
6790 programStats.depth = plylev;
6791 programStats.time = time;
6792 programStats.nodes = nodes;
6793 programStats.moves_left = mvleft;
6794 programStats.nr_moves = mvtot;
6795 strcpy(programStats.move_name, mvname);
6796 programStats.ok_to_send = 1;
6797 programStats.movelist[0] = '\0';
6799 SendProgramStatsToFrontend( cps, &programStats );
6804 } else if (strncmp(message,"++",2) == 0) {
6805 /* Crafty 9.29+ outputs this */
6806 programStats.got_fail = 2;
6809 } else if (strncmp(message,"--",2) == 0) {
6810 /* Crafty 9.29+ outputs this */
6811 programStats.got_fail = 1;
6814 } else if (thinkOutput[0] != NULLCHAR &&
6815 strncmp(message, " ", 4) == 0) {
6816 unsigned message_len;
6819 while (*p && *p == ' ') p++;
6821 message_len = strlen( p );
6823 /* [AS] Avoid buffer overflow */
6824 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6825 strcat(thinkOutput, " ");
6826 strcat(thinkOutput, p);
6829 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6830 strcat(programStats.movelist, " ");
6831 strcat(programStats.movelist, p);
6834 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6835 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6836 DisplayMove(currentMove - 1);
6845 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6846 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6848 ChessProgramStats cpstats;
6850 if (plyext != ' ' && plyext != '\t') {
6854 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6855 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6856 curscore = -curscore;
6859 cpstats.depth = plylev;
6860 cpstats.nodes = nodes;
6861 cpstats.time = time;
6862 cpstats.score = curscore;
6863 cpstats.got_only_move = 0;
6864 cpstats.movelist[0] = '\0';
6866 if (buf1[0] != NULLCHAR) {
6867 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6870 cpstats.ok_to_send = 0;
6871 cpstats.line_is_book = 0;
6872 cpstats.nr_moves = 0;
6873 cpstats.moves_left = 0;
6875 SendProgramStatsToFrontend( cps, &cpstats );
6882 /* Parse a game score from the character string "game", and
6883 record it as the history of the current game. The game
6884 score is NOT assumed to start from the standard position.
6885 The display is not updated in any way.
6888 ParseGameHistory(game)
6892 int fromX, fromY, toX, toY, boardIndex;
6897 if (appData.debugMode)
6898 fprintf(debugFP, "Parsing game history: %s\n", game);
6900 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6901 gameInfo.site = StrSave(appData.icsHost);
6902 gameInfo.date = PGNDate();
6903 gameInfo.round = StrSave("-");
6905 /* Parse out names of players */
6906 while (*game == ' ') game++;
6908 while (*game != ' ') *p++ = *game++;
6910 gameInfo.white = StrSave(buf);
6911 while (*game == ' ') game++;
6913 while (*game != ' ' && *game != '\n') *p++ = *game++;
6915 gameInfo.black = StrSave(buf);
6918 boardIndex = blackPlaysFirst ? 1 : 0;
6921 yyboardindex = boardIndex;
6922 moveType = (ChessMove) yylex();
6924 case IllegalMove: /* maybe suicide chess, etc. */
6925 if (appData.debugMode) {
6926 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6927 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6928 setbuf(debugFP, NULL);
6930 case WhitePromotionChancellor:
6931 case BlackPromotionChancellor:
6932 case WhitePromotionArchbishop:
6933 case BlackPromotionArchbishop:
6934 case WhitePromotionQueen:
6935 case BlackPromotionQueen:
6936 case WhitePromotionRook:
6937 case BlackPromotionRook:
6938 case WhitePromotionBishop:
6939 case BlackPromotionBishop:
6940 case WhitePromotionKnight:
6941 case BlackPromotionKnight:
6942 case WhitePromotionKing:
6943 case BlackPromotionKing:
6945 case WhiteCapturesEnPassant:
6946 case BlackCapturesEnPassant:
6947 case WhiteKingSideCastle:
6948 case WhiteQueenSideCastle:
6949 case BlackKingSideCastle:
6950 case BlackQueenSideCastle:
6951 case WhiteKingSideCastleWild:
6952 case WhiteQueenSideCastleWild:
6953 case BlackKingSideCastleWild:
6954 case BlackQueenSideCastleWild:
6956 case WhiteHSideCastleFR:
6957 case WhiteASideCastleFR:
6958 case BlackHSideCastleFR:
6959 case BlackASideCastleFR:
6961 fromX = currentMoveString[0] - AAA;
6962 fromY = currentMoveString[1] - ONE;
6963 toX = currentMoveString[2] - AAA;
6964 toY = currentMoveString[3] - ONE;
6965 promoChar = currentMoveString[4];
6969 fromX = moveType == WhiteDrop ?
6970 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6971 (int) CharToPiece(ToLower(currentMoveString[0]));
6973 toX = currentMoveString[2] - AAA;
6974 toY = currentMoveString[3] - ONE;
6975 promoChar = NULLCHAR;
6979 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6980 if (appData.debugMode) {
6981 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6982 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6983 setbuf(debugFP, NULL);
6985 DisplayError(buf, 0);
6987 case ImpossibleMove:
6989 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6990 if (appData.debugMode) {
6991 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6992 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6993 setbuf(debugFP, NULL);
6995 DisplayError(buf, 0);
6997 case (ChessMove) 0: /* end of file */
6998 if (boardIndex < backwardMostMove) {
6999 /* Oops, gap. How did that happen? */
7000 DisplayError(_("Gap in move list"), 0);
7003 backwardMostMove = blackPlaysFirst ? 1 : 0;
7004 if (boardIndex > forwardMostMove) {
7005 forwardMostMove = boardIndex;
7009 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7010 strcat(parseList[boardIndex-1], " ");
7011 strcat(parseList[boardIndex-1], yy_text);
7023 case GameUnfinished:
7024 if (gameMode == IcsExamining) {
7025 if (boardIndex < backwardMostMove) {
7026 /* Oops, gap. How did that happen? */
7029 backwardMostMove = blackPlaysFirst ? 1 : 0;
7032 gameInfo.result = moveType;
7033 p = strchr(yy_text, '{');
7034 if (p == NULL) p = strchr(yy_text, '(');
7037 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7039 q = strchr(p, *p == '{' ? '}' : ')');
7040 if (q != NULL) *q = NULLCHAR;
7043 gameInfo.resultDetails = StrSave(p);
7046 if (boardIndex >= forwardMostMove &&
7047 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7048 backwardMostMove = blackPlaysFirst ? 1 : 0;
7051 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7052 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7053 parseList[boardIndex]);
7054 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7055 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7056 /* currentMoveString is set as a side-effect of yylex */
7057 strcpy(moveList[boardIndex], currentMoveString);
7058 strcat(moveList[boardIndex], "\n");
7060 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7061 castlingRights[boardIndex], &epStatus[boardIndex]);
7062 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7063 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7069 if(gameInfo.variant != VariantShogi)
7070 strcat(parseList[boardIndex - 1], "+");
7074 strcat(parseList[boardIndex - 1], "#");
7081 /* Apply a move to the given board */
7083 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7084 int fromX, fromY, toX, toY;
7090 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7092 /* [HGM] compute & store e.p. status and castling rights for new position */
7093 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7096 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7100 if( board[toY][toX] != EmptySquare )
7103 if( board[fromY][fromX] == WhitePawn ) {
7104 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7107 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7108 gameInfo.variant != VariantBerolina || toX < fromX)
7109 *ep = toX | berolina;
7110 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7111 gameInfo.variant != VariantBerolina || toX > fromX)
7115 if( board[fromY][fromX] == BlackPawn ) {
7116 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7118 if( toY-fromY== -2) {
7119 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7120 gameInfo.variant != VariantBerolina || toX < fromX)
7121 *ep = toX | berolina;
7122 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7123 gameInfo.variant != VariantBerolina || toX > fromX)
7128 for(i=0; i<nrCastlingRights; i++) {
7129 if(castling[i] == fromX && castlingRank[i] == fromY ||
7130 castling[i] == toX && castlingRank[i] == toY
7131 ) castling[i] = -1; // revoke for moved or captured piece
7136 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7137 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7138 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7140 if (fromX == toX && fromY == toY) return;
7142 if (fromY == DROP_RANK) {
7144 piece = board[toY][toX] = (ChessSquare) fromX;
7146 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7147 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7148 if(gameInfo.variant == VariantKnightmate)
7149 king += (int) WhiteUnicorn - (int) WhiteKing;
7151 /* Code added by Tord: */
7152 /* FRC castling assumed when king captures friendly rook. */
7153 if (board[fromY][fromX] == WhiteKing &&
7154 board[toY][toX] == WhiteRook) {
7155 board[fromY][fromX] = EmptySquare;
7156 board[toY][toX] = EmptySquare;
7158 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7160 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7162 } else if (board[fromY][fromX] == BlackKing &&
7163 board[toY][toX] == BlackRook) {
7164 board[fromY][fromX] = EmptySquare;
7165 board[toY][toX] = EmptySquare;
7167 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7169 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7171 /* End of code added by Tord */
7173 } else if (board[fromY][fromX] == king
7174 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7175 && toY == fromY && toX > fromX+1) {
7176 board[fromY][fromX] = EmptySquare;
7177 board[toY][toX] = king;
7178 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7179 board[fromY][BOARD_RGHT-1] = EmptySquare;
7180 } else if (board[fromY][fromX] == king
7181 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7182 && toY == fromY && toX < fromX-1) {
7183 board[fromY][fromX] = EmptySquare;
7184 board[toY][toX] = king;
7185 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7186 board[fromY][BOARD_LEFT] = EmptySquare;
7187 } else if (board[fromY][fromX] == WhitePawn
7188 && toY == BOARD_HEIGHT-1
7189 && gameInfo.variant != VariantXiangqi
7191 /* white pawn promotion */
7192 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7193 if (board[toY][toX] == EmptySquare) {
7194 board[toY][toX] = WhiteQueen;
7196 if(gameInfo.variant==VariantBughouse ||
7197 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7198 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7199 board[fromY][fromX] = EmptySquare;
7200 } else if ((fromY == BOARD_HEIGHT-4)
7202 && gameInfo.variant != VariantXiangqi
7203 && gameInfo.variant != VariantBerolina
7204 && (board[fromY][fromX] == WhitePawn)
7205 && (board[toY][toX] == EmptySquare)) {
7206 board[fromY][fromX] = EmptySquare;
7207 board[toY][toX] = WhitePawn;
7208 captured = board[toY - 1][toX];
7209 board[toY - 1][toX] = EmptySquare;
7210 } else if ((fromY == BOARD_HEIGHT-4)
7212 && gameInfo.variant == VariantBerolina
7213 && (board[fromY][fromX] == WhitePawn)
7214 && (board[toY][toX] == EmptySquare)) {
7215 board[fromY][fromX] = EmptySquare;
7216 board[toY][toX] = WhitePawn;
7217 if(oldEP & EP_BEROLIN_A) {
7218 captured = board[fromY][fromX-1];
7219 board[fromY][fromX-1] = EmptySquare;
7220 }else{ captured = board[fromY][fromX+1];
7221 board[fromY][fromX+1] = EmptySquare;
7223 } else if (board[fromY][fromX] == king
7224 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7225 && toY == fromY && toX > fromX+1) {
7226 board[fromY][fromX] = EmptySquare;
7227 board[toY][toX] = king;
7228 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7229 board[fromY][BOARD_RGHT-1] = EmptySquare;
7230 } else if (board[fromY][fromX] == king
7231 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7232 && toY == fromY && toX < fromX-1) {
7233 board[fromY][fromX] = EmptySquare;
7234 board[toY][toX] = king;
7235 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7236 board[fromY][BOARD_LEFT] = EmptySquare;
7237 } else if (fromY == 7 && fromX == 3
7238 && board[fromY][fromX] == BlackKing
7239 && toY == 7 && toX == 5) {
7240 board[fromY][fromX] = EmptySquare;
7241 board[toY][toX] = BlackKing;
7242 board[fromY][7] = EmptySquare;
7243 board[toY][4] = BlackRook;
7244 } else if (fromY == 7 && fromX == 3
7245 && board[fromY][fromX] == BlackKing
7246 && toY == 7 && toX == 1) {
7247 board[fromY][fromX] = EmptySquare;
7248 board[toY][toX] = BlackKing;
7249 board[fromY][0] = EmptySquare;
7250 board[toY][2] = BlackRook;
7251 } else if (board[fromY][fromX] == BlackPawn
7253 && gameInfo.variant != VariantXiangqi
7255 /* black pawn promotion */
7256 board[0][toX] = CharToPiece(ToLower(promoChar));
7257 if (board[0][toX] == EmptySquare) {
7258 board[0][toX] = BlackQueen;
7260 if(gameInfo.variant==VariantBughouse ||
7261 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7262 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7263 board[fromY][fromX] = EmptySquare;
7264 } else if ((fromY == 3)
7266 && gameInfo.variant != VariantXiangqi
7267 && gameInfo.variant != VariantBerolina
7268 && (board[fromY][fromX] == BlackPawn)
7269 && (board[toY][toX] == EmptySquare)) {
7270 board[fromY][fromX] = EmptySquare;
7271 board[toY][toX] = BlackPawn;
7272 captured = board[toY + 1][toX];
7273 board[toY + 1][toX] = EmptySquare;
7274 } else if ((fromY == 3)
7276 && gameInfo.variant == VariantBerolina
7277 && (board[fromY][fromX] == BlackPawn)
7278 && (board[toY][toX] == EmptySquare)) {
7279 board[fromY][fromX] = EmptySquare;
7280 board[toY][toX] = BlackPawn;
7281 if(oldEP & EP_BEROLIN_A) {
7282 captured = board[fromY][fromX-1];
7283 board[fromY][fromX-1] = EmptySquare;
7284 }else{ captured = board[fromY][fromX+1];
7285 board[fromY][fromX+1] = EmptySquare;
7288 board[toY][toX] = board[fromY][fromX];
7289 board[fromY][fromX] = EmptySquare;
7292 /* [HGM] now we promote for Shogi, if needed */
7293 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7294 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7297 if (gameInfo.holdingsWidth != 0) {
7299 /* !!A lot more code needs to be written to support holdings */
7300 /* [HGM] OK, so I have written it. Holdings are stored in the */
7301 /* penultimate board files, so they are automaticlly stored */
7302 /* in the game history. */
7303 if (fromY == DROP_RANK) {
7304 /* Delete from holdings, by decreasing count */
7305 /* and erasing image if necessary */
7307 if(p < (int) BlackPawn) { /* white drop */
7308 p -= (int)WhitePawn;
7309 if(p >= gameInfo.holdingsSize) p = 0;
7310 if(--board[p][BOARD_WIDTH-2] == 0)
7311 board[p][BOARD_WIDTH-1] = EmptySquare;
7312 } else { /* black drop */
7313 p -= (int)BlackPawn;
7314 if(p >= gameInfo.holdingsSize) p = 0;
7315 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7316 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7319 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7320 && gameInfo.variant != VariantBughouse ) {
7321 /* [HGM] holdings: Add to holdings, if holdings exist */
7322 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7323 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7324 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7327 if (p >= (int) BlackPawn) {
7328 p -= (int)BlackPawn;
7329 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7330 /* in Shogi restore piece to its original first */
7331 captured = (ChessSquare) (DEMOTED captured);
7334 p = PieceToNumber((ChessSquare)p);
7335 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7336 board[p][BOARD_WIDTH-2]++;
7337 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7339 p -= (int)WhitePawn;
7340 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7341 captured = (ChessSquare) (DEMOTED captured);
7344 p = PieceToNumber((ChessSquare)p);
7345 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7346 board[BOARD_HEIGHT-1-p][1]++;
7347 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7351 } else if (gameInfo.variant == VariantAtomic) {
7352 if (captured != EmptySquare) {
7354 for (y = toY-1; y <= toY+1; y++) {
7355 for (x = toX-1; x <= toX+1; x++) {
7356 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7357 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7358 board[y][x] = EmptySquare;
7362 board[toY][toX] = EmptySquare;
7365 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7366 /* [HGM] Shogi promotions */
7367 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7370 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7371 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7372 // [HGM] superchess: take promotion piece out of holdings
7373 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7374 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7375 if(!--board[k][BOARD_WIDTH-2])
7376 board[k][BOARD_WIDTH-1] = EmptySquare;
7378 if(!--board[BOARD_HEIGHT-1-k][1])
7379 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7385 /* Updates forwardMostMove */
7387 MakeMove(fromX, fromY, toX, toY, promoChar)
7388 int fromX, fromY, toX, toY;
7391 // forwardMostMove++; // [HGM] bare: moved downstream
7393 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7394 int timeLeft; static int lastLoadFlag=0; int king, piece;
7395 piece = boards[forwardMostMove][fromY][fromX];
7396 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7397 if(gameInfo.variant == VariantKnightmate)
7398 king += (int) WhiteUnicorn - (int) WhiteKing;
7399 if(forwardMostMove == 0) {
7401 fprintf(serverMoves, "%s;", second.tidy);
7402 fprintf(serverMoves, "%s;", first.tidy);
7403 if(!blackPlaysFirst)
7404 fprintf(serverMoves, "%s;", second.tidy);
7405 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7406 lastLoadFlag = loadFlag;
7408 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7409 // print castling suffix
7410 if( toY == fromY && piece == king ) {
7412 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7414 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7417 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7418 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7419 boards[forwardMostMove][toY][toX] == EmptySquare
7421 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7423 if(promoChar != NULLCHAR)
7424 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7426 fprintf(serverMoves, "/%d/%d",
7427 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7428 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7429 else timeLeft = blackTimeRemaining/1000;
7430 fprintf(serverMoves, "/%d", timeLeft);
7432 fflush(serverMoves);
7435 if (forwardMostMove+1 >= MAX_MOVES) {
7436 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7441 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7442 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7443 if (commentList[forwardMostMove+1] != NULL) {
7444 free(commentList[forwardMostMove+1]);
7445 commentList[forwardMostMove+1] = NULL;
7447 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7448 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7449 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7450 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7451 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7452 gameInfo.result = GameUnfinished;
7453 if (gameInfo.resultDetails != NULL) {
7454 free(gameInfo.resultDetails);
7455 gameInfo.resultDetails = NULL;
7457 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7458 moveList[forwardMostMove - 1]);
7459 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7460 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7461 fromY, fromX, toY, toX, promoChar,
7462 parseList[forwardMostMove - 1]);
7463 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7464 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7465 castlingRights[forwardMostMove]) ) {
7471 if(gameInfo.variant != VariantShogi)
7472 strcat(parseList[forwardMostMove - 1], "+");
7476 strcat(parseList[forwardMostMove - 1], "#");
7479 if (appData.debugMode) {
7480 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7485 /* Updates currentMove if not pausing */
7487 ShowMove(fromX, fromY, toX, toY)
7489 int instant = (gameMode == PlayFromGameFile) ?
7490 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7491 if(appData.noGUI) return;
7492 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7494 if (forwardMostMove == currentMove + 1) {
7495 AnimateMove(boards[forwardMostMove - 1],
7496 fromX, fromY, toX, toY);
7498 if (appData.highlightLastMove) {
7499 SetHighlights(fromX, fromY, toX, toY);
7502 currentMove = forwardMostMove;
7505 if (instant) return;
7507 DisplayMove(currentMove - 1);
7508 DrawPosition(FALSE, boards[currentMove]);
7509 DisplayBothClocks();
7510 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7513 void SendEgtPath(ChessProgramState *cps)
7514 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7515 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7517 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7520 char c, *q = name+1, *r, *s;
7522 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7523 while(*p && *p != ',') *q++ = *p++;
7525 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7526 strcmp(name, ",nalimov:") == 0 ) {
7527 // take nalimov path from the menu-changeable option first, if it is defined
7528 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7529 SendToProgram(buf,cps); // send egtbpath command for nalimov
7531 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7532 (s = StrStr(appData.egtFormats, name)) != NULL) {
7533 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7534 s = r = StrStr(s, ":") + 1; // beginning of path info
7535 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7536 c = *r; *r = 0; // temporarily null-terminate path info
7537 *--q = 0; // strip of trailig ':' from name
7538 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7540 SendToProgram(buf,cps); // send egtbpath command for this format
7542 if(*p == ',') p++; // read away comma to position for next format name
7547 InitChessProgram(cps, setup)
7548 ChessProgramState *cps;
7549 int setup; /* [HGM] needed to setup FRC opening position */
7551 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7552 if (appData.noChessProgram) return;
7553 hintRequested = FALSE;
7554 bookRequested = FALSE;
7556 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7557 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7558 if(cps->memSize) { /* [HGM] memory */
7559 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7560 SendToProgram(buf, cps);
7562 SendEgtPath(cps); /* [HGM] EGT */
7563 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7564 sprintf(buf, "cores %d\n", appData.smpCores);
7565 SendToProgram(buf, cps);
7568 SendToProgram(cps->initString, cps);
7569 if (gameInfo.variant != VariantNormal &&
7570 gameInfo.variant != VariantLoadable
7571 /* [HGM] also send variant if board size non-standard */
7572 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7574 char *v = VariantName(gameInfo.variant);
7575 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7576 /* [HGM] in protocol 1 we have to assume all variants valid */
7577 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7578 DisplayFatalError(buf, 0, 1);
7582 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7583 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7584 if( gameInfo.variant == VariantXiangqi )
7585 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7586 if( gameInfo.variant == VariantShogi )
7587 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7588 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7589 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7590 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7591 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7592 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7593 if( gameInfo.variant == VariantCourier )
7594 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7595 if( gameInfo.variant == VariantSuper )
7596 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7597 if( gameInfo.variant == VariantGreat )
7598 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7601 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7602 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7603 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7604 if(StrStr(cps->variants, b) == NULL) {
7605 // specific sized variant not known, check if general sizing allowed
7606 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7607 if(StrStr(cps->variants, "boardsize") == NULL) {
7608 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7609 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7610 DisplayFatalError(buf, 0, 1);
7613 /* [HGM] here we really should compare with the maximum supported board size */
7616 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7617 sprintf(buf, "variant %s\n", b);
7618 SendToProgram(buf, cps);
7620 currentlyInitializedVariant = gameInfo.variant;
7622 /* [HGM] send opening position in FRC to first engine */
7624 SendToProgram("force\n", cps);
7626 /* engine is now in force mode! Set flag to wake it up after first move. */
7627 setboardSpoiledMachineBlack = 1;
7631 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7632 SendToProgram(buf, cps);
7634 cps->maybeThinking = FALSE;
7635 cps->offeredDraw = 0;
7636 if (!appData.icsActive) {
7637 SendTimeControl(cps, movesPerSession, timeControl,
7638 timeIncrement, appData.searchDepth,
7641 if (appData.showThinking
7642 // [HGM] thinking: four options require thinking output to be sent
7643 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7645 SendToProgram("post\n", cps);
7647 SendToProgram("hard\n", cps);
7648 if (!appData.ponderNextMove) {
7649 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7650 it without being sure what state we are in first. "hard"
7651 is not a toggle, so that one is OK.
7653 SendToProgram("easy\n", cps);
7656 sprintf(buf, "ping %d\n", ++cps->lastPing);
7657 SendToProgram(buf, cps);
7659 cps->initDone = TRUE;
7664 StartChessProgram(cps)
7665 ChessProgramState *cps;
7670 if (appData.noChessProgram) return;
7671 cps->initDone = FALSE;
7673 if (strcmp(cps->host, "localhost") == 0) {
7674 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7675 } else if (*appData.remoteShell == NULLCHAR) {
7676 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7678 if (*appData.remoteUser == NULLCHAR) {
7679 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7682 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7683 cps->host, appData.remoteUser, cps->program);
7685 err = StartChildProcess(buf, "", &cps->pr);
7689 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7690 DisplayFatalError(buf, err, 1);
7696 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7697 if (cps->protocolVersion > 1) {
7698 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7699 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7700 cps->comboCnt = 0; // and values of combo boxes
7701 SendToProgram(buf, cps);
7703 SendToProgram("xboard\n", cps);
7709 TwoMachinesEventIfReady P((void))
7711 if (first.lastPing != first.lastPong) {
7712 DisplayMessage("", _("Waiting for first chess program"));
7713 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7716 if (second.lastPing != second.lastPong) {
7717 DisplayMessage("", _("Waiting for second chess program"));
7718 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7726 NextMatchGame P((void))
7728 int index; /* [HGM] autoinc: step lod index during match */
7730 if (*appData.loadGameFile != NULLCHAR) {
7731 index = appData.loadGameIndex;
7732 if(index < 0) { // [HGM] autoinc
7733 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7734 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7736 LoadGameFromFile(appData.loadGameFile,
7738 appData.loadGameFile, FALSE);
7739 } else if (*appData.loadPositionFile != NULLCHAR) {
7740 index = appData.loadPositionIndex;
7741 if(index < 0) { // [HGM] autoinc
7742 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7743 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7745 LoadPositionFromFile(appData.loadPositionFile,
7747 appData.loadPositionFile);
7749 TwoMachinesEventIfReady();
7752 void UserAdjudicationEvent( int result )
7754 ChessMove gameResult = GameIsDrawn;
7757 gameResult = WhiteWins;
7759 else if( result < 0 ) {
7760 gameResult = BlackWins;
7763 if( gameMode == TwoMachinesPlay ) {
7764 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7769 // [HGM] save: calculate checksum of game to make games easily identifiable
7770 int StringCheckSum(char *s)
7773 if(s==NULL) return 0;
7774 while(*s) i = i*259 + *s++;
7781 for(i=backwardMostMove; i<forwardMostMove; i++) {
7782 sum += pvInfoList[i].depth;
7783 sum += StringCheckSum(parseList[i]);
7784 sum += StringCheckSum(commentList[i]);
7787 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7788 return sum + StringCheckSum(commentList[i]);
7789 } // end of save patch
7792 GameEnds(result, resultDetails, whosays)
7794 char *resultDetails;
7797 GameMode nextGameMode;
7801 if(endingGame) return; /* [HGM] crash: forbid recursion */
7804 if (appData.debugMode) {
7805 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7806 result, resultDetails ? resultDetails : "(null)", whosays);
7809 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7810 /* If we are playing on ICS, the server decides when the
7811 game is over, but the engine can offer to draw, claim
7815 if (appData.zippyPlay && first.initDone) {
7816 if (result == GameIsDrawn) {
7817 /* In case draw still needs to be claimed */
7818 SendToICS(ics_prefix);
7819 SendToICS("draw\n");
7820 } else if (StrCaseStr(resultDetails, "resign")) {
7821 SendToICS(ics_prefix);
7822 SendToICS("resign\n");
7826 endingGame = 0; /* [HGM] crash */
7830 /* If we're loading the game from a file, stop */
7831 if (whosays == GE_FILE) {
7832 (void) StopLoadGameTimer();
7836 /* Cancel draw offers */
7837 first.offeredDraw = second.offeredDraw = 0;
7839 /* If this is an ICS game, only ICS can really say it's done;
7840 if not, anyone can. */
7841 isIcsGame = (gameMode == IcsPlayingWhite ||
7842 gameMode == IcsPlayingBlack ||
7843 gameMode == IcsObserving ||
7844 gameMode == IcsExamining);
7846 if (!isIcsGame || whosays == GE_ICS) {
7847 /* OK -- not an ICS game, or ICS said it was done */
7849 if (!isIcsGame && !appData.noChessProgram)
7850 SetUserThinkingEnables();
7852 /* [HGM] if a machine claims the game end we verify this claim */
7853 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7854 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7856 ChessMove trueResult = (ChessMove) -1;
7858 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7859 first.twoMachinesColor[0] :
7860 second.twoMachinesColor[0] ;
7862 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7863 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7864 /* [HGM] verify: engine mate claims accepted if they were flagged */
7865 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7867 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7868 /* [HGM] verify: engine mate claims accepted if they were flagged */
7869 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7871 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7872 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7875 // now verify win claims, but not in drop games, as we don't understand those yet
7876 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7877 || gameInfo.variant == VariantGreat) &&
7878 (result == WhiteWins && claimer == 'w' ||
7879 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7880 if (appData.debugMode) {
7881 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7882 result, epStatus[forwardMostMove], forwardMostMove);
7884 if(result != trueResult) {
7885 sprintf(buf, "False win claim: '%s'", resultDetails);
7886 result = claimer == 'w' ? BlackWins : WhiteWins;
7887 resultDetails = buf;
7890 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7891 && (forwardMostMove <= backwardMostMove ||
7892 epStatus[forwardMostMove-1] > EP_DRAWS ||
7893 (claimer=='b')==(forwardMostMove&1))
7895 /* [HGM] verify: draws that were not flagged are false claims */
7896 sprintf(buf, "False draw claim: '%s'", resultDetails);
7897 result = claimer == 'w' ? BlackWins : WhiteWins;
7898 resultDetails = buf;
7900 /* (Claiming a loss is accepted no questions asked!) */
7902 /* [HGM] bare: don't allow bare King to win */
7903 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7904 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7905 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7906 && result != GameIsDrawn)
7907 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7908 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7909 int p = (int)boards[forwardMostMove][i][j] - color;
7910 if(p >= 0 && p <= (int)WhiteKing) k++;
7912 if (appData.debugMode) {
7913 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7914 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7917 result = GameIsDrawn;
7918 sprintf(buf, "%s but bare king", resultDetails);
7919 resultDetails = buf;
7925 if(serverMoves != NULL && !loadFlag) { char c = '=';
7926 if(result==WhiteWins) c = '+';
7927 if(result==BlackWins) c = '-';
7928 if(resultDetails != NULL)
7929 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7931 if (resultDetails != NULL) {
7932 gameInfo.result = result;
7933 gameInfo.resultDetails = StrSave(resultDetails);
7935 /* display last move only if game was not loaded from file */
7936 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7937 DisplayMove(currentMove - 1);
7939 if (forwardMostMove != 0) {
7940 if (gameMode != PlayFromGameFile && gameMode != EditGame
7941 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7943 if (*appData.saveGameFile != NULLCHAR) {
7944 SaveGameToFile(appData.saveGameFile, TRUE);
7945 } else if (appData.autoSaveGames) {
7948 if (*appData.savePositionFile != NULLCHAR) {
7949 SavePositionToFile(appData.savePositionFile);
7954 /* Tell program how game ended in case it is learning */
7955 /* [HGM] Moved this to after saving the PGN, just in case */
7956 /* engine died and we got here through time loss. In that */
7957 /* case we will get a fatal error writing the pipe, which */
7958 /* would otherwise lose us the PGN. */
7959 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7960 /* output during GameEnds should never be fatal anymore */
7961 if (gameMode == MachinePlaysWhite ||
7962 gameMode == MachinePlaysBlack ||
7963 gameMode == TwoMachinesPlay ||
7964 gameMode == IcsPlayingWhite ||
7965 gameMode == IcsPlayingBlack ||
7966 gameMode == BeginningOfGame) {
7968 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7970 if (first.pr != NoProc) {
7971 SendToProgram(buf, &first);
7973 if (second.pr != NoProc &&
7974 gameMode == TwoMachinesPlay) {
7975 SendToProgram(buf, &second);
7980 if (appData.icsActive) {
7981 if (appData.quietPlay &&
7982 (gameMode == IcsPlayingWhite ||
7983 gameMode == IcsPlayingBlack)) {
7984 SendToICS(ics_prefix);
7985 SendToICS("set shout 1\n");
7987 nextGameMode = IcsIdle;
7988 ics_user_moved = FALSE;
7989 /* clean up premove. It's ugly when the game has ended and the
7990 * premove highlights are still on the board.
7994 ClearPremoveHighlights();
7995 DrawPosition(FALSE, boards[currentMove]);
7997 if (whosays == GE_ICS) {
8000 if (gameMode == IcsPlayingWhite)
8002 else if(gameMode == IcsPlayingBlack)
8006 if (gameMode == IcsPlayingBlack)
8008 else if(gameMode == IcsPlayingWhite)
8015 PlayIcsUnfinishedSound();
8018 } else if (gameMode == EditGame ||
8019 gameMode == PlayFromGameFile ||
8020 gameMode == AnalyzeMode ||
8021 gameMode == AnalyzeFile) {
8022 nextGameMode = gameMode;
8024 nextGameMode = EndOfGame;
8029 nextGameMode = gameMode;
8032 if (appData.noChessProgram) {
8033 gameMode = nextGameMode;
8035 endingGame = 0; /* [HGM] crash */
8040 /* Put first chess program into idle state */
8041 if (first.pr != NoProc &&
8042 (gameMode == MachinePlaysWhite ||
8043 gameMode == MachinePlaysBlack ||
8044 gameMode == TwoMachinesPlay ||
8045 gameMode == IcsPlayingWhite ||
8046 gameMode == IcsPlayingBlack ||
8047 gameMode == BeginningOfGame)) {
8048 SendToProgram("force\n", &first);
8049 if (first.usePing) {
8051 sprintf(buf, "ping %d\n", ++first.lastPing);
8052 SendToProgram(buf, &first);
8055 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8056 /* Kill off first chess program */
8057 if (first.isr != NULL)
8058 RemoveInputSource(first.isr);
8061 if (first.pr != NoProc) {
8063 DoSleep( appData.delayBeforeQuit );
8064 SendToProgram("quit\n", &first);
8065 DoSleep( appData.delayAfterQuit );
8066 DestroyChildProcess(first.pr, first.useSigterm);
8071 /* Put second chess program into idle state */
8072 if (second.pr != NoProc &&
8073 gameMode == TwoMachinesPlay) {
8074 SendToProgram("force\n", &second);
8075 if (second.usePing) {
8077 sprintf(buf, "ping %d\n", ++second.lastPing);
8078 SendToProgram(buf, &second);
8081 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8082 /* Kill off second chess program */
8083 if (second.isr != NULL)
8084 RemoveInputSource(second.isr);
8087 if (second.pr != NoProc) {
8088 DoSleep( appData.delayBeforeQuit );
8089 SendToProgram("quit\n", &second);
8090 DoSleep( appData.delayAfterQuit );
8091 DestroyChildProcess(second.pr, second.useSigterm);
8096 if (matchMode && gameMode == TwoMachinesPlay) {
8099 if (first.twoMachinesColor[0] == 'w') {
8106 if (first.twoMachinesColor[0] == 'b') {
8115 if (matchGame < appData.matchGames) {
8117 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8118 tmp = first.twoMachinesColor;
8119 first.twoMachinesColor = second.twoMachinesColor;
8120 second.twoMachinesColor = tmp;
8122 gameMode = nextGameMode;
8124 if(appData.matchPause>10000 || appData.matchPause<10)
8125 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8126 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8127 endingGame = 0; /* [HGM] crash */
8131 gameMode = nextGameMode;
8132 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8133 first.tidy, second.tidy,
8134 first.matchWins, second.matchWins,
8135 appData.matchGames - (first.matchWins + second.matchWins));
8136 DisplayFatalError(buf, 0, 0);
8139 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8140 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8142 gameMode = nextGameMode;
8144 endingGame = 0; /* [HGM] crash */
8147 /* Assumes program was just initialized (initString sent).
8148 Leaves program in force mode. */
8150 FeedMovesToProgram(cps, upto)
8151 ChessProgramState *cps;
8156 if (appData.debugMode)
8157 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8158 startedFromSetupPosition ? "position and " : "",
8159 backwardMostMove, upto, cps->which);
8160 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8161 // [HGM] variantswitch: make engine aware of new variant
8162 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8163 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8164 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8165 SendToProgram(buf, cps);
8166 currentlyInitializedVariant = gameInfo.variant;
8168 SendToProgram("force\n", cps);
8169 if (startedFromSetupPosition) {
8170 SendBoard(cps, backwardMostMove);
8171 if (appData.debugMode) {
8172 fprintf(debugFP, "feedMoves\n");
8175 for (i = backwardMostMove; i < upto; i++) {
8176 SendMoveToProgram(i, cps);
8182 ResurrectChessProgram()
8184 /* The chess program may have exited.
8185 If so, restart it and feed it all the moves made so far. */
8187 if (appData.noChessProgram || first.pr != NoProc) return;
8189 StartChessProgram(&first);
8190 InitChessProgram(&first, FALSE);
8191 FeedMovesToProgram(&first, currentMove);
8193 if (!first.sendTime) {
8194 /* can't tell gnuchess what its clock should read,
8195 so we bow to its notion. */
8197 timeRemaining[0][currentMove] = whiteTimeRemaining;
8198 timeRemaining[1][currentMove] = blackTimeRemaining;
8201 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8202 appData.icsEngineAnalyze) && first.analysisSupport) {
8203 SendToProgram("analyze\n", &first);
8204 first.analyzing = TRUE;
8217 if (appData.debugMode) {
8218 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8219 redraw, init, gameMode);
8221 pausing = pauseExamInvalid = FALSE;
8222 startedFromSetupPosition = blackPlaysFirst = FALSE;
8224 whiteFlag = blackFlag = FALSE;
8225 userOfferedDraw = FALSE;
8226 hintRequested = bookRequested = FALSE;
8227 first.maybeThinking = FALSE;
8228 second.maybeThinking = FALSE;
8229 first.bookSuspend = FALSE; // [HGM] book
8230 second.bookSuspend = FALSE;
8231 thinkOutput[0] = NULLCHAR;
8232 lastHint[0] = NULLCHAR;
8233 ClearGameInfo(&gameInfo);
8234 gameInfo.variant = StringToVariant(appData.variant);
8235 ics_user_moved = ics_clock_paused = FALSE;
8236 ics_getting_history = H_FALSE;
8238 white_holding[0] = black_holding[0] = NULLCHAR;
8239 ClearProgramStats();
8240 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8244 flipView = appData.flipView;
8245 ClearPremoveHighlights();
8247 alarmSounded = FALSE;
8249 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8250 if(appData.serverMovesName != NULL) {
8251 /* [HGM] prepare to make moves file for broadcasting */
8252 clock_t t = clock();
8253 if(serverMoves != NULL) fclose(serverMoves);
8254 serverMoves = fopen(appData.serverMovesName, "r");
8255 if(serverMoves != NULL) {
8256 fclose(serverMoves);
8257 /* delay 15 sec before overwriting, so all clients can see end */
8258 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8260 serverMoves = fopen(appData.serverMovesName, "w");
8264 gameMode = BeginningOfGame;
8266 if(appData.icsActive) gameInfo.variant = VariantNormal;
8267 InitPosition(redraw);
8268 for (i = 0; i < MAX_MOVES; i++) {
8269 if (commentList[i] != NULL) {
8270 free(commentList[i]);
8271 commentList[i] = NULL;
8275 timeRemaining[0][0] = whiteTimeRemaining;
8276 timeRemaining[1][0] = blackTimeRemaining;
8277 if (first.pr == NULL) {
8278 StartChessProgram(&first);
8281 InitChessProgram(&first, startedFromSetupPosition);
8284 DisplayMessage("", "");
8285 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8286 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8293 if (!AutoPlayOneMove())
8295 if (matchMode || appData.timeDelay == 0)
8297 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8299 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8308 int fromX, fromY, toX, toY;
8310 if (appData.debugMode) {
8311 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8314 if (gameMode != PlayFromGameFile)
8317 if (currentMove >= forwardMostMove) {
8318 gameMode = EditGame;
8321 /* [AS] Clear current move marker at the end of a game */
8322 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8327 toX = moveList[currentMove][2] - AAA;
8328 toY = moveList[currentMove][3] - ONE;
8330 if (moveList[currentMove][1] == '@') {
8331 if (appData.highlightLastMove) {
8332 SetHighlights(-1, -1, toX, toY);
8335 fromX = moveList[currentMove][0] - AAA;
8336 fromY = moveList[currentMove][1] - ONE;
8338 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8340 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8342 if (appData.highlightLastMove) {
8343 SetHighlights(fromX, fromY, toX, toY);
8346 DisplayMove(currentMove);
8347 SendMoveToProgram(currentMove++, &first);
8348 DisplayBothClocks();
8349 DrawPosition(FALSE, boards[currentMove]);
8350 // [HGM] PV info: always display, routine tests if empty
8351 DisplayComment(currentMove - 1, commentList[currentMove]);
8357 LoadGameOneMove(readAhead)
8358 ChessMove readAhead;
8360 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8361 char promoChar = NULLCHAR;
8366 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8367 gameMode != AnalyzeMode && gameMode != Training) {
8372 yyboardindex = forwardMostMove;
8373 if (readAhead != (ChessMove)0) {
8374 moveType = readAhead;
8376 if (gameFileFP == NULL)
8378 moveType = (ChessMove) yylex();
8384 if (appData.debugMode)
8385 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8387 if (*p == '{' || *p == '[' || *p == '(') {
8388 p[strlen(p) - 1] = NULLCHAR;
8392 /* append the comment but don't display it */
8393 while (*p == '\n') p++;
8394 AppendComment(currentMove, p);
8397 case WhiteCapturesEnPassant:
8398 case BlackCapturesEnPassant:
8399 case WhitePromotionChancellor:
8400 case BlackPromotionChancellor:
8401 case WhitePromotionArchbishop:
8402 case BlackPromotionArchbishop:
8403 case WhitePromotionCentaur:
8404 case BlackPromotionCentaur:
8405 case WhitePromotionQueen:
8406 case BlackPromotionQueen:
8407 case WhitePromotionRook:
8408 case BlackPromotionRook:
8409 case WhitePromotionBishop:
8410 case BlackPromotionBishop:
8411 case WhitePromotionKnight:
8412 case BlackPromotionKnight:
8413 case WhitePromotionKing:
8414 case BlackPromotionKing:
8416 case WhiteKingSideCastle:
8417 case WhiteQueenSideCastle:
8418 case BlackKingSideCastle:
8419 case BlackQueenSideCastle:
8420 case WhiteKingSideCastleWild:
8421 case WhiteQueenSideCastleWild:
8422 case BlackKingSideCastleWild:
8423 case BlackQueenSideCastleWild:
8425 case WhiteHSideCastleFR:
8426 case WhiteASideCastleFR:
8427 case BlackHSideCastleFR:
8428 case BlackASideCastleFR:
8430 if (appData.debugMode)
8431 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8432 fromX = currentMoveString[0] - AAA;
8433 fromY = currentMoveString[1] - ONE;
8434 toX = currentMoveString[2] - AAA;
8435 toY = currentMoveString[3] - ONE;
8436 promoChar = currentMoveString[4];
8441 if (appData.debugMode)
8442 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8443 fromX = moveType == WhiteDrop ?
8444 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8445 (int) CharToPiece(ToLower(currentMoveString[0]));
8447 toX = currentMoveString[2] - AAA;
8448 toY = currentMoveString[3] - ONE;
8454 case GameUnfinished:
8455 if (appData.debugMode)
8456 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8457 p = strchr(yy_text, '{');
8458 if (p == NULL) p = strchr(yy_text, '(');
8461 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8463 q = strchr(p, *p == '{' ? '}' : ')');
8464 if (q != NULL) *q = NULLCHAR;
8467 GameEnds(moveType, p, GE_FILE);
8469 if (cmailMsgLoaded) {
8471 flipView = WhiteOnMove(currentMove);
8472 if (moveType == GameUnfinished) flipView = !flipView;
8473 if (appData.debugMode)
8474 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8478 case (ChessMove) 0: /* end of file */
8479 if (appData.debugMode)
8480 fprintf(debugFP, "Parser hit end of file\n");
8481 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8482 EP_UNKNOWN, castlingRights[currentMove]) ) {
8488 if (WhiteOnMove(currentMove)) {
8489 GameEnds(BlackWins, "Black mates", GE_FILE);
8491 GameEnds(WhiteWins, "White mates", GE_FILE);
8495 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8502 if (lastLoadGameStart == GNUChessGame) {
8503 /* GNUChessGames have numbers, but they aren't move numbers */
8504 if (appData.debugMode)
8505 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8506 yy_text, (int) moveType);
8507 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8509 /* else fall thru */
8514 /* Reached start of next game in file */
8515 if (appData.debugMode)
8516 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8517 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8518 EP_UNKNOWN, castlingRights[currentMove]) ) {
8524 if (WhiteOnMove(currentMove)) {
8525 GameEnds(BlackWins, "Black mates", GE_FILE);
8527 GameEnds(WhiteWins, "White mates", GE_FILE);
8531 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8537 case PositionDiagram: /* should not happen; ignore */
8538 case ElapsedTime: /* ignore */
8539 case NAG: /* ignore */
8540 if (appData.debugMode)
8541 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8542 yy_text, (int) moveType);
8543 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8546 if (appData.testLegality) {
8547 if (appData.debugMode)
8548 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8549 sprintf(move, _("Illegal move: %d.%s%s"),
8550 (forwardMostMove / 2) + 1,
8551 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8552 DisplayError(move, 0);
8555 if (appData.debugMode)
8556 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8557 yy_text, currentMoveString);
8558 fromX = currentMoveString[0] - AAA;
8559 fromY = currentMoveString[1] - ONE;
8560 toX = currentMoveString[2] - AAA;
8561 toY = currentMoveString[3] - ONE;
8562 promoChar = currentMoveString[4];
8567 if (appData.debugMode)
8568 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8569 sprintf(move, _("Ambiguous move: %d.%s%s"),
8570 (forwardMostMove / 2) + 1,
8571 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8572 DisplayError(move, 0);
8577 case ImpossibleMove:
8578 if (appData.debugMode)
8579 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8580 sprintf(move, _("Illegal move: %d.%s%s"),
8581 (forwardMostMove / 2) + 1,
8582 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8583 DisplayError(move, 0);
8589 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8590 DrawPosition(FALSE, boards[currentMove]);
8591 DisplayBothClocks();
8592 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8593 DisplayComment(currentMove - 1, commentList[currentMove]);
8595 (void) StopLoadGameTimer();
8597 cmailOldMove = forwardMostMove;
8600 /* currentMoveString is set as a side-effect of yylex */
8601 strcat(currentMoveString, "\n");
8602 strcpy(moveList[forwardMostMove], currentMoveString);
8604 thinkOutput[0] = NULLCHAR;
8605 MakeMove(fromX, fromY, toX, toY, promoChar);
8606 currentMove = forwardMostMove;
8611 /* Load the nth game from the given file */
8613 LoadGameFromFile(filename, n, title, useList)
8617 /*Boolean*/ int useList;
8622 if (strcmp(filename, "-") == 0) {
8626 f = fopen(filename, "rb");
8628 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8629 DisplayError(buf, errno);
8633 if (fseek(f, 0, 0) == -1) {
8634 /* f is not seekable; probably a pipe */
8637 if (useList && n == 0) {
8638 int error = GameListBuild(f);
8640 DisplayError(_("Cannot build game list"), error);
8641 } else if (!ListEmpty(&gameList) &&
8642 ((ListGame *) gameList.tailPred)->number > 1) {
8643 GameListPopUp(f, title);
8650 return LoadGame(f, n, title, FALSE);
8655 MakeRegisteredMove()
8657 int fromX, fromY, toX, toY;
8659 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8660 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8663 if (appData.debugMode)
8664 fprintf(debugFP, "Restoring %s for game %d\n",
8665 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8667 thinkOutput[0] = NULLCHAR;
8668 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8669 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8670 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8671 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8672 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8673 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8674 MakeMove(fromX, fromY, toX, toY, promoChar);
8675 ShowMove(fromX, fromY, toX, toY);
8677 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8678 EP_UNKNOWN, castlingRights[currentMove]) ) {
8685 if (WhiteOnMove(currentMove)) {
8686 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8688 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8693 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8700 if (WhiteOnMove(currentMove)) {
8701 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8703 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8708 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8719 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8721 CmailLoadGame(f, gameNumber, title, useList)
8729 if (gameNumber > nCmailGames) {
8730 DisplayError(_("No more games in this message"), 0);
8733 if (f == lastLoadGameFP) {
8734 int offset = gameNumber - lastLoadGameNumber;
8736 cmailMsg[0] = NULLCHAR;
8737 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8738 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8739 nCmailMovesRegistered--;
8741 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8742 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8743 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8746 if (! RegisterMove()) return FALSE;
8750 retVal = LoadGame(f, gameNumber, title, useList);
8752 /* Make move registered during previous look at this game, if any */
8753 MakeRegisteredMove();
8755 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8756 commentList[currentMove]
8757 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8758 DisplayComment(currentMove - 1, commentList[currentMove]);
8764 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8769 int gameNumber = lastLoadGameNumber + offset;
8770 if (lastLoadGameFP == NULL) {
8771 DisplayError(_("No game has been loaded yet"), 0);
8774 if (gameNumber <= 0) {
8775 DisplayError(_("Can't back up any further"), 0);
8778 if (cmailMsgLoaded) {
8779 return CmailLoadGame(lastLoadGameFP, gameNumber,
8780 lastLoadGameTitle, lastLoadGameUseList);
8782 return LoadGame(lastLoadGameFP, gameNumber,
8783 lastLoadGameTitle, lastLoadGameUseList);
8789 /* Load the nth game from open file f */
8791 LoadGame(f, gameNumber, title, useList)
8799 int gn = gameNumber;
8800 ListGame *lg = NULL;
8803 GameMode oldGameMode;
8804 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8806 if (appData.debugMode)
8807 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8809 if (gameMode == Training )
8810 SetTrainingModeOff();
8812 oldGameMode = gameMode;
8813 if (gameMode != BeginningOfGame) {
8818 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8819 fclose(lastLoadGameFP);
8823 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8826 fseek(f, lg->offset, 0);
8827 GameListHighlight(gameNumber);
8831 DisplayError(_("Game number out of range"), 0);
8836 if (fseek(f, 0, 0) == -1) {
8837 if (f == lastLoadGameFP ?
8838 gameNumber == lastLoadGameNumber + 1 :
8842 DisplayError(_("Can't seek on game file"), 0);
8848 lastLoadGameNumber = gameNumber;
8849 strcpy(lastLoadGameTitle, title);
8850 lastLoadGameUseList = useList;
8854 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8855 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8856 lg->gameInfo.black);
8858 } else if (*title != NULLCHAR) {
8859 if (gameNumber > 1) {
8860 sprintf(buf, "%s %d", title, gameNumber);
8863 DisplayTitle(title);
8867 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8868 gameMode = PlayFromGameFile;
8872 currentMove = forwardMostMove = backwardMostMove = 0;
8873 CopyBoard(boards[0], initialPosition);
8877 * Skip the first gn-1 games in the file.
8878 * Also skip over anything that precedes an identifiable
8879 * start of game marker, to avoid being confused by
8880 * garbage at the start of the file. Currently
8881 * recognized start of game markers are the move number "1",
8882 * the pattern "gnuchess .* game", the pattern
8883 * "^[#;%] [^ ]* game file", and a PGN tag block.
8884 * A game that starts with one of the latter two patterns
8885 * will also have a move number 1, possibly
8886 * following a position diagram.
8887 * 5-4-02: Let's try being more lenient and allowing a game to
8888 * start with an unnumbered move. Does that break anything?
8890 cm = lastLoadGameStart = (ChessMove) 0;
8892 yyboardindex = forwardMostMove;
8893 cm = (ChessMove) yylex();
8896 if (cmailMsgLoaded) {
8897 nCmailGames = CMAIL_MAX_GAMES - gn;
8900 DisplayError(_("Game not found in file"), 0);
8907 lastLoadGameStart = cm;
8911 switch (lastLoadGameStart) {
8918 gn--; /* count this game */
8919 lastLoadGameStart = cm;
8928 switch (lastLoadGameStart) {
8933 gn--; /* count this game */
8934 lastLoadGameStart = cm;
8937 lastLoadGameStart = cm; /* game counted already */
8945 yyboardindex = forwardMostMove;
8946 cm = (ChessMove) yylex();
8947 } while (cm == PGNTag || cm == Comment);
8954 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8955 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8956 != CMAIL_OLD_RESULT) {
8958 cmailResult[ CMAIL_MAX_GAMES
8959 - gn - 1] = CMAIL_OLD_RESULT;
8965 /* Only a NormalMove can be at the start of a game
8966 * without a position diagram. */
8967 if (lastLoadGameStart == (ChessMove) 0) {
8969 lastLoadGameStart = MoveNumberOne;
8978 if (appData.debugMode)
8979 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8981 if (cm == XBoardGame) {
8982 /* Skip any header junk before position diagram and/or move 1 */
8984 yyboardindex = forwardMostMove;
8985 cm = (ChessMove) yylex();
8987 if (cm == (ChessMove) 0 ||
8988 cm == GNUChessGame || cm == XBoardGame) {
8989 /* Empty game; pretend end-of-file and handle later */
8994 if (cm == MoveNumberOne || cm == PositionDiagram ||
8995 cm == PGNTag || cm == Comment)
8998 } else if (cm == GNUChessGame) {
8999 if (gameInfo.event != NULL) {
9000 free(gameInfo.event);
9002 gameInfo.event = StrSave(yy_text);
9005 startedFromSetupPosition = FALSE;
9006 while (cm == PGNTag) {
9007 if (appData.debugMode)
9008 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9009 err = ParsePGNTag(yy_text, &gameInfo);
9010 if (!err) numPGNTags++;
9012 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9013 if(gameInfo.variant != oldVariant) {
9014 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9016 oldVariant = gameInfo.variant;
9017 if (appData.debugMode)
9018 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9022 if (gameInfo.fen != NULL) {
9023 Board initial_position;
9024 startedFromSetupPosition = TRUE;
9025 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9027 DisplayError(_("Bad FEN position in file"), 0);
9030 CopyBoard(boards[0], initial_position);
9031 if (blackPlaysFirst) {
9032 currentMove = forwardMostMove = backwardMostMove = 1;
9033 CopyBoard(boards[1], initial_position);
9034 strcpy(moveList[0], "");
9035 strcpy(parseList[0], "");
9036 timeRemaining[0][1] = whiteTimeRemaining;
9037 timeRemaining[1][1] = blackTimeRemaining;
9038 if (commentList[0] != NULL) {
9039 commentList[1] = commentList[0];
9040 commentList[0] = NULL;
9043 currentMove = forwardMostMove = backwardMostMove = 0;
9045 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9047 initialRulePlies = FENrulePlies;
9048 epStatus[forwardMostMove] = FENepStatus;
9049 for( i=0; i< nrCastlingRights; i++ )
9050 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9052 yyboardindex = forwardMostMove;
9054 gameInfo.fen = NULL;
9057 yyboardindex = forwardMostMove;
9058 cm = (ChessMove) yylex();
9060 /* Handle comments interspersed among the tags */
9061 while (cm == Comment) {
9063 if (appData.debugMode)
9064 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9066 if (*p == '{' || *p == '[' || *p == '(') {
9067 p[strlen(p) - 1] = NULLCHAR;
9070 while (*p == '\n') p++;
9071 AppendComment(currentMove, p);
9072 yyboardindex = forwardMostMove;
9073 cm = (ChessMove) yylex();
9077 /* don't rely on existence of Event tag since if game was
9078 * pasted from clipboard the Event tag may not exist
9080 if (numPGNTags > 0){
9082 if (gameInfo.variant == VariantNormal) {
9083 gameInfo.variant = StringToVariant(gameInfo.event);
9086 if( appData.autoDisplayTags ) {
9087 tags = PGNTags(&gameInfo);
9088 TagsPopUp(tags, CmailMsg());
9093 /* Make something up, but don't display it now */
9098 if (cm == PositionDiagram) {
9101 Board initial_position;
9103 if (appData.debugMode)
9104 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9106 if (!startedFromSetupPosition) {
9108 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9109 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9119 initial_position[i][j++] = CharToPiece(*p);
9122 while (*p == ' ' || *p == '\t' ||
9123 *p == '\n' || *p == '\r') p++;
9125 if (strncmp(p, "black", strlen("black"))==0)
9126 blackPlaysFirst = TRUE;
9128 blackPlaysFirst = FALSE;
9129 startedFromSetupPosition = TRUE;
9131 CopyBoard(boards[0], initial_position);
9132 if (blackPlaysFirst) {
9133 currentMove = forwardMostMove = backwardMostMove = 1;
9134 CopyBoard(boards[1], initial_position);
9135 strcpy(moveList[0], "");
9136 strcpy(parseList[0], "");
9137 timeRemaining[0][1] = whiteTimeRemaining;
9138 timeRemaining[1][1] = blackTimeRemaining;
9139 if (commentList[0] != NULL) {
9140 commentList[1] = commentList[0];
9141 commentList[0] = NULL;
9144 currentMove = forwardMostMove = backwardMostMove = 0;
9147 yyboardindex = forwardMostMove;
9148 cm = (ChessMove) yylex();
9151 if (first.pr == NoProc) {
9152 StartChessProgram(&first);
9154 InitChessProgram(&first, FALSE);
9155 SendToProgram("force\n", &first);
9156 if (startedFromSetupPosition) {
9157 SendBoard(&first, forwardMostMove);
9158 if (appData.debugMode) {
9159 fprintf(debugFP, "Load Game\n");
9161 DisplayBothClocks();
9164 /* [HGM] server: flag to write setup moves in broadcast file as one */
9165 loadFlag = appData.suppressLoadMoves;
9167 while (cm == Comment) {
9169 if (appData.debugMode)
9170 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9172 if (*p == '{' || *p == '[' || *p == '(') {
9173 p[strlen(p) - 1] = NULLCHAR;
9176 while (*p == '\n') p++;
9177 AppendComment(currentMove, p);
9178 yyboardindex = forwardMostMove;
9179 cm = (ChessMove) yylex();
9182 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9183 cm == WhiteWins || cm == BlackWins ||
9184 cm == GameIsDrawn || cm == GameUnfinished) {
9185 DisplayMessage("", _("No moves in game"));
9186 if (cmailMsgLoaded) {
9187 if (appData.debugMode)
9188 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9192 DrawPosition(FALSE, boards[currentMove]);
9193 DisplayBothClocks();
9194 gameMode = EditGame;
9201 // [HGM] PV info: routine tests if comment empty
9202 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9203 DisplayComment(currentMove - 1, commentList[currentMove]);
9205 if (!matchMode && appData.timeDelay != 0)
9206 DrawPosition(FALSE, boards[currentMove]);
9208 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9209 programStats.ok_to_send = 1;
9212 /* if the first token after the PGN tags is a move
9213 * and not move number 1, retrieve it from the parser
9215 if (cm != MoveNumberOne)
9216 LoadGameOneMove(cm);
9218 /* load the remaining moves from the file */
9219 while (LoadGameOneMove((ChessMove)0)) {
9220 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9221 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9224 /* rewind to the start of the game */
9225 currentMove = backwardMostMove;
9227 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9229 if (oldGameMode == AnalyzeFile ||
9230 oldGameMode == AnalyzeMode) {
9234 if (matchMode || appData.timeDelay == 0) {
9236 gameMode = EditGame;
9238 } else if (appData.timeDelay > 0) {
9242 if (appData.debugMode)
9243 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9245 loadFlag = 0; /* [HGM] true game starts */
9249 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9251 ReloadPosition(offset)
9254 int positionNumber = lastLoadPositionNumber + offset;
9255 if (lastLoadPositionFP == NULL) {
9256 DisplayError(_("No position has been loaded yet"), 0);
9259 if (positionNumber <= 0) {
9260 DisplayError(_("Can't back up any further"), 0);
9263 return LoadPosition(lastLoadPositionFP, positionNumber,
9264 lastLoadPositionTitle);
9267 /* Load the nth position from the given file */
9269 LoadPositionFromFile(filename, n, title)
9277 if (strcmp(filename, "-") == 0) {
9278 return LoadPosition(stdin, n, "stdin");
9280 f = fopen(filename, "rb");
9282 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9283 DisplayError(buf, errno);
9286 return LoadPosition(f, n, title);
9291 /* Load the nth position from the given open file, and close it */
9293 LoadPosition(f, positionNumber, title)
9298 char *p, line[MSG_SIZ];
9299 Board initial_position;
9300 int i, j, fenMode, pn;
9302 if (gameMode == Training )
9303 SetTrainingModeOff();
9305 if (gameMode != BeginningOfGame) {
9308 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9309 fclose(lastLoadPositionFP);
9311 if (positionNumber == 0) positionNumber = 1;
9312 lastLoadPositionFP = f;
9313 lastLoadPositionNumber = positionNumber;
9314 strcpy(lastLoadPositionTitle, title);
9315 if (first.pr == NoProc) {
9316 StartChessProgram(&first);
9317 InitChessProgram(&first, FALSE);
9319 pn = positionNumber;
9320 if (positionNumber < 0) {
9321 /* Negative position number means to seek to that byte offset */
9322 if (fseek(f, -positionNumber, 0) == -1) {
9323 DisplayError(_("Can't seek on position file"), 0);
9328 if (fseek(f, 0, 0) == -1) {
9329 if (f == lastLoadPositionFP ?
9330 positionNumber == lastLoadPositionNumber + 1 :
9331 positionNumber == 1) {
9334 DisplayError(_("Can't seek on position file"), 0);
9339 /* See if this file is FEN or old-style xboard */
9340 if (fgets(line, MSG_SIZ, f) == NULL) {
9341 DisplayError(_("Position not found in file"), 0);
9350 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9351 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9352 case '1': case '2': case '3': case '4': case '5': case '6':
9353 case '7': case '8': case '9':
9354 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9355 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9356 case 'C': case 'W': case 'c': case 'w':
9361 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9362 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9366 if (fenMode || line[0] == '#') pn--;
9368 /* skip positions before number pn */
9369 if (fgets(line, MSG_SIZ, f) == NULL) {
9371 DisplayError(_("Position not found in file"), 0);
9374 if (fenMode || line[0] == '#') pn--;
9379 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9380 DisplayError(_("Bad FEN position in file"), 0);
9384 (void) fgets(line, MSG_SIZ, f);
9385 (void) fgets(line, MSG_SIZ, f);
9387 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9388 (void) fgets(line, MSG_SIZ, f);
9389 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9392 initial_position[i][j++] = CharToPiece(*p);
9396 blackPlaysFirst = FALSE;
9398 (void) fgets(line, MSG_SIZ, f);
9399 if (strncmp(line, "black", strlen("black"))==0)
9400 blackPlaysFirst = TRUE;
9403 startedFromSetupPosition = TRUE;
9405 SendToProgram("force\n", &first);
9406 CopyBoard(boards[0], initial_position);
9407 if (blackPlaysFirst) {
9408 currentMove = forwardMostMove = backwardMostMove = 1;
9409 strcpy(moveList[0], "");
9410 strcpy(parseList[0], "");
9411 CopyBoard(boards[1], initial_position);
9412 DisplayMessage("", _("Black to play"));
9414 currentMove = forwardMostMove = backwardMostMove = 0;
9415 DisplayMessage("", _("White to play"));
9417 /* [HGM] copy FEN attributes as well */
9419 initialRulePlies = FENrulePlies;
9420 epStatus[forwardMostMove] = FENepStatus;
9421 for( i=0; i< nrCastlingRights; i++ )
9422 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9424 SendBoard(&first, forwardMostMove);
9425 if (appData.debugMode) {
9427 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9428 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9429 fprintf(debugFP, "Load Position\n");
9432 if (positionNumber > 1) {
9433 sprintf(line, "%s %d", title, positionNumber);
9436 DisplayTitle(title);
9438 gameMode = EditGame;
9441 timeRemaining[0][1] = whiteTimeRemaining;
9442 timeRemaining[1][1] = blackTimeRemaining;
9443 DrawPosition(FALSE, boards[currentMove]);
9450 CopyPlayerNameIntoFileName(dest, src)
9453 while (*src != NULLCHAR && *src != ',') {
9458 *(*dest)++ = *src++;
9463 char *DefaultFileName(ext)
9466 static char def[MSG_SIZ];
9469 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9471 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9473 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9482 /* Save the current game to the given file */
9484 SaveGameToFile(filename, append)
9491 if (strcmp(filename, "-") == 0) {
9492 return SaveGame(stdout, 0, NULL);
9494 f = fopen(filename, append ? "a" : "w");
9496 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9497 DisplayError(buf, errno);
9500 return SaveGame(f, 0, NULL);
9509 static char buf[MSG_SIZ];
9512 p = strchr(str, ' ');
9513 if (p == NULL) return str;
9514 strncpy(buf, str, p - str);
9515 buf[p - str] = NULLCHAR;
9519 #define PGN_MAX_LINE 75
9521 #define PGN_SIDE_WHITE 0
9522 #define PGN_SIDE_BLACK 1
9525 static int FindFirstMoveOutOfBook( int side )
9529 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9530 int index = backwardMostMove;
9531 int has_book_hit = 0;
9533 if( (index % 2) != side ) {
9537 while( index < forwardMostMove ) {
9538 /* Check to see if engine is in book */
9539 int depth = pvInfoList[index].depth;
9540 int score = pvInfoList[index].score;
9546 else if( score == 0 && depth == 63 ) {
9547 in_book = 1; /* Zappa */
9549 else if( score == 2 && depth == 99 ) {
9550 in_book = 1; /* Abrok */
9553 has_book_hit += in_book;
9569 void GetOutOfBookInfo( char * buf )
9573 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9575 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9576 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9580 if( oob[0] >= 0 || oob[1] >= 0 ) {
9581 for( i=0; i<2; i++ ) {
9585 if( i > 0 && oob[0] >= 0 ) {
9589 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9590 sprintf( buf+strlen(buf), "%s%.2f",
9591 pvInfoList[idx].score >= 0 ? "+" : "",
9592 pvInfoList[idx].score / 100.0 );
9598 /* Save game in PGN style and close the file */
9603 int i, offset, linelen, newblock;
9607 int movelen, numlen, blank;
9608 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9610 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9612 tm = time((time_t *) NULL);
9614 PrintPGNTags(f, &gameInfo);
9616 if (backwardMostMove > 0 || startedFromSetupPosition) {
9617 char *fen = PositionToFEN(backwardMostMove, NULL);
9618 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9619 fprintf(f, "\n{--------------\n");
9620 PrintPosition(f, backwardMostMove);
9621 fprintf(f, "--------------}\n");
9625 /* [AS] Out of book annotation */
9626 if( appData.saveOutOfBookInfo ) {
9629 GetOutOfBookInfo( buf );
9631 if( buf[0] != '\0' ) {
9632 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9639 i = backwardMostMove;
9643 while (i < forwardMostMove) {
9644 /* Print comments preceding this move */
9645 if (commentList[i] != NULL) {
9646 if (linelen > 0) fprintf(f, "\n");
9647 fprintf(f, "{\n%s}\n", commentList[i]);
9652 /* Format move number */
9654 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9657 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9659 numtext[0] = NULLCHAR;
9662 numlen = strlen(numtext);
9665 /* Print move number */
9666 blank = linelen > 0 && numlen > 0;
9667 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9676 fprintf(f, numtext);
9680 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9681 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9683 // SavePart already does this!
9684 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9685 int p = movelen - 1;
9686 if(move_buffer[p] == ' ') p--;
9687 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9688 while(p && move_buffer[--p] != '(');
9689 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9694 blank = linelen > 0 && movelen > 0;
9695 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9704 fprintf(f, move_buffer);
9707 /* [AS] Add PV info if present */
9708 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9709 /* [HGM] add time */
9710 char buf[MSG_SIZ]; int seconds = 0;
9713 if(i >= backwardMostMove) {
9715 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9716 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9718 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9719 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9721 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9723 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9726 if( seconds <= 0) buf[0] = 0; else
9727 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9728 seconds = (seconds + 4)/10; // round to full seconds
9729 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9730 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9733 sprintf( move_buffer, "{%s%.2f/%d%s}",
9734 pvInfoList[i].score >= 0 ? "+" : "",
9735 pvInfoList[i].score / 100.0,
9736 pvInfoList[i].depth,
9739 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9741 /* Print score/depth */
9742 blank = linelen > 0 && movelen > 0;
9743 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9752 fprintf(f, move_buffer);
9759 /* Start a new line */
9760 if (linelen > 0) fprintf(f, "\n");
9762 /* Print comments after last move */
9763 if (commentList[i] != NULL) {
9764 fprintf(f, "{\n%s}\n", commentList[i]);
9768 if (gameInfo.resultDetails != NULL &&
9769 gameInfo.resultDetails[0] != NULLCHAR) {
9770 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9771 PGNResult(gameInfo.result));
9773 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9777 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9781 /* Save game in old style and close the file */
9789 tm = time((time_t *) NULL);
9791 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9794 if (backwardMostMove > 0 || startedFromSetupPosition) {
9795 fprintf(f, "\n[--------------\n");
9796 PrintPosition(f, backwardMostMove);
9797 fprintf(f, "--------------]\n");
9802 i = backwardMostMove;
9803 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9805 while (i < forwardMostMove) {
9806 if (commentList[i] != NULL) {
9807 fprintf(f, "[%s]\n", commentList[i]);
9811 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9814 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9816 if (commentList[i] != NULL) {
9820 if (i >= forwardMostMove) {
9824 fprintf(f, "%s\n", parseList[i]);
9829 if (commentList[i] != NULL) {
9830 fprintf(f, "[%s]\n", commentList[i]);
9833 /* This isn't really the old style, but it's close enough */
9834 if (gameInfo.resultDetails != NULL &&
9835 gameInfo.resultDetails[0] != NULLCHAR) {
9836 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9837 gameInfo.resultDetails);
9839 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9846 /* Save the current game to open file f and close the file */
9848 SaveGame(f, dummy, dummy2)
9853 if (gameMode == EditPosition) EditPositionDone();
9854 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9855 if (appData.oldSaveStyle)
9856 return SaveGameOldStyle(f);
9858 return SaveGamePGN(f);
9861 /* Save the current position to the given file */
9863 SavePositionToFile(filename)
9869 if (strcmp(filename, "-") == 0) {
9870 return SavePosition(stdout, 0, NULL);
9872 f = fopen(filename, "a");
9874 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9875 DisplayError(buf, errno);
9878 SavePosition(f, 0, NULL);
9884 /* Save the current position to the given open file and close the file */
9886 SavePosition(f, dummy, dummy2)
9894 if (appData.oldSaveStyle) {
9895 tm = time((time_t *) NULL);
9897 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9899 fprintf(f, "[--------------\n");
9900 PrintPosition(f, currentMove);
9901 fprintf(f, "--------------]\n");
9903 fen = PositionToFEN(currentMove, NULL);
9904 fprintf(f, "%s\n", fen);
9912 ReloadCmailMsgEvent(unregister)
9916 static char *inFilename = NULL;
9917 static char *outFilename;
9919 struct stat inbuf, outbuf;
9922 /* Any registered moves are unregistered if unregister is set, */
9923 /* i.e. invoked by the signal handler */
9925 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9926 cmailMoveRegistered[i] = FALSE;
9927 if (cmailCommentList[i] != NULL) {
9928 free(cmailCommentList[i]);
9929 cmailCommentList[i] = NULL;
9932 nCmailMovesRegistered = 0;
9935 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9936 cmailResult[i] = CMAIL_NOT_RESULT;
9940 if (inFilename == NULL) {
9941 /* Because the filenames are static they only get malloced once */
9942 /* and they never get freed */
9943 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9944 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9946 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9947 sprintf(outFilename, "%s.out", appData.cmailGameName);
9950 status = stat(outFilename, &outbuf);
9952 cmailMailedMove = FALSE;
9954 status = stat(inFilename, &inbuf);
9955 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9958 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9959 counts the games, notes how each one terminated, etc.
9961 It would be nice to remove this kludge and instead gather all
9962 the information while building the game list. (And to keep it
9963 in the game list nodes instead of having a bunch of fixed-size
9964 parallel arrays.) Note this will require getting each game's
9965 termination from the PGN tags, as the game list builder does
9966 not process the game moves. --mann
9968 cmailMsgLoaded = TRUE;
9969 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9971 /* Load first game in the file or popup game menu */
9972 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9982 char string[MSG_SIZ];
9984 if ( cmailMailedMove
9985 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9986 return TRUE; /* Allow free viewing */
9989 /* Unregister move to ensure that we don't leave RegisterMove */
9990 /* with the move registered when the conditions for registering no */
9992 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9993 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9994 nCmailMovesRegistered --;
9996 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9998 free(cmailCommentList[lastLoadGameNumber - 1]);
9999 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10003 if (cmailOldMove == -1) {
10004 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10008 if (currentMove > cmailOldMove + 1) {
10009 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10013 if (currentMove < cmailOldMove) {
10014 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10018 if (forwardMostMove > currentMove) {
10019 /* Silently truncate extra moves */
10023 if ( (currentMove == cmailOldMove + 1)
10024 || ( (currentMove == cmailOldMove)
10025 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10026 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10027 if (gameInfo.result != GameUnfinished) {
10028 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10031 if (commentList[currentMove] != NULL) {
10032 cmailCommentList[lastLoadGameNumber - 1]
10033 = StrSave(commentList[currentMove]);
10035 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10037 if (appData.debugMode)
10038 fprintf(debugFP, "Saving %s for game %d\n",
10039 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10042 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10044 f = fopen(string, "w");
10045 if (appData.oldSaveStyle) {
10046 SaveGameOldStyle(f); /* also closes the file */
10048 sprintf(string, "%s.pos.out", appData.cmailGameName);
10049 f = fopen(string, "w");
10050 SavePosition(f, 0, NULL); /* also closes the file */
10052 fprintf(f, "{--------------\n");
10053 PrintPosition(f, currentMove);
10054 fprintf(f, "--------------}\n\n");
10056 SaveGame(f, 0, NULL); /* also closes the file*/
10059 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10060 nCmailMovesRegistered ++;
10061 } else if (nCmailGames == 1) {
10062 DisplayError(_("You have not made a move yet"), 0);
10073 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10074 FILE *commandOutput;
10075 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10076 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10082 if (! cmailMsgLoaded) {
10083 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10087 if (nCmailGames == nCmailResults) {
10088 DisplayError(_("No unfinished games"), 0);
10092 #if CMAIL_PROHIBIT_REMAIL
10093 if (cmailMailedMove) {
10094 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);
10095 DisplayError(msg, 0);
10100 if (! (cmailMailedMove || RegisterMove())) return;
10102 if ( cmailMailedMove
10103 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10104 sprintf(string, partCommandString,
10105 appData.debugMode ? " -v" : "", appData.cmailGameName);
10106 commandOutput = popen(string, "r");
10108 if (commandOutput == NULL) {
10109 DisplayError(_("Failed to invoke cmail"), 0);
10111 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10112 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10114 if (nBuffers > 1) {
10115 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10116 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10117 nBytes = MSG_SIZ - 1;
10119 (void) memcpy(msg, buffer, nBytes);
10121 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10123 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10124 cmailMailedMove = TRUE; /* Prevent >1 moves */
10127 for (i = 0; i < nCmailGames; i ++) {
10128 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10133 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10135 sprintf(buffer, "%s/%s.%s.archive",
10137 appData.cmailGameName,
10139 LoadGameFromFile(buffer, 1, buffer, FALSE);
10140 cmailMsgLoaded = FALSE;
10144 DisplayInformation(msg);
10145 pclose(commandOutput);
10148 if ((*cmailMsg) != '\0') {
10149 DisplayInformation(cmailMsg);
10154 #endif /* !WIN32 */
10163 int prependComma = 0;
10165 char string[MSG_SIZ]; /* Space for game-list */
10168 if (!cmailMsgLoaded) return "";
10170 if (cmailMailedMove) {
10171 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10173 /* Create a list of games left */
10174 sprintf(string, "[");
10175 for (i = 0; i < nCmailGames; i ++) {
10176 if (! ( cmailMoveRegistered[i]
10177 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10178 if (prependComma) {
10179 sprintf(number, ",%d", i + 1);
10181 sprintf(number, "%d", i + 1);
10185 strcat(string, number);
10188 strcat(string, "]");
10190 if (nCmailMovesRegistered + nCmailResults == 0) {
10191 switch (nCmailGames) {
10194 _("Still need to make move for game\n"));
10199 _("Still need to make moves for both games\n"));
10204 _("Still need to make moves for all %d games\n"),
10209 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10212 _("Still need to make a move for game %s\n"),
10217 if (nCmailResults == nCmailGames) {
10218 sprintf(cmailMsg, _("No unfinished games\n"));
10220 sprintf(cmailMsg, _("Ready to send mail\n"));
10226 _("Still need to make moves for games %s\n"),
10238 if (gameMode == Training)
10239 SetTrainingModeOff();
10242 cmailMsgLoaded = FALSE;
10243 if (appData.icsActive) {
10244 SendToICS(ics_prefix);
10245 SendToICS("refresh\n");
10255 /* Give up on clean exit */
10259 /* Keep trying for clean exit */
10263 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10265 if (telnetISR != NULL) {
10266 RemoveInputSource(telnetISR);
10268 if (icsPR != NoProc) {
10269 DestroyChildProcess(icsPR, TRUE);
10272 /* Save game if resource set and not already saved by GameEnds() */
10273 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10274 && forwardMostMove > 0) {
10275 if (*appData.saveGameFile != NULLCHAR) {
10276 SaveGameToFile(appData.saveGameFile, TRUE);
10277 } else if (appData.autoSaveGames) {
10280 if (*appData.savePositionFile != NULLCHAR) {
10281 SavePositionToFile(appData.savePositionFile);
10284 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10286 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10287 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10289 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10290 /* make sure this other one finishes before killing it! */
10291 if(endingGame) { int count = 0;
10292 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10293 while(endingGame && count++ < 10) DoSleep(1);
10294 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10297 /* Kill off chess programs */
10298 if (first.pr != NoProc) {
10301 DoSleep( appData.delayBeforeQuit );
10302 SendToProgram("quit\n", &first);
10303 DoSleep( appData.delayAfterQuit );
10304 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10306 if (second.pr != NoProc) {
10307 DoSleep( appData.delayBeforeQuit );
10308 SendToProgram("quit\n", &second);
10309 DoSleep( appData.delayAfterQuit );
10310 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10312 if (first.isr != NULL) {
10313 RemoveInputSource(first.isr);
10315 if (second.isr != NULL) {
10316 RemoveInputSource(second.isr);
10319 ShutDownFrontEnd();
10326 if (appData.debugMode)
10327 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10331 if (gameMode == MachinePlaysWhite ||
10332 gameMode == MachinePlaysBlack) {
10335 DisplayBothClocks();
10337 if (gameMode == PlayFromGameFile) {
10338 if (appData.timeDelay >= 0)
10339 AutoPlayGameLoop();
10340 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10341 Reset(FALSE, TRUE);
10342 SendToICS(ics_prefix);
10343 SendToICS("refresh\n");
10344 } else if (currentMove < forwardMostMove) {
10345 ForwardInner(forwardMostMove);
10347 pauseExamInvalid = FALSE;
10349 switch (gameMode) {
10353 pauseExamForwardMostMove = forwardMostMove;
10354 pauseExamInvalid = FALSE;
10357 case IcsPlayingWhite:
10358 case IcsPlayingBlack:
10362 case PlayFromGameFile:
10363 (void) StopLoadGameTimer();
10367 case BeginningOfGame:
10368 if (appData.icsActive) return;
10369 /* else fall through */
10370 case MachinePlaysWhite:
10371 case MachinePlaysBlack:
10372 case TwoMachinesPlay:
10373 if (forwardMostMove == 0)
10374 return; /* don't pause if no one has moved */
10375 if ((gameMode == MachinePlaysWhite &&
10376 !WhiteOnMove(forwardMostMove)) ||
10377 (gameMode == MachinePlaysBlack &&
10378 WhiteOnMove(forwardMostMove))) {
10391 char title[MSG_SIZ];
10393 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10394 strcpy(title, _("Edit comment"));
10396 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10397 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10398 parseList[currentMove - 1]);
10401 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10408 char *tags = PGNTags(&gameInfo);
10409 EditTagsPopUp(tags);
10416 if (appData.noChessProgram || gameMode == AnalyzeMode)
10419 if (gameMode != AnalyzeFile) {
10420 if (!appData.icsEngineAnalyze) {
10422 if (gameMode != EditGame) return;
10424 ResurrectChessProgram();
10425 SendToProgram("analyze\n", &first);
10426 first.analyzing = TRUE;
10427 /*first.maybeThinking = TRUE;*/
10428 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10429 AnalysisPopUp(_("Analysis"),
10430 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10432 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10437 StartAnalysisClock();
10438 GetTimeMark(&lastNodeCountTime);
10445 if (appData.noChessProgram || gameMode == AnalyzeFile)
10448 if (gameMode != AnalyzeMode) {
10450 if (gameMode != EditGame) return;
10451 ResurrectChessProgram();
10452 SendToProgram("analyze\n", &first);
10453 first.analyzing = TRUE;
10454 /*first.maybeThinking = TRUE;*/
10455 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10456 AnalysisPopUp(_("Analysis"),
10457 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10459 gameMode = AnalyzeFile;
10464 StartAnalysisClock();
10465 GetTimeMark(&lastNodeCountTime);
10470 MachineWhiteEvent()
10473 char *bookHit = NULL;
10475 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10479 if (gameMode == PlayFromGameFile ||
10480 gameMode == TwoMachinesPlay ||
10481 gameMode == Training ||
10482 gameMode == AnalyzeMode ||
10483 gameMode == EndOfGame)
10486 if (gameMode == EditPosition)
10487 EditPositionDone();
10489 if (!WhiteOnMove(currentMove)) {
10490 DisplayError(_("It is not White's turn"), 0);
10494 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10497 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10498 gameMode == AnalyzeFile)
10501 ResurrectChessProgram(); /* in case it isn't running */
10502 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10503 gameMode = MachinePlaysWhite;
10506 gameMode = MachinePlaysWhite;
10510 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10512 if (first.sendName) {
10513 sprintf(buf, "name %s\n", gameInfo.black);
10514 SendToProgram(buf, &first);
10516 if (first.sendTime) {
10517 if (first.useColors) {
10518 SendToProgram("black\n", &first); /*gnu kludge*/
10520 SendTimeRemaining(&first, TRUE);
10522 if (first.useColors) {
10523 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10525 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10526 SetMachineThinkingEnables();
10527 first.maybeThinking = TRUE;
10530 if (appData.autoFlipView && !flipView) {
10531 flipView = !flipView;
10532 DrawPosition(FALSE, NULL);
10533 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10536 if(bookHit) { // [HGM] book: simulate book reply
10537 static char bookMove[MSG_SIZ]; // a bit generous?
10539 programStats.nodes = programStats.depth = programStats.time =
10540 programStats.score = programStats.got_only_move = 0;
10541 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10543 strcpy(bookMove, "move ");
10544 strcat(bookMove, bookHit);
10545 HandleMachineMove(bookMove, &first);
10550 MachineBlackEvent()
10553 char *bookHit = NULL;
10555 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10559 if (gameMode == PlayFromGameFile ||
10560 gameMode == TwoMachinesPlay ||
10561 gameMode == Training ||
10562 gameMode == AnalyzeMode ||
10563 gameMode == EndOfGame)
10566 if (gameMode == EditPosition)
10567 EditPositionDone();
10569 if (WhiteOnMove(currentMove)) {
10570 DisplayError(_("It is not Black's turn"), 0);
10574 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10577 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10578 gameMode == AnalyzeFile)
10581 ResurrectChessProgram(); /* in case it isn't running */
10582 gameMode = MachinePlaysBlack;
10586 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10588 if (first.sendName) {
10589 sprintf(buf, "name %s\n", gameInfo.white);
10590 SendToProgram(buf, &first);
10592 if (first.sendTime) {
10593 if (first.useColors) {
10594 SendToProgram("white\n", &first); /*gnu kludge*/
10596 SendTimeRemaining(&first, FALSE);
10598 if (first.useColors) {
10599 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10601 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10602 SetMachineThinkingEnables();
10603 first.maybeThinking = TRUE;
10606 if (appData.autoFlipView && flipView) {
10607 flipView = !flipView;
10608 DrawPosition(FALSE, NULL);
10609 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10611 if(bookHit) { // [HGM] book: simulate book reply
10612 static char bookMove[MSG_SIZ]; // a bit generous?
10614 programStats.nodes = programStats.depth = programStats.time =
10615 programStats.score = programStats.got_only_move = 0;
10616 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10618 strcpy(bookMove, "move ");
10619 strcat(bookMove, bookHit);
10620 HandleMachineMove(bookMove, &first);
10626 DisplayTwoMachinesTitle()
10629 if (appData.matchGames > 0) {
10630 if (first.twoMachinesColor[0] == 'w') {
10631 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10632 gameInfo.white, gameInfo.black,
10633 first.matchWins, second.matchWins,
10634 matchGame - 1 - (first.matchWins + second.matchWins));
10636 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10637 gameInfo.white, gameInfo.black,
10638 second.matchWins, first.matchWins,
10639 matchGame - 1 - (first.matchWins + second.matchWins));
10642 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10648 TwoMachinesEvent P((void))
10652 ChessProgramState *onmove;
10653 char *bookHit = NULL;
10655 if (appData.noChessProgram) return;
10657 switch (gameMode) {
10658 case TwoMachinesPlay:
10660 case MachinePlaysWhite:
10661 case MachinePlaysBlack:
10662 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10663 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10667 case BeginningOfGame:
10668 case PlayFromGameFile:
10671 if (gameMode != EditGame) return;
10674 EditPositionDone();
10685 forwardMostMove = currentMove;
10686 ResurrectChessProgram(); /* in case first program isn't running */
10688 if (second.pr == NULL) {
10689 StartChessProgram(&second);
10690 if (second.protocolVersion == 1) {
10691 TwoMachinesEventIfReady();
10693 /* kludge: allow timeout for initial "feature" command */
10695 DisplayMessage("", _("Starting second chess program"));
10696 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10700 DisplayMessage("", "");
10701 InitChessProgram(&second, FALSE);
10702 SendToProgram("force\n", &second);
10703 if (startedFromSetupPosition) {
10704 SendBoard(&second, backwardMostMove);
10705 if (appData.debugMode) {
10706 fprintf(debugFP, "Two Machines\n");
10709 for (i = backwardMostMove; i < forwardMostMove; i++) {
10710 SendMoveToProgram(i, &second);
10713 gameMode = TwoMachinesPlay;
10717 DisplayTwoMachinesTitle();
10719 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10725 SendToProgram(first.computerString, &first);
10726 if (first.sendName) {
10727 sprintf(buf, "name %s\n", second.tidy);
10728 SendToProgram(buf, &first);
10730 SendToProgram(second.computerString, &second);
10731 if (second.sendName) {
10732 sprintf(buf, "name %s\n", first.tidy);
10733 SendToProgram(buf, &second);
10737 if (!first.sendTime || !second.sendTime) {
10738 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10739 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10741 if (onmove->sendTime) {
10742 if (onmove->useColors) {
10743 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10745 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10747 if (onmove->useColors) {
10748 SendToProgram(onmove->twoMachinesColor, onmove);
10750 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10751 // SendToProgram("go\n", onmove);
10752 onmove->maybeThinking = TRUE;
10753 SetMachineThinkingEnables();
10757 if(bookHit) { // [HGM] book: simulate book reply
10758 static char bookMove[MSG_SIZ]; // a bit generous?
10760 programStats.nodes = programStats.depth = programStats.time =
10761 programStats.score = programStats.got_only_move = 0;
10762 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10764 strcpy(bookMove, "move ");
10765 strcat(bookMove, bookHit);
10766 HandleMachineMove(bookMove, &first);
10773 if (gameMode == Training) {
10774 SetTrainingModeOff();
10775 gameMode = PlayFromGameFile;
10776 DisplayMessage("", _("Training mode off"));
10778 gameMode = Training;
10779 animateTraining = appData.animate;
10781 /* make sure we are not already at the end of the game */
10782 if (currentMove < forwardMostMove) {
10783 SetTrainingModeOn();
10784 DisplayMessage("", _("Training mode on"));
10786 gameMode = PlayFromGameFile;
10787 DisplayError(_("Already at end of game"), 0);
10796 if (!appData.icsActive) return;
10797 switch (gameMode) {
10798 case IcsPlayingWhite:
10799 case IcsPlayingBlack:
10802 case BeginningOfGame:
10810 EditPositionDone();
10823 gameMode = IcsIdle;
10834 switch (gameMode) {
10836 SetTrainingModeOff();
10838 case MachinePlaysWhite:
10839 case MachinePlaysBlack:
10840 case BeginningOfGame:
10841 SendToProgram("force\n", &first);
10842 SetUserThinkingEnables();
10844 case PlayFromGameFile:
10845 (void) StopLoadGameTimer();
10846 if (gameFileFP != NULL) {
10851 EditPositionDone();
10856 SendToProgram("force\n", &first);
10858 case TwoMachinesPlay:
10859 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10860 ResurrectChessProgram();
10861 SetUserThinkingEnables();
10864 ResurrectChessProgram();
10866 case IcsPlayingBlack:
10867 case IcsPlayingWhite:
10868 DisplayError(_("Warning: You are still playing a game"), 0);
10871 DisplayError(_("Warning: You are still observing a game"), 0);
10874 DisplayError(_("Warning: You are still examining a game"), 0);
10885 first.offeredDraw = second.offeredDraw = 0;
10887 if (gameMode == PlayFromGameFile) {
10888 whiteTimeRemaining = timeRemaining[0][currentMove];
10889 blackTimeRemaining = timeRemaining[1][currentMove];
10893 if (gameMode == MachinePlaysWhite ||
10894 gameMode == MachinePlaysBlack ||
10895 gameMode == TwoMachinesPlay ||
10896 gameMode == EndOfGame) {
10897 i = forwardMostMove;
10898 while (i > currentMove) {
10899 SendToProgram("undo\n", &first);
10902 whiteTimeRemaining = timeRemaining[0][currentMove];
10903 blackTimeRemaining = timeRemaining[1][currentMove];
10904 DisplayBothClocks();
10905 if (whiteFlag || blackFlag) {
10906 whiteFlag = blackFlag = 0;
10911 gameMode = EditGame;
10918 EditPositionEvent()
10920 if (gameMode == EditPosition) {
10926 if (gameMode != EditGame) return;
10928 gameMode = EditPosition;
10931 if (currentMove > 0)
10932 CopyBoard(boards[0], boards[currentMove]);
10934 blackPlaysFirst = !WhiteOnMove(currentMove);
10936 currentMove = forwardMostMove = backwardMostMove = 0;
10937 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10944 /* [DM] icsEngineAnalyze - possible call from other functions */
10945 if (appData.icsEngineAnalyze) {
10946 appData.icsEngineAnalyze = FALSE;
10948 DisplayMessage("",_("Close ICS engine analyze..."));
10950 if (first.analysisSupport && first.analyzing) {
10951 SendToProgram("exit\n", &first);
10952 first.analyzing = FALSE;
10955 thinkOutput[0] = NULLCHAR;
10961 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10963 startedFromSetupPosition = TRUE;
10964 InitChessProgram(&first, FALSE);
10965 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10966 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10967 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10968 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10969 } else castlingRights[0][2] = -1;
10970 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10971 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10972 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10973 } else castlingRights[0][5] = -1;
10974 SendToProgram("force\n", &first);
10975 if (blackPlaysFirst) {
10976 strcpy(moveList[0], "");
10977 strcpy(parseList[0], "");
10978 currentMove = forwardMostMove = backwardMostMove = 1;
10979 CopyBoard(boards[1], boards[0]);
10980 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10982 epStatus[1] = epStatus[0];
10983 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10986 currentMove = forwardMostMove = backwardMostMove = 0;
10988 SendBoard(&first, forwardMostMove);
10989 if (appData.debugMode) {
10990 fprintf(debugFP, "EditPosDone\n");
10993 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10994 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10995 gameMode = EditGame;
10997 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10998 ClearHighlights(); /* [AS] */
11001 /* Pause for `ms' milliseconds */
11002 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11012 } while (SubtractTimeMarks(&m2, &m1) < ms);
11015 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11017 SendMultiLineToICS(buf)
11020 char temp[MSG_SIZ+1], *p;
11027 strncpy(temp, buf, len);
11032 if (*p == '\n' || *p == '\r')
11037 strcat(temp, "\n");
11039 SendToPlayer(temp, strlen(temp));
11043 SetWhiteToPlayEvent()
11045 if (gameMode == EditPosition) {
11046 blackPlaysFirst = FALSE;
11047 DisplayBothClocks(); /* works because currentMove is 0 */
11048 } else if (gameMode == IcsExamining) {
11049 SendToICS(ics_prefix);
11050 SendToICS("tomove white\n");
11055 SetBlackToPlayEvent()
11057 if (gameMode == EditPosition) {
11058 blackPlaysFirst = TRUE;
11059 currentMove = 1; /* kludge */
11060 DisplayBothClocks();
11062 } else if (gameMode == IcsExamining) {
11063 SendToICS(ics_prefix);
11064 SendToICS("tomove black\n");
11069 EditPositionMenuEvent(selection, x, y)
11070 ChessSquare selection;
11074 ChessSquare piece = boards[0][y][x];
11076 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11078 switch (selection) {
11080 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11081 SendToICS(ics_prefix);
11082 SendToICS("bsetup clear\n");
11083 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11084 SendToICS(ics_prefix);
11085 SendToICS("clearboard\n");
11087 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11088 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11089 for (y = 0; y < BOARD_HEIGHT; y++) {
11090 if (gameMode == IcsExamining) {
11091 if (boards[currentMove][y][x] != EmptySquare) {
11092 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11097 boards[0][y][x] = p;
11102 if (gameMode == EditPosition) {
11103 DrawPosition(FALSE, boards[0]);
11108 SetWhiteToPlayEvent();
11112 SetBlackToPlayEvent();
11116 if (gameMode == IcsExamining) {
11117 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11120 boards[0][y][x] = EmptySquare;
11121 DrawPosition(FALSE, boards[0]);
11126 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11127 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11128 selection = (ChessSquare) (PROMOTED piece);
11129 } else if(piece == EmptySquare) selection = WhiteSilver;
11130 else selection = (ChessSquare)((int)piece - 1);
11134 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11135 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11136 selection = (ChessSquare) (DEMOTED piece);
11137 } else if(piece == EmptySquare) selection = BlackSilver;
11138 else selection = (ChessSquare)((int)piece + 1);
11143 if(gameInfo.variant == VariantShatranj ||
11144 gameInfo.variant == VariantXiangqi ||
11145 gameInfo.variant == VariantCourier )
11146 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11151 if(gameInfo.variant == VariantXiangqi)
11152 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11153 if(gameInfo.variant == VariantKnightmate)
11154 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11157 if (gameMode == IcsExamining) {
11158 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11159 PieceToChar(selection), AAA + x, ONE + y);
11162 boards[0][y][x] = selection;
11163 DrawPosition(FALSE, boards[0]);
11171 DropMenuEvent(selection, x, y)
11172 ChessSquare selection;
11175 ChessMove moveType;
11177 switch (gameMode) {
11178 case IcsPlayingWhite:
11179 case MachinePlaysBlack:
11180 if (!WhiteOnMove(currentMove)) {
11181 DisplayMoveError(_("It is Black's turn"));
11184 moveType = WhiteDrop;
11186 case IcsPlayingBlack:
11187 case MachinePlaysWhite:
11188 if (WhiteOnMove(currentMove)) {
11189 DisplayMoveError(_("It is White's turn"));
11192 moveType = BlackDrop;
11195 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11201 if (moveType == BlackDrop && selection < BlackPawn) {
11202 selection = (ChessSquare) ((int) selection
11203 + (int) BlackPawn - (int) WhitePawn);
11205 if (boards[currentMove][y][x] != EmptySquare) {
11206 DisplayMoveError(_("That square is occupied"));
11210 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11216 /* Accept a pending offer of any kind from opponent */
11218 if (appData.icsActive) {
11219 SendToICS(ics_prefix);
11220 SendToICS("accept\n");
11221 } else if (cmailMsgLoaded) {
11222 if (currentMove == cmailOldMove &&
11223 commentList[cmailOldMove] != NULL &&
11224 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11225 "Black offers a draw" : "White offers a draw")) {
11227 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11228 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11230 DisplayError(_("There is no pending offer on this move"), 0);
11231 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11234 /* Not used for offers from chess program */
11241 /* Decline a pending offer of any kind from opponent */
11243 if (appData.icsActive) {
11244 SendToICS(ics_prefix);
11245 SendToICS("decline\n");
11246 } else if (cmailMsgLoaded) {
11247 if (currentMove == cmailOldMove &&
11248 commentList[cmailOldMove] != NULL &&
11249 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11250 "Black offers a draw" : "White offers a draw")) {
11252 AppendComment(cmailOldMove, "Draw declined");
11253 DisplayComment(cmailOldMove - 1, "Draw declined");
11256 DisplayError(_("There is no pending offer on this move"), 0);
11259 /* Not used for offers from chess program */
11266 /* Issue ICS rematch command */
11267 if (appData.icsActive) {
11268 SendToICS(ics_prefix);
11269 SendToICS("rematch\n");
11276 /* Call your opponent's flag (claim a win on time) */
11277 if (appData.icsActive) {
11278 SendToICS(ics_prefix);
11279 SendToICS("flag\n");
11281 switch (gameMode) {
11284 case MachinePlaysWhite:
11287 GameEnds(GameIsDrawn, "Both players ran out of time",
11290 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11292 DisplayError(_("Your opponent is not out of time"), 0);
11295 case MachinePlaysBlack:
11298 GameEnds(GameIsDrawn, "Both players ran out of time",
11301 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11303 DisplayError(_("Your opponent is not out of time"), 0);
11313 /* Offer draw or accept pending draw offer from opponent */
11315 if (appData.icsActive) {
11316 /* Note: tournament rules require draw offers to be
11317 made after you make your move but before you punch
11318 your clock. Currently ICS doesn't let you do that;
11319 instead, you immediately punch your clock after making
11320 a move, but you can offer a draw at any time. */
11322 SendToICS(ics_prefix);
11323 SendToICS("draw\n");
11324 } else if (cmailMsgLoaded) {
11325 if (currentMove == cmailOldMove &&
11326 commentList[cmailOldMove] != NULL &&
11327 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11328 "Black offers a draw" : "White offers a draw")) {
11329 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11330 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11331 } else if (currentMove == cmailOldMove + 1) {
11332 char *offer = WhiteOnMove(cmailOldMove) ?
11333 "White offers a draw" : "Black offers a draw";
11334 AppendComment(currentMove, offer);
11335 DisplayComment(currentMove - 1, offer);
11336 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11338 DisplayError(_("You must make your move before offering a draw"), 0);
11339 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11341 } else if (first.offeredDraw) {
11342 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11344 if (first.sendDrawOffers) {
11345 SendToProgram("draw\n", &first);
11346 userOfferedDraw = TRUE;
11354 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11356 if (appData.icsActive) {
11357 SendToICS(ics_prefix);
11358 SendToICS("adjourn\n");
11360 /* Currently GNU Chess doesn't offer or accept Adjourns */
11368 /* Offer Abort or accept pending Abort offer from opponent */
11370 if (appData.icsActive) {
11371 SendToICS(ics_prefix);
11372 SendToICS("abort\n");
11374 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11381 /* Resign. You can do this even if it's not your turn. */
11383 if (appData.icsActive) {
11384 SendToICS(ics_prefix);
11385 SendToICS("resign\n");
11387 switch (gameMode) {
11388 case MachinePlaysWhite:
11389 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11391 case MachinePlaysBlack:
11392 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11395 if (cmailMsgLoaded) {
11397 if (WhiteOnMove(cmailOldMove)) {
11398 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11400 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11402 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11413 StopObservingEvent()
11415 /* Stop observing current games */
11416 SendToICS(ics_prefix);
11417 SendToICS("unobserve\n");
11421 StopExaminingEvent()
11423 /* Stop observing current game */
11424 SendToICS(ics_prefix);
11425 SendToICS("unexamine\n");
11429 ForwardInner(target)
11434 if (appData.debugMode)
11435 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11436 target, currentMove, forwardMostMove);
11438 if (gameMode == EditPosition)
11441 if (gameMode == PlayFromGameFile && !pausing)
11444 if (gameMode == IcsExamining && pausing)
11445 limit = pauseExamForwardMostMove;
11447 limit = forwardMostMove;
11449 if (target > limit) target = limit;
11451 if (target > 0 && moveList[target - 1][0]) {
11452 int fromX, fromY, toX, toY;
11453 toX = moveList[target - 1][2] - AAA;
11454 toY = moveList[target - 1][3] - ONE;
11455 if (moveList[target - 1][1] == '@') {
11456 if (appData.highlightLastMove) {
11457 SetHighlights(-1, -1, toX, toY);
11460 fromX = moveList[target - 1][0] - AAA;
11461 fromY = moveList[target - 1][1] - ONE;
11462 if (target == currentMove + 1) {
11463 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11465 if (appData.highlightLastMove) {
11466 SetHighlights(fromX, fromY, toX, toY);
11470 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11471 gameMode == Training || gameMode == PlayFromGameFile ||
11472 gameMode == AnalyzeFile) {
11473 while (currentMove < target) {
11474 SendMoveToProgram(currentMove++, &first);
11477 currentMove = target;
11480 if (gameMode == EditGame || gameMode == EndOfGame) {
11481 whiteTimeRemaining = timeRemaining[0][currentMove];
11482 blackTimeRemaining = timeRemaining[1][currentMove];
11484 DisplayBothClocks();
11485 DisplayMove(currentMove - 1);
11486 DrawPosition(FALSE, boards[currentMove]);
11487 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11488 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11489 DisplayComment(currentMove - 1, commentList[currentMove]);
11497 if (gameMode == IcsExamining && !pausing) {
11498 SendToICS(ics_prefix);
11499 SendToICS("forward\n");
11501 ForwardInner(currentMove + 1);
11508 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11509 /* to optimze, we temporarily turn off analysis mode while we feed
11510 * the remaining moves to the engine. Otherwise we get analysis output
11513 if (first.analysisSupport) {
11514 SendToProgram("exit\nforce\n", &first);
11515 first.analyzing = FALSE;
11519 if (gameMode == IcsExamining && !pausing) {
11520 SendToICS(ics_prefix);
11521 SendToICS("forward 999999\n");
11523 ForwardInner(forwardMostMove);
11526 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11527 /* we have fed all the moves, so reactivate analysis mode */
11528 SendToProgram("analyze\n", &first);
11529 first.analyzing = TRUE;
11530 /*first.maybeThinking = TRUE;*/
11531 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11536 BackwardInner(target)
11539 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11541 if (appData.debugMode)
11542 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11543 target, currentMove, forwardMostMove);
11545 if (gameMode == EditPosition) return;
11546 if (currentMove <= backwardMostMove) {
11548 DrawPosition(full_redraw, boards[currentMove]);
11551 if (gameMode == PlayFromGameFile && !pausing)
11554 if (moveList[target][0]) {
11555 int fromX, fromY, toX, toY;
11556 toX = moveList[target][2] - AAA;
11557 toY = moveList[target][3] - ONE;
11558 if (moveList[target][1] == '@') {
11559 if (appData.highlightLastMove) {
11560 SetHighlights(-1, -1, toX, toY);
11563 fromX = moveList[target][0] - AAA;
11564 fromY = moveList[target][1] - ONE;
11565 if (target == currentMove - 1) {
11566 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11568 if (appData.highlightLastMove) {
11569 SetHighlights(fromX, fromY, toX, toY);
11573 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11574 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11575 while (currentMove > target) {
11576 SendToProgram("undo\n", &first);
11580 currentMove = target;
11583 if (gameMode == EditGame || gameMode == EndOfGame) {
11584 whiteTimeRemaining = timeRemaining[0][currentMove];
11585 blackTimeRemaining = timeRemaining[1][currentMove];
11587 DisplayBothClocks();
11588 DisplayMove(currentMove - 1);
11589 DrawPosition(full_redraw, boards[currentMove]);
11590 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11591 // [HGM] PV info: routine tests if comment empty
11592 DisplayComment(currentMove - 1, commentList[currentMove]);
11598 if (gameMode == IcsExamining && !pausing) {
11599 SendToICS(ics_prefix);
11600 SendToICS("backward\n");
11602 BackwardInner(currentMove - 1);
11609 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11610 /* to optimze, we temporarily turn off analysis mode while we undo
11611 * all the moves. Otherwise we get analysis output after each undo.
11613 if (first.analysisSupport) {
11614 SendToProgram("exit\nforce\n", &first);
11615 first.analyzing = FALSE;
11619 if (gameMode == IcsExamining && !pausing) {
11620 SendToICS(ics_prefix);
11621 SendToICS("backward 999999\n");
11623 BackwardInner(backwardMostMove);
11626 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11627 /* we have fed all the moves, so reactivate analysis mode */
11628 SendToProgram("analyze\n", &first);
11629 first.analyzing = TRUE;
11630 /*first.maybeThinking = TRUE;*/
11631 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11638 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11639 if (to >= forwardMostMove) to = forwardMostMove;
11640 if (to <= backwardMostMove) to = backwardMostMove;
11641 if (to < currentMove) {
11651 if (gameMode != IcsExamining) {
11652 DisplayError(_("You are not examining a game"), 0);
11656 DisplayError(_("You can't revert while pausing"), 0);
11659 SendToICS(ics_prefix);
11660 SendToICS("revert\n");
11666 switch (gameMode) {
11667 case MachinePlaysWhite:
11668 case MachinePlaysBlack:
11669 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11670 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11673 if (forwardMostMove < 2) return;
11674 currentMove = forwardMostMove = forwardMostMove - 2;
11675 whiteTimeRemaining = timeRemaining[0][currentMove];
11676 blackTimeRemaining = timeRemaining[1][currentMove];
11677 DisplayBothClocks();
11678 DisplayMove(currentMove - 1);
11679 ClearHighlights();/*!! could figure this out*/
11680 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11681 SendToProgram("remove\n", &first);
11682 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11685 case BeginningOfGame:
11689 case IcsPlayingWhite:
11690 case IcsPlayingBlack:
11691 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11692 SendToICS(ics_prefix);
11693 SendToICS("takeback 2\n");
11695 SendToICS(ics_prefix);
11696 SendToICS("takeback 1\n");
11705 ChessProgramState *cps;
11707 switch (gameMode) {
11708 case MachinePlaysWhite:
11709 if (!WhiteOnMove(forwardMostMove)) {
11710 DisplayError(_("It is your turn"), 0);
11715 case MachinePlaysBlack:
11716 if (WhiteOnMove(forwardMostMove)) {
11717 DisplayError(_("It is your turn"), 0);
11722 case TwoMachinesPlay:
11723 if (WhiteOnMove(forwardMostMove) ==
11724 (first.twoMachinesColor[0] == 'w')) {
11730 case BeginningOfGame:
11734 SendToProgram("?\n", cps);
11738 TruncateGameEvent()
11741 if (gameMode != EditGame) return;
11748 if (forwardMostMove > currentMove) {
11749 if (gameInfo.resultDetails != NULL) {
11750 free(gameInfo.resultDetails);
11751 gameInfo.resultDetails = NULL;
11752 gameInfo.result = GameUnfinished;
11754 forwardMostMove = currentMove;
11755 HistorySet(parseList, backwardMostMove, forwardMostMove,
11763 if (appData.noChessProgram) return;
11764 switch (gameMode) {
11765 case MachinePlaysWhite:
11766 if (WhiteOnMove(forwardMostMove)) {
11767 DisplayError(_("Wait until your turn"), 0);
11771 case BeginningOfGame:
11772 case MachinePlaysBlack:
11773 if (!WhiteOnMove(forwardMostMove)) {
11774 DisplayError(_("Wait until your turn"), 0);
11779 DisplayError(_("No hint available"), 0);
11782 SendToProgram("hint\n", &first);
11783 hintRequested = TRUE;
11789 if (appData.noChessProgram) return;
11790 switch (gameMode) {
11791 case MachinePlaysWhite:
11792 if (WhiteOnMove(forwardMostMove)) {
11793 DisplayError(_("Wait until your turn"), 0);
11797 case BeginningOfGame:
11798 case MachinePlaysBlack:
11799 if (!WhiteOnMove(forwardMostMove)) {
11800 DisplayError(_("Wait until your turn"), 0);
11805 EditPositionDone();
11807 case TwoMachinesPlay:
11812 SendToProgram("bk\n", &first);
11813 bookOutput[0] = NULLCHAR;
11814 bookRequested = TRUE;
11820 char *tags = PGNTags(&gameInfo);
11821 TagsPopUp(tags, CmailMsg());
11825 /* end button procedures */
11828 PrintPosition(fp, move)
11834 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11835 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11836 char c = PieceToChar(boards[move][i][j]);
11837 fputc(c == 'x' ? '.' : c, fp);
11838 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11841 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11842 fprintf(fp, "white to play\n");
11844 fprintf(fp, "black to play\n");
11851 if (gameInfo.white != NULL) {
11852 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11858 /* Find last component of program's own name, using some heuristics */
11860 TidyProgramName(prog, host, buf)
11861 char *prog, *host, buf[MSG_SIZ];
11864 int local = (strcmp(host, "localhost") == 0);
11865 while (!local && (p = strchr(prog, ';')) != NULL) {
11867 while (*p == ' ') p++;
11870 if (*prog == '"' || *prog == '\'') {
11871 q = strchr(prog + 1, *prog);
11873 q = strchr(prog, ' ');
11875 if (q == NULL) q = prog + strlen(prog);
11877 while (p >= prog && *p != '/' && *p != '\\') p--;
11879 if(p == prog && *p == '"') p++;
11880 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11881 memcpy(buf, p, q - p);
11882 buf[q - p] = NULLCHAR;
11890 TimeControlTagValue()
11893 if (!appData.clockMode) {
11895 } else if (movesPerSession > 0) {
11896 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11897 } else if (timeIncrement == 0) {
11898 sprintf(buf, "%ld", timeControl/1000);
11900 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11902 return StrSave(buf);
11908 /* This routine is used only for certain modes */
11909 VariantClass v = gameInfo.variant;
11910 ClearGameInfo(&gameInfo);
11911 gameInfo.variant = v;
11913 switch (gameMode) {
11914 case MachinePlaysWhite:
11915 gameInfo.event = StrSave( appData.pgnEventHeader );
11916 gameInfo.site = StrSave(HostName());
11917 gameInfo.date = PGNDate();
11918 gameInfo.round = StrSave("-");
11919 gameInfo.white = StrSave(first.tidy);
11920 gameInfo.black = StrSave(UserName());
11921 gameInfo.timeControl = TimeControlTagValue();
11924 case MachinePlaysBlack:
11925 gameInfo.event = StrSave( appData.pgnEventHeader );
11926 gameInfo.site = StrSave(HostName());
11927 gameInfo.date = PGNDate();
11928 gameInfo.round = StrSave("-");
11929 gameInfo.white = StrSave(UserName());
11930 gameInfo.black = StrSave(first.tidy);
11931 gameInfo.timeControl = TimeControlTagValue();
11934 case TwoMachinesPlay:
11935 gameInfo.event = StrSave( appData.pgnEventHeader );
11936 gameInfo.site = StrSave(HostName());
11937 gameInfo.date = PGNDate();
11938 if (matchGame > 0) {
11940 sprintf(buf, "%d", matchGame);
11941 gameInfo.round = StrSave(buf);
11943 gameInfo.round = StrSave("-");
11945 if (first.twoMachinesColor[0] == 'w') {
11946 gameInfo.white = StrSave(first.tidy);
11947 gameInfo.black = StrSave(second.tidy);
11949 gameInfo.white = StrSave(second.tidy);
11950 gameInfo.black = StrSave(first.tidy);
11952 gameInfo.timeControl = TimeControlTagValue();
11956 gameInfo.event = StrSave("Edited game");
11957 gameInfo.site = StrSave(HostName());
11958 gameInfo.date = PGNDate();
11959 gameInfo.round = StrSave("-");
11960 gameInfo.white = StrSave("-");
11961 gameInfo.black = StrSave("-");
11965 gameInfo.event = StrSave("Edited position");
11966 gameInfo.site = StrSave(HostName());
11967 gameInfo.date = PGNDate();
11968 gameInfo.round = StrSave("-");
11969 gameInfo.white = StrSave("-");
11970 gameInfo.black = StrSave("-");
11973 case IcsPlayingWhite:
11974 case IcsPlayingBlack:
11979 case PlayFromGameFile:
11980 gameInfo.event = StrSave("Game from non-PGN file");
11981 gameInfo.site = StrSave(HostName());
11982 gameInfo.date = PGNDate();
11983 gameInfo.round = StrSave("-");
11984 gameInfo.white = StrSave("?");
11985 gameInfo.black = StrSave("?");
11994 ReplaceComment(index, text)
12000 while (*text == '\n') text++;
12001 len = strlen(text);
12002 while (len > 0 && text[len - 1] == '\n') len--;
12004 if (commentList[index] != NULL)
12005 free(commentList[index]);
12008 commentList[index] = NULL;
12011 commentList[index] = (char *) malloc(len + 2);
12012 strncpy(commentList[index], text, len);
12013 commentList[index][len] = '\n';
12014 commentList[index][len + 1] = NULLCHAR;
12027 if (ch == '\r') continue;
12029 } while (ch != '\0');
12033 AppendComment(index, text)
12040 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12043 while (*text == '\n') text++;
12044 len = strlen(text);
12045 while (len > 0 && text[len - 1] == '\n') len--;
12047 if (len == 0) return;
12049 if (commentList[index] != NULL) {
12050 old = commentList[index];
12051 oldlen = strlen(old);
12052 commentList[index] = (char *) malloc(oldlen + len + 2);
12053 strcpy(commentList[index], old);
12055 strncpy(&commentList[index][oldlen], text, len);
12056 commentList[index][oldlen + len] = '\n';
12057 commentList[index][oldlen + len + 1] = NULLCHAR;
12059 commentList[index] = (char *) malloc(len + 2);
12060 strncpy(commentList[index], text, len);
12061 commentList[index][len] = '\n';
12062 commentList[index][len + 1] = NULLCHAR;
12066 static char * FindStr( char * text, char * sub_text )
12068 char * result = strstr( text, sub_text );
12070 if( result != NULL ) {
12071 result += strlen( sub_text );
12077 /* [AS] Try to extract PV info from PGN comment */
12078 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12079 char *GetInfoFromComment( int index, char * text )
12083 if( text != NULL && index > 0 ) {
12086 int time = -1, sec = 0, deci;
12087 char * s_eval = FindStr( text, "[%eval " );
12088 char * s_emt = FindStr( text, "[%emt " );
12090 if( s_eval != NULL || s_emt != NULL ) {
12094 if( s_eval != NULL ) {
12095 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12099 if( delim != ']' ) {
12104 if( s_emt != NULL ) {
12108 /* We expect something like: [+|-]nnn.nn/dd */
12111 sep = strchr( text, '/' );
12112 if( sep == NULL || sep < (text+4) ) {
12116 time = -1; sec = -1; deci = -1;
12117 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12118 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12119 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12120 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12124 if( score_lo < 0 || score_lo >= 100 ) {
12128 if(sec >= 0) time = 600*time + 10*sec; else
12129 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12131 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12133 /* [HGM] PV time: now locate end of PV info */
12134 while( *++sep >= '0' && *sep <= '9'); // strip depth
12136 while( *++sep >= '0' && *sep <= '9'); // strip time
12138 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12140 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12141 while(*sep == ' ') sep++;
12152 pvInfoList[index-1].depth = depth;
12153 pvInfoList[index-1].score = score;
12154 pvInfoList[index-1].time = 10*time; // centi-sec
12160 SendToProgram(message, cps)
12162 ChessProgramState *cps;
12164 int count, outCount, error;
12167 if (cps->pr == NULL) return;
12170 if (appData.debugMode) {
12173 fprintf(debugFP, "%ld >%-6s: %s",
12174 SubtractTimeMarks(&now, &programStartTime),
12175 cps->which, message);
12178 count = strlen(message);
12179 outCount = OutputToProcess(cps->pr, message, count, &error);
12180 if (outCount < count && !exiting
12181 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12182 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12183 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12184 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12185 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12186 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12188 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12190 gameInfo.resultDetails = buf;
12192 DisplayFatalError(buf, error, 1);
12197 ReceiveFromProgram(isr, closure, message, count, error)
12198 InputSourceRef isr;
12206 ChessProgramState *cps = (ChessProgramState *)closure;
12208 if (isr != cps->isr) return; /* Killed intentionally */
12212 _("Error: %s chess program (%s) exited unexpectedly"),
12213 cps->which, cps->program);
12214 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12215 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12216 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12217 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12219 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12221 gameInfo.resultDetails = buf;
12223 RemoveInputSource(cps->isr);
12224 DisplayFatalError(buf, 0, 1);
12227 _("Error reading from %s chess program (%s)"),
12228 cps->which, cps->program);
12229 RemoveInputSource(cps->isr);
12231 /* [AS] Program is misbehaving badly... kill it */
12232 if( count == -2 ) {
12233 DestroyChildProcess( cps->pr, 9 );
12237 DisplayFatalError(buf, error, 1);
12242 if ((end_str = strchr(message, '\r')) != NULL)
12243 *end_str = NULLCHAR;
12244 if ((end_str = strchr(message, '\n')) != NULL)
12245 *end_str = NULLCHAR;
12247 if (appData.debugMode) {
12248 TimeMark now; int print = 1;
12249 char *quote = ""; char c; int i;
12251 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12252 char start = message[0];
12253 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12254 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12255 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12256 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12257 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12258 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12259 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12260 { quote = "# "; print = (appData.engineComments == 2); }
12261 message[0] = start; // restore original message
12265 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12266 SubtractTimeMarks(&now, &programStartTime), cps->which,
12272 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12273 if (appData.icsEngineAnalyze) {
12274 if (strstr(message, "whisper") != NULL ||
12275 strstr(message, "kibitz") != NULL ||
12276 strstr(message, "tellics") != NULL) return;
12279 HandleMachineMove(message, cps);
12284 SendTimeControl(cps, mps, tc, inc, sd, st)
12285 ChessProgramState *cps;
12286 int mps, inc, sd, st;
12292 if( timeControl_2 > 0 ) {
12293 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12294 tc = timeControl_2;
12297 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12298 inc /= cps->timeOdds;
12299 st /= cps->timeOdds;
12301 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12304 /* Set exact time per move, normally using st command */
12305 if (cps->stKludge) {
12306 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12308 if (seconds == 0) {
12309 sprintf(buf, "level 1 %d\n", st/60);
12311 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12314 sprintf(buf, "st %d\n", st);
12317 /* Set conventional or incremental time control, using level command */
12318 if (seconds == 0) {
12319 /* Note old gnuchess bug -- minutes:seconds used to not work.
12320 Fixed in later versions, but still avoid :seconds
12321 when seconds is 0. */
12322 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12324 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12325 seconds, inc/1000);
12328 SendToProgram(buf, cps);
12330 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12331 /* Orthogonally, limit search to given depth */
12333 if (cps->sdKludge) {
12334 sprintf(buf, "depth\n%d\n", sd);
12336 sprintf(buf, "sd %d\n", sd);
12338 SendToProgram(buf, cps);
12341 if(cps->nps > 0) { /* [HGM] nps */
12342 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12344 sprintf(buf, "nps %d\n", cps->nps);
12345 SendToProgram(buf, cps);
12350 ChessProgramState *WhitePlayer()
12351 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12353 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12354 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12360 SendTimeRemaining(cps, machineWhite)
12361 ChessProgramState *cps;
12362 int /*boolean*/ machineWhite;
12364 char message[MSG_SIZ];
12367 /* Note: this routine must be called when the clocks are stopped
12368 or when they have *just* been set or switched; otherwise
12369 it will be off by the time since the current tick started.
12371 if (machineWhite) {
12372 time = whiteTimeRemaining / 10;
12373 otime = blackTimeRemaining / 10;
12375 time = blackTimeRemaining / 10;
12376 otime = whiteTimeRemaining / 10;
12378 /* [HGM] translate opponent's time by time-odds factor */
12379 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12380 if (appData.debugMode) {
12381 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12384 if (time <= 0) time = 1;
12385 if (otime <= 0) otime = 1;
12387 sprintf(message, "time %ld\n", time);
12388 SendToProgram(message, cps);
12390 sprintf(message, "otim %ld\n", otime);
12391 SendToProgram(message, cps);
12395 BoolFeature(p, name, loc, cps)
12399 ChessProgramState *cps;
12402 int len = strlen(name);
12404 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12406 sscanf(*p, "%d", &val);
12408 while (**p && **p != ' ') (*p)++;
12409 sprintf(buf, "accepted %s\n", name);
12410 SendToProgram(buf, cps);
12417 IntFeature(p, name, loc, cps)
12421 ChessProgramState *cps;
12424 int len = strlen(name);
12425 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12427 sscanf(*p, "%d", loc);
12428 while (**p && **p != ' ') (*p)++;
12429 sprintf(buf, "accepted %s\n", name);
12430 SendToProgram(buf, cps);
12437 StringFeature(p, name, loc, cps)
12441 ChessProgramState *cps;
12444 int len = strlen(name);
12445 if (strncmp((*p), name, len) == 0
12446 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12448 sscanf(*p, "%[^\"]", loc);
12449 while (**p && **p != '\"') (*p)++;
12450 if (**p == '\"') (*p)++;
12451 sprintf(buf, "accepted %s\n", name);
12452 SendToProgram(buf, cps);
12459 ParseOption(Option *opt, ChessProgramState *cps)
12460 // [HGM] options: process the string that defines an engine option, and determine
12461 // name, type, default value, and allowed value range
12463 char *p, *q, buf[MSG_SIZ];
12464 int n, min = (-1)<<31, max = 1<<31, def;
12466 if(p = strstr(opt->name, " -spin ")) {
12467 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12468 if(max < min) max = min; // enforce consistency
12469 if(def < min) def = min;
12470 if(def > max) def = max;
12475 } else if((p = strstr(opt->name, " -slider "))) {
12476 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12477 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12478 if(max < min) max = min; // enforce consistency
12479 if(def < min) def = min;
12480 if(def > max) def = max;
12484 opt->type = Spin; // Slider;
12485 } else if((p = strstr(opt->name, " -string "))) {
12486 opt->textValue = p+9;
12487 opt->type = TextBox;
12488 } else if((p = strstr(opt->name, " -file "))) {
12489 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12490 opt->textValue = p+7;
12491 opt->type = TextBox; // FileName;
12492 } else if((p = strstr(opt->name, " -path "))) {
12493 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12494 opt->textValue = p+7;
12495 opt->type = TextBox; // PathName;
12496 } else if(p = strstr(opt->name, " -check ")) {
12497 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12498 opt->value = (def != 0);
12499 opt->type = CheckBox;
12500 } else if(p = strstr(opt->name, " -combo ")) {
12501 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12502 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12503 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12504 opt->value = n = 0;
12505 while(q = StrStr(q, " /// ")) {
12506 n++; *q = 0; // count choices, and null-terminate each of them
12508 if(*q == '*') { // remember default, which is marked with * prefix
12512 cps->comboList[cps->comboCnt++] = q;
12514 cps->comboList[cps->comboCnt++] = NULL;
12516 opt->type = ComboBox;
12517 } else if(p = strstr(opt->name, " -button")) {
12518 opt->type = Button;
12519 } else if(p = strstr(opt->name, " -save")) {
12520 opt->type = SaveButton;
12521 } else return FALSE;
12522 *p = 0; // terminate option name
12523 // now look if the command-line options define a setting for this engine option.
12524 if(cps->optionSettings && cps->optionSettings[0])
12525 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12526 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12527 sprintf(buf, "option %s", p);
12528 if(p = strstr(buf, ",")) *p = 0;
12530 SendToProgram(buf, cps);
12536 FeatureDone(cps, val)
12537 ChessProgramState* cps;
12540 DelayedEventCallback cb = GetDelayedEvent();
12541 if ((cb == InitBackEnd3 && cps == &first) ||
12542 (cb == TwoMachinesEventIfReady && cps == &second)) {
12543 CancelDelayedEvent();
12544 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12546 cps->initDone = val;
12549 /* Parse feature command from engine */
12551 ParseFeatures(args, cps)
12553 ChessProgramState *cps;
12561 while (*p == ' ') p++;
12562 if (*p == NULLCHAR) return;
12564 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12565 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12566 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12567 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12568 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12569 if (BoolFeature(&p, "reuse", &val, cps)) {
12570 /* Engine can disable reuse, but can't enable it if user said no */
12571 if (!val) cps->reuse = FALSE;
12574 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12575 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12576 if (gameMode == TwoMachinesPlay) {
12577 DisplayTwoMachinesTitle();
12583 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12584 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12585 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12586 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12587 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12588 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12589 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12590 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12591 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12592 if (IntFeature(&p, "done", &val, cps)) {
12593 FeatureDone(cps, val);
12596 /* Added by Tord: */
12597 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12598 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12599 /* End of additions by Tord */
12601 /* [HGM] added features: */
12602 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12603 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12604 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12605 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12606 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12607 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12608 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12609 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12610 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12611 SendToProgram(buf, cps);
12614 if(cps->nrOptions >= MAX_OPTIONS) {
12616 sprintf(buf, "%s engine has too many options\n", cps->which);
12617 DisplayError(buf, 0);
12621 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12622 /* End of additions by HGM */
12624 /* unknown feature: complain and skip */
12626 while (*q && *q != '=') q++;
12627 sprintf(buf, "rejected %.*s\n", q-p, p);
12628 SendToProgram(buf, cps);
12634 while (*p && *p != '\"') p++;
12635 if (*p == '\"') p++;
12637 while (*p && *p != ' ') p++;
12645 PeriodicUpdatesEvent(newState)
12648 if (newState == appData.periodicUpdates)
12651 appData.periodicUpdates=newState;
12653 /* Display type changes, so update it now */
12656 /* Get the ball rolling again... */
12658 AnalysisPeriodicEvent(1);
12659 StartAnalysisClock();
12664 PonderNextMoveEvent(newState)
12667 if (newState == appData.ponderNextMove) return;
12668 if (gameMode == EditPosition) EditPositionDone();
12670 SendToProgram("hard\n", &first);
12671 if (gameMode == TwoMachinesPlay) {
12672 SendToProgram("hard\n", &second);
12675 SendToProgram("easy\n", &first);
12676 thinkOutput[0] = NULLCHAR;
12677 if (gameMode == TwoMachinesPlay) {
12678 SendToProgram("easy\n", &second);
12681 appData.ponderNextMove = newState;
12685 NewSettingEvent(option, command, value)
12691 if (gameMode == EditPosition) EditPositionDone();
12692 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12693 SendToProgram(buf, &first);
12694 if (gameMode == TwoMachinesPlay) {
12695 SendToProgram(buf, &second);
12700 ShowThinkingEvent()
12701 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12703 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12704 int newState = appData.showThinking
12705 // [HGM] thinking: other features now need thinking output as well
12706 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12708 if (oldState == newState) return;
12709 oldState = newState;
12710 if (gameMode == EditPosition) EditPositionDone();
12712 SendToProgram("post\n", &first);
12713 if (gameMode == TwoMachinesPlay) {
12714 SendToProgram("post\n", &second);
12717 SendToProgram("nopost\n", &first);
12718 thinkOutput[0] = NULLCHAR;
12719 if (gameMode == TwoMachinesPlay) {
12720 SendToProgram("nopost\n", &second);
12723 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12727 AskQuestionEvent(title, question, replyPrefix, which)
12728 char *title; char *question; char *replyPrefix; char *which;
12730 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12731 if (pr == NoProc) return;
12732 AskQuestion(title, question, replyPrefix, pr);
12736 DisplayMove(moveNumber)
12739 char message[MSG_SIZ];
12741 char cpThinkOutput[MSG_SIZ];
12743 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12745 if (moveNumber == forwardMostMove - 1 ||
12746 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12748 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12750 if (strchr(cpThinkOutput, '\n')) {
12751 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12754 *cpThinkOutput = NULLCHAR;
12757 /* [AS] Hide thinking from human user */
12758 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12759 *cpThinkOutput = NULLCHAR;
12760 if( thinkOutput[0] != NULLCHAR ) {
12763 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12764 cpThinkOutput[i] = '.';
12766 cpThinkOutput[i] = NULLCHAR;
12767 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12771 if (moveNumber == forwardMostMove - 1 &&
12772 gameInfo.resultDetails != NULL) {
12773 if (gameInfo.resultDetails[0] == NULLCHAR) {
12774 sprintf(res, " %s", PGNResult(gameInfo.result));
12776 sprintf(res, " {%s} %s",
12777 gameInfo.resultDetails, PGNResult(gameInfo.result));
12783 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12784 DisplayMessage(res, cpThinkOutput);
12786 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12787 WhiteOnMove(moveNumber) ? " " : ".. ",
12788 parseList[moveNumber], res);
12789 DisplayMessage(message, cpThinkOutput);
12794 DisplayAnalysisText(text)
12799 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12800 || appData.icsEngineAnalyze) {
12801 sprintf(buf, "Analysis (%s)", first.tidy);
12802 AnalysisPopUp(buf, text);
12810 while (*str && isspace(*str)) ++str;
12811 while (*str && !isspace(*str)) ++str;
12812 if (!*str) return 1;
12813 while (*str && isspace(*str)) ++str;
12814 if (!*str) return 1;
12822 char lst[MSG_SIZ / 2];
12824 static char *xtra[] = { "", " (--)", " (++)" };
12827 if (programStats.time == 0) {
12828 programStats.time = 1;
12831 if (programStats.got_only_move) {
12832 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12834 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12836 nps = (u64ToDouble(programStats.nodes) /
12837 ((double)programStats.time /100.0));
12839 cs = programStats.time % 100;
12840 s = programStats.time / 100;
12846 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12847 if (programStats.move_name[0] != NULLCHAR) {
12848 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12849 programStats.depth,
12850 programStats.nr_moves-programStats.moves_left,
12851 programStats.nr_moves, programStats.move_name,
12852 ((float)programStats.score)/100.0, lst,
12853 only_one_move(lst)?
12854 xtra[programStats.got_fail] : "",
12855 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12857 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12858 programStats.depth,
12859 programStats.nr_moves-programStats.moves_left,
12860 programStats.nr_moves, ((float)programStats.score)/100.0,
12862 only_one_move(lst)?
12863 xtra[programStats.got_fail] : "",
12864 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12867 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12868 programStats.depth,
12869 ((float)programStats.score)/100.0,
12871 only_one_move(lst)?
12872 xtra[programStats.got_fail] : "",
12873 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12876 DisplayAnalysisText(buf);
12880 DisplayComment(moveNumber, text)
12884 char title[MSG_SIZ];
12885 char buf[8000]; // comment can be long!
12888 if( appData.autoDisplayComment ) {
12889 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12890 strcpy(title, "Comment");
12892 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12893 WhiteOnMove(moveNumber) ? " " : ".. ",
12894 parseList[moveNumber]);
12896 // [HGM] PV info: display PV info together with (or as) comment
12897 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12898 if(text == NULL) text = "";
12899 score = pvInfoList[moveNumber].score;
12900 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12901 depth, (pvInfoList[moveNumber].time+50)/100, text);
12904 } else title[0] = 0;
12907 CommentPopUp(title, text);
12910 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12911 * might be busy thinking or pondering. It can be omitted if your
12912 * gnuchess is configured to stop thinking immediately on any user
12913 * input. However, that gnuchess feature depends on the FIONREAD
12914 * ioctl, which does not work properly on some flavors of Unix.
12918 ChessProgramState *cps;
12921 if (!cps->useSigint) return;
12922 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12923 switch (gameMode) {
12924 case MachinePlaysWhite:
12925 case MachinePlaysBlack:
12926 case TwoMachinesPlay:
12927 case IcsPlayingWhite:
12928 case IcsPlayingBlack:
12931 /* Skip if we know it isn't thinking */
12932 if (!cps->maybeThinking) return;
12933 if (appData.debugMode)
12934 fprintf(debugFP, "Interrupting %s\n", cps->which);
12935 InterruptChildProcess(cps->pr);
12936 cps->maybeThinking = FALSE;
12941 #endif /*ATTENTION*/
12947 if (whiteTimeRemaining <= 0) {
12950 if (appData.icsActive) {
12951 if (appData.autoCallFlag &&
12952 gameMode == IcsPlayingBlack && !blackFlag) {
12953 SendToICS(ics_prefix);
12954 SendToICS("flag\n");
12958 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12960 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12961 if (appData.autoCallFlag) {
12962 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12969 if (blackTimeRemaining <= 0) {
12972 if (appData.icsActive) {
12973 if (appData.autoCallFlag &&
12974 gameMode == IcsPlayingWhite && !whiteFlag) {
12975 SendToICS(ics_prefix);
12976 SendToICS("flag\n");
12980 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12982 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12983 if (appData.autoCallFlag) {
12984 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12997 if (!appData.clockMode || appData.icsActive ||
12998 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13001 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13003 if ( !WhiteOnMove(forwardMostMove) )
13004 /* White made time control */
13005 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13006 /* [HGM] time odds: correct new time quota for time odds! */
13007 / WhitePlayer()->timeOdds;
13009 /* Black made time control */
13010 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13011 / WhitePlayer()->other->timeOdds;
13015 DisplayBothClocks()
13017 int wom = gameMode == EditPosition ?
13018 !blackPlaysFirst : WhiteOnMove(currentMove);
13019 DisplayWhiteClock(whiteTimeRemaining, wom);
13020 DisplayBlackClock(blackTimeRemaining, !wom);
13024 /* Timekeeping seems to be a portability nightmare. I think everyone
13025 has ftime(), but I'm really not sure, so I'm including some ifdefs
13026 to use other calls if you don't. Clocks will be less accurate if
13027 you have neither ftime nor gettimeofday.
13030 /* VS 2008 requires the #include outside of the function */
13031 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13032 #include <sys/timeb.h>
13035 /* Get the current time as a TimeMark */
13040 #if HAVE_GETTIMEOFDAY
13042 struct timeval timeVal;
13043 struct timezone timeZone;
13045 gettimeofday(&timeVal, &timeZone);
13046 tm->sec = (long) timeVal.tv_sec;
13047 tm->ms = (int) (timeVal.tv_usec / 1000L);
13049 #else /*!HAVE_GETTIMEOFDAY*/
13052 // include <sys/timeb.h> / moved to just above start of function
13053 struct timeb timeB;
13056 tm->sec = (long) timeB.time;
13057 tm->ms = (int) timeB.millitm;
13059 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13060 tm->sec = (long) time(NULL);
13066 /* Return the difference in milliseconds between two
13067 time marks. We assume the difference will fit in a long!
13070 SubtractTimeMarks(tm2, tm1)
13071 TimeMark *tm2, *tm1;
13073 return 1000L*(tm2->sec - tm1->sec) +
13074 (long) (tm2->ms - tm1->ms);
13079 * Code to manage the game clocks.
13081 * In tournament play, black starts the clock and then white makes a move.
13082 * We give the human user a slight advantage if he is playing white---the
13083 * clocks don't run until he makes his first move, so it takes zero time.
13084 * Also, we don't account for network lag, so we could get out of sync
13085 * with GNU Chess's clock -- but then, referees are always right.
13088 static TimeMark tickStartTM;
13089 static long intendedTickLength;
13092 NextTickLength(timeRemaining)
13093 long timeRemaining;
13095 long nominalTickLength, nextTickLength;
13097 if (timeRemaining > 0L && timeRemaining <= 10000L)
13098 nominalTickLength = 100L;
13100 nominalTickLength = 1000L;
13101 nextTickLength = timeRemaining % nominalTickLength;
13102 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13104 return nextTickLength;
13107 /* Adjust clock one minute up or down */
13109 AdjustClock(Boolean which, int dir)
13111 if(which) blackTimeRemaining += 60000*dir;
13112 else whiteTimeRemaining += 60000*dir;
13113 DisplayBothClocks();
13116 /* Stop clocks and reset to a fresh time control */
13120 (void) StopClockTimer();
13121 if (appData.icsActive) {
13122 whiteTimeRemaining = blackTimeRemaining = 0;
13123 } else { /* [HGM] correct new time quote for time odds */
13124 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13125 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13127 if (whiteFlag || blackFlag) {
13129 whiteFlag = blackFlag = FALSE;
13131 DisplayBothClocks();
13134 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13136 /* Decrement running clock by amount of time that has passed */
13140 long timeRemaining;
13141 long lastTickLength, fudge;
13144 if (!appData.clockMode) return;
13145 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13149 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13151 /* Fudge if we woke up a little too soon */
13152 fudge = intendedTickLength - lastTickLength;
13153 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13155 if (WhiteOnMove(forwardMostMove)) {
13156 if(whiteNPS >= 0) lastTickLength = 0;
13157 timeRemaining = whiteTimeRemaining -= lastTickLength;
13158 DisplayWhiteClock(whiteTimeRemaining - fudge,
13159 WhiteOnMove(currentMove));
13161 if(blackNPS >= 0) lastTickLength = 0;
13162 timeRemaining = blackTimeRemaining -= lastTickLength;
13163 DisplayBlackClock(blackTimeRemaining - fudge,
13164 !WhiteOnMove(currentMove));
13167 if (CheckFlags()) return;
13170 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13171 StartClockTimer(intendedTickLength);
13173 /* if the time remaining has fallen below the alarm threshold, sound the
13174 * alarm. if the alarm has sounded and (due to a takeback or time control
13175 * with increment) the time remaining has increased to a level above the
13176 * threshold, reset the alarm so it can sound again.
13179 if (appData.icsActive && appData.icsAlarm) {
13181 /* make sure we are dealing with the user's clock */
13182 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13183 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13186 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13187 alarmSounded = FALSE;
13188 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13190 alarmSounded = TRUE;
13196 /* A player has just moved, so stop the previously running
13197 clock and (if in clock mode) start the other one.
13198 We redisplay both clocks in case we're in ICS mode, because
13199 ICS gives us an update to both clocks after every move.
13200 Note that this routine is called *after* forwardMostMove
13201 is updated, so the last fractional tick must be subtracted
13202 from the color that is *not* on move now.
13207 long lastTickLength;
13209 int flagged = FALSE;
13213 if (StopClockTimer() && appData.clockMode) {
13214 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13215 if (WhiteOnMove(forwardMostMove)) {
13216 if(blackNPS >= 0) lastTickLength = 0;
13217 blackTimeRemaining -= lastTickLength;
13218 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13219 // if(pvInfoList[forwardMostMove-1].time == -1)
13220 pvInfoList[forwardMostMove-1].time = // use GUI time
13221 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13223 if(whiteNPS >= 0) lastTickLength = 0;
13224 whiteTimeRemaining -= lastTickLength;
13225 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13226 // if(pvInfoList[forwardMostMove-1].time == -1)
13227 pvInfoList[forwardMostMove-1].time =
13228 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13230 flagged = CheckFlags();
13232 CheckTimeControl();
13234 if (flagged || !appData.clockMode) return;
13236 switch (gameMode) {
13237 case MachinePlaysBlack:
13238 case MachinePlaysWhite:
13239 case BeginningOfGame:
13240 if (pausing) return;
13244 case PlayFromGameFile:
13253 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13254 whiteTimeRemaining : blackTimeRemaining);
13255 StartClockTimer(intendedTickLength);
13259 /* Stop both clocks */
13263 long lastTickLength;
13266 if (!StopClockTimer()) return;
13267 if (!appData.clockMode) return;
13271 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13272 if (WhiteOnMove(forwardMostMove)) {
13273 if(whiteNPS >= 0) lastTickLength = 0;
13274 whiteTimeRemaining -= lastTickLength;
13275 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13277 if(blackNPS >= 0) lastTickLength = 0;
13278 blackTimeRemaining -= lastTickLength;
13279 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13284 /* Start clock of player on move. Time may have been reset, so
13285 if clock is already running, stop and restart it. */
13289 (void) StopClockTimer(); /* in case it was running already */
13290 DisplayBothClocks();
13291 if (CheckFlags()) return;
13293 if (!appData.clockMode) return;
13294 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13296 GetTimeMark(&tickStartTM);
13297 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13298 whiteTimeRemaining : blackTimeRemaining);
13300 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13301 whiteNPS = blackNPS = -1;
13302 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13303 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13304 whiteNPS = first.nps;
13305 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13306 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13307 blackNPS = first.nps;
13308 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13309 whiteNPS = second.nps;
13310 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13311 blackNPS = second.nps;
13312 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13314 StartClockTimer(intendedTickLength);
13321 long second, minute, hour, day;
13323 static char buf[32];
13325 if (ms > 0 && ms <= 9900) {
13326 /* convert milliseconds to tenths, rounding up */
13327 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13329 sprintf(buf, " %03.1f ", tenths/10.0);
13333 /* convert milliseconds to seconds, rounding up */
13334 /* use floating point to avoid strangeness of integer division
13335 with negative dividends on many machines */
13336 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13343 day = second / (60 * 60 * 24);
13344 second = second % (60 * 60 * 24);
13345 hour = second / (60 * 60);
13346 second = second % (60 * 60);
13347 minute = second / 60;
13348 second = second % 60;
13351 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13352 sign, day, hour, minute, second);
13354 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13356 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13363 * This is necessary because some C libraries aren't ANSI C compliant yet.
13366 StrStr(string, match)
13367 char *string, *match;
13371 length = strlen(match);
13373 for (i = strlen(string) - length; i >= 0; i--, string++)
13374 if (!strncmp(match, string, length))
13381 StrCaseStr(string, match)
13382 char *string, *match;
13386 length = strlen(match);
13388 for (i = strlen(string) - length; i >= 0; i--, string++) {
13389 for (j = 0; j < length; j++) {
13390 if (ToLower(match[j]) != ToLower(string[j]))
13393 if (j == length) return string;
13407 c1 = ToLower(*s1++);
13408 c2 = ToLower(*s2++);
13409 if (c1 > c2) return 1;
13410 if (c1 < c2) return -1;
13411 if (c1 == NULLCHAR) return 0;
13420 return isupper(c) ? tolower(c) : c;
13428 return islower(c) ? toupper(c) : c;
13430 #endif /* !_amigados */
13438 if ((ret = (char *) malloc(strlen(s) + 1))) {
13445 StrSavePtr(s, savePtr)
13446 char *s, **savePtr;
13451 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13452 strcpy(*savePtr, s);
13464 clock = time((time_t *)NULL);
13465 tm = localtime(&clock);
13466 sprintf(buf, "%04d.%02d.%02d",
13467 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13468 return StrSave(buf);
13473 PositionToFEN(move, overrideCastling)
13475 char *overrideCastling;
13477 int i, j, fromX, fromY, toX, toY;
13484 whiteToPlay = (gameMode == EditPosition) ?
13485 !blackPlaysFirst : (move % 2 == 0);
13488 /* Piece placement data */
13489 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13491 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13492 if (boards[move][i][j] == EmptySquare) {
13494 } else { ChessSquare piece = boards[move][i][j];
13495 if (emptycount > 0) {
13496 if(emptycount<10) /* [HGM] can be >= 10 */
13497 *p++ = '0' + emptycount;
13498 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13501 if(PieceToChar(piece) == '+') {
13502 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13504 piece = (ChessSquare)(DEMOTED piece);
13506 *p++ = PieceToChar(piece);
13508 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13509 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
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; }
13524 /* [HGM] print Crazyhouse or Shogi holdings */
13525 if( gameInfo.holdingsWidth ) {
13526 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13528 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13529 piece = boards[move][i][BOARD_WIDTH-1];
13530 if( piece != EmptySquare )
13531 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13532 *p++ = PieceToChar(piece);
13534 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13535 piece = boards[move][BOARD_HEIGHT-i-1][0];
13536 if( piece != EmptySquare )
13537 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13538 *p++ = PieceToChar(piece);
13541 if( q == p ) *p++ = '-';
13547 *p++ = whiteToPlay ? 'w' : 'b';
13550 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13551 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13553 if(nrCastlingRights) {
13555 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13556 /* [HGM] write directly from rights */
13557 if(castlingRights[move][2] >= 0 &&
13558 castlingRights[move][0] >= 0 )
13559 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13560 if(castlingRights[move][2] >= 0 &&
13561 castlingRights[move][1] >= 0 )
13562 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13563 if(castlingRights[move][5] >= 0 &&
13564 castlingRights[move][3] >= 0 )
13565 *p++ = castlingRights[move][3] + AAA;
13566 if(castlingRights[move][5] >= 0 &&
13567 castlingRights[move][4] >= 0 )
13568 *p++ = castlingRights[move][4] + AAA;
13571 /* [HGM] write true castling rights */
13572 if( nrCastlingRights == 6 ) {
13573 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13574 castlingRights[move][2] >= 0 ) *p++ = 'K';
13575 if(castlingRights[move][1] == BOARD_LEFT &&
13576 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13577 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13578 castlingRights[move][5] >= 0 ) *p++ = 'k';
13579 if(castlingRights[move][4] == BOARD_LEFT &&
13580 castlingRights[move][5] >= 0 ) *p++ = 'q';
13583 if (q == p) *p++ = '-'; /* No castling rights */
13587 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13588 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13589 /* En passant target square */
13590 if (move > backwardMostMove) {
13591 fromX = moveList[move - 1][0] - AAA;
13592 fromY = moveList[move - 1][1] - ONE;
13593 toX = moveList[move - 1][2] - AAA;
13594 toY = moveList[move - 1][3] - ONE;
13595 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13596 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13597 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13599 /* 2-square pawn move just happened */
13601 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13612 /* [HGM] find reversible plies */
13613 { int i = 0, j=move;
13615 if (appData.debugMode) { int k;
13616 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13617 for(k=backwardMostMove; k<=forwardMostMove; k++)
13618 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13622 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13623 if( j == backwardMostMove ) i += initialRulePlies;
13624 sprintf(p, "%d ", i);
13625 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13627 /* Fullmove number */
13628 sprintf(p, "%d", (move / 2) + 1);
13630 return StrSave(buf);
13634 ParseFEN(board, blackPlaysFirst, fen)
13636 int *blackPlaysFirst;
13646 /* [HGM] by default clear Crazyhouse holdings, if present */
13647 if(gameInfo.holdingsWidth) {
13648 for(i=0; i<BOARD_HEIGHT; i++) {
13649 board[i][0] = EmptySquare; /* black holdings */
13650 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13651 board[i][1] = (ChessSquare) 0; /* black counts */
13652 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13656 /* Piece placement data */
13657 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13660 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13661 if (*p == '/') p++;
13662 emptycount = gameInfo.boardWidth - j;
13663 while (emptycount--)
13664 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13666 #if(BOARD_SIZE >= 10)
13667 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13668 p++; emptycount=10;
13669 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13670 while (emptycount--)
13671 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13673 } else if (isdigit(*p)) {
13674 emptycount = *p++ - '0';
13675 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13676 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13677 while (emptycount--)
13678 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13679 } else if (*p == '+' || isalpha(*p)) {
13680 if (j >= gameInfo.boardWidth) return FALSE;
13682 piece = CharToPiece(*++p);
13683 if(piece == EmptySquare) return FALSE; /* unknown piece */
13684 piece = (ChessSquare) (PROMOTED piece ); p++;
13685 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13686 } else piece = CharToPiece(*p++);
13688 if(piece==EmptySquare) return FALSE; /* unknown piece */
13689 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13690 piece = (ChessSquare) (PROMOTED piece);
13691 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13694 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13700 while (*p == '/' || *p == ' ') p++;
13702 /* [HGM] look for Crazyhouse holdings here */
13703 while(*p==' ') p++;
13704 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13706 if(*p == '-' ) *p++; /* empty holdings */ else {
13707 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13708 /* if we would allow FEN reading to set board size, we would */
13709 /* have to add holdings and shift the board read so far here */
13710 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13712 if((int) piece >= (int) BlackPawn ) {
13713 i = (int)piece - (int)BlackPawn;
13714 i = PieceToNumber((ChessSquare)i);
13715 if( i >= gameInfo.holdingsSize ) return FALSE;
13716 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13717 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13719 i = (int)piece - (int)WhitePawn;
13720 i = PieceToNumber((ChessSquare)i);
13721 if( i >= gameInfo.holdingsSize ) return FALSE;
13722 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13723 board[i][BOARD_WIDTH-2]++; /* black holdings */
13727 if(*p == ']') *p++;
13730 while(*p == ' ') p++;
13735 *blackPlaysFirst = FALSE;
13738 *blackPlaysFirst = TRUE;
13744 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13745 /* return the extra info in global variiables */
13747 /* set defaults in case FEN is incomplete */
13748 FENepStatus = EP_UNKNOWN;
13749 for(i=0; i<nrCastlingRights; i++ ) {
13750 FENcastlingRights[i] =
13751 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13752 } /* assume possible unless obviously impossible */
13753 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13754 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13755 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13756 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13757 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13758 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13761 while(*p==' ') p++;
13762 if(nrCastlingRights) {
13763 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13764 /* castling indicator present, so default becomes no castlings */
13765 for(i=0; i<nrCastlingRights; i++ ) {
13766 FENcastlingRights[i] = -1;
13769 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13770 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13771 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13772 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13773 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13775 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13776 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13777 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13781 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13782 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13783 FENcastlingRights[2] = whiteKingFile;
13786 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13787 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13788 FENcastlingRights[2] = whiteKingFile;
13791 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13792 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13793 FENcastlingRights[5] = blackKingFile;
13796 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13797 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13798 FENcastlingRights[5] = blackKingFile;
13801 default: /* FRC castlings */
13802 if(c >= 'a') { /* black rights */
13803 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13804 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13805 if(i == BOARD_RGHT) break;
13806 FENcastlingRights[5] = i;
13808 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13809 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13811 FENcastlingRights[3] = c;
13813 FENcastlingRights[4] = c;
13814 } else { /* white rights */
13815 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13816 if(board[0][i] == WhiteKing) break;
13817 if(i == BOARD_RGHT) break;
13818 FENcastlingRights[2] = i;
13819 c -= AAA - 'a' + 'A';
13820 if(board[0][c] >= WhiteKing) break;
13822 FENcastlingRights[0] = c;
13824 FENcastlingRights[1] = c;
13828 if (appData.debugMode) {
13829 fprintf(debugFP, "FEN castling rights:");
13830 for(i=0; i<nrCastlingRights; i++)
13831 fprintf(debugFP, " %d", FENcastlingRights[i]);
13832 fprintf(debugFP, "\n");
13835 while(*p==' ') p++;
13838 /* read e.p. field in games that know e.p. capture */
13839 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13840 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13842 p++; FENepStatus = EP_NONE;
13844 char c = *p++ - AAA;
13846 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13847 if(*p >= '0' && *p <='9') *p++;
13853 if(sscanf(p, "%d", &i) == 1) {
13854 FENrulePlies = i; /* 50-move ply counter */
13855 /* (The move number is still ignored) */
13862 EditPositionPasteFEN(char *fen)
13865 Board initial_position;
13867 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13868 DisplayError(_("Bad FEN position in clipboard"), 0);
13871 int savedBlackPlaysFirst = blackPlaysFirst;
13872 EditPositionEvent();
13873 blackPlaysFirst = savedBlackPlaysFirst;
13874 CopyBoard(boards[0], initial_position);
13875 /* [HGM] copy FEN attributes as well */
13877 initialRulePlies = FENrulePlies;
13878 epStatus[0] = FENepStatus;
13879 for( i=0; i<nrCastlingRights; i++ )
13880 castlingRights[0][i] = FENcastlingRights[i];
13882 EditPositionDone();
13883 DisplayBothClocks();
13884 DrawPosition(FALSE, boards[currentMove]);