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 InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166 /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177 char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179 int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
240 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
245 int lastSavedGame; /* [HGM] save: ID of game */
247 /* States for ics_getting_history */
249 #define H_REQUESTED 1
250 #define H_GOT_REQ_HEADER 2
251 #define H_GOT_UNREQ_HEADER 3
252 #define H_GETTING_MOVES 4
253 #define H_GOT_UNWANTED_HEADER 5
255 /* whosays values for GameEnds */
264 /* Maximum number of games in a cmail message */
265 #define CMAIL_MAX_GAMES 20
267 /* Different types of move when calling RegisterMove */
269 #define CMAIL_RESIGN 1
271 #define CMAIL_ACCEPT 3
273 /* Different types of result to remember for each game */
274 #define CMAIL_NOT_RESULT 0
275 #define CMAIL_OLD_RESULT 1
276 #define CMAIL_NEW_RESULT 2
278 /* Telnet protocol constants */
289 static char * safeStrCpy( char * dst, const char * src, size_t count )
291 assert( dst != NULL );
292 assert( src != NULL );
295 strncpy( dst, src, count );
296 dst[ count-1 ] = '\0';
301 //[HGM] for future use? Conditioned out for now to suppress warning.
302 static char * safeStrCat( char * dst, const char * src, size_t count )
306 assert( dst != NULL );
307 assert( src != NULL );
310 dst_len = strlen(dst);
312 assert( count > dst_len ); /* Buffer size must be greater than current length */
314 safeStrCpy( dst + dst_len, src, count - dst_len );
320 /* Some compiler can't cast u64 to double
321 * This function do the job for us:
323 * We use the highest bit for cast, this only
324 * works if the highest bit is not
325 * in use (This should not happen)
327 * We used this for all compiler
330 u64ToDouble(u64 value)
333 u64 tmp = value & u64Const(0x7fffffffffffffff);
334 r = (double)(s64)tmp;
335 if (value & u64Const(0x8000000000000000))
336 r += 9.2233720368547758080e18; /* 2^63 */
340 /* Fake up flags for now, as we aren't keeping track of castling
341 availability yet. [HGM] Change of logic: the flag now only
342 indicates the type of castlings allowed by the rule of the game.
343 The actual rights themselves are maintained in the array
344 castlingRights, as part of the game history, and are not probed
350 int flags = F_ALL_CASTLE_OK;
351 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
352 switch (gameInfo.variant) {
354 flags &= ~F_ALL_CASTLE_OK;
355 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
356 flags |= F_IGNORE_CHECK;
358 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
361 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
363 case VariantKriegspiel:
364 flags |= F_KRIEGSPIEL_CAPTURE;
366 case VariantCapaRandom:
367 case VariantFischeRandom:
368 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
369 case VariantNoCastle:
370 case VariantShatranj:
372 flags &= ~F_ALL_CASTLE_OK;
380 FILE *gameFileFP, *debugFP;
383 [AS] Note: sometimes, the sscanf() function is used to parse the input
384 into a fixed-size buffer. Because of this, we must be prepared to
385 receive strings as long as the size of the input buffer, which is currently
386 set to 4K for Windows and 8K for the rest.
387 So, we must either allocate sufficiently large buffers here, or
388 reduce the size of the input buffer in the input reading part.
391 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
392 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
393 char thinkOutput1[MSG_SIZ*10];
395 ChessProgramState first, second;
397 /* premove variables */
400 int premoveFromX = 0;
401 int premoveFromY = 0;
402 int premovePromoChar = 0;
404 Boolean alarmSounded;
405 /* end premove variables */
407 char *ics_prefix = "$";
408 int ics_type = ICS_GENERIC;
410 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
411 int pauseExamForwardMostMove = 0;
412 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
413 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
414 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
415 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
416 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
417 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
418 int whiteFlag = FALSE, blackFlag = FALSE;
419 int userOfferedDraw = FALSE;
420 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
421 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
422 int cmailMoveType[CMAIL_MAX_GAMES];
423 long ics_clock_paused = 0;
424 ProcRef icsPR = NoProc, cmailPR = NoProc;
425 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
426 GameMode gameMode = BeginningOfGame;
427 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
428 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
429 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
430 int hiddenThinkOutputState = 0; /* [AS] */
431 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
432 int adjudicateLossPlies = 6;
433 char white_holding[64], black_holding[64];
434 TimeMark lastNodeCountTime;
435 long lastNodeCount=0;
436 int have_sent_ICS_logon = 0;
438 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
439 long timeControl_2; /* [AS] Allow separate time controls */
440 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
441 long timeRemaining[2][MAX_MOVES];
443 TimeMark programStartTime;
444 char ics_handle[MSG_SIZ];
445 int have_set_title = 0;
447 /* animateTraining preserves the state of appData.animate
448 * when Training mode is activated. This allows the
449 * response to be animated when appData.animate == TRUE and
450 * appData.animateDragging == TRUE.
452 Boolean animateTraining;
458 Board boards[MAX_MOVES];
459 /* [HGM] Following 7 needed for accurate legality tests: */
460 char epStatus[MAX_MOVES];
461 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
462 char castlingRank[BOARD_SIZE]; // and corresponding ranks
463 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
464 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
465 int initialRulePlies, FENrulePlies;
467 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
471 ChessSquare FIDEArray[2][BOARD_SIZE] = {
472 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
473 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
474 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
475 BlackKing, BlackBishop, BlackKnight, BlackRook }
478 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
479 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
481 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482 BlackKing, BlackKing, BlackKnight, BlackRook }
485 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
486 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
487 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
488 { BlackRook, BlackMan, BlackBishop, BlackQueen,
489 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
492 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
493 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
494 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
495 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
496 BlackKing, BlackBishop, BlackKnight, BlackRook }
499 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
500 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
501 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
502 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
503 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
508 ChessSquare ShogiArray[2][BOARD_SIZE] = {
509 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
510 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
511 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
512 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
515 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
516 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
517 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
519 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
523 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
525 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
526 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
529 ChessSquare GreatArray[2][BOARD_SIZE] = {
530 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
531 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
532 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
533 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
536 ChessSquare JanusArray[2][BOARD_SIZE] = {
537 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
538 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
539 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
540 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
544 ChessSquare GothicArray[2][BOARD_SIZE] = {
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
546 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
548 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
551 #define GothicArray CapablancaArray
555 ChessSquare FalconArray[2][BOARD_SIZE] = {
556 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
557 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
559 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
562 #define FalconArray CapablancaArray
565 #else // !(BOARD_SIZE>=10)
566 #define XiangqiPosition FIDEArray
567 #define CapablancaArray FIDEArray
568 #define GothicArray FIDEArray
569 #define GreatArray FIDEArray
570 #endif // !(BOARD_SIZE>=10)
573 ChessSquare CourierArray[2][BOARD_SIZE] = {
574 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
575 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
577 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
579 #else // !(BOARD_SIZE>=12)
580 #define CourierArray CapablancaArray
581 #endif // !(BOARD_SIZE>=12)
584 Board initialPosition;
587 /* Convert str to a rating. Checks for special cases of "----",
589 "++++", etc. Also strips ()'s */
591 string_to_rating(str)
594 while(*str && !isdigit(*str)) ++str;
596 return 0; /* One of the special "no rating" cases */
604 /* Init programStats */
605 programStats.movelist[0] = 0;
606 programStats.depth = 0;
607 programStats.nr_moves = 0;
608 programStats.moves_left = 0;
609 programStats.nodes = 0;
610 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
611 programStats.score = 0;
612 programStats.got_only_move = 0;
613 programStats.got_fail = 0;
614 programStats.line_is_book = 0;
620 int matched, min, sec;
622 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
624 GetTimeMark(&programStartTime);
625 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
628 programStats.ok_to_send = 1;
629 programStats.seen_stat = 0;
632 * Initialize game list
638 * Internet chess server status
640 if (appData.icsActive) {
641 appData.matchMode = FALSE;
642 appData.matchGames = 0;
644 appData.noChessProgram = !appData.zippyPlay;
646 appData.zippyPlay = FALSE;
647 appData.zippyTalk = FALSE;
648 appData.noChessProgram = TRUE;
650 if (*appData.icsHelper != NULLCHAR) {
651 appData.useTelnet = TRUE;
652 appData.telnetProgram = appData.icsHelper;
655 appData.zippyTalk = appData.zippyPlay = FALSE;
658 /* [AS] Initialize pv info list [HGM] and game state */
662 for( i=0; i<MAX_MOVES; i++ ) {
663 pvInfoList[i].depth = -1;
665 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
670 * Parse timeControl resource
672 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
673 appData.movesPerSession)) {
675 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
676 DisplayFatalError(buf, 0, 2);
680 * Parse searchTime resource
682 if (*appData.searchTime != NULLCHAR) {
683 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
685 searchTime = min * 60;
686 } else if (matched == 2) {
687 searchTime = min * 60 + sec;
690 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
691 DisplayFatalError(buf, 0, 2);
695 /* [AS] Adjudication threshold */
696 adjudicateLossThreshold = appData.adjudicateLossThreshold;
698 first.which = "first";
699 second.which = "second";
700 first.maybeThinking = second.maybeThinking = FALSE;
701 first.pr = second.pr = NoProc;
702 first.isr = second.isr = NULL;
703 first.sendTime = second.sendTime = 2;
704 first.sendDrawOffers = 1;
705 if (appData.firstPlaysBlack) {
706 first.twoMachinesColor = "black\n";
707 second.twoMachinesColor = "white\n";
709 first.twoMachinesColor = "white\n";
710 second.twoMachinesColor = "black\n";
712 first.program = appData.firstChessProgram;
713 second.program = appData.secondChessProgram;
714 first.host = appData.firstHost;
715 second.host = appData.secondHost;
716 first.dir = appData.firstDirectory;
717 second.dir = appData.secondDirectory;
718 first.other = &second;
719 second.other = &first;
720 first.initString = appData.initString;
721 second.initString = appData.secondInitString;
722 first.computerString = appData.firstComputerString;
723 second.computerString = appData.secondComputerString;
724 first.useSigint = second.useSigint = TRUE;
725 first.useSigterm = second.useSigterm = TRUE;
726 first.reuse = appData.reuseFirst;
727 second.reuse = appData.reuseSecond;
728 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
729 second.nps = appData.secondNPS;
730 first.useSetboard = second.useSetboard = FALSE;
731 first.useSAN = second.useSAN = FALSE;
732 first.usePing = second.usePing = FALSE;
733 first.lastPing = second.lastPing = 0;
734 first.lastPong = second.lastPong = 0;
735 first.usePlayother = second.usePlayother = FALSE;
736 first.useColors = second.useColors = TRUE;
737 first.useUsermove = second.useUsermove = FALSE;
738 first.sendICS = second.sendICS = FALSE;
739 first.sendName = second.sendName = appData.icsActive;
740 first.sdKludge = second.sdKludge = FALSE;
741 first.stKludge = second.stKludge = FALSE;
742 TidyProgramName(first.program, first.host, first.tidy);
743 TidyProgramName(second.program, second.host, second.tidy);
744 first.matchWins = second.matchWins = 0;
745 strcpy(first.variants, appData.variant);
746 strcpy(second.variants, appData.variant);
747 first.analysisSupport = second.analysisSupport = 2; /* detect */
748 first.analyzing = second.analyzing = FALSE;
749 first.initDone = second.initDone = FALSE;
751 /* New features added by Tord: */
752 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
753 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
754 /* End of new features added by Tord. */
755 first.fenOverride = appData.fenOverride1;
756 second.fenOverride = appData.fenOverride2;
758 /* [HGM] time odds: set factor for each machine */
759 first.timeOdds = appData.firstTimeOdds;
760 second.timeOdds = appData.secondTimeOdds;
762 if(appData.timeOddsMode) {
763 norm = first.timeOdds;
764 if(norm > second.timeOdds) norm = second.timeOdds;
766 first.timeOdds /= norm;
767 second.timeOdds /= norm;
770 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
771 first.accumulateTC = appData.firstAccumulateTC;
772 second.accumulateTC = appData.secondAccumulateTC;
773 first.maxNrOfSessions = second.maxNrOfSessions = 1;
776 first.debug = second.debug = FALSE;
777 first.supportsNPS = second.supportsNPS = UNKNOWN;
780 first.optionSettings = appData.firstOptions;
781 second.optionSettings = appData.secondOptions;
783 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
784 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
785 first.isUCI = appData.firstIsUCI; /* [AS] */
786 second.isUCI = appData.secondIsUCI; /* [AS] */
787 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
788 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
790 if (appData.firstProtocolVersion > PROTOVER ||
791 appData.firstProtocolVersion < 1) {
793 sprintf(buf, _("protocol version %d not supported"),
794 appData.firstProtocolVersion);
795 DisplayFatalError(buf, 0, 2);
797 first.protocolVersion = appData.firstProtocolVersion;
800 if (appData.secondProtocolVersion > PROTOVER ||
801 appData.secondProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.secondProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 second.protocolVersion = appData.secondProtocolVersion;
810 if (appData.icsActive) {
811 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
812 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
813 appData.clockMode = FALSE;
814 first.sendTime = second.sendTime = 0;
818 /* Override some settings from environment variables, for backward
819 compatibility. Unfortunately it's not feasible to have the env
820 vars just set defaults, at least in xboard. Ugh.
822 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
827 if (appData.noChessProgram) {
828 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829 sprintf(programVersion, "%s", PACKAGE_STRING);
834 while (*q != ' ' && *q != NULLCHAR) q++;
836 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
837 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
838 sprintf(programVersion, "%s + ", PACKAGE_STRING);
839 strncat(programVersion, p, q - p);
841 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
842 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
843 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantBerolina: /* might work if TestLegality is off */
903 case VariantCapaRandom: /* should work */
904 case VariantJanus: /* should work */
905 case VariantSuper: /* experimental */
906 case VariantGreat: /* experimental, requires legality testing to be off */
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
912 InitEngineUCI( installDir, &second );
915 int NextIntegerFromString( char ** str, long * value )
920 while( *s == ' ' || *s == '\t' ) {
926 if( *s >= '0' && *s <= '9' ) {
927 while( *s >= '0' && *s <= '9' ) {
928 *value = *value * 10 + (*s - '0');
940 int NextTimeControlFromString( char ** str, long * value )
943 int result = NextIntegerFromString( str, &temp );
946 *value = temp * 60; /* Minutes */
949 result = NextIntegerFromString( str, &temp );
950 *value += temp; /* Seconds */
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
959 int result = -1; long temp, temp2;
961 if(**str != '+') return -1; // old params remain in force!
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
966 /* time only: incremental or sudden-death time control */
967 if(**str == '+') { /* increment follows; read it */
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
972 *moves = 0; *tc = temp * 1000;
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
976 (*str)++; /* classical time control */
977 result = NextTimeControlFromString( str, &temp2);
986 int GetTimeQuota(int movenr)
987 { /* [HGM] get time to add from the multi-session time-control string */
988 int moves=1; /* kludge to force reading of first session */
989 long time, increment;
990 char *s = fullTimeControlString;
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996 if(movenr == -1) return time; /* last move before new session */
997 if(!moves) return increment; /* current session is incremental */
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
999 } while(movenr >= -1); /* try again for next session */
1001 return 0; // no new time quota on this move
1005 ParseTimeControl(tc, ti, mps)
1011 int matched, min, sec;
1013 matched = sscanf(tc, "%d:%d", &min, &sec);
1015 timeControl = min * 60 * 1000;
1016 } else if (matched == 2) {
1017 timeControl = (min * 60 + sec) * 1000;
1026 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1029 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1030 else sprintf(buf, "+%s+%d", tc, ti);
1033 sprintf(buf, "+%d/%s", mps, tc);
1034 else sprintf(buf, "+%s", tc);
1036 fullTimeControlString = StrSave(buf);
1038 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1043 /* Parse second time control */
1046 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1054 timeControl_2 = tc2 * 1000;
1064 timeControl = tc1 * 1000;
1068 timeIncrement = ti * 1000; /* convert to ms */
1069 movesPerSession = 0;
1072 movesPerSession = mps;
1080 if (appData.debugMode) {
1081 fprintf(debugFP, "%s\n", programVersion);
1084 if (appData.matchGames > 0) {
1085 appData.matchMode = TRUE;
1086 } else if (appData.matchMode) {
1087 appData.matchGames = 1;
1089 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1090 appData.matchGames = appData.sameColorGames;
1091 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1092 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1093 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1096 if (appData.noChessProgram || first.protocolVersion == 1) {
1099 /* kludge: allow timeout for initial "feature" commands */
1101 DisplayMessage("", _("Starting chess program"));
1102 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1107 InitBackEnd3 P((void))
1109 GameMode initialMode;
1113 InitChessProgram(&first, startedFromSetupPosition);
1116 if (appData.icsActive) {
1118 /* [DM] Make a console window if needed [HGM] merged ifs */
1123 if (*appData.icsCommPort != NULLCHAR) {
1124 sprintf(buf, _("Could not open comm port %s"),
1125 appData.icsCommPort);
1127 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1128 appData.icsHost, appData.icsPort);
1130 DisplayFatalError(buf, err, 1);
1135 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1137 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1138 } else if (appData.noChessProgram) {
1144 if (*appData.cmailGameName != NULLCHAR) {
1146 OpenLoopback(&cmailPR);
1148 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152 DisplayMessage("", "");
1153 if (StrCaseCmp(appData.initialMode, "") == 0) {
1154 initialMode = BeginningOfGame;
1155 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1156 initialMode = TwoMachinesPlay;
1157 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1158 initialMode = AnalyzeFile;
1159 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1160 initialMode = AnalyzeMode;
1161 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1162 initialMode = MachinePlaysWhite;
1163 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1164 initialMode = MachinePlaysBlack;
1165 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1166 initialMode = EditGame;
1167 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1168 initialMode = EditPosition;
1169 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1170 initialMode = Training;
1172 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1173 DisplayFatalError(buf, 0, 2);
1177 if (appData.matchMode) {
1178 /* Set up machine vs. machine match */
1179 if (appData.noChessProgram) {
1180 DisplayFatalError(_("Can't have a match with no chess programs"),
1186 if (*appData.loadGameFile != NULLCHAR) {
1187 int index = appData.loadGameIndex; // [HGM] autoinc
1188 if(index<0) lastIndex = index = 1;
1189 if (!LoadGameFromFile(appData.loadGameFile,
1191 appData.loadGameFile, FALSE)) {
1192 DisplayFatalError(_("Bad game file"), 0, 1);
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
1196 int index = appData.loadPositionIndex; // [HGM] autoinc
1197 if(index<0) lastIndex = index = 1;
1198 if (!LoadPositionFromFile(appData.loadPositionFile,
1200 appData.loadPositionFile)) {
1201 DisplayFatalError(_("Bad position file"), 0, 1);
1206 } else if (*appData.cmailGameName != NULLCHAR) {
1207 /* Set up cmail mode */
1208 ReloadCmailMsgEvent(TRUE);
1210 /* Set up other modes */
1211 if (initialMode == AnalyzeFile) {
1212 if (*appData.loadGameFile == NULLCHAR) {
1213 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217 if (*appData.loadGameFile != NULLCHAR) {
1218 (void) LoadGameFromFile(appData.loadGameFile,
1219 appData.loadGameIndex,
1220 appData.loadGameFile, TRUE);
1221 } else if (*appData.loadPositionFile != NULLCHAR) {
1222 (void) LoadPositionFromFile(appData.loadPositionFile,
1223 appData.loadPositionIndex,
1224 appData.loadPositionFile);
1225 /* [HGM] try to make self-starting even after FEN load */
1226 /* to allow automatic setup of fairy variants with wtm */
1227 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1228 gameMode = BeginningOfGame;
1229 setboardSpoiledMachineBlack = 1;
1231 /* [HGM] loadPos: make that every new game uses the setup */
1232 /* from file as long as we do not switch variant */
1233 if(!blackPlaysFirst) { int i;
1234 startedFromPositionFile = TRUE;
1235 CopyBoard(filePosition, boards[0]);
1236 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1239 if (initialMode == AnalyzeMode) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1244 if (appData.icsActive) {
1245 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1249 } else if (initialMode == AnalyzeFile) {
1250 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1251 ShowThinkingEvent();
1253 AnalysisPeriodicEvent(1);
1254 } else if (initialMode == MachinePlaysWhite) {
1255 if (appData.noChessProgram) {
1256 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1260 if (appData.icsActive) {
1261 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1265 MachineWhiteEvent();
1266 } else if (initialMode == MachinePlaysBlack) {
1267 if (appData.noChessProgram) {
1268 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1272 if (appData.icsActive) {
1273 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1277 MachineBlackEvent();
1278 } else if (initialMode == TwoMachinesPlay) {
1279 if (appData.noChessProgram) {
1280 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1284 if (appData.icsActive) {
1285 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1290 } else if (initialMode == EditGame) {
1292 } else if (initialMode == EditPosition) {
1293 EditPositionEvent();
1294 } else if (initialMode == Training) {
1295 if (*appData.loadGameFile == NULLCHAR) {
1296 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1305 * Establish will establish a contact to a remote host.port.
1306 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1307 * used to talk to the host.
1308 * Returns 0 if okay, error code if not.
1315 if (*appData.icsCommPort != NULLCHAR) {
1316 /* Talk to the host through a serial comm port */
1317 return OpenCommPort(appData.icsCommPort, &icsPR);
1319 } else if (*appData.gateway != NULLCHAR) {
1320 if (*appData.remoteShell == NULLCHAR) {
1321 /* Use the rcmd protocol to run telnet program on a gateway host */
1322 snprintf(buf, sizeof(buf), "%s %s %s",
1323 appData.telnetProgram, appData.icsHost, appData.icsPort);
1324 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1327 /* Use the rsh program to run telnet program on a gateway host */
1328 if (*appData.remoteUser == NULLCHAR) {
1329 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1330 appData.gateway, appData.telnetProgram,
1331 appData.icsHost, appData.icsPort);
1333 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1334 appData.remoteShell, appData.gateway,
1335 appData.remoteUser, appData.telnetProgram,
1336 appData.icsHost, appData.icsPort);
1338 return StartChildProcess(buf, "", &icsPR);
1341 } else if (appData.useTelnet) {
1342 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1345 /* TCP socket interface differs somewhat between
1346 Unix and NT; handle details in the front end.
1348 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1353 show_bytes(fp, buf, count)
1359 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1360 fprintf(fp, "\\%03o", *buf & 0xff);
1369 /* Returns an errno value */
1371 OutputMaybeTelnet(pr, message, count, outError)
1377 char buf[8192], *p, *q, *buflim;
1378 int left, newcount, outcount;
1380 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1381 *appData.gateway != NULLCHAR) {
1382 if (appData.debugMode) {
1383 fprintf(debugFP, ">ICS: ");
1384 show_bytes(debugFP, message, count);
1385 fprintf(debugFP, "\n");
1387 return OutputToProcess(pr, message, count, outError);
1390 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1397 if (appData.debugMode) {
1398 fprintf(debugFP, ">ICS: ");
1399 show_bytes(debugFP, buf, newcount);
1400 fprintf(debugFP, "\n");
1402 outcount = OutputToProcess(pr, buf, newcount, outError);
1403 if (outcount < newcount) return -1; /* to be sure */
1410 } else if (((unsigned char) *p) == TN_IAC) {
1411 *q++ = (char) TN_IAC;
1418 if (appData.debugMode) {
1419 fprintf(debugFP, ">ICS: ");
1420 show_bytes(debugFP, buf, newcount);
1421 fprintf(debugFP, "\n");
1423 outcount = OutputToProcess(pr, buf, newcount, outError);
1424 if (outcount < newcount) return -1; /* to be sure */
1429 read_from_player(isr, closure, message, count, error)
1436 int outError, outCount;
1437 static int gotEof = 0;
1439 /* Pass data read from player on to ICS */
1442 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1443 if (outCount < count) {
1444 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1446 } else if (count < 0) {
1447 RemoveInputSource(isr);
1448 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1449 } else if (gotEof++ > 0) {
1450 RemoveInputSource(isr);
1451 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1459 int count, outCount, outError;
1461 if (icsPR == NULL) return;
1464 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1465 if (outCount < count) {
1466 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1470 /* This is used for sending logon scripts to the ICS. Sending
1471 without a delay causes problems when using timestamp on ICC
1472 (at least on my machine). */
1474 SendToICSDelayed(s,msdelay)
1478 int count, outCount, outError;
1480 if (icsPR == NULL) return;
1483 if (appData.debugMode) {
1484 fprintf(debugFP, ">ICS: ");
1485 show_bytes(debugFP, s, count);
1486 fprintf(debugFP, "\n");
1488 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1490 if (outCount < count) {
1491 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1496 /* Remove all highlighting escape sequences in s
1497 Also deletes any suffix starting with '('
1500 StripHighlightAndTitle(s)
1503 static char retbuf[MSG_SIZ];
1506 while (*s != NULLCHAR) {
1507 while (*s == '\033') {
1508 while (*s != NULLCHAR && !isalpha(*s)) s++;
1509 if (*s != NULLCHAR) s++;
1511 while (*s != NULLCHAR && *s != '\033') {
1512 if (*s == '(' || *s == '[') {
1523 /* Remove all highlighting escape sequences in s */
1528 static char retbuf[MSG_SIZ];
1531 while (*s != NULLCHAR) {
1532 while (*s == '\033') {
1533 while (*s != NULLCHAR && !isalpha(*s)) s++;
1534 if (*s != NULLCHAR) s++;
1536 while (*s != NULLCHAR && *s != '\033') {
1544 char *variantNames[] = VARIANT_NAMES;
1549 return variantNames[v];
1553 /* Identify a variant from the strings the chess servers use or the
1554 PGN Variant tag names we use. */
1561 VariantClass v = VariantNormal;
1562 int i, found = FALSE;
1567 /* [HGM] skip over optional board-size prefixes */
1568 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1569 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1570 while( *e++ != '_');
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574 if (StrCaseStr(e, variantNames[i])) {
1575 v = (VariantClass) i;
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583 || StrCaseStr(e, "wild/fr")
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585 v = VariantFischeRandom;
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587 (i = 1, p = StrCaseStr(e, "w"))) {
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1596 case 0: /* FICS only, actually */
1598 /* Castling legal even if K starts on d-file */
1599 v = VariantWildCastle;
1604 /* Castling illegal even if K & R happen to start in
1605 normal positions. */
1606 v = VariantNoCastle;
1619 /* Castling legal iff K & R start in normal positions */
1625 /* Special wilds for position setup; unclear what to do here */
1626 v = VariantLoadable;
1629 /* Bizarre ICC game */
1630 v = VariantTwoKings;
1633 v = VariantKriegspiel;
1639 v = VariantFischeRandom;
1642 v = VariantCrazyhouse;
1645 v = VariantBughouse;
1651 /* Not quite the same as FICS suicide! */
1652 v = VariantGiveaway;
1658 v = VariantShatranj;
1661 /* Temporary names for future ICC types. The name *will* change in
1662 the next xboard/WinBoard release after ICC defines it. */
1700 v = VariantCapablanca;
1703 v = VariantKnightmate;
1709 v = VariantCylinder;
1715 v = VariantCapaRandom;
1718 v = VariantBerolina;
1730 /* Found "wild" or "w" in the string but no number;
1731 must assume it's normal chess. */
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
1736 DisplayError(buf, 0);
1742 if (appData.debugMode) {
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744 e, wnum, VariantName(v));
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753 advance *index beyond it, and set leftover_start to the new value of
1754 *index; else return FALSE. If pattern contains the character '*', it
1755 matches any sequence of characters not containing '\r', '\n', or the
1756 character following the '*' (if any), and the matched sequence(s) are
1757 copied into star_match.
1760 looking_at(buf, index, pattern)
1765 char *bufp = &buf[*index], *patternp = pattern;
1767 char *matchp = star_match[0];
1770 if (*patternp == NULLCHAR) {
1771 *index = leftover_start = bufp - buf;
1775 if (*bufp == NULLCHAR) return FALSE;
1776 if (*patternp == '*') {
1777 if (*bufp == *(patternp + 1)) {
1779 matchp = star_match[++star_count];
1783 } else if (*bufp == '\n' || *bufp == '\r') {
1785 if (*patternp == NULLCHAR)
1790 *matchp++ = *bufp++;
1794 if (*patternp != *bufp) return FALSE;
1801 SendToPlayer(data, length)
1805 int error, outCount;
1806 outCount = OutputToProcess(NoProc, data, length, &error);
1807 if (outCount < length) {
1808 DisplayFatalError(_("Error writing to display"), error, 1);
1813 PackHolding(packed, holding)
1825 switch (runlength) {
1836 sprintf(q, "%d", runlength);
1848 /* Telnet protocol requests from the front end */
1850 TelnetRequest(ddww, option)
1851 unsigned char ddww, option;
1853 unsigned char msg[3];
1854 int outCount, outError;
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858 if (appData.debugMode) {
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1875 sprintf(buf1, "%d", ddww);
1884 sprintf(buf2, "%d", option);
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901 if (!appData.icsActive) return;
1902 TelnetRequest(TN_DO, TN_ECHO);
1908 if (!appData.icsActive) return;
1909 TelnetRequest(TN_DONT, TN_ECHO);
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 /* put the holdings sent to us by the server on the board holdings area */
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1920 if(gameInfo.holdingsWidth < 2) return;
1922 if( (int)lowestPiece >= BlackPawn ) {
1925 holdingsStartRow = BOARD_HEIGHT-1;
1928 holdingsColumn = BOARD_WIDTH-1;
1929 countsColumn = BOARD_WIDTH-2;
1930 holdingsStartRow = 0;
1934 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1935 board[i][holdingsColumn] = EmptySquare;
1936 board[i][countsColumn] = (ChessSquare) 0;
1938 while( (p=*holdings++) != NULLCHAR ) {
1939 piece = CharToPiece( ToUpper(p) );
1940 if(piece == EmptySquare) continue;
1941 /*j = (int) piece - (int) WhitePawn;*/
1942 j = PieceToNumber(piece);
1943 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1944 if(j < 0) continue; /* should not happen */
1945 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1946 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1947 board[holdingsStartRow+j*direction][countsColumn]++;
1954 VariantSwitch(Board board, VariantClass newVariant)
1956 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1958 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1960 startedFromPositionFile = FALSE;
1961 if(gameInfo.variant == newVariant) return;
1963 /* [HGM] This routine is called each time an assignment is made to
1964 * gameInfo.variant during a game, to make sure the board sizes
1965 * are set to match the new variant. If that means adding or deleting
1966 * holdings, we shift the playing board accordingly
1967 * This kludge is needed because in ICS observe mode, we get boards
1968 * of an ongoing game without knowing the variant, and learn about the
1969 * latter only later. This can be because of the move list we requested,
1970 * in which case the game history is refilled from the beginning anyway,
1971 * but also when receiving holdings of a crazyhouse game. In the latter
1972 * case we want to add those holdings to the already received position.
1976 if (appData.debugMode) {
1977 fprintf(debugFP, "Switch board from %s to %s\n",
1978 VariantName(gameInfo.variant), VariantName(newVariant));
1979 setbuf(debugFP, NULL);
1981 shuffleOpenings = 0; /* [HGM] shuffle */
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983 switch(newVariant) {
1985 newWidth = 9; newHeight = 9;
1986 gameInfo.holdingsSize = 7;
1987 case VariantBughouse:
1988 case VariantCrazyhouse:
1989 newHoldingsWidth = 2; break;
1991 newHoldingsWidth = gameInfo.holdingsSize = 0;
1994 if(newWidth != gameInfo.boardWidth ||
1995 newHeight != gameInfo.boardHeight ||
1996 newHoldingsWidth != gameInfo.holdingsWidth ) {
1998 /* shift position to new playing area, if needed */
1999 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2000 for(i=0; i<BOARD_HEIGHT; i++)
2001 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2002 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2004 for(i=0; i<newHeight; i++) {
2005 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2006 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2008 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2009 for(i=0; i<BOARD_HEIGHT; i++)
2010 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015 gameInfo.boardWidth = newWidth;
2016 gameInfo.boardHeight = newHeight;
2017 gameInfo.holdingsWidth = newHoldingsWidth;
2018 gameInfo.variant = newVariant;
2019 InitDrawingSizes(-2, 0);
2021 /* [HGM] The following should definitely be solved in a better way */
2023 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2024 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2025 saveEP = epStatus[0];
2027 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2029 epStatus[0] = saveEP;
2030 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2031 CopyBoard(tempBoard, board); /* restore position received from ICS */
2033 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2035 forwardMostMove = oldForwardMostMove;
2036 backwardMostMove = oldBackwardMostMove;
2037 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2040 static int loggedOn = FALSE;
2042 /*-- Game start info cache: --*/
2044 char gs_kind[MSG_SIZ];
2045 static char player1Name[128] = "";
2046 static char player2Name[128] = "";
2047 static int player1Rating = -1;
2048 static int player2Rating = -1;
2049 /*----------------------------*/
2051 ColorClass curColor = ColorNormal;
2052 int suppressKibitz = 0;
2055 read_from_ics(isr, closure, data, count, error)
2062 #define BUF_SIZE 8192
2063 #define STARTED_NONE 0
2064 #define STARTED_MOVES 1
2065 #define STARTED_BOARD 2
2066 #define STARTED_OBSERVE 3
2067 #define STARTED_HOLDINGS 4
2068 #define STARTED_CHATTER 5
2069 #define STARTED_COMMENT 6
2070 #define STARTED_MOVES_NOHIDE 7
2072 static int started = STARTED_NONE;
2073 static char parse[20000];
2074 static int parse_pos = 0;
2075 static char buf[BUF_SIZE + 1];
2076 static int firstTime = TRUE, intfSet = FALSE;
2077 static ColorClass prevColor = ColorNormal;
2078 static int savingComment = FALSE;
2084 int backup; /* [DM] For zippy color lines */
2087 if (appData.debugMode) {
2089 fprintf(debugFP, "<ICS: ");
2090 show_bytes(debugFP, data, count);
2091 fprintf(debugFP, "\n");
2095 if (appData.debugMode) { int f = forwardMostMove;
2096 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2100 /* If last read ended with a partial line that we couldn't parse,
2101 prepend it to the new read and try again. */
2102 if (leftover_len > 0) {
2103 for (i=0; i<leftover_len; i++)
2104 buf[i] = buf[leftover_start + i];
2107 /* Copy in new characters, removing nulls and \r's */
2108 buf_len = leftover_len;
2109 for (i = 0; i < count; i++) {
2110 if (data[i] != NULLCHAR && data[i] != '\r')
2111 buf[buf_len++] = data[i];
2112 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2113 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2114 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2115 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2119 buf[buf_len] = NULLCHAR;
2120 next_out = leftover_len;
2124 while (i < buf_len) {
2125 /* Deal with part of the TELNET option negotiation
2126 protocol. We refuse to do anything beyond the
2127 defaults, except that we allow the WILL ECHO option,
2128 which ICS uses to turn off password echoing when we are
2129 directly connected to it. We reject this option
2130 if localLineEditing mode is on (always on in xboard)
2131 and we are talking to port 23, which might be a real
2132 telnet server that will try to keep WILL ECHO on permanently.
2134 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2135 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2136 unsigned char option;
2138 switch ((unsigned char) buf[++i]) {
2140 if (appData.debugMode)
2141 fprintf(debugFP, "\n<WILL ");
2142 switch (option = (unsigned char) buf[++i]) {
2144 if (appData.debugMode)
2145 fprintf(debugFP, "ECHO ");
2146 /* Reply only if this is a change, according
2147 to the protocol rules. */
2148 if (remoteEchoOption) break;
2149 if (appData.localLineEditing &&
2150 atoi(appData.icsPort) == TN_PORT) {
2151 TelnetRequest(TN_DONT, TN_ECHO);
2154 TelnetRequest(TN_DO, TN_ECHO);
2155 remoteEchoOption = TRUE;
2159 if (appData.debugMode)
2160 fprintf(debugFP, "%d ", option);
2161 /* Whatever this is, we don't want it. */
2162 TelnetRequest(TN_DONT, option);
2167 if (appData.debugMode)
2168 fprintf(debugFP, "\n<WONT ");
2169 switch (option = (unsigned char) buf[++i]) {
2171 if (appData.debugMode)
2172 fprintf(debugFP, "ECHO ");
2173 /* Reply only if this is a change, according
2174 to the protocol rules. */
2175 if (!remoteEchoOption) break;
2177 TelnetRequest(TN_DONT, TN_ECHO);
2178 remoteEchoOption = FALSE;
2181 if (appData.debugMode)
2182 fprintf(debugFP, "%d ", (unsigned char) option);
2183 /* Whatever this is, it must already be turned
2184 off, because we never agree to turn on
2185 anything non-default, so according to the
2186 protocol rules, we don't reply. */
2191 if (appData.debugMode)
2192 fprintf(debugFP, "\n<DO ");
2193 switch (option = (unsigned char) buf[++i]) {
2195 /* Whatever this is, we refuse to do it. */
2196 if (appData.debugMode)
2197 fprintf(debugFP, "%d ", option);
2198 TelnetRequest(TN_WONT, option);
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<DONT ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 if (appData.debugMode)
2208 fprintf(debugFP, "%d ", option);
2209 /* Whatever this is, we are already not doing
2210 it, because we never agree to do anything
2211 non-default, so according to the protocol
2212 rules, we don't reply. */
2217 if (appData.debugMode)
2218 fprintf(debugFP, "\n<IAC ");
2219 /* Doubled IAC; pass it through */
2223 if (appData.debugMode)
2224 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2225 /* Drop all other telnet commands on the floor */
2228 if (oldi > next_out)
2229 SendToPlayer(&buf[next_out], oldi - next_out);
2235 /* OK, this at least will *usually* work */
2236 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2240 if (loggedOn && !intfSet) {
2241 if (ics_type == ICS_ICC) {
2243 "/set-quietly interface %s\n/set-quietly style 12\n",
2246 } else if (ics_type == ICS_CHESSNET) {
2247 sprintf(str, "/style 12\n");
2249 strcpy(str, "alias $ @\n$set interface ");
2250 strcat(str, programVersion);
2251 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2253 strcat(str, "$iset nohighlight 1\n");
2255 strcat(str, "$iset lock 1\n$style 12\n");
2261 if (started == STARTED_COMMENT) {
2262 /* Accumulate characters in comment */
2263 parse[parse_pos++] = buf[i];
2264 if (buf[i] == '\n') {
2265 parse[parse_pos] = NULLCHAR;
2266 if(!suppressKibitz) // [HGM] kibitz
2267 AppendComment(forwardMostMove, StripHighlight(parse));
2268 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2269 int nrDigit = 0, nrAlph = 0, i;
2270 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2271 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2272 parse[parse_pos] = NULLCHAR;
2273 // try to be smart: if it does not look like search info, it should go to
2274 // ICS interaction window after all, not to engine-output window.
2275 for(i=0; i<parse_pos; i++) { // count letters and digits
2276 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2277 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2278 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2280 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2281 int depth=0; float score;
2282 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2283 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2284 pvInfoList[forwardMostMove-1].depth = depth;
2285 pvInfoList[forwardMostMove-1].score = 100*score;
2287 OutputKibitz(suppressKibitz, parse);
2290 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2291 SendToPlayer(tmp, strlen(tmp));
2294 started = STARTED_NONE;
2296 /* Don't match patterns against characters in chatter */
2301 if (started == STARTED_CHATTER) {
2302 if (buf[i] != '\n') {
2303 /* Don't match patterns against characters in chatter */
2307 started = STARTED_NONE;
2310 /* Kludge to deal with rcmd protocol */
2311 if (firstTime && looking_at(buf, &i, "\001*")) {
2312 DisplayFatalError(&buf[1], 0, 1);
2318 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2321 if (appData.debugMode)
2322 fprintf(debugFP, "ics_type %d\n", ics_type);
2325 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2326 ics_type = ICS_FICS;
2328 if (appData.debugMode)
2329 fprintf(debugFP, "ics_type %d\n", ics_type);
2332 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2333 ics_type = ICS_CHESSNET;
2335 if (appData.debugMode)
2336 fprintf(debugFP, "ics_type %d\n", ics_type);
2341 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2342 looking_at(buf, &i, "Logging you in as \"*\"") ||
2343 looking_at(buf, &i, "will be \"*\""))) {
2344 strcpy(ics_handle, star_match[0]);
2348 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2350 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2351 DisplayIcsInteractionTitle(buf);
2352 have_set_title = TRUE;
2355 /* skip finger notes */
2356 if (started == STARTED_NONE &&
2357 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2358 (buf[i] == '1' && buf[i+1] == '0')) &&
2359 buf[i+2] == ':' && buf[i+3] == ' ') {
2360 started = STARTED_CHATTER;
2365 /* skip formula vars */
2366 if (started == STARTED_NONE &&
2367 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2368 started = STARTED_CHATTER;
2374 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2375 if (appData.autoKibitz && started == STARTED_NONE &&
2376 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2377 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2378 if(looking_at(buf, &i, "* kibitzes: ") &&
2379 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2380 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2381 suppressKibitz = TRUE;
2382 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2383 && (gameMode == IcsPlayingWhite)) ||
2384 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2385 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2386 started = STARTED_CHATTER; // own kibitz we simply discard
2388 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2389 parse_pos = 0; parse[0] = NULLCHAR;
2390 savingComment = TRUE;
2391 suppressKibitz = gameMode != IcsObserving ? 2 :
2392 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2396 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2397 started = STARTED_CHATTER;
2398 suppressKibitz = TRUE;
2400 } // [HGM] kibitz: end of patch
2402 if (appData.zippyTalk || appData.zippyPlay) {
2403 /* [DM] Backup address for color zippy lines */
2407 if (loggedOn == TRUE)
2408 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2409 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2411 if (ZippyControl(buf, &i) ||
2412 ZippyConverse(buf, &i) ||
2413 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2415 if (!appData.colorize) continue;
2419 } // [DM] 'else { ' deleted
2420 if (/* Don't color "message" or "messages" output */
2421 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2422 looking_at(buf, &i, "*. * at *:*: ") ||
2423 looking_at(buf, &i, "--* (*:*): ") ||
2424 /* Regular tells and says */
2425 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2426 looking_at(buf, &i, "* (your partner) tells you: ") ||
2427 looking_at(buf, &i, "* says: ") ||
2428 /* Message notifications (same color as tells) */
2429 looking_at(buf, &i, "* has left a message ") ||
2430 looking_at(buf, &i, "* just sent you a message:\n") ||
2431 /* Whispers and kibitzes */
2432 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2433 looking_at(buf, &i, "* kibitzes: ") ||
2435 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2437 if (tkind == 1 && strchr(star_match[0], ':')) {
2438 /* Avoid "tells you:" spoofs in channels */
2441 if (star_match[0][0] == NULLCHAR ||
2442 strchr(star_match[0], ' ') ||
2443 (tkind == 3 && strchr(star_match[1], ' '))) {
2444 /* Reject bogus matches */
2447 if (appData.colorize) {
2448 if (oldi > next_out) {
2449 SendToPlayer(&buf[next_out], oldi - next_out);
2454 Colorize(ColorTell, FALSE);
2455 curColor = ColorTell;
2458 Colorize(ColorKibitz, FALSE);
2459 curColor = ColorKibitz;
2462 p = strrchr(star_match[1], '(');
2469 Colorize(ColorChannel1, FALSE);
2470 curColor = ColorChannel1;
2472 Colorize(ColorChannel, FALSE);
2473 curColor = ColorChannel;
2477 curColor = ColorNormal;
2481 if (started == STARTED_NONE && appData.autoComment &&
2482 (gameMode == IcsObserving ||
2483 gameMode == IcsPlayingWhite ||
2484 gameMode == IcsPlayingBlack)) {
2485 parse_pos = i - oldi;
2486 memcpy(parse, &buf[oldi], parse_pos);
2487 parse[parse_pos] = NULLCHAR;
2488 started = STARTED_COMMENT;
2489 savingComment = TRUE;
2491 started = STARTED_CHATTER;
2492 savingComment = FALSE;
2499 if (looking_at(buf, &i, "* s-shouts: ") ||
2500 looking_at(buf, &i, "* c-shouts: ")) {
2501 if (appData.colorize) {
2502 if (oldi > next_out) {
2503 SendToPlayer(&buf[next_out], oldi - next_out);
2506 Colorize(ColorSShout, FALSE);
2507 curColor = ColorSShout;
2510 started = STARTED_CHATTER;
2514 if (looking_at(buf, &i, "--->")) {
2519 if (looking_at(buf, &i, "* shouts: ") ||
2520 looking_at(buf, &i, "--> ")) {
2521 if (appData.colorize) {
2522 if (oldi > next_out) {
2523 SendToPlayer(&buf[next_out], oldi - next_out);
2526 Colorize(ColorShout, FALSE);
2527 curColor = ColorShout;
2530 started = STARTED_CHATTER;
2534 if (looking_at( buf, &i, "Challenge:")) {
2535 if (appData.colorize) {
2536 if (oldi > next_out) {
2537 SendToPlayer(&buf[next_out], oldi - next_out);
2540 Colorize(ColorChallenge, FALSE);
2541 curColor = ColorChallenge;
2547 if (looking_at(buf, &i, "* offers you") ||
2548 looking_at(buf, &i, "* offers to be") ||
2549 looking_at(buf, &i, "* would like to") ||
2550 looking_at(buf, &i, "* requests to") ||
2551 looking_at(buf, &i, "Your opponent offers") ||
2552 looking_at(buf, &i, "Your opponent requests")) {
2554 if (appData.colorize) {
2555 if (oldi > next_out) {
2556 SendToPlayer(&buf[next_out], oldi - next_out);
2559 Colorize(ColorRequest, FALSE);
2560 curColor = ColorRequest;
2565 if (looking_at(buf, &i, "* (*) seeking")) {
2566 if (appData.colorize) {
2567 if (oldi > next_out) {
2568 SendToPlayer(&buf[next_out], oldi - next_out);
2571 Colorize(ColorSeek, FALSE);
2572 curColor = ColorSeek;
2577 if (looking_at(buf, &i, "\\ ")) {
2578 if (prevColor != ColorNormal) {
2579 if (oldi > next_out) {
2580 SendToPlayer(&buf[next_out], oldi - next_out);
2583 Colorize(prevColor, TRUE);
2584 curColor = prevColor;
2586 if (savingComment) {
2587 parse_pos = i - oldi;
2588 memcpy(parse, &buf[oldi], parse_pos);
2589 parse[parse_pos] = NULLCHAR;
2590 started = STARTED_COMMENT;
2592 started = STARTED_CHATTER;
2597 if (looking_at(buf, &i, "Black Strength :") ||
2598 looking_at(buf, &i, "<<< style 10 board >>>") ||
2599 looking_at(buf, &i, "<10>") ||
2600 looking_at(buf, &i, "#@#")) {
2601 /* Wrong board style */
2603 SendToICS(ics_prefix);
2604 SendToICS("set style 12\n");
2605 SendToICS(ics_prefix);
2606 SendToICS("refresh\n");
2610 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2612 have_sent_ICS_logon = 1;
2616 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2617 (looking_at(buf, &i, "\n<12> ") ||
2618 looking_at(buf, &i, "<12> "))) {
2620 if (oldi > next_out) {
2621 SendToPlayer(&buf[next_out], oldi - next_out);
2624 started = STARTED_BOARD;
2629 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2630 looking_at(buf, &i, "<b1> ")) {
2631 if (oldi > next_out) {
2632 SendToPlayer(&buf[next_out], oldi - next_out);
2635 started = STARTED_HOLDINGS;
2640 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2642 /* Header for a move list -- first line */
2644 switch (ics_getting_history) {
2648 case BeginningOfGame:
2649 /* User typed "moves" or "oldmoves" while we
2650 were idle. Pretend we asked for these
2651 moves and soak them up so user can step
2652 through them and/or save them.
2655 gameMode = IcsObserving;
2658 ics_getting_history = H_GOT_UNREQ_HEADER;
2660 case EditGame: /*?*/
2661 case EditPosition: /*?*/
2662 /* Should above feature work in these modes too? */
2663 /* For now it doesn't */
2664 ics_getting_history = H_GOT_UNWANTED_HEADER;
2667 ics_getting_history = H_GOT_UNWANTED_HEADER;
2672 /* Is this the right one? */
2673 if (gameInfo.white && gameInfo.black &&
2674 strcmp(gameInfo.white, star_match[0]) == 0 &&
2675 strcmp(gameInfo.black, star_match[2]) == 0) {
2677 ics_getting_history = H_GOT_REQ_HEADER;
2680 case H_GOT_REQ_HEADER:
2681 case H_GOT_UNREQ_HEADER:
2682 case H_GOT_UNWANTED_HEADER:
2683 case H_GETTING_MOVES:
2684 /* Should not happen */
2685 DisplayError(_("Error gathering move list: two headers"), 0);
2686 ics_getting_history = H_FALSE;
2690 /* Save player ratings into gameInfo if needed */
2691 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2692 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2693 (gameInfo.whiteRating == -1 ||
2694 gameInfo.blackRating == -1)) {
2696 gameInfo.whiteRating = string_to_rating(star_match[1]);
2697 gameInfo.blackRating = string_to_rating(star_match[3]);
2698 if (appData.debugMode)
2699 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2700 gameInfo.whiteRating, gameInfo.blackRating);
2705 if (looking_at(buf, &i,
2706 "* * match, initial time: * minute*, increment: * second")) {
2707 /* Header for a move list -- second line */
2708 /* Initial board will follow if this is a wild game */
2709 if (gameInfo.event != NULL) free(gameInfo.event);
2710 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2711 gameInfo.event = StrSave(str);
2712 /* [HGM] we switched variant. Translate boards if needed. */
2713 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2717 if (looking_at(buf, &i, "Move ")) {
2718 /* Beginning of a move list */
2719 switch (ics_getting_history) {
2721 /* Normally should not happen */
2722 /* Maybe user hit reset while we were parsing */
2725 /* Happens if we are ignoring a move list that is not
2726 * the one we just requested. Common if the user
2727 * tries to observe two games without turning off
2730 case H_GETTING_MOVES:
2731 /* Should not happen */
2732 DisplayError(_("Error gathering move list: nested"), 0);
2733 ics_getting_history = H_FALSE;
2735 case H_GOT_REQ_HEADER:
2736 ics_getting_history = H_GETTING_MOVES;
2737 started = STARTED_MOVES;
2739 if (oldi > next_out) {
2740 SendToPlayer(&buf[next_out], oldi - next_out);
2743 case H_GOT_UNREQ_HEADER:
2744 ics_getting_history = H_GETTING_MOVES;
2745 started = STARTED_MOVES_NOHIDE;
2748 case H_GOT_UNWANTED_HEADER:
2749 ics_getting_history = H_FALSE;
2755 if (looking_at(buf, &i, "% ") ||
2756 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2757 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2758 savingComment = FALSE;
2761 case STARTED_MOVES_NOHIDE:
2762 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2763 parse[parse_pos + i - oldi] = NULLCHAR;
2764 ParseGameHistory(parse);
2766 if (appData.zippyPlay && first.initDone) {
2767 FeedMovesToProgram(&first, forwardMostMove);
2768 if (gameMode == IcsPlayingWhite) {
2769 if (WhiteOnMove(forwardMostMove)) {
2770 if (first.sendTime) {
2771 if (first.useColors) {
2772 SendToProgram("black\n", &first);
2774 SendTimeRemaining(&first, TRUE);
2777 if (first.useColors) {
2778 SendToProgram("white\ngo\n", &first);
2780 SendToProgram("go\n", &first);
2783 if (first.useColors) {
2784 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2786 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2788 first.maybeThinking = TRUE;
2790 if (first.usePlayother) {
2791 if (first.sendTime) {
2792 SendTimeRemaining(&first, TRUE);
2794 SendToProgram("playother\n", &first);
2800 } else if (gameMode == IcsPlayingBlack) {
2801 if (!WhiteOnMove(forwardMostMove)) {
2802 if (first.sendTime) {
2803 if (first.useColors) {
2804 SendToProgram("white\n", &first);
2806 SendTimeRemaining(&first, FALSE);
2809 if (first.useColors) {
2810 SendToProgram("black\ngo\n", &first);
2812 SendToProgram("go\n", &first);
2815 if (first.useColors) {
2816 SendToProgram("black\n", &first);
2818 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2820 first.maybeThinking = TRUE;
2822 if (first.usePlayother) {
2823 if (first.sendTime) {
2824 SendTimeRemaining(&first, FALSE);
2826 SendToProgram("playother\n", &first);
2835 if (gameMode == IcsObserving && ics_gamenum == -1) {
2836 /* Moves came from oldmoves or moves command
2837 while we weren't doing anything else.
2839 currentMove = forwardMostMove;
2840 ClearHighlights();/*!!could figure this out*/
2841 flipView = appData.flipView;
2842 DrawPosition(FALSE, boards[currentMove]);
2843 DisplayBothClocks();
2844 sprintf(str, "%s vs. %s",
2845 gameInfo.white, gameInfo.black);
2849 /* Moves were history of an active game */
2850 if (gameInfo.resultDetails != NULL) {
2851 free(gameInfo.resultDetails);
2852 gameInfo.resultDetails = NULL;
2855 HistorySet(parseList, backwardMostMove,
2856 forwardMostMove, currentMove-1);
2857 DisplayMove(currentMove - 1);
2858 if (started == STARTED_MOVES) next_out = i;
2859 started = STARTED_NONE;
2860 ics_getting_history = H_FALSE;
2863 case STARTED_OBSERVE:
2864 started = STARTED_NONE;
2865 SendToICS(ics_prefix);
2866 SendToICS("refresh\n");
2872 if(bookHit) { // [HGM] book: simulate book reply
2873 static char bookMove[MSG_SIZ]; // a bit generous?
2875 programStats.nodes = programStats.depth = programStats.time =
2876 programStats.score = programStats.got_only_move = 0;
2877 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2879 strcpy(bookMove, "move ");
2880 strcat(bookMove, bookHit);
2881 HandleMachineMove(bookMove, &first);
2886 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2887 started == STARTED_HOLDINGS ||
2888 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2889 /* Accumulate characters in move list or board */
2890 parse[parse_pos++] = buf[i];
2893 /* Start of game messages. Mostly we detect start of game
2894 when the first board image arrives. On some versions
2895 of the ICS, though, we need to do a "refresh" after starting
2896 to observe in order to get the current board right away. */
2897 if (looking_at(buf, &i, "Adding game * to observation list")) {
2898 started = STARTED_OBSERVE;
2902 /* Handle auto-observe */
2903 if (appData.autoObserve &&
2904 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2905 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2907 /* Choose the player that was highlighted, if any. */
2908 if (star_match[0][0] == '\033' ||
2909 star_match[1][0] != '\033') {
2910 player = star_match[0];
2912 player = star_match[2];
2914 sprintf(str, "%sobserve %s\n",
2915 ics_prefix, StripHighlightAndTitle(player));
2918 /* Save ratings from notify string */
2919 strcpy(player1Name, star_match[0]);
2920 player1Rating = string_to_rating(star_match[1]);
2921 strcpy(player2Name, star_match[2]);
2922 player2Rating = string_to_rating(star_match[3]);
2924 if (appData.debugMode)
2926 "Ratings from 'Game notification:' %s %d, %s %d\n",
2927 player1Name, player1Rating,
2928 player2Name, player2Rating);
2933 /* Deal with automatic examine mode after a game,
2934 and with IcsObserving -> IcsExamining transition */
2935 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2936 looking_at(buf, &i, "has made you an examiner of game *")) {
2938 int gamenum = atoi(star_match[0]);
2939 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2940 gamenum == ics_gamenum) {
2941 /* We were already playing or observing this game;
2942 no need to refetch history */
2943 gameMode = IcsExamining;
2945 pauseExamForwardMostMove = forwardMostMove;
2946 } else if (currentMove < forwardMostMove) {
2947 ForwardInner(forwardMostMove);
2950 /* I don't think this case really can happen */
2951 SendToICS(ics_prefix);
2952 SendToICS("refresh\n");
2957 /* Error messages */
2958 // if (ics_user_moved) {
2959 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2960 if (looking_at(buf, &i, "Illegal move") ||
2961 looking_at(buf, &i, "Not a legal move") ||
2962 looking_at(buf, &i, "Your king is in check") ||
2963 looking_at(buf, &i, "It isn't your turn") ||
2964 looking_at(buf, &i, "It is not your move")) {
2966 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2967 currentMove = --forwardMostMove;
2968 DisplayMove(currentMove - 1); /* before DMError */
2969 DrawPosition(FALSE, boards[currentMove]);
2971 DisplayBothClocks();
2973 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2979 if (looking_at(buf, &i, "still have time") ||
2980 looking_at(buf, &i, "not out of time") ||
2981 looking_at(buf, &i, "either player is out of time") ||
2982 looking_at(buf, &i, "has timeseal; checking")) {
2983 /* We must have called his flag a little too soon */
2984 whiteFlag = blackFlag = FALSE;
2988 if (looking_at(buf, &i, "added * seconds to") ||
2989 looking_at(buf, &i, "seconds were added to")) {
2990 /* Update the clocks */
2991 SendToICS(ics_prefix);
2992 SendToICS("refresh\n");
2996 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2997 ics_clock_paused = TRUE;
3002 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3003 ics_clock_paused = FALSE;
3008 /* Grab player ratings from the Creating: message.
3009 Note we have to check for the special case when
3010 the ICS inserts things like [white] or [black]. */
3011 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3012 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3014 0 player 1 name (not necessarily white)
3016 2 empty, white, or black (IGNORED)
3017 3 player 2 name (not necessarily black)
3020 The names/ratings are sorted out when the game
3021 actually starts (below).
3023 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3024 player1Rating = string_to_rating(star_match[1]);
3025 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3026 player2Rating = string_to_rating(star_match[4]);
3028 if (appData.debugMode)
3030 "Ratings from 'Creating:' %s %d, %s %d\n",
3031 player1Name, player1Rating,
3032 player2Name, player2Rating);
3037 /* Improved generic start/end-of-game messages */
3038 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3039 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3040 /* If tkind == 0: */
3041 /* star_match[0] is the game number */
3042 /* [1] is the white player's name */
3043 /* [2] is the black player's name */
3044 /* For end-of-game: */
3045 /* [3] is the reason for the game end */
3046 /* [4] is a PGN end game-token, preceded by " " */
3047 /* For start-of-game: */
3048 /* [3] begins with "Creating" or "Continuing" */
3049 /* [4] is " *" or empty (don't care). */
3050 int gamenum = atoi(star_match[0]);
3051 char *whitename, *blackname, *why, *endtoken;
3052 ChessMove endtype = (ChessMove) 0;
3055 whitename = star_match[1];
3056 blackname = star_match[2];
3057 why = star_match[3];
3058 endtoken = star_match[4];
3060 whitename = star_match[1];
3061 blackname = star_match[3];
3062 why = star_match[5];
3063 endtoken = star_match[6];
3066 /* Game start messages */
3067 if (strncmp(why, "Creating ", 9) == 0 ||
3068 strncmp(why, "Continuing ", 11) == 0) {
3069 gs_gamenum = gamenum;
3070 strcpy(gs_kind, strchr(why, ' ') + 1);
3072 if (appData.zippyPlay) {
3073 ZippyGameStart(whitename, blackname);
3079 /* Game end messages */
3080 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3081 ics_gamenum != gamenum) {
3084 while (endtoken[0] == ' ') endtoken++;
3085 switch (endtoken[0]) {
3088 endtype = GameUnfinished;
3091 endtype = BlackWins;
3094 if (endtoken[1] == '/')
3095 endtype = GameIsDrawn;
3097 endtype = WhiteWins;
3100 GameEnds(endtype, why, GE_ICS);
3102 if (appData.zippyPlay && first.initDone) {
3103 ZippyGameEnd(endtype, why);
3104 if (first.pr == NULL) {
3105 /* Start the next process early so that we'll
3106 be ready for the next challenge */
3107 StartChessProgram(&first);
3109 /* Send "new" early, in case this command takes
3110 a long time to finish, so that we'll be ready
3111 for the next challenge. */
3112 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3119 if (looking_at(buf, &i, "Removing game * from observation") ||
3120 looking_at(buf, &i, "no longer observing game *") ||
3121 looking_at(buf, &i, "Game * (*) has no examiners")) {
3122 if (gameMode == IcsObserving &&
3123 atoi(star_match[0]) == ics_gamenum)
3125 /* icsEngineAnalyze */
3126 if (appData.icsEngineAnalyze) {
3133 ics_user_moved = FALSE;
3138 if (looking_at(buf, &i, "no longer examining game *")) {
3139 if (gameMode == IcsExamining &&
3140 atoi(star_match[0]) == ics_gamenum)
3144 ics_user_moved = FALSE;
3149 /* Advance leftover_start past any newlines we find,
3150 so only partial lines can get reparsed */
3151 if (looking_at(buf, &i, "\n")) {
3152 prevColor = curColor;
3153 if (curColor != ColorNormal) {
3154 if (oldi > next_out) {
3155 SendToPlayer(&buf[next_out], oldi - next_out);
3158 Colorize(ColorNormal, FALSE);
3159 curColor = ColorNormal;
3161 if (started == STARTED_BOARD) {
3162 started = STARTED_NONE;
3163 parse[parse_pos] = NULLCHAR;
3164 ParseBoard12(parse);
3167 /* Send premove here */
3168 if (appData.premove) {
3170 if (currentMove == 0 &&
3171 gameMode == IcsPlayingWhite &&
3172 appData.premoveWhite) {
3173 sprintf(str, "%s%s\n", ics_prefix,
3174 appData.premoveWhiteText);
3175 if (appData.debugMode)
3176 fprintf(debugFP, "Sending premove:\n");
3178 } else if (currentMove == 1 &&
3179 gameMode == IcsPlayingBlack &&
3180 appData.premoveBlack) {
3181 sprintf(str, "%s%s\n", ics_prefix,
3182 appData.premoveBlackText);
3183 if (appData.debugMode)
3184 fprintf(debugFP, "Sending premove:\n");
3186 } else if (gotPremove) {
3188 ClearPremoveHighlights();
3189 if (appData.debugMode)
3190 fprintf(debugFP, "Sending premove:\n");
3191 UserMoveEvent(premoveFromX, premoveFromY,
3192 premoveToX, premoveToY,
3197 /* Usually suppress following prompt */
3198 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3199 if (looking_at(buf, &i, "*% ")) {
3200 savingComment = FALSE;
3204 } else if (started == STARTED_HOLDINGS) {
3206 char new_piece[MSG_SIZ];
3207 started = STARTED_NONE;
3208 parse[parse_pos] = NULLCHAR;
3209 if (appData.debugMode)
3210 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3211 parse, currentMove);
3212 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3213 gamenum == ics_gamenum) {
3214 if (gameInfo.variant == VariantNormal) {
3215 /* [HGM] We seem to switch variant during a game!
3216 * Presumably no holdings were displayed, so we have
3217 * to move the position two files to the right to
3218 * create room for them!
3220 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3221 /* Get a move list just to see the header, which
3222 will tell us whether this is really bug or zh */
3223 if (ics_getting_history == H_FALSE) {
3224 ics_getting_history = H_REQUESTED;
3225 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3229 new_piece[0] = NULLCHAR;
3230 sscanf(parse, "game %d white [%s black [%s <- %s",
3231 &gamenum, white_holding, black_holding,
3233 white_holding[strlen(white_holding)-1] = NULLCHAR;
3234 black_holding[strlen(black_holding)-1] = NULLCHAR;
3235 /* [HGM] copy holdings to board holdings area */
3236 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3237 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3239 if (appData.zippyPlay && first.initDone) {
3240 ZippyHoldings(white_holding, black_holding,
3244 if (tinyLayout || smallLayout) {
3245 char wh[16], bh[16];
3246 PackHolding(wh, white_holding);
3247 PackHolding(bh, black_holding);
3248 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3249 gameInfo.white, gameInfo.black);
3251 sprintf(str, "%s [%s] vs. %s [%s]",
3252 gameInfo.white, white_holding,
3253 gameInfo.black, black_holding);
3256 DrawPosition(FALSE, boards[currentMove]);
3259 /* Suppress following prompt */
3260 if (looking_at(buf, &i, "*% ")) {
3261 savingComment = FALSE;
3268 i++; /* skip unparsed character and loop back */
3271 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3272 started != STARTED_HOLDINGS && i > next_out) {
3273 SendToPlayer(&buf[next_out], i - next_out);
3276 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3278 leftover_len = buf_len - leftover_start;
3279 /* if buffer ends with something we couldn't parse,
3280 reparse it after appending the next read */
3282 } else if (count == 0) {
3283 RemoveInputSource(isr);
3284 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3286 DisplayFatalError(_("Error reading from ICS"), error, 1);
3291 /* Board style 12 looks like this:
3293 <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
3295 * The "<12> " is stripped before it gets to this routine. The two
3296 * trailing 0's (flip state and clock ticking) are later addition, and
3297 * some chess servers may not have them, or may have only the first.
3298 * Additional trailing fields may be added in the future.
3301 #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"
3303 #define RELATION_OBSERVING_PLAYED 0
3304 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3305 #define RELATION_PLAYING_MYMOVE 1
3306 #define RELATION_PLAYING_NOTMYMOVE -1
3307 #define RELATION_EXAMINING 2
3308 #define RELATION_ISOLATED_BOARD -3
3309 #define RELATION_STARTING_POSITION -4 /* FICS only */
3312 ParseBoard12(string)
3315 GameMode newGameMode;
3316 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3317 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3318 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3319 char to_play, board_chars[200];
3320 char move_str[500], str[500], elapsed_time[500];
3321 char black[32], white[32];
3323 int prevMove = currentMove;
3326 int fromX, fromY, toX, toY;
3328 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3329 char *bookHit = NULL; // [HGM] book
3331 fromX = fromY = toX = toY = -1;
3335 if (appData.debugMode)
3336 fprintf(debugFP, _("Parsing board: %s\n"), string);
3338 move_str[0] = NULLCHAR;
3339 elapsed_time[0] = NULLCHAR;
3340 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3342 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3343 if(string[i] == ' ') { ranks++; files = 0; }
3347 for(j = 0; j <i; j++) board_chars[j] = string[j];
3348 board_chars[i] = '\0';
3351 n = sscanf(string, PATTERN, &to_play, &double_push,
3352 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3353 &gamenum, white, black, &relation, &basetime, &increment,
3354 &white_stren, &black_stren, &white_time, &black_time,
3355 &moveNum, str, elapsed_time, move_str, &ics_flip,
3359 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3360 DisplayError(str, 0);
3364 /* Convert the move number to internal form */
3365 moveNum = (moveNum - 1) * 2;
3366 if (to_play == 'B') moveNum++;
3367 if (moveNum >= MAX_MOVES) {
3368 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3374 case RELATION_OBSERVING_PLAYED:
3375 case RELATION_OBSERVING_STATIC:
3376 if (gamenum == -1) {
3377 /* Old ICC buglet */
3378 relation = RELATION_OBSERVING_STATIC;
3380 newGameMode = IcsObserving;
3382 case RELATION_PLAYING_MYMOVE:
3383 case RELATION_PLAYING_NOTMYMOVE:
3385 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3386 IcsPlayingWhite : IcsPlayingBlack;
3388 case RELATION_EXAMINING:
3389 newGameMode = IcsExamining;
3391 case RELATION_ISOLATED_BOARD:
3393 /* Just display this board. If user was doing something else,
3394 we will forget about it until the next board comes. */
3395 newGameMode = IcsIdle;
3397 case RELATION_STARTING_POSITION:
3398 newGameMode = gameMode;
3402 /* Modify behavior for initial board display on move listing
3405 switch (ics_getting_history) {
3409 case H_GOT_REQ_HEADER:
3410 case H_GOT_UNREQ_HEADER:
3411 /* This is the initial position of the current game */
3412 gamenum = ics_gamenum;
3413 moveNum = 0; /* old ICS bug workaround */
3414 if (to_play == 'B') {
3415 startedFromSetupPosition = TRUE;
3416 blackPlaysFirst = TRUE;
3418 if (forwardMostMove == 0) forwardMostMove = 1;
3419 if (backwardMostMove == 0) backwardMostMove = 1;
3420 if (currentMove == 0) currentMove = 1;
3422 newGameMode = gameMode;
3423 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3425 case H_GOT_UNWANTED_HEADER:
3426 /* This is an initial board that we don't want */
3428 case H_GETTING_MOVES:
3429 /* Should not happen */
3430 DisplayError(_("Error gathering move list: extra board"), 0);
3431 ics_getting_history = H_FALSE;
3435 /* Take action if this is the first board of a new game, or of a
3436 different game than is currently being displayed. */
3437 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3438 relation == RELATION_ISOLATED_BOARD) {
3440 /* Forget the old game and get the history (if any) of the new one */
3441 if (gameMode != BeginningOfGame) {
3445 if (appData.autoRaiseBoard) BoardToTop();
3447 if (gamenum == -1) {
3448 newGameMode = IcsIdle;
3449 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3450 appData.getMoveList) {
3451 /* Need to get game history */
3452 ics_getting_history = H_REQUESTED;
3453 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3457 /* Initially flip the board to have black on the bottom if playing
3458 black or if the ICS flip flag is set, but let the user change
3459 it with the Flip View button. */
3460 flipView = appData.autoFlipView ?
3461 (newGameMode == IcsPlayingBlack) || ics_flip :
3464 /* Done with values from previous mode; copy in new ones */
3465 gameMode = newGameMode;
3467 ics_gamenum = gamenum;
3468 if (gamenum == gs_gamenum) {
3469 int klen = strlen(gs_kind);
3470 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3471 sprintf(str, "ICS %s", gs_kind);
3472 gameInfo.event = StrSave(str);
3474 gameInfo.event = StrSave("ICS game");
3476 gameInfo.site = StrSave(appData.icsHost);
3477 gameInfo.date = PGNDate();
3478 gameInfo.round = StrSave("-");
3479 gameInfo.white = StrSave(white);
3480 gameInfo.black = StrSave(black);
3481 timeControl = basetime * 60 * 1000;
3483 timeIncrement = increment * 1000;
3484 movesPerSession = 0;
3485 gameInfo.timeControl = TimeControlTagValue();
3486 VariantSwitch(board, StringToVariant(gameInfo.event) );
3487 if (appData.debugMode) {
3488 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3489 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3490 setbuf(debugFP, NULL);
3493 gameInfo.outOfBook = NULL;
3495 /* Do we have the ratings? */
3496 if (strcmp(player1Name, white) == 0 &&
3497 strcmp(player2Name, black) == 0) {
3498 if (appData.debugMode)
3499 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3500 player1Rating, player2Rating);
3501 gameInfo.whiteRating = player1Rating;
3502 gameInfo.blackRating = player2Rating;
3503 } else if (strcmp(player2Name, white) == 0 &&
3504 strcmp(player1Name, black) == 0) {
3505 if (appData.debugMode)
3506 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3507 player2Rating, player1Rating);
3508 gameInfo.whiteRating = player2Rating;
3509 gameInfo.blackRating = player1Rating;
3511 player1Name[0] = player2Name[0] = NULLCHAR;
3513 /* Silence shouts if requested */
3514 if (appData.quietPlay &&
3515 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3516 SendToICS(ics_prefix);
3517 SendToICS("set shout 0\n");
3521 /* Deal with midgame name changes */
3523 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3524 if (gameInfo.white) free(gameInfo.white);
3525 gameInfo.white = StrSave(white);
3527 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3528 if (gameInfo.black) free(gameInfo.black);
3529 gameInfo.black = StrSave(black);
3533 /* Throw away game result if anything actually changes in examine mode */
3534 if (gameMode == IcsExamining && !newGame) {
3535 gameInfo.result = GameUnfinished;
3536 if (gameInfo.resultDetails != NULL) {
3537 free(gameInfo.resultDetails);
3538 gameInfo.resultDetails = NULL;
3542 /* In pausing && IcsExamining mode, we ignore boards coming
3543 in if they are in a different variation than we are. */
3544 if (pauseExamInvalid) return;
3545 if (pausing && gameMode == IcsExamining) {
3546 if (moveNum <= pauseExamForwardMostMove) {
3547 pauseExamInvalid = TRUE;
3548 forwardMostMove = pauseExamForwardMostMove;
3553 if (appData.debugMode) {
3554 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3556 /* Parse the board */
3557 for (k = 0; k < ranks; k++) {
3558 for (j = 0; j < files; j++)
3559 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3560 if(gameInfo.holdingsWidth > 1) {
3561 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3562 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3565 CopyBoard(boards[moveNum], board);
3567 startedFromSetupPosition =
3568 !CompareBoards(board, initialPosition);
3569 if(startedFromSetupPosition)
3570 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3573 /* [HGM] Set castling rights. Take the outermost Rooks,
3574 to make it also work for FRC opening positions. Note that board12
3575 is really defective for later FRC positions, as it has no way to
3576 indicate which Rook can castle if they are on the same side of King.
3577 For the initial position we grant rights to the outermost Rooks,
3578 and remember thos rights, and we then copy them on positions
3579 later in an FRC game. This means WB might not recognize castlings with
3580 Rooks that have moved back to their original position as illegal,
3581 but in ICS mode that is not its job anyway.
3583 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3584 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3586 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3587 if(board[0][i] == WhiteRook) j = i;
3588 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3590 if(board[0][i] == WhiteRook) j = i;
3591 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3593 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3596 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3597 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3599 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3600 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3602 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3603 if(board[BOARD_HEIGHT-1][k] == bKing)
3604 initialRights[5] = castlingRights[moveNum][5] = k;
3606 r = castlingRights[moveNum][0] = initialRights[0];
3607 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3608 r = castlingRights[moveNum][1] = initialRights[1];
3609 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3610 r = castlingRights[moveNum][3] = initialRights[3];
3611 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3612 r = castlingRights[moveNum][4] = initialRights[4];
3613 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3614 /* wildcastle kludge: always assume King has rights */
3615 r = castlingRights[moveNum][2] = initialRights[2];
3616 r = castlingRights[moveNum][5] = initialRights[5];
3618 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3619 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3622 if (ics_getting_history == H_GOT_REQ_HEADER ||
3623 ics_getting_history == H_GOT_UNREQ_HEADER) {
3624 /* This was an initial position from a move list, not
3625 the current position */
3629 /* Update currentMove and known move number limits */
3630 newMove = newGame || moveNum > forwardMostMove;
3632 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3633 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3634 takeback = forwardMostMove - moveNum;
3635 for (i = 0; i < takeback; i++) {
3636 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3637 SendToProgram("undo\n", &first);
3642 forwardMostMove = backwardMostMove = currentMove = moveNum;
3643 if (gameMode == IcsExamining && moveNum == 0) {
3644 /* Workaround for ICS limitation: we are not told the wild
3645 type when starting to examine a game. But if we ask for
3646 the move list, the move list header will tell us */
3647 ics_getting_history = H_REQUESTED;
3648 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3651 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3652 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3653 forwardMostMove = moveNum;
3654 if (!pausing || currentMove > forwardMostMove)
3655 currentMove = forwardMostMove;
3657 /* New part of history that is not contiguous with old part */
3658 if (pausing && gameMode == IcsExamining) {
3659 pauseExamInvalid = TRUE;
3660 forwardMostMove = pauseExamForwardMostMove;
3663 forwardMostMove = backwardMostMove = currentMove = moveNum;
3664 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3665 ics_getting_history = H_REQUESTED;
3666 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3671 /* Update the clocks */
3672 if (strchr(elapsed_time, '.')) {
3674 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3675 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3677 /* Time is in seconds */
3678 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3679 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3684 if (appData.zippyPlay && newGame &&
3685 gameMode != IcsObserving && gameMode != IcsIdle &&
3686 gameMode != IcsExamining)
3687 ZippyFirstBoard(moveNum, basetime, increment);
3690 /* Put the move on the move list, first converting
3691 to canonical algebraic form. */
3693 if (appData.debugMode) {
3694 if (appData.debugMode) { int f = forwardMostMove;
3695 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3696 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3698 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3699 fprintf(debugFP, "moveNum = %d\n", moveNum);
3700 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3701 setbuf(debugFP, NULL);
3703 if (moveNum <= backwardMostMove) {
3704 /* We don't know what the board looked like before
3706 strcpy(parseList[moveNum - 1], move_str);
3707 strcat(parseList[moveNum - 1], " ");
3708 strcat(parseList[moveNum - 1], elapsed_time);
3709 moveList[moveNum - 1][0] = NULLCHAR;
3710 } else if (strcmp(move_str, "none") == 0) {
3711 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3712 /* Again, we don't know what the board looked like;
3713 this is really the start of the game. */
3714 parseList[moveNum - 1][0] = NULLCHAR;
3715 moveList[moveNum - 1][0] = NULLCHAR;
3716 backwardMostMove = moveNum;
3717 startedFromSetupPosition = TRUE;
3718 fromX = fromY = toX = toY = -1;
3720 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3721 // So we parse the long-algebraic move string in stead of the SAN move
3722 int valid; char buf[MSG_SIZ], *prom;
3724 // str looks something like "Q/a1-a2"; kill the slash
3726 sprintf(buf, "%c%s", str[0], str+2);
3727 else strcpy(buf, str); // might be castling
3728 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3729 strcat(buf, prom); // long move lacks promo specification!
3730 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3731 if(appData.debugMode)
3732 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3733 strcpy(move_str, buf);
3735 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3736 &fromX, &fromY, &toX, &toY, &promoChar)
3737 || ParseOneMove(buf, moveNum - 1, &moveType,
3738 &fromX, &fromY, &toX, &toY, &promoChar);
3739 // end of long SAN patch
3741 (void) CoordsToAlgebraic(boards[moveNum - 1],
3742 PosFlags(moveNum - 1), EP_UNKNOWN,
3743 fromY, fromX, toY, toX, promoChar,
3744 parseList[moveNum-1]);
3745 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3746 castlingRights[moveNum]) ) {
3752 if(gameInfo.variant != VariantShogi)
3753 strcat(parseList[moveNum - 1], "+");
3756 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3757 strcat(parseList[moveNum - 1], "#");
3760 strcat(parseList[moveNum - 1], " ");
3761 strcat(parseList[moveNum - 1], elapsed_time);
3762 /* currentMoveString is set as a side-effect of ParseOneMove */
3763 strcpy(moveList[moveNum - 1], currentMoveString);
3764 strcat(moveList[moveNum - 1], "\n");
3766 /* Move from ICS was illegal!? Punt. */
3767 if (appData.debugMode) {
3768 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3769 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3772 if (appData.testLegality && appData.debugMode) {
3773 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3774 DisplayError(str, 0);
3777 strcpy(parseList[moveNum - 1], move_str);
3778 strcat(parseList[moveNum - 1], " ");
3779 strcat(parseList[moveNum - 1], elapsed_time);
3780 moveList[moveNum - 1][0] = NULLCHAR;
3781 fromX = fromY = toX = toY = -1;
3784 if (appData.debugMode) {
3785 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3786 setbuf(debugFP, NULL);
3790 /* Send move to chess program (BEFORE animating it). */
3791 if (appData.zippyPlay && !newGame && newMove &&
3792 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3794 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3795 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3796 if (moveList[moveNum - 1][0] == NULLCHAR) {
3797 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3799 DisplayError(str, 0);
3801 if (first.sendTime) {
3802 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3804 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3805 if (firstMove && !bookHit) {
3807 if (first.useColors) {
3808 SendToProgram(gameMode == IcsPlayingWhite ?
3810 "black\ngo\n", &first);
3812 SendToProgram("go\n", &first);
3814 first.maybeThinking = TRUE;
3817 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3818 if (moveList[moveNum - 1][0] == NULLCHAR) {
3819 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3820 DisplayError(str, 0);
3822 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3823 SendMoveToProgram(moveNum - 1, &first);
3830 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3831 /* If move comes from a remote source, animate it. If it
3832 isn't remote, it will have already been animated. */
3833 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3834 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3836 if (!pausing && appData.highlightLastMove) {
3837 SetHighlights(fromX, fromY, toX, toY);
3841 /* Start the clocks */
3842 whiteFlag = blackFlag = FALSE;
3843 appData.clockMode = !(basetime == 0 && increment == 0);
3845 ics_clock_paused = TRUE;
3847 } else if (ticking == 1) {
3848 ics_clock_paused = FALSE;
3850 if (gameMode == IcsIdle ||
3851 relation == RELATION_OBSERVING_STATIC ||
3852 relation == RELATION_EXAMINING ||
3854 DisplayBothClocks();
3858 /* Display opponents and material strengths */
3859 if (gameInfo.variant != VariantBughouse &&
3860 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3861 if (tinyLayout || smallLayout) {
3862 if(gameInfo.variant == VariantNormal)
3863 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3864 gameInfo.white, white_stren, gameInfo.black, black_stren,
3865 basetime, increment);
3867 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3868 gameInfo.white, white_stren, gameInfo.black, black_stren,
3869 basetime, increment, (int) gameInfo.variant);
3871 if(gameInfo.variant == VariantNormal)
3872 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3873 gameInfo.white, white_stren, gameInfo.black, black_stren,
3874 basetime, increment);
3876 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3877 gameInfo.white, white_stren, gameInfo.black, black_stren,
3878 basetime, increment, VariantName(gameInfo.variant));
3881 if (appData.debugMode) {
3882 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3887 /* Display the board */
3888 if (!pausing && !appData.noGUI) {
3889 if (appData.premove)
3891 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3892 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3893 ClearPremoveHighlights();
3895 DrawPosition(FALSE, boards[currentMove]);
3896 DisplayMove(moveNum - 1);
3897 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3898 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3899 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3900 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3904 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3906 if(bookHit) { // [HGM] book: simulate book reply
3907 static char bookMove[MSG_SIZ]; // a bit generous?
3909 programStats.nodes = programStats.depth = programStats.time =
3910 programStats.score = programStats.got_only_move = 0;
3911 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3913 strcpy(bookMove, "move ");
3914 strcat(bookMove, bookHit);
3915 HandleMachineMove(bookMove, &first);
3924 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3925 ics_getting_history = H_REQUESTED;
3926 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3932 AnalysisPeriodicEvent(force)
3935 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3936 && !force) || !appData.periodicUpdates)
3939 /* Send . command to Crafty to collect stats */
3940 SendToProgram(".\n", &first);
3942 /* Don't send another until we get a response (this makes
3943 us stop sending to old Crafty's which don't understand
3944 the "." command (sending illegal cmds resets node count & time,
3945 which looks bad)) */
3946 programStats.ok_to_send = 0;
3950 SendMoveToProgram(moveNum, cps)
3952 ChessProgramState *cps;
3956 if (cps->useUsermove) {
3957 SendToProgram("usermove ", cps);
3961 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3962 int len = space - parseList[moveNum];
3963 memcpy(buf, parseList[moveNum], len);
3965 buf[len] = NULLCHAR;
3967 sprintf(buf, "%s\n", parseList[moveNum]);
3969 SendToProgram(buf, cps);
3971 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3972 AlphaRank(moveList[moveNum], 4);
3973 SendToProgram(moveList[moveNum], cps);
3974 AlphaRank(moveList[moveNum], 4); // and back
3976 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3977 * the engine. It would be nice to have a better way to identify castle
3979 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3980 && cps->useOOCastle) {
3981 int fromX = moveList[moveNum][0] - AAA;
3982 int fromY = moveList[moveNum][1] - ONE;
3983 int toX = moveList[moveNum][2] - AAA;
3984 int toY = moveList[moveNum][3] - ONE;
3985 if((boards[moveNum][fromY][fromX] == WhiteKing
3986 && boards[moveNum][toY][toX] == WhiteRook)
3987 || (boards[moveNum][fromY][fromX] == BlackKing
3988 && boards[moveNum][toY][toX] == BlackRook)) {
3989 if(toX > fromX) SendToProgram("O-O\n", cps);
3990 else SendToProgram("O-O-O\n", cps);
3992 else SendToProgram(moveList[moveNum], cps);
3994 else SendToProgram(moveList[moveNum], cps);
3995 /* End of additions by Tord */
3998 /* [HGM] setting up the opening has brought engine in force mode! */
3999 /* Send 'go' if we are in a mode where machine should play. */
4000 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4001 (gameMode == TwoMachinesPlay ||
4003 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4005 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4006 SendToProgram("go\n", cps);
4007 if (appData.debugMode) {
4008 fprintf(debugFP, "(extra)\n");
4011 setboardSpoiledMachineBlack = 0;
4015 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4017 int fromX, fromY, toX, toY;
4019 char user_move[MSG_SIZ];
4023 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4024 (int)moveType, fromX, fromY, toX, toY);
4025 DisplayError(user_move + strlen("say "), 0);
4027 case WhiteKingSideCastle:
4028 case BlackKingSideCastle:
4029 case WhiteQueenSideCastleWild:
4030 case BlackQueenSideCastleWild:
4032 case WhiteHSideCastleFR:
4033 case BlackHSideCastleFR:
4035 sprintf(user_move, "o-o\n");
4037 case WhiteQueenSideCastle:
4038 case BlackQueenSideCastle:
4039 case WhiteKingSideCastleWild:
4040 case BlackKingSideCastleWild:
4042 case WhiteASideCastleFR:
4043 case BlackASideCastleFR:
4045 sprintf(user_move, "o-o-o\n");
4047 case WhitePromotionQueen:
4048 case BlackPromotionQueen:
4049 case WhitePromotionRook:
4050 case BlackPromotionRook:
4051 case WhitePromotionBishop:
4052 case BlackPromotionBishop:
4053 case WhitePromotionKnight:
4054 case BlackPromotionKnight:
4055 case WhitePromotionKing:
4056 case BlackPromotionKing:
4057 case WhitePromotionChancellor:
4058 case BlackPromotionChancellor:
4059 case WhitePromotionArchbishop:
4060 case BlackPromotionArchbishop:
4061 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4062 sprintf(user_move, "%c%c%c%c=%c\n",
4063 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4064 PieceToChar(WhiteFerz));
4065 else if(gameInfo.variant == VariantGreat)
4066 sprintf(user_move, "%c%c%c%c=%c\n",
4067 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4068 PieceToChar(WhiteMan));
4070 sprintf(user_move, "%c%c%c%c=%c\n",
4071 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4072 PieceToChar(PromoPiece(moveType)));
4076 sprintf(user_move, "%c@%c%c\n",
4077 ToUpper(PieceToChar((ChessSquare) fromX)),
4078 AAA + toX, ONE + toY);
4081 case WhiteCapturesEnPassant:
4082 case BlackCapturesEnPassant:
4083 case IllegalMove: /* could be a variant we don't quite understand */
4084 sprintf(user_move, "%c%c%c%c\n",
4085 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4088 SendToICS(user_move);
4092 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4097 if (rf == DROP_RANK) {
4098 sprintf(move, "%c@%c%c\n",
4099 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4101 if (promoChar == 'x' || promoChar == NULLCHAR) {
4102 sprintf(move, "%c%c%c%c\n",
4103 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4105 sprintf(move, "%c%c%c%c%c\n",
4106 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4112 ProcessICSInitScript(f)
4117 while (fgets(buf, MSG_SIZ, f)) {
4118 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4125 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4127 AlphaRank(char *move, int n)
4129 // char *p = move, c; int x, y;
4131 if (appData.debugMode) {
4132 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4136 move[2]>='0' && move[2]<='9' &&
4137 move[3]>='a' && move[3]<='x' ) {
4139 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4140 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4142 if(move[0]>='0' && move[0]<='9' &&
4143 move[1]>='a' && move[1]<='x' &&
4144 move[2]>='0' && move[2]<='9' &&
4145 move[3]>='a' && move[3]<='x' ) {
4146 /* input move, Shogi -> normal */
4147 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4148 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4149 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4150 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4153 move[3]>='0' && move[3]<='9' &&
4154 move[2]>='a' && move[2]<='x' ) {
4156 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4157 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4160 move[0]>='a' && move[0]<='x' &&
4161 move[3]>='0' && move[3]<='9' &&
4162 move[2]>='a' && move[2]<='x' ) {
4163 /* output move, normal -> Shogi */
4164 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4165 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4166 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4167 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4168 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4170 if (appData.debugMode) {
4171 fprintf(debugFP, " out = '%s'\n", move);
4175 /* Parser for moves from gnuchess, ICS, or user typein box */
4177 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4180 ChessMove *moveType;
4181 int *fromX, *fromY, *toX, *toY;
4184 if (appData.debugMode) {
4185 fprintf(debugFP, "move to parse: %s\n", move);
4187 *moveType = yylexstr(moveNum, move);
4189 switch (*moveType) {
4190 case WhitePromotionChancellor:
4191 case BlackPromotionChancellor:
4192 case WhitePromotionArchbishop:
4193 case BlackPromotionArchbishop:
4194 case WhitePromotionQueen:
4195 case BlackPromotionQueen:
4196 case WhitePromotionRook:
4197 case BlackPromotionRook:
4198 case WhitePromotionBishop:
4199 case BlackPromotionBishop:
4200 case WhitePromotionKnight:
4201 case BlackPromotionKnight:
4202 case WhitePromotionKing:
4203 case BlackPromotionKing:
4205 case WhiteCapturesEnPassant:
4206 case BlackCapturesEnPassant:
4207 case WhiteKingSideCastle:
4208 case WhiteQueenSideCastle:
4209 case BlackKingSideCastle:
4210 case BlackQueenSideCastle:
4211 case WhiteKingSideCastleWild:
4212 case WhiteQueenSideCastleWild:
4213 case BlackKingSideCastleWild:
4214 case BlackQueenSideCastleWild:
4215 /* Code added by Tord: */
4216 case WhiteHSideCastleFR:
4217 case WhiteASideCastleFR:
4218 case BlackHSideCastleFR:
4219 case BlackASideCastleFR:
4220 /* End of code added by Tord */
4221 case IllegalMove: /* bug or odd chess variant */
4222 *fromX = currentMoveString[0] - AAA;
4223 *fromY = currentMoveString[1] - ONE;
4224 *toX = currentMoveString[2] - AAA;
4225 *toY = currentMoveString[3] - ONE;
4226 *promoChar = currentMoveString[4];
4227 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4228 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4229 if (appData.debugMode) {
4230 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4232 *fromX = *fromY = *toX = *toY = 0;
4235 if (appData.testLegality) {
4236 return (*moveType != IllegalMove);
4238 return !(fromX == fromY && toX == toY);
4243 *fromX = *moveType == WhiteDrop ?
4244 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4245 (int) CharToPiece(ToLower(currentMoveString[0]));
4247 *toX = currentMoveString[2] - AAA;
4248 *toY = currentMoveString[3] - ONE;
4249 *promoChar = NULLCHAR;
4253 case ImpossibleMove:
4254 case (ChessMove) 0: /* end of file */
4263 if (appData.debugMode) {
4264 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4267 *fromX = *fromY = *toX = *toY = 0;
4268 *promoChar = NULLCHAR;
4273 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4274 // All positions will have equal probability, but the current method will not provide a unique
4275 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4281 int piecesLeft[(int)BlackPawn];
4282 int seed, nrOfShuffles;
4284 void GetPositionNumber()
4285 { // sets global variable seed
4288 seed = appData.defaultFrcPosition;
4289 if(seed < 0) { // randomize based on time for negative FRC position numbers
4290 for(i=0; i<50; i++) seed += random();
4291 seed = random() ^ random() >> 8 ^ random() << 8;
4292 if(seed<0) seed = -seed;
4296 int put(Board board, int pieceType, int rank, int n, int shade)
4297 // put the piece on the (n-1)-th empty squares of the given shade
4301 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4302 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4303 board[rank][i] = (ChessSquare) pieceType;
4304 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4306 piecesLeft[pieceType]--;
4314 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4315 // calculate where the next piece goes, (any empty square), and put it there
4319 i = seed % squaresLeft[shade];
4320 nrOfShuffles *= squaresLeft[shade];
4321 seed /= squaresLeft[shade];
4322 put(board, pieceType, rank, i, shade);
4325 void AddTwoPieces(Board board, int pieceType, int rank)
4326 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4328 int i, n=squaresLeft[ANY], j=n-1, k;
4330 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4331 i = seed % k; // pick one
4334 while(i >= j) i -= j--;
4335 j = n - 1 - j; i += j;
4336 put(board, pieceType, rank, j, ANY);
4337 put(board, pieceType, rank, i, ANY);
4340 void SetUpShuffle(Board board, int number)
4344 GetPositionNumber(); nrOfShuffles = 1;
4346 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4347 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4348 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4350 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4352 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4353 p = (int) board[0][i];
4354 if(p < (int) BlackPawn) piecesLeft[p] ++;
4355 board[0][i] = EmptySquare;
4358 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4359 // shuffles restricted to allow normal castling put KRR first
4360 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4361 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4362 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4363 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4364 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4365 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4366 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4367 put(board, WhiteRook, 0, 0, ANY);
4368 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4371 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4372 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4373 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4374 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4375 while(piecesLeft[p] >= 2) {
4376 AddOnePiece(board, p, 0, LITE);
4377 AddOnePiece(board, p, 0, DARK);
4379 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4382 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4383 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4384 // but we leave King and Rooks for last, to possibly obey FRC restriction
4385 if(p == (int)WhiteRook) continue;
4386 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4387 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4390 // now everything is placed, except perhaps King (Unicorn) and Rooks
4392 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4393 // Last King gets castling rights
4394 while(piecesLeft[(int)WhiteUnicorn]) {
4395 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4396 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4399 while(piecesLeft[(int)WhiteKing]) {
4400 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4401 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4406 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4407 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4410 // Only Rooks can be left; simply place them all
4411 while(piecesLeft[(int)WhiteRook]) {
4412 i = put(board, WhiteRook, 0, 0, ANY);
4413 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4416 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4418 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4421 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4422 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4425 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4428 int SetCharTable( char *table, const char * map )
4429 /* [HGM] moved here from winboard.c because of its general usefulness */
4430 /* Basically a safe strcpy that uses the last character as King */
4432 int result = FALSE; int NrPieces;
4434 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4435 && NrPieces >= 12 && !(NrPieces&1)) {
4436 int i; /* [HGM] Accept even length from 12 to 34 */
4438 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4439 for( i=0; i<NrPieces/2-1; i++ ) {
4441 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4443 table[(int) WhiteKing] = map[NrPieces/2-1];
4444 table[(int) BlackKing] = map[NrPieces-1];
4452 void Prelude(Board board)
4453 { // [HGM] superchess: random selection of exo-pieces
4454 int i, j, k; ChessSquare p;
4455 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4457 GetPositionNumber(); // use FRC position number
4459 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4460 SetCharTable(pieceToChar, appData.pieceToCharTable);
4461 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4462 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4465 j = seed%4; seed /= 4;
4466 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4467 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4468 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4469 j = seed%3 + (seed%3 >= j); seed /= 3;
4470 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4471 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4472 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4473 j = seed%3; seed /= 3;
4474 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4475 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4476 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4477 j = seed%2 + (seed%2 >= j); seed /= 2;
4478 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4479 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4480 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4481 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4482 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4483 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4484 put(board, exoPieces[0], 0, 0, ANY);
4485 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4489 InitPosition(redraw)
4492 ChessSquare (* pieces)[BOARD_SIZE];
4493 int i, j, pawnRow, overrule,
4494 oldx = gameInfo.boardWidth,
4495 oldy = gameInfo.boardHeight,
4496 oldh = gameInfo.holdingsWidth,
4497 oldv = gameInfo.variant;
4499 currentMove = forwardMostMove = backwardMostMove = 0;
4500 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4502 /* [AS] Initialize pv info list [HGM] and game status */
4504 for( i=0; i<MAX_MOVES; i++ ) {
4505 pvInfoList[i].depth = 0;
4506 epStatus[i]=EP_NONE;
4507 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4510 initialRulePlies = 0; /* 50-move counter start */
4512 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4513 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4517 /* [HGM] logic here is completely changed. In stead of full positions */
4518 /* the initialized data only consist of the two backranks. The switch */
4519 /* selects which one we will use, which is than copied to the Board */
4520 /* initialPosition, which for the rest is initialized by Pawns and */
4521 /* empty squares. This initial position is then copied to boards[0], */
4522 /* possibly after shuffling, so that it remains available. */
4524 gameInfo.holdingsWidth = 0; /* default board sizes */
4525 gameInfo.boardWidth = 8;
4526 gameInfo.boardHeight = 8;
4527 gameInfo.holdingsSize = 0;
4528 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4529 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4530 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4532 switch (gameInfo.variant) {
4533 case VariantFischeRandom:
4534 shuffleOpenings = TRUE;
4538 case VariantShatranj:
4539 pieces = ShatranjArray;
4540 nrCastlingRights = 0;
4541 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4543 case VariantTwoKings:
4544 pieces = twoKingsArray;
4546 case VariantCapaRandom:
4547 shuffleOpenings = TRUE;
4548 case VariantCapablanca:
4549 pieces = CapablancaArray;
4550 gameInfo.boardWidth = 10;
4551 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4554 pieces = GothicArray;
4555 gameInfo.boardWidth = 10;
4556 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4559 pieces = JanusArray;
4560 gameInfo.boardWidth = 10;
4561 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4562 nrCastlingRights = 6;
4563 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4564 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4565 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4566 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4567 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4568 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4571 pieces = FalconArray;
4572 gameInfo.boardWidth = 10;
4573 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4575 case VariantXiangqi:
4576 pieces = XiangqiArray;
4577 gameInfo.boardWidth = 9;
4578 gameInfo.boardHeight = 10;
4579 nrCastlingRights = 0;
4580 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4583 pieces = ShogiArray;
4584 gameInfo.boardWidth = 9;
4585 gameInfo.boardHeight = 9;
4586 gameInfo.holdingsSize = 7;
4587 nrCastlingRights = 0;
4588 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4590 case VariantCourier:
4591 pieces = CourierArray;
4592 gameInfo.boardWidth = 12;
4593 nrCastlingRights = 0;
4594 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4595 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4597 case VariantKnightmate:
4598 pieces = KnightmateArray;
4599 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4602 pieces = fairyArray;
4603 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4606 pieces = GreatArray;
4607 gameInfo.boardWidth = 10;
4608 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4609 gameInfo.holdingsSize = 8;
4613 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4614 gameInfo.holdingsSize = 8;
4615 startedFromSetupPosition = TRUE;
4617 case VariantCrazyhouse:
4618 case VariantBughouse:
4620 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4621 gameInfo.holdingsSize = 5;
4623 case VariantWildCastle:
4625 /* !!?shuffle with kings guaranteed to be on d or e file */
4626 shuffleOpenings = 1;
4628 case VariantNoCastle:
4630 nrCastlingRights = 0;
4631 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4632 /* !!?unconstrained back-rank shuffle */
4633 shuffleOpenings = 1;
4638 if(appData.NrFiles >= 0) {
4639 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4640 gameInfo.boardWidth = appData.NrFiles;
4642 if(appData.NrRanks >= 0) {
4643 gameInfo.boardHeight = appData.NrRanks;
4645 if(appData.holdingsSize >= 0) {
4646 i = appData.holdingsSize;
4647 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4648 gameInfo.holdingsSize = i;
4650 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4651 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4652 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4654 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4655 if(pawnRow < 1) pawnRow = 1;
4657 /* User pieceToChar list overrules defaults */
4658 if(appData.pieceToCharTable != NULL)
4659 SetCharTable(pieceToChar, appData.pieceToCharTable);
4661 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4663 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4664 s = (ChessSquare) 0; /* account holding counts in guard band */
4665 for( i=0; i<BOARD_HEIGHT; i++ )
4666 initialPosition[i][j] = s;
4668 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4669 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4670 initialPosition[pawnRow][j] = WhitePawn;
4671 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4672 if(gameInfo.variant == VariantXiangqi) {
4674 initialPosition[pawnRow][j] =
4675 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4676 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4677 initialPosition[2][j] = WhiteCannon;
4678 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4682 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4684 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4687 initialPosition[1][j] = WhiteBishop;
4688 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4690 initialPosition[1][j] = WhiteRook;
4691 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4694 if( nrCastlingRights == -1) {
4695 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4696 /* This sets default castling rights from none to normal corners */
4697 /* Variants with other castling rights must set them themselves above */
4698 nrCastlingRights = 6;
4700 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4701 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4702 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4703 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4704 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4705 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4708 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4709 if(gameInfo.variant == VariantGreat) { // promotion commoners
4710 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4711 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4712 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4713 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4716 if(gameInfo.variant == VariantFischeRandom) {
4717 if( appData.defaultFrcPosition < 0 ) {
4718 ShuffleFRC( initialPosition );
4721 SetupFRC( initialPosition, appData.defaultFrcPosition );
4723 startedFromSetupPosition = TRUE;
4726 if (appData.debugMode) {
4727 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4729 if(shuffleOpenings) {
4730 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4731 startedFromSetupPosition = TRUE;
4734 if(startedFromPositionFile) {
4735 /* [HGM] loadPos: use PositionFile for every new game */
4736 CopyBoard(initialPosition, filePosition);
4737 for(i=0; i<nrCastlingRights; i++)
4738 castlingRights[0][i] = initialRights[i] = fileRights[i];
4739 startedFromSetupPosition = TRUE;
4742 CopyBoard(boards[0], initialPosition);
4743 if(oldx != gameInfo.boardWidth ||
4744 oldy != gameInfo.boardHeight ||
4745 oldh != gameInfo.holdingsWidth
4747 || oldv == VariantGothic || // For licensing popups
4748 gameInfo.variant == VariantGothic
4751 || oldv == VariantFalcon ||
4752 gameInfo.variant == VariantFalcon
4756 InitDrawingSizes(-2 ,0);
4760 DrawPosition(TRUE, boards[currentMove]);
4765 SendBoard(cps, moveNum)
4766 ChessProgramState *cps;
4769 char message[MSG_SIZ];
4771 if (cps->useSetboard) {
4772 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4773 sprintf(message, "setboard %s\n", fen);
4774 SendToProgram(message, cps);
4780 /* Kludge to set black to move, avoiding the troublesome and now
4781 * deprecated "black" command.
4783 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4785 SendToProgram("edit\n", cps);
4786 SendToProgram("#\n", cps);
4787 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4788 bp = &boards[moveNum][i][BOARD_LEFT];
4789 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4790 if ((int) *bp < (int) BlackPawn) {
4791 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4793 if(message[0] == '+' || message[0] == '~') {
4794 sprintf(message, "%c%c%c+\n",
4795 PieceToChar((ChessSquare)(DEMOTED *bp)),
4798 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4799 message[1] = BOARD_RGHT - 1 - j + '1';
4800 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4802 SendToProgram(message, cps);
4807 SendToProgram("c\n", cps);
4808 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4809 bp = &boards[moveNum][i][BOARD_LEFT];
4810 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4811 if (((int) *bp != (int) EmptySquare)
4812 && ((int) *bp >= (int) BlackPawn)) {
4813 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4815 if(message[0] == '+' || message[0] == '~') {
4816 sprintf(message, "%c%c%c+\n",
4817 PieceToChar((ChessSquare)(DEMOTED *bp)),
4820 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4821 message[1] = BOARD_RGHT - 1 - j + '1';
4822 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4824 SendToProgram(message, cps);
4829 SendToProgram(".\n", cps);
4831 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4835 IsPromotion(fromX, fromY, toX, toY)
4836 int fromX, fromY, toX, toY;
4838 /* [HGM] add Shogi promotions */
4839 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4842 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4843 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4844 /* [HGM] Note to self: line above also weeds out drops */
4845 piece = boards[currentMove][fromY][fromX];
4846 if(gameInfo.variant == VariantShogi) {
4847 promotionZoneSize = 3;
4848 highestPromotingPiece = (int)WhiteKing;
4849 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4850 and if in normal chess we then allow promotion to King, why not
4851 allow promotion of other piece in Shogi? */
4853 if((int)piece >= BlackPawn) {
4854 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4856 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4858 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4859 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4861 return ( (int)piece <= highestPromotingPiece );
4865 InPalace(row, column)
4867 { /* [HGM] for Xiangqi */
4868 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4869 column < (BOARD_WIDTH + 4)/2 &&
4870 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4875 PieceForSquare (x, y)
4879 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4882 return boards[currentMove][y][x];
4886 OKToStartUserMove(x, y)
4889 ChessSquare from_piece;
4892 if (matchMode) return FALSE;
4893 if (gameMode == EditPosition) return TRUE;
4895 if (x >= 0 && y >= 0)
4896 from_piece = boards[currentMove][y][x];
4898 from_piece = EmptySquare;
4900 if (from_piece == EmptySquare) return FALSE;
4902 white_piece = (int)from_piece >= (int)WhitePawn &&
4903 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4906 case PlayFromGameFile:
4908 case TwoMachinesPlay:
4916 case MachinePlaysWhite:
4917 case IcsPlayingBlack:
4918 if (appData.zippyPlay) return FALSE;
4920 DisplayMoveError(_("You are playing Black"));
4925 case MachinePlaysBlack:
4926 case IcsPlayingWhite:
4927 if (appData.zippyPlay) return FALSE;
4929 DisplayMoveError(_("You are playing White"));
4935 if (!white_piece && WhiteOnMove(currentMove)) {
4936 DisplayMoveError(_("It is White's turn"));
4939 if (white_piece && !WhiteOnMove(currentMove)) {
4940 DisplayMoveError(_("It is Black's turn"));
4943 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4944 /* Editing correspondence game history */
4945 /* Could disallow this or prompt for confirmation */
4948 if (currentMove < forwardMostMove) {
4949 /* Discarding moves */
4950 /* Could prompt for confirmation here,
4951 but I don't think that's such a good idea */
4952 forwardMostMove = currentMove;
4956 case BeginningOfGame:
4957 if (appData.icsActive) return FALSE;
4958 if (!appData.noChessProgram) {
4960 DisplayMoveError(_("You are playing White"));
4967 if (!white_piece && WhiteOnMove(currentMove)) {
4968 DisplayMoveError(_("It is White's turn"));
4971 if (white_piece && !WhiteOnMove(currentMove)) {
4972 DisplayMoveError(_("It is Black's turn"));
4981 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4982 && gameMode != AnalyzeFile && gameMode != Training) {
4983 DisplayMoveError(_("Displayed position is not current"));
4989 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4990 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4991 int lastLoadGameUseList = FALSE;
4992 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4993 ChessMove lastLoadGameStart = (ChessMove) 0;
4997 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4998 int fromX, fromY, toX, toY;
5002 ChessSquare pdown, pup;
5004 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5005 if ((fromX == toX) && (fromY == toY)) {
5006 return ImpossibleMove;
5009 /* [HGM] suppress all moves into holdings area and guard band */
5010 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5011 return ImpossibleMove;
5013 /* [HGM] <sameColor> moved to here from winboard.c */
5014 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5015 pdown = boards[currentMove][fromY][fromX];
5016 pup = boards[currentMove][toY][toX];
5017 if ( gameMode != EditPosition &&
5018 (WhitePawn <= pdown && pdown < BlackPawn &&
5019 WhitePawn <= pup && pup < BlackPawn ||
5020 BlackPawn <= pdown && pdown < EmptySquare &&
5021 BlackPawn <= pup && pup < EmptySquare
5022 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5023 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5024 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5026 return ImpossibleMove;
5028 /* Check if the user is playing in turn. This is complicated because we
5029 let the user "pick up" a piece before it is his turn. So the piece he
5030 tried to pick up may have been captured by the time he puts it down!
5031 Therefore we use the color the user is supposed to be playing in this
5032 test, not the color of the piece that is currently on the starting
5033 square---except in EditGame mode, where the user is playing both
5034 sides; fortunately there the capture race can't happen. (It can
5035 now happen in IcsExamining mode, but that's just too bad. The user
5036 will get a somewhat confusing message in that case.)
5040 case PlayFromGameFile:
5042 case TwoMachinesPlay:
5046 /* We switched into a game mode where moves are not accepted,
5047 perhaps while the mouse button was down. */
5048 return ImpossibleMove;
5050 case MachinePlaysWhite:
5051 /* User is moving for Black */
5052 if (WhiteOnMove(currentMove)) {
5053 DisplayMoveError(_("It is White's turn"));
5054 return ImpossibleMove;
5058 case MachinePlaysBlack:
5059 /* User is moving for White */
5060 if (!WhiteOnMove(currentMove)) {
5061 DisplayMoveError(_("It is Black's turn"));
5062 return ImpossibleMove;
5068 case BeginningOfGame:
5071 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5072 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5073 /* User is moving for Black */
5074 if (WhiteOnMove(currentMove)) {
5075 DisplayMoveError(_("It is White's turn"));
5076 return ImpossibleMove;
5079 /* User is moving for White */
5080 if (!WhiteOnMove(currentMove)) {
5081 DisplayMoveError(_("It is Black's turn"));
5082 return ImpossibleMove;
5087 case IcsPlayingBlack:
5088 /* User is moving for Black */
5089 if (WhiteOnMove(currentMove)) {
5090 if (!appData.premove) {
5091 DisplayMoveError(_("It is White's turn"));
5092 } else if (toX >= 0 && toY >= 0) {
5095 premoveFromX = fromX;
5096 premoveFromY = fromY;
5097 premovePromoChar = promoChar;
5099 if (appData.debugMode)
5100 fprintf(debugFP, "Got premove: fromX %d,"
5101 "fromY %d, toX %d, toY %d\n",
5102 fromX, fromY, toX, toY);
5104 return ImpossibleMove;
5108 case IcsPlayingWhite:
5109 /* User is moving for White */
5110 if (!WhiteOnMove(currentMove)) {
5111 if (!appData.premove) {
5112 DisplayMoveError(_("It is Black's turn"));
5113 } else if (toX >= 0 && toY >= 0) {
5116 premoveFromX = fromX;
5117 premoveFromY = fromY;
5118 premovePromoChar = promoChar;
5120 if (appData.debugMode)
5121 fprintf(debugFP, "Got premove: fromX %d,"
5122 "fromY %d, toX %d, toY %d\n",
5123 fromX, fromY, toX, toY);
5125 return ImpossibleMove;
5133 /* EditPosition, empty square, or different color piece;
5134 click-click move is possible */
5135 if (toX == -2 || toY == -2) {
5136 boards[0][fromY][fromX] = EmptySquare;
5137 return AmbiguousMove;
5138 } else if (toX >= 0 && toY >= 0) {
5139 boards[0][toY][toX] = boards[0][fromY][fromX];
5140 boards[0][fromY][fromX] = EmptySquare;
5141 return AmbiguousMove;
5143 return ImpossibleMove;
5146 /* [HGM] If move started in holdings, it means a drop */
5147 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5148 if( pup != EmptySquare ) return ImpossibleMove;
5149 if(appData.testLegality) {
5150 /* it would be more logical if LegalityTest() also figured out
5151 * which drops are legal. For now we forbid pawns on back rank.
5152 * Shogi is on its own here...
5154 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5155 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5156 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5158 return WhiteDrop; /* Not needed to specify white or black yet */
5161 userOfferedDraw = FALSE;
5163 /* [HGM] always test for legality, to get promotion info */
5164 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5165 epStatus[currentMove], castlingRights[currentMove],
5166 fromY, fromX, toY, toX, promoChar);
5168 /* [HGM] but possibly ignore an IllegalMove result */
5169 if (appData.testLegality) {
5170 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5171 DisplayMoveError(_("Illegal move"));
5172 return ImpossibleMove;
5175 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5177 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5178 function is made into one that returns an OK move type if FinishMove
5179 should be called. This to give the calling driver routine the
5180 opportunity to finish the userMove input with a promotion popup,
5181 without bothering the user with this for invalid or illegal moves */
5183 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5186 /* Common tail of UserMoveEvent and DropMenuEvent */
5188 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5190 int fromX, fromY, toX, toY;
5191 /*char*/int promoChar;
5195 if(appData.debugMode)
5196 fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5198 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5200 // [HGM] superchess: suppress promotions to non-available piece
5201 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5202 if(WhiteOnMove(currentMove))
5204 if(!boards[currentMove][k][BOARD_WIDTH-2])
5209 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5214 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5215 move type in caller when we know the move is a legal promotion */
5216 if(moveType == NormalMove && promoChar)
5217 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5219 if(appData.debugMode)
5220 fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5222 /* [HGM] convert drag-and-drop piece drops to standard form */
5223 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1)
5225 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5226 if(appData.debugMode)
5227 fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5228 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5229 // fromX = boards[currentMove][fromY][fromX];
5230 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5232 fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5234 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5236 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare)
5242 /* [HGM] <popupFix> The following if has been moved here from
5243 UserMoveEvent(). Because it seemed to belon here (why not allow
5244 piece drops in training games?), and because it can only be
5245 performed after it is known to what we promote. */
5246 if (gameMode == Training)
5248 /* compare the move played on the board to the next move in the
5249 * game. If they match, display the move and the opponent's response.
5250 * If they don't match, display an error message.
5253 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5254 CopyBoard(testBoard, boards[currentMove]);
5255 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5257 if (CompareBoards(testBoard, boards[currentMove+1]))
5259 ForwardInner(currentMove+1);
5261 /* Autoplay the opponent's response.
5262 * if appData.animate was TRUE when Training mode was entered,
5263 * the response will be animated.
5265 saveAnimate = appData.animate;
5266 appData.animate = animateTraining;
5267 ForwardInner(currentMove+1);
5268 appData.animate = saveAnimate;
5270 /* check for the end of the game */
5271 if (currentMove >= forwardMostMove)
5273 gameMode = PlayFromGameFile;
5275 SetTrainingModeOff();
5276 DisplayInformation(_("End of game"));
5281 DisplayError(_("Incorrect move"), 0);
5286 /* Ok, now we know that the move is good, so we can kill
5287 the previous line in Analysis Mode */
5288 if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5290 forwardMostMove = currentMove;
5293 /* If we need the chess program but it's dead, restart it */
5294 ResurrectChessProgram();
5296 /* A user move restarts a paused game*/
5300 thinkOutput[0] = NULLCHAR;
5302 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5304 if (gameMode == BeginningOfGame)
5306 if (appData.noChessProgram)
5308 gameMode = EditGame;
5314 gameMode = MachinePlaysBlack;
5317 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5321 sprintf(buf, "name %s\n", gameInfo.white);
5322 SendToProgram(buf, &first);
5328 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5330 /* Relay move to ICS or chess engine */
5331 if (appData.icsActive)
5333 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5334 gameMode == IcsExamining)
5336 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5342 if (first.sendTime && (gameMode == BeginningOfGame ||
5343 gameMode == MachinePlaysWhite ||
5344 gameMode == MachinePlaysBlack))
5346 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5348 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5350 // [HGM] book: if program might be playing, let it use book
5351 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5352 first.maybeThinking = TRUE;
5355 SendMoveToProgram(forwardMostMove-1, &first);
5356 if (currentMove == cmailOldMove + 1)
5358 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5362 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5367 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5368 EP_UNKNOWN, castlingRights[currentMove]) )
5375 if (WhiteOnMove(currentMove))
5377 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5381 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5385 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5390 case MachinePlaysBlack:
5391 case MachinePlaysWhite:
5392 /* disable certain menu options while machine is thinking */
5393 SetMachineThinkingEnables();
5401 { // [HGM] book: simulate book reply
5402 static char bookMove[MSG_SIZ]; // a bit generous?
5404 programStats.nodes = programStats.depth = programStats.time =
5405 programStats.score = programStats.got_only_move = 0;
5406 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5408 strcpy(bookMove, "move ");
5409 strcat(bookMove, bookHit);
5410 HandleMachineMove(bookMove, &first);
5417 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5418 int fromX, fromY, toX, toY;
5421 /* [HGM] This routine was added to allow calling of its two logical
5422 parts from other modules in the old way. Before, UserMoveEvent()
5423 automatically called FinishMove() if the move was OK, and returned
5424 otherwise. I separated the two, in order to make it possible to
5425 slip a promotion popup in between. But that it always needs two
5426 calls, to the first part, (now called UserMoveTest() ), and to
5427 FinishMove if the first part succeeded. Calls that do not need
5428 to do anything in between, can call this routine the old way.
5430 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5431 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5432 if(moveType != ImpossibleMove)
5433 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5436 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5438 // char * hint = lastHint;
5439 FrontEndProgramStats stats;
5441 stats.which = cps == &first ? 0 : 1;
5442 stats.depth = cpstats->depth;
5443 stats.nodes = cpstats->nodes;
5444 stats.score = cpstats->score;
5445 stats.time = cpstats->time;
5446 stats.pv = cpstats->movelist;
5447 stats.hint = lastHint;
5448 stats.an_move_index = 0;
5449 stats.an_move_count = 0;
5451 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5452 stats.hint = cpstats->move_name;
5453 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5454 stats.an_move_count = cpstats->nr_moves;
5457 SetProgramStats( &stats );
5460 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5461 { // [HGM] book: this routine intercepts moves to simulate book replies
5462 char *bookHit = NULL;
5464 //first determine if the incoming move brings opponent into his book
5465 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5466 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5467 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5468 if(bookHit != NULL && !cps->bookSuspend) {
5469 // make sure opponent is not going to reply after receiving move to book position
5470 SendToProgram("force\n", cps);
5471 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5473 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5474 // now arrange restart after book miss
5476 // after a book hit we never send 'go', and the code after the call to this routine
5477 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5479 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5480 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5481 SendToProgram(buf, cps);
5482 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5483 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5484 SendToProgram("go\n", cps);
5485 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5486 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5487 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5488 SendToProgram("go\n", cps);
5489 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5491 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5495 ChessProgramState *savedState;
5496 void DeferredBookMove(void)
5498 if(savedState->lastPing != savedState->lastPong)
5499 ScheduleDelayedEvent(DeferredBookMove, 10);
5501 HandleMachineMove(savedMessage, savedState);
5505 HandleMachineMove(message, cps)
5507 ChessProgramState *cps;
5509 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5510 char realname[MSG_SIZ];
5511 int fromX, fromY, toX, toY;
5518 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5520 * Kludge to ignore BEL characters
5522 while (*message == '\007') message++;
5525 * [HGM] engine debug message: ignore lines starting with '#' character
5527 if(cps->debug && *message == '#') return;
5530 * Look for book output
5532 if (cps == &first && bookRequested) {
5533 if (message[0] == '\t' || message[0] == ' ') {
5534 /* Part of the book output is here; append it */
5535 strcat(bookOutput, message);
5536 strcat(bookOutput, " \n");
5538 } else if (bookOutput[0] != NULLCHAR) {
5539 /* All of book output has arrived; display it */
5540 char *p = bookOutput;
5541 while (*p != NULLCHAR) {
5542 if (*p == '\t') *p = ' ';
5545 DisplayInformation(bookOutput);
5546 bookRequested = FALSE;
5547 /* Fall through to parse the current output */
5552 * Look for machine move.
5554 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5555 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5557 /* This method is only useful on engines that support ping */
5558 if (cps->lastPing != cps->lastPong) {
5559 if (gameMode == BeginningOfGame) {
5560 /* Extra move from before last new; ignore */
5561 if (appData.debugMode) {
5562 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5565 if (appData.debugMode) {
5566 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5567 cps->which, gameMode);
5570 SendToProgram("undo\n", cps);
5576 case BeginningOfGame:
5577 /* Extra move from before last reset; ignore */
5578 if (appData.debugMode) {
5579 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5586 /* Extra move after we tried to stop. The mode test is
5587 not a reliable way of detecting this problem, but it's
5588 the best we can do on engines that don't support ping.
5590 if (appData.debugMode) {
5591 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5592 cps->which, gameMode);
5594 SendToProgram("undo\n", cps);
5597 case MachinePlaysWhite:
5598 case IcsPlayingWhite:
5599 machineWhite = TRUE;
5602 case MachinePlaysBlack:
5603 case IcsPlayingBlack:
5604 machineWhite = FALSE;
5607 case TwoMachinesPlay:
5608 machineWhite = (cps->twoMachinesColor[0] == 'w');
5611 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5612 if (appData.debugMode) {
5614 "Ignoring move out of turn by %s, gameMode %d"
5615 ", forwardMost %d\n",
5616 cps->which, gameMode, forwardMostMove);
5621 if (appData.debugMode) { int f = forwardMostMove;
5622 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5623 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5625 if(cps->alphaRank) AlphaRank(machineMove, 4);
5626 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5627 &fromX, &fromY, &toX, &toY, &promoChar)) {
5628 /* Machine move could not be parsed; ignore it. */
5629 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5630 machineMove, cps->which);
5631 DisplayError(buf1, 0);
5632 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5633 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5634 if (gameMode == TwoMachinesPlay) {
5635 GameEnds(machineWhite ? BlackWins : WhiteWins,
5641 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5642 /* So we have to redo legality test with true e.p. status here, */
5643 /* to make sure an illegal e.p. capture does not slip through, */
5644 /* to cause a forfeit on a justified illegal-move complaint */
5645 /* of the opponent. */
5646 if( gameMode==TwoMachinesPlay && appData.testLegality
5647 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5650 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5651 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5652 fromY, fromX, toY, toX, promoChar);
5653 if (appData.debugMode) {
5655 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5656 castlingRights[forwardMostMove][i], castlingRank[i]);
5657 fprintf(debugFP, "castling rights\n");
5659 if(moveType == IllegalMove) {
5660 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5661 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5662 GameEnds(machineWhite ? BlackWins : WhiteWins,
5665 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5666 /* [HGM] Kludge to handle engines that send FRC-style castling
5667 when they shouldn't (like TSCP-Gothic) */
5669 case WhiteASideCastleFR:
5670 case BlackASideCastleFR:
5672 currentMoveString[2]++;
5674 case WhiteHSideCastleFR:
5675 case BlackHSideCastleFR:
5677 currentMoveString[2]--;
5679 default: ; // nothing to do, but suppresses warning of pedantic compilers
5682 hintRequested = FALSE;
5683 lastHint[0] = NULLCHAR;
5684 bookRequested = FALSE;
5685 /* Program may be pondering now */
5686 cps->maybeThinking = TRUE;
5687 if (cps->sendTime == 2) cps->sendTime = 1;
5688 if (cps->offeredDraw) cps->offeredDraw--;
5691 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5693 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5695 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5696 char buf[3*MSG_SIZ];
5698 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %1.0f knps) PV=%s\n",
5699 programStats.score / 100.,
5701 programStats.time / 100.,
5702 (unsigned int)programStats.nodes,
5703 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5704 programStats.movelist);
5709 /* currentMoveString is set as a side-effect of ParseOneMove */
5710 strcpy(machineMove, currentMoveString);
5711 strcat(machineMove, "\n");
5712 strcpy(moveList[forwardMostMove], machineMove);
5714 /* [AS] Save move info and clear stats for next move */
5715 pvInfoList[ forwardMostMove ].score = programStats.score;
5716 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5717 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5718 ClearProgramStats();
5719 thinkOutput[0] = NULLCHAR;
5720 hiddenThinkOutputState = 0;
5722 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5724 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5725 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5728 while( count < adjudicateLossPlies ) {
5729 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5732 score = -score; /* Flip score for winning side */
5735 if( score > adjudicateLossThreshold ) {
5742 if( count >= adjudicateLossPlies ) {
5743 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5745 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5746 "Xboard adjudication",
5753 if( gameMode == TwoMachinesPlay ) {
5754 // [HGM] some adjudications useful with buggy engines
5755 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5756 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5759 if( appData.testLegality )
5760 { /* [HGM] Some more adjudications for obstinate engines */
5761 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5762 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5763 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5764 static int moveCount = 6;
5766 char *reason = NULL;
5768 /* Count what is on board. */
5769 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5770 { ChessSquare p = boards[forwardMostMove][i][j];
5774 { /* count B,N,R and other of each side */
5777 NrK++; break; // [HGM] atomic: count Kings
5781 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5782 bishopsColor |= 1 << ((i^j)&1);
5787 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5788 bishopsColor |= 1 << ((i^j)&1);
5803 PawnAdvance += m; NrPawns++;
5805 NrPieces += (p != EmptySquare);
5806 NrW += ((int)p < (int)BlackPawn);
5807 if(gameInfo.variant == VariantXiangqi &&
5808 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5809 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5810 NrW -= ((int)p < (int)BlackPawn);
5814 /* Some material-based adjudications that have to be made before stalemate test */
5815 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5816 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5817 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5818 if(appData.checkMates) {
5819 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5820 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5821 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5822 "Xboard adjudication: King destroyed", GE_XBOARD );
5827 /* Bare King in Shatranj (loses) or Losers (wins) */
5828 if( NrW == 1 || NrPieces - NrW == 1) {
5829 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5830 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5831 if(appData.checkMates) {
5832 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5833 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5834 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5835 "Xboard adjudication: Bare king", GE_XBOARD );
5839 if( gameInfo.variant == VariantShatranj && --bare < 0)
5841 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5842 if(appData.checkMates) {
5843 /* but only adjudicate if adjudication enabled */
5844 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5845 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5846 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5847 "Xboard adjudication: Bare king", GE_XBOARD );
5854 // don't wait for engine to announce game end if we can judge ourselves
5855 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5856 castlingRights[forwardMostMove]) ) {
5858 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5859 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5860 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5861 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5864 reason = "Xboard adjudication: 3rd check";
5865 epStatus[forwardMostMove] = EP_CHECKMATE;
5875 reason = "Xboard adjudication: Stalemate";
5876 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5877 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5878 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5879 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5880 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5881 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5882 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5883 EP_CHECKMATE : EP_WINS);
5884 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5885 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5889 reason = "Xboard adjudication: Checkmate";
5890 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5894 switch(i = epStatus[forwardMostMove]) {
5896 result = GameIsDrawn; break;
5898 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5900 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5902 result = (ChessMove) 0;
5904 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5905 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5906 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5907 GameEnds( result, reason, GE_XBOARD );
5911 /* Next absolutely insufficient mating material. */
5912 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5913 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5914 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5915 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5916 { /* KBK, KNK, KK of KBKB with like Bishops */
5918 /* always flag draws, for judging claims */
5919 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5921 if(appData.materialDraws) {
5922 /* but only adjudicate them if adjudication enabled */
5923 SendToProgram("force\n", cps->other); // suppress reply
5924 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5925 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5926 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5931 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5933 ( NrWR == 1 && NrBR == 1 /* KRKR */
5934 || NrWQ==1 && NrBQ==1 /* KQKQ */
5935 || NrWN==2 || NrBN==2 /* KNNK */
5936 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5938 if(--moveCount < 0 && appData.trivialDraws)
5939 { /* if the first 3 moves do not show a tactical win, declare draw */
5940 SendToProgram("force\n", cps->other); // suppress reply
5941 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5942 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5943 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5946 } else moveCount = 6;
5950 if (appData.debugMode) { int i;
5951 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5952 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5953 appData.drawRepeats);
5954 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5955 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5959 /* Check for rep-draws */
5961 for(k = forwardMostMove-2;
5962 k>=backwardMostMove && k>=forwardMostMove-100 &&
5963 epStatus[k] < EP_UNKNOWN &&
5964 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5968 if (appData.debugMode) {
5969 fprintf(debugFP, " loop\n");
5972 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5974 if (appData.debugMode) {
5975 fprintf(debugFP, "match\n");
5978 /* compare castling rights */
5979 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5980 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5981 rights++; /* King lost rights, while rook still had them */
5982 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5983 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5984 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5985 rights++; /* but at least one rook lost them */
5987 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5988 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5990 if( castlingRights[forwardMostMove][5] >= 0 ) {
5991 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5992 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5996 if (appData.debugMode) {
5997 for(i=0; i<nrCastlingRights; i++)
5998 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6001 if (appData.debugMode) {
6002 fprintf(debugFP, " %d %d\n", rights, k);
6005 if( rights == 0 && ++count > appData.drawRepeats-2
6006 && appData.drawRepeats > 1) {
6007 /* adjudicate after user-specified nr of repeats */
6008 SendToProgram("force\n", cps->other); // suppress reply
6009 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6010 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6011 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6012 // [HGM] xiangqi: check for forbidden perpetuals
6013 int m, ourPerpetual = 1, hisPerpetual = 1;
6014 for(m=forwardMostMove; m>k; m-=2) {
6015 if(MateTest(boards[m], PosFlags(m),
6016 EP_NONE, castlingRights[m]) != MT_CHECK)
6017 ourPerpetual = 0; // the current mover did not always check
6018 if(MateTest(boards[m-1], PosFlags(m-1),
6019 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6020 hisPerpetual = 0; // the opponent did not always check
6022 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6023 ourPerpetual, hisPerpetual);
6024 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6025 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6026 "Xboard adjudication: perpetual checking", GE_XBOARD );
6029 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6030 break; // (or we would have caught him before). Abort repetition-checking loop.
6031 // Now check for perpetual chases
6032 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6033 hisPerpetual = PerpetualChase(k, forwardMostMove);
6034 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6035 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6036 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6037 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6040 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6041 break; // Abort repetition-checking loop.
6043 // if neither of us is checking or chasing all the time, or both are, it is draw
6045 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6048 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6049 epStatus[forwardMostMove] = EP_REP_DRAW;
6053 /* Now we test for 50-move draws. Determine ply count */
6054 count = forwardMostMove;
6055 /* look for last irreversble move */
6056 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6058 /* if we hit starting position, add initial plies */
6059 if( count == backwardMostMove )
6060 count -= initialRulePlies;
6061 count = forwardMostMove - count;
6063 epStatus[forwardMostMove] = EP_RULE_DRAW;
6064 /* this is used to judge if draw claims are legal */
6065 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6066 SendToProgram("force\n", cps->other); // suppress reply
6067 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6068 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6073 /* if draw offer is pending, treat it as a draw claim
6074 * when draw condition present, to allow engines a way to
6075 * claim draws before making their move to avoid a race
6076 * condition occurring after their move
6078 if( cps->other->offeredDraw || cps->offeredDraw ) {
6080 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6081 p = "Draw claim: 50-move rule";
6082 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6083 p = "Draw claim: 3-fold repetition";
6084 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6085 p = "Draw claim: insufficient mating material";
6087 SendToProgram("force\n", cps->other); // suppress reply
6088 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6089 GameEnds( GameIsDrawn, p, GE_XBOARD );
6090 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6096 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6097 SendToProgram("force\n", cps->other); // suppress reply
6098 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6099 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6101 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6108 if (gameMode == TwoMachinesPlay) {
6109 /* [HGM] relaying draw offers moved to after reception of move */
6110 /* and interpreting offer as claim if it brings draw condition */
6111 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6112 SendToProgram("draw\n", cps->other);
6114 if (cps->other->sendTime) {
6115 SendTimeRemaining(cps->other,
6116 cps->other->twoMachinesColor[0] == 'w');
6118 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6119 if (firstMove && !bookHit) {
6121 if (cps->other->useColors) {
6122 SendToProgram(cps->other->twoMachinesColor, cps->other);
6124 SendToProgram("go\n", cps->other);
6126 cps->other->maybeThinking = TRUE;
6129 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6131 if (!pausing && appData.ringBellAfterMoves) {
6136 * Reenable menu items that were disabled while
6137 * machine was thinking
6139 if (gameMode != TwoMachinesPlay)
6140 SetUserThinkingEnables();
6142 // [HGM] book: after book hit opponent has received move and is now in force mode
6143 // force the book reply into it, and then fake that it outputted this move by jumping
6144 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6146 static char bookMove[MSG_SIZ]; // a bit generous?
6148 strcpy(bookMove, "move ");
6149 strcat(bookMove, bookHit);
6152 programStats.nodes = programStats.depth = programStats.time =
6153 programStats.score = programStats.got_only_move = 0;
6154 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6156 if(cps->lastPing != cps->lastPong) {
6157 savedMessage = message; // args for deferred call
6159 ScheduleDelayedEvent(DeferredBookMove, 10);
6168 /* Set special modes for chess engines. Later something general
6169 * could be added here; for now there is just one kludge feature,
6170 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6171 * when "xboard" is given as an interactive command.
6173 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6174 cps->useSigint = FALSE;
6175 cps->useSigterm = FALSE;
6177 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6178 ParseFeatures(message+8, cps);
6179 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6182 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6183 * want this, I was asked to put it in, and obliged.
6185 if (!strncmp(message, "setboard ", 9)) {
6186 Board initial_position; int i;
6188 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6190 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6191 DisplayError(_("Bad FEN received from engine"), 0);
6194 Reset(FALSE, FALSE);
6195 CopyBoard(boards[0], initial_position);
6196 initialRulePlies = FENrulePlies;
6197 epStatus[0] = FENepStatus;
6198 for( i=0; i<nrCastlingRights; i++ )
6199 castlingRights[0][i] = FENcastlingRights[i];
6200 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6201 else gameMode = MachinePlaysBlack;
6202 DrawPosition(FALSE, boards[currentMove]);
6208 * Look for communication commands
6210 if (!strncmp(message, "telluser ", 9)) {
6211 DisplayNote(message + 9);
6214 if (!strncmp(message, "tellusererror ", 14)) {
6215 DisplayError(message + 14, 0);
6218 if (!strncmp(message, "tellopponent ", 13)) {
6219 if (appData.icsActive) {
6221 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6225 DisplayNote(message + 13);
6229 if (!strncmp(message, "tellothers ", 11)) {
6230 if (appData.icsActive) {
6232 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6238 if (!strncmp(message, "tellall ", 8)) {
6239 if (appData.icsActive) {
6241 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6245 DisplayNote(message + 8);
6249 if (strncmp(message, "warning", 7) == 0) {
6250 /* Undocumented feature, use tellusererror in new code */
6251 DisplayError(message, 0);
6254 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6255 strcpy(realname, cps->tidy);
6256 strcat(realname, " query");
6257 AskQuestion(realname, buf2, buf1, cps->pr);
6260 /* Commands from the engine directly to ICS. We don't allow these to be
6261 * sent until we are logged on. Crafty kibitzes have been known to
6262 * interfere with the login process.
6265 if (!strncmp(message, "tellics ", 8)) {
6266 SendToICS(message + 8);
6270 if (!strncmp(message, "tellicsnoalias ", 15)) {
6271 SendToICS(ics_prefix);
6272 SendToICS(message + 15);
6276 /* The following are for backward compatibility only */
6277 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6278 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6279 SendToICS(ics_prefix);
6285 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6289 * If the move is illegal, cancel it and redraw the board.
6290 * Also deal with other error cases. Matching is rather loose
6291 * here to accommodate engines written before the spec.
6293 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6294 strncmp(message, "Error", 5) == 0) {
6295 if (StrStr(message, "name") ||
6296 StrStr(message, "rating") || StrStr(message, "?") ||
6297 StrStr(message, "result") || StrStr(message, "board") ||
6298 StrStr(message, "bk") || StrStr(message, "computer") ||
6299 StrStr(message, "variant") || StrStr(message, "hint") ||
6300 StrStr(message, "random") || StrStr(message, "depth") ||
6301 StrStr(message, "accepted")) {
6304 if (StrStr(message, "protover")) {
6305 /* Program is responding to input, so it's apparently done
6306 initializing, and this error message indicates it is
6307 protocol version 1. So we don't need to wait any longer
6308 for it to initialize and send feature commands. */
6309 FeatureDone(cps, 1);
6310 cps->protocolVersion = 1;
6313 cps->maybeThinking = FALSE;
6315 if (StrStr(message, "draw")) {
6316 /* Program doesn't have "draw" command */
6317 cps->sendDrawOffers = 0;
6320 if (cps->sendTime != 1 &&
6321 (StrStr(message, "time") || StrStr(message, "otim"))) {
6322 /* Program apparently doesn't have "time" or "otim" command */
6326 if (StrStr(message, "analyze")) {
6327 cps->analysisSupport = FALSE;
6328 cps->analyzing = FALSE;
6330 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6331 DisplayError(buf2, 0);
6334 if (StrStr(message, "(no matching move)st")) {
6335 /* Special kludge for GNU Chess 4 only */
6336 cps->stKludge = TRUE;
6337 SendTimeControl(cps, movesPerSession, timeControl,
6338 timeIncrement, appData.searchDepth,
6342 if (StrStr(message, "(no matching move)sd")) {
6343 /* Special kludge for GNU Chess 4 only */
6344 cps->sdKludge = TRUE;
6345 SendTimeControl(cps, movesPerSession, timeControl,
6346 timeIncrement, appData.searchDepth,
6350 if (!StrStr(message, "llegal")) {
6353 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6354 gameMode == IcsIdle) return;
6355 if (forwardMostMove <= backwardMostMove) return;
6357 /* Following removed: it caused a bug where a real illegal move
6358 message in analyze mored would be ignored. */
6359 if (cps == &first && programStats.ok_to_send == 0) {
6360 /* Bogus message from Crafty responding to "." This filtering
6361 can miss some of the bad messages, but fortunately the bug
6362 is fixed in current Crafty versions, so it doesn't matter. */
6366 if (pausing) PauseEvent();
6367 if (gameMode == PlayFromGameFile) {
6368 /* Stop reading this game file */
6369 gameMode = EditGame;
6372 currentMove = --forwardMostMove;
6373 DisplayMove(currentMove-1); /* before DisplayMoveError */
6375 DisplayBothClocks();
6376 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6377 parseList[currentMove], cps->which);
6378 DisplayMoveError(buf1);
6379 DrawPosition(FALSE, boards[currentMove]);
6381 /* [HGM] illegal-move claim should forfeit game when Xboard */
6382 /* only passes fully legal moves */
6383 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6384 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6385 "False illegal-move claim", GE_XBOARD );
6389 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6390 /* Program has a broken "time" command that
6391 outputs a string not ending in newline.
6397 * If chess program startup fails, exit with an error message.
6398 * Attempts to recover here are futile.
6400 if ((StrStr(message, "unknown host") != NULL)
6401 || (StrStr(message, "No remote directory") != NULL)
6402 || (StrStr(message, "not found") != NULL)
6403 || (StrStr(message, "No such file") != NULL)
6404 || (StrStr(message, "can't alloc") != NULL)
6405 || (StrStr(message, "Permission denied") != NULL)) {
6407 cps->maybeThinking = FALSE;
6408 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6409 cps->which, cps->program, cps->host, message);
6410 RemoveInputSource(cps->isr);
6411 DisplayFatalError(buf1, 0, 1);
6416 * Look for hint output
6418 if (sscanf(message, "Hint: %s", buf1) == 1) {
6419 if (cps == &first && hintRequested) {
6420 hintRequested = FALSE;
6421 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6422 &fromX, &fromY, &toX, &toY, &promoChar)) {
6423 (void) CoordsToAlgebraic(boards[forwardMostMove],
6424 PosFlags(forwardMostMove), EP_UNKNOWN,
6425 fromY, fromX, toY, toX, promoChar, buf1);
6426 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6427 DisplayInformation(buf2);
6429 /* Hint move could not be parsed!? */
6430 snprintf(buf2, sizeof(buf2),
6431 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6433 DisplayError(buf2, 0);
6436 strcpy(lastHint, buf1);
6442 * Ignore other messages if game is not in progress
6444 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6445 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6448 * look for win, lose, draw, or draw offer
6450 if (strncmp(message, "1-0", 3) == 0) {
6451 char *p, *q, *r = "";
6452 p = strchr(message, '{');
6460 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6462 } else if (strncmp(message, "0-1", 3) == 0) {
6463 char *p, *q, *r = "";
6464 p = strchr(message, '{');
6472 /* Kludge for Arasan 4.1 bug */
6473 if (strcmp(r, "Black resigns") == 0) {
6474 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6477 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6479 } else if (strncmp(message, "1/2", 3) == 0) {
6480 char *p, *q, *r = "";
6481 p = strchr(message, '{');
6490 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6493 } else if (strncmp(message, "White resign", 12) == 0) {
6494 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6496 } else if (strncmp(message, "Black resign", 12) == 0) {
6497 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6499 } else if (strncmp(message, "White matches", 13) == 0 ||
6500 strncmp(message, "Black matches", 13) == 0 ) {
6501 /* [HGM] ignore GNUShogi noises */
6503 } else if (strncmp(message, "White", 5) == 0 &&
6504 message[5] != '(' &&
6505 StrStr(message, "Black") == NULL) {
6506 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6508 } else if (strncmp(message, "Black", 5) == 0 &&
6509 message[5] != '(') {
6510 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6512 } else if (strcmp(message, "resign") == 0 ||
6513 strcmp(message, "computer resigns") == 0) {
6515 case MachinePlaysBlack:
6516 case IcsPlayingBlack:
6517 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6519 case MachinePlaysWhite:
6520 case IcsPlayingWhite:
6521 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6523 case TwoMachinesPlay:
6524 if (cps->twoMachinesColor[0] == 'w')
6525 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6527 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6534 } else if (strncmp(message, "opponent mates", 14) == 0) {
6536 case MachinePlaysBlack:
6537 case IcsPlayingBlack:
6538 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6540 case MachinePlaysWhite:
6541 case IcsPlayingWhite:
6542 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6544 case TwoMachinesPlay:
6545 if (cps->twoMachinesColor[0] == 'w')
6546 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6548 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6555 } else if (strncmp(message, "computer mates", 14) == 0) {
6557 case MachinePlaysBlack:
6558 case IcsPlayingBlack:
6559 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6561 case MachinePlaysWhite:
6562 case IcsPlayingWhite:
6563 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6565 case TwoMachinesPlay:
6566 if (cps->twoMachinesColor[0] == 'w')
6567 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6569 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6576 } else if (strncmp(message, "checkmate", 9) == 0) {
6577 if (WhiteOnMove(forwardMostMove)) {
6578 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6580 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6583 } else if (strstr(message, "Draw") != NULL ||
6584 strstr(message, "game is a draw") != NULL) {
6585 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6587 } else if (strstr(message, "offer") != NULL &&
6588 strstr(message, "draw") != NULL) {
6590 if (appData.zippyPlay && first.initDone) {
6591 /* Relay offer to ICS */
6592 SendToICS(ics_prefix);
6593 SendToICS("draw\n");
6596 cps->offeredDraw = 2; /* valid until this engine moves twice */
6597 if (gameMode == TwoMachinesPlay) {
6598 if (cps->other->offeredDraw) {
6599 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6600 /* [HGM] in two-machine mode we delay relaying draw offer */
6601 /* until after we also have move, to see if it is really claim */
6605 if (cps->other->sendDrawOffers) {
6606 SendToProgram("draw\n", cps->other);
6610 } else if (gameMode == MachinePlaysWhite ||
6611 gameMode == MachinePlaysBlack) {
6612 if (userOfferedDraw) {
6613 DisplayInformation(_("Machine accepts your draw offer"));
6614 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6616 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6623 * Look for thinking output
6625 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6626 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6628 int plylev, mvleft, mvtot, curscore, time;
6629 char mvname[MOVE_LEN];
6633 int prefixHint = FALSE;
6634 mvname[0] = NULLCHAR;
6637 case MachinePlaysBlack:
6638 case IcsPlayingBlack:
6639 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6641 case MachinePlaysWhite:
6642 case IcsPlayingWhite:
6643 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6648 case IcsObserving: /* [DM] icsEngineAnalyze */
6649 if (!appData.icsEngineAnalyze) ignore = TRUE;
6651 case TwoMachinesPlay:
6652 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6663 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6664 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6666 if (plyext != ' ' && plyext != '\t') {
6670 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6671 if( cps->scoreIsAbsolute &&
6672 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6674 curscore = -curscore;
6678 programStats.depth = plylev;
6679 programStats.nodes = nodes;
6680 programStats.time = time;
6681 programStats.score = curscore;
6682 programStats.got_only_move = 0;
6684 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6687 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6688 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6689 if(WhiteOnMove(forwardMostMove))
6690 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6691 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6694 /* Buffer overflow protection */
6695 if (buf1[0] != NULLCHAR) {
6696 if (strlen(buf1) >= sizeof(programStats.movelist)
6697 && appData.debugMode) {
6699 "PV is too long; using the first %d bytes.\n",
6700 sizeof(programStats.movelist) - 1);
6703 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6705 sprintf(programStats.movelist, " no PV\n");
6708 if (programStats.seen_stat) {
6709 programStats.ok_to_send = 1;
6712 if (strchr(programStats.movelist, '(') != NULL) {
6713 programStats.line_is_book = 1;
6714 programStats.nr_moves = 0;
6715 programStats.moves_left = 0;
6717 programStats.line_is_book = 0;
6720 SendProgramStatsToFrontend( cps, &programStats );
6723 [AS] Protect the thinkOutput buffer from overflow... this
6724 is only useful if buf1 hasn't overflowed first!
6726 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6728 (gameMode == TwoMachinesPlay ?
6729 ToUpper(cps->twoMachinesColor[0]) : ' '),
6730 ((double) curscore) / 100.0,
6731 prefixHint ? lastHint : "",
6732 prefixHint ? " " : "" );
6734 if( buf1[0] != NULLCHAR ) {
6735 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6737 if( strlen(buf1) > max_len ) {
6738 if( appData.debugMode) {
6739 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6741 buf1[max_len+1] = '\0';
6744 strcat( thinkOutput, buf1 );
6747 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6748 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6749 DisplayMove(currentMove - 1);
6754 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6755 /* crafty (9.25+) says "(only move) <move>"
6756 * if there is only 1 legal move
6758 sscanf(p, "(only move) %s", buf1);
6759 sprintf(thinkOutput, "%s (only move)", buf1);
6760 sprintf(programStats.movelist, "%s (only move)", buf1);
6761 programStats.depth = 1;
6762 programStats.nr_moves = 1;
6763 programStats.moves_left = 1;
6764 programStats.nodes = 1;
6765 programStats.time = 1;
6766 programStats.got_only_move = 1;
6768 /* Not really, but we also use this member to
6769 mean "line isn't going to change" (Crafty
6770 isn't searching, so stats won't change) */
6771 programStats.line_is_book = 1;
6773 SendProgramStatsToFrontend( cps, &programStats );
6775 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6776 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6777 DisplayMove(currentMove - 1);
6781 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6782 &time, &nodes, &plylev, &mvleft,
6783 &mvtot, mvname) >= 5) {
6784 /* The stat01: line is from Crafty (9.29+) in response
6785 to the "." command */
6786 programStats.seen_stat = 1;
6787 cps->maybeThinking = TRUE;
6789 if (programStats.got_only_move || !appData.periodicUpdates)
6792 programStats.depth = plylev;
6793 programStats.time = time;
6794 programStats.nodes = nodes;
6795 programStats.moves_left = mvleft;
6796 programStats.nr_moves = mvtot;
6797 strcpy(programStats.move_name, mvname);
6798 programStats.ok_to_send = 1;
6799 programStats.movelist[0] = '\0';
6801 SendProgramStatsToFrontend( cps, &programStats );
6806 } else if (strncmp(message,"++",2) == 0) {
6807 /* Crafty 9.29+ outputs this */
6808 programStats.got_fail = 2;
6811 } else if (strncmp(message,"--",2) == 0) {
6812 /* Crafty 9.29+ outputs this */
6813 programStats.got_fail = 1;
6816 } else if (thinkOutput[0] != NULLCHAR &&
6817 strncmp(message, " ", 4) == 0) {
6818 unsigned message_len;
6821 while (*p && *p == ' ') p++;
6823 message_len = strlen( p );
6825 /* [AS] Avoid buffer overflow */
6826 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6827 strcat(thinkOutput, " ");
6828 strcat(thinkOutput, p);
6831 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6832 strcat(programStats.movelist, " ");
6833 strcat(programStats.movelist, p);
6836 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6837 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6838 DisplayMove(currentMove - 1);
6847 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6848 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6850 ChessProgramStats cpstats;
6852 if (plyext != ' ' && plyext != '\t') {
6856 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6857 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6858 curscore = -curscore;
6861 cpstats.depth = plylev;
6862 cpstats.nodes = nodes;
6863 cpstats.time = time;
6864 cpstats.score = curscore;
6865 cpstats.got_only_move = 0;
6866 cpstats.movelist[0] = '\0';
6868 if (buf1[0] != NULLCHAR) {
6869 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6872 cpstats.ok_to_send = 0;
6873 cpstats.line_is_book = 0;
6874 cpstats.nr_moves = 0;
6875 cpstats.moves_left = 0;
6877 SendProgramStatsToFrontend( cps, &cpstats );
6884 /* Parse a game score from the character string "game", and
6885 record it as the history of the current game. The game
6886 score is NOT assumed to start from the standard position.
6887 The display is not updated in any way.
6890 ParseGameHistory(game)
6894 int fromX, fromY, toX, toY, boardIndex;
6899 if (appData.debugMode)
6900 fprintf(debugFP, "Parsing game history: %s\n", game);
6902 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6903 gameInfo.site = StrSave(appData.icsHost);
6904 gameInfo.date = PGNDate();
6905 gameInfo.round = StrSave("-");
6907 /* Parse out names of players */
6908 while (*game == ' ') game++;
6910 while (*game != ' ') *p++ = *game++;
6912 gameInfo.white = StrSave(buf);
6913 while (*game == ' ') game++;
6915 while (*game != ' ' && *game != '\n') *p++ = *game++;
6917 gameInfo.black = StrSave(buf);
6920 boardIndex = blackPlaysFirst ? 1 : 0;
6923 yyboardindex = boardIndex;
6924 moveType = (ChessMove) yylex();
6926 case IllegalMove: /* maybe suicide chess, etc. */
6927 if (appData.debugMode) {
6928 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6929 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6930 setbuf(debugFP, NULL);
6932 case WhitePromotionChancellor:
6933 case BlackPromotionChancellor:
6934 case WhitePromotionArchbishop:
6935 case BlackPromotionArchbishop:
6936 case WhitePromotionQueen:
6937 case BlackPromotionQueen:
6938 case WhitePromotionRook:
6939 case BlackPromotionRook:
6940 case WhitePromotionBishop:
6941 case BlackPromotionBishop:
6942 case WhitePromotionKnight:
6943 case BlackPromotionKnight:
6944 case WhitePromotionKing:
6945 case BlackPromotionKing:
6947 case WhiteCapturesEnPassant:
6948 case BlackCapturesEnPassant:
6949 case WhiteKingSideCastle:
6950 case WhiteQueenSideCastle:
6951 case BlackKingSideCastle:
6952 case BlackQueenSideCastle:
6953 case WhiteKingSideCastleWild:
6954 case WhiteQueenSideCastleWild:
6955 case BlackKingSideCastleWild:
6956 case BlackQueenSideCastleWild:
6958 case WhiteHSideCastleFR:
6959 case WhiteASideCastleFR:
6960 case BlackHSideCastleFR:
6961 case BlackASideCastleFR:
6963 fromX = currentMoveString[0] - AAA;
6964 fromY = currentMoveString[1] - ONE;
6965 toX = currentMoveString[2] - AAA;
6966 toY = currentMoveString[3] - ONE;
6967 promoChar = currentMoveString[4];
6971 fromX = moveType == WhiteDrop ?
6972 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6973 (int) CharToPiece(ToLower(currentMoveString[0]));
6975 toX = currentMoveString[2] - AAA;
6976 toY = currentMoveString[3] - ONE;
6977 promoChar = NULLCHAR;
6981 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6982 if (appData.debugMode) {
6983 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6984 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6985 setbuf(debugFP, NULL);
6987 DisplayError(buf, 0);
6989 case ImpossibleMove:
6991 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6992 if (appData.debugMode) {
6993 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6994 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6995 setbuf(debugFP, NULL);
6997 DisplayError(buf, 0);
6999 case (ChessMove) 0: /* end of file */
7000 if (boardIndex < backwardMostMove) {
7001 /* Oops, gap. How did that happen? */
7002 DisplayError(_("Gap in move list"), 0);
7005 backwardMostMove = blackPlaysFirst ? 1 : 0;
7006 if (boardIndex > forwardMostMove) {
7007 forwardMostMove = boardIndex;
7011 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7012 strcat(parseList[boardIndex-1], " ");
7013 strcat(parseList[boardIndex-1], yy_text);
7025 case GameUnfinished:
7026 if (gameMode == IcsExamining) {
7027 if (boardIndex < backwardMostMove) {
7028 /* Oops, gap. How did that happen? */
7031 backwardMostMove = blackPlaysFirst ? 1 : 0;
7034 gameInfo.result = moveType;
7035 p = strchr(yy_text, '{');
7036 if (p == NULL) p = strchr(yy_text, '(');
7039 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7041 q = strchr(p, *p == '{' ? '}' : ')');
7042 if (q != NULL) *q = NULLCHAR;
7045 gameInfo.resultDetails = StrSave(p);
7048 if (boardIndex >= forwardMostMove &&
7049 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7050 backwardMostMove = blackPlaysFirst ? 1 : 0;
7053 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7054 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7055 parseList[boardIndex]);
7056 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7057 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7058 /* currentMoveString is set as a side-effect of yylex */
7059 strcpy(moveList[boardIndex], currentMoveString);
7060 strcat(moveList[boardIndex], "\n");
7062 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7063 castlingRights[boardIndex], &epStatus[boardIndex]);
7064 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7065 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7071 if(gameInfo.variant != VariantShogi)
7072 strcat(parseList[boardIndex - 1], "+");
7076 strcat(parseList[boardIndex - 1], "#");
7083 /* Apply a move to the given board */
7085 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7086 int fromX, fromY, toX, toY;
7092 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7094 /* [HGM] compute & store e.p. status and castling rights for new position */
7095 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7098 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7102 if( board[toY][toX] != EmptySquare )
7105 if( board[fromY][fromX] == WhitePawn ) {
7106 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7109 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7110 gameInfo.variant != VariantBerolina || toX < fromX)
7111 *ep = toX | berolina;
7112 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7113 gameInfo.variant != VariantBerolina || toX > fromX)
7117 if( board[fromY][fromX] == BlackPawn ) {
7118 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7120 if( toY-fromY== -2) {
7121 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7122 gameInfo.variant != VariantBerolina || toX < fromX)
7123 *ep = toX | berolina;
7124 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7125 gameInfo.variant != VariantBerolina || toX > fromX)
7130 for(i=0; i<nrCastlingRights; i++) {
7131 if(castling[i] == fromX && castlingRank[i] == fromY ||
7132 castling[i] == toX && castlingRank[i] == toY
7133 ) castling[i] = -1; // revoke for moved or captured piece
7138 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7139 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7140 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7142 if (fromX == toX && fromY == toY) return;
7144 if (fromY == DROP_RANK) {
7146 piece = board[toY][toX] = (ChessSquare) fromX;
7148 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7149 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7150 if(gameInfo.variant == VariantKnightmate)
7151 king += (int) WhiteUnicorn - (int) WhiteKing;
7153 /* Code added by Tord: */
7154 /* FRC castling assumed when king captures friendly rook. */
7155 if (board[fromY][fromX] == WhiteKing &&
7156 board[toY][toX] == WhiteRook) {
7157 board[fromY][fromX] = EmptySquare;
7158 board[toY][toX] = EmptySquare;
7160 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7162 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7164 } else if (board[fromY][fromX] == BlackKing &&
7165 board[toY][toX] == BlackRook) {
7166 board[fromY][fromX] = EmptySquare;
7167 board[toY][toX] = EmptySquare;
7169 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7171 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7173 /* End of code added by Tord */
7175 } else if (board[fromY][fromX] == king
7176 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7177 && toY == fromY && toX > fromX+1) {
7178 board[fromY][fromX] = EmptySquare;
7179 board[toY][toX] = king;
7180 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7181 board[fromY][BOARD_RGHT-1] = EmptySquare;
7182 } else if (board[fromY][fromX] == king
7183 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7184 && toY == fromY && toX < fromX-1) {
7185 board[fromY][fromX] = EmptySquare;
7186 board[toY][toX] = king;
7187 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7188 board[fromY][BOARD_LEFT] = EmptySquare;
7189 } else if (board[fromY][fromX] == WhitePawn
7190 && toY == BOARD_HEIGHT-1
7191 && gameInfo.variant != VariantXiangqi
7193 /* white pawn promotion */
7194 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7195 if (board[toY][toX] == EmptySquare) {
7196 board[toY][toX] = WhiteQueen;
7198 if(gameInfo.variant==VariantBughouse ||
7199 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7200 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7201 board[fromY][fromX] = EmptySquare;
7202 } else if ((fromY == BOARD_HEIGHT-4)
7204 && gameInfo.variant != VariantXiangqi
7205 && gameInfo.variant != VariantBerolina
7206 && (board[fromY][fromX] == WhitePawn)
7207 && (board[toY][toX] == EmptySquare)) {
7208 board[fromY][fromX] = EmptySquare;
7209 board[toY][toX] = WhitePawn;
7210 captured = board[toY - 1][toX];
7211 board[toY - 1][toX] = EmptySquare;
7212 } else if ((fromY == BOARD_HEIGHT-4)
7214 && gameInfo.variant == VariantBerolina
7215 && (board[fromY][fromX] == WhitePawn)
7216 && (board[toY][toX] == EmptySquare)) {
7217 board[fromY][fromX] = EmptySquare;
7218 board[toY][toX] = WhitePawn;
7219 if(oldEP & EP_BEROLIN_A) {
7220 captured = board[fromY][fromX-1];
7221 board[fromY][fromX-1] = EmptySquare;
7222 }else{ captured = board[fromY][fromX+1];
7223 board[fromY][fromX+1] = EmptySquare;
7225 } else if (board[fromY][fromX] == king
7226 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7227 && toY == fromY && toX > fromX+1) {
7228 board[fromY][fromX] = EmptySquare;
7229 board[toY][toX] = king;
7230 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7231 board[fromY][BOARD_RGHT-1] = EmptySquare;
7232 } else if (board[fromY][fromX] == king
7233 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7234 && toY == fromY && toX < fromX-1) {
7235 board[fromY][fromX] = EmptySquare;
7236 board[toY][toX] = king;
7237 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7238 board[fromY][BOARD_LEFT] = EmptySquare;
7239 } else if (fromY == 7 && fromX == 3
7240 && board[fromY][fromX] == BlackKing
7241 && toY == 7 && toX == 5) {
7242 board[fromY][fromX] = EmptySquare;
7243 board[toY][toX] = BlackKing;
7244 board[fromY][7] = EmptySquare;
7245 board[toY][4] = BlackRook;
7246 } else if (fromY == 7 && fromX == 3
7247 && board[fromY][fromX] == BlackKing
7248 && toY == 7 && toX == 1) {
7249 board[fromY][fromX] = EmptySquare;
7250 board[toY][toX] = BlackKing;
7251 board[fromY][0] = EmptySquare;
7252 board[toY][2] = BlackRook;
7253 } else if (board[fromY][fromX] == BlackPawn
7255 && gameInfo.variant != VariantXiangqi
7257 /* black pawn promotion */
7258 board[0][toX] = CharToPiece(ToLower(promoChar));
7259 if (board[0][toX] == EmptySquare) {
7260 board[0][toX] = BlackQueen;
7262 if(gameInfo.variant==VariantBughouse ||
7263 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7264 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7265 board[fromY][fromX] = EmptySquare;
7266 } else if ((fromY == 3)
7268 && gameInfo.variant != VariantXiangqi
7269 && gameInfo.variant != VariantBerolina
7270 && (board[fromY][fromX] == BlackPawn)
7271 && (board[toY][toX] == EmptySquare)) {
7272 board[fromY][fromX] = EmptySquare;
7273 board[toY][toX] = BlackPawn;
7274 captured = board[toY + 1][toX];
7275 board[toY + 1][toX] = EmptySquare;
7276 } else if ((fromY == 3)
7278 && gameInfo.variant == VariantBerolina
7279 && (board[fromY][fromX] == BlackPawn)
7280 && (board[toY][toX] == EmptySquare)) {
7281 board[fromY][fromX] = EmptySquare;
7282 board[toY][toX] = BlackPawn;
7283 if(oldEP & EP_BEROLIN_A) {
7284 captured = board[fromY][fromX-1];
7285 board[fromY][fromX-1] = EmptySquare;
7286 }else{ captured = board[fromY][fromX+1];
7287 board[fromY][fromX+1] = EmptySquare;
7290 board[toY][toX] = board[fromY][fromX];
7291 board[fromY][fromX] = EmptySquare;
7294 /* [HGM] now we promote for Shogi, if needed */
7295 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7296 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7299 if (gameInfo.holdingsWidth != 0) {
7301 /* !!A lot more code needs to be written to support holdings */
7302 /* [HGM] OK, so I have written it. Holdings are stored in the */
7303 /* penultimate board files, so they are automaticlly stored */
7304 /* in the game history. */
7305 if (fromY == DROP_RANK) {
7306 /* Delete from holdings, by decreasing count */
7307 /* and erasing image if necessary */
7309 if(p < (int) BlackPawn) { /* white drop */
7310 p -= (int)WhitePawn;
7311 if(p >= gameInfo.holdingsSize) p = 0;
7312 if(--board[p][BOARD_WIDTH-2] == 0)
7313 board[p][BOARD_WIDTH-1] = EmptySquare;
7314 } else { /* black drop */
7315 p -= (int)BlackPawn;
7316 if(p >= gameInfo.holdingsSize) p = 0;
7317 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7318 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7321 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7322 && gameInfo.variant != VariantBughouse ) {
7323 /* [HGM] holdings: Add to holdings, if holdings exist */
7324 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7325 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7326 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7329 if (p >= (int) BlackPawn) {
7330 p -= (int)BlackPawn;
7331 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7332 /* in Shogi restore piece to its original first */
7333 captured = (ChessSquare) (DEMOTED captured);
7336 p = PieceToNumber((ChessSquare)p);
7337 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7338 board[p][BOARD_WIDTH-2]++;
7339 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7341 p -= (int)WhitePawn;
7342 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7343 captured = (ChessSquare) (DEMOTED captured);
7346 p = PieceToNumber((ChessSquare)p);
7347 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7348 board[BOARD_HEIGHT-1-p][1]++;
7349 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7353 } else if (gameInfo.variant == VariantAtomic) {
7354 if (captured != EmptySquare) {
7356 for (y = toY-1; y <= toY+1; y++) {
7357 for (x = toX-1; x <= toX+1; x++) {
7358 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7359 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7360 board[y][x] = EmptySquare;
7364 board[toY][toX] = EmptySquare;
7367 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7368 /* [HGM] Shogi promotions */
7369 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7372 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7373 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7374 // [HGM] superchess: take promotion piece out of holdings
7375 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7376 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7377 if(!--board[k][BOARD_WIDTH-2])
7378 board[k][BOARD_WIDTH-1] = EmptySquare;
7380 if(!--board[BOARD_HEIGHT-1-k][1])
7381 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7387 /* Updates forwardMostMove */
7389 MakeMove(fromX, fromY, toX, toY, promoChar)
7390 int fromX, fromY, toX, toY;
7393 // forwardMostMove++; // [HGM] bare: moved downstream
7395 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7396 int timeLeft; static int lastLoadFlag=0; int king, piece;
7397 piece = boards[forwardMostMove][fromY][fromX];
7398 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7399 if(gameInfo.variant == VariantKnightmate)
7400 king += (int) WhiteUnicorn - (int) WhiteKing;
7401 if(forwardMostMove == 0) {
7403 fprintf(serverMoves, "%s;", second.tidy);
7404 fprintf(serverMoves, "%s;", first.tidy);
7405 if(!blackPlaysFirst)
7406 fprintf(serverMoves, "%s;", second.tidy);
7407 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7408 lastLoadFlag = loadFlag;
7410 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7411 // print castling suffix
7412 if( toY == fromY && piece == king ) {
7414 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7416 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7419 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7420 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7421 boards[forwardMostMove][toY][toX] == EmptySquare
7423 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7425 if(promoChar != NULLCHAR)
7426 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7428 fprintf(serverMoves, "/%d/%d",
7429 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7430 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7431 else timeLeft = blackTimeRemaining/1000;
7432 fprintf(serverMoves, "/%d", timeLeft);
7434 fflush(serverMoves);
7437 if (forwardMostMove+1 >= MAX_MOVES) {
7438 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7443 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7444 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7445 if (commentList[forwardMostMove+1] != NULL) {
7446 free(commentList[forwardMostMove+1]);
7447 commentList[forwardMostMove+1] = NULL;
7449 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7450 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7451 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7452 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7453 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7454 gameInfo.result = GameUnfinished;
7455 if (gameInfo.resultDetails != NULL) {
7456 free(gameInfo.resultDetails);
7457 gameInfo.resultDetails = NULL;
7459 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7460 moveList[forwardMostMove - 1]);
7461 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7462 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7463 fromY, fromX, toY, toX, promoChar,
7464 parseList[forwardMostMove - 1]);
7465 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7466 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7467 castlingRights[forwardMostMove]) ) {
7473 if(gameInfo.variant != VariantShogi)
7474 strcat(parseList[forwardMostMove - 1], "+");
7478 strcat(parseList[forwardMostMove - 1], "#");
7481 if (appData.debugMode) {
7482 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7487 /* Updates currentMove if not pausing */
7489 ShowMove(fromX, fromY, toX, toY)
7491 int instant = (gameMode == PlayFromGameFile) ?
7492 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7494 if(appData.noGUI) return;
7496 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7500 if (forwardMostMove == currentMove + 1)
7503 // AnimateMove(boards[forwardMostMove - 1],
7504 // fromX, fromY, toX, toY);
7506 if (appData.highlightLastMove)
7508 SetHighlights(fromX, fromY, toX, toY);
7511 currentMove = forwardMostMove;
7514 if (instant) return;
7516 DisplayMove(currentMove - 1);
7517 DrawPosition(FALSE, boards[currentMove]);
7518 DisplayBothClocks();
7519 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7524 void SendEgtPath(ChessProgramState *cps)
7525 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7526 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7528 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7531 char c, *q = name+1, *r, *s;
7533 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7534 while(*p && *p != ',') *q++ = *p++;
7536 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7537 strcmp(name, ",nalimov:") == 0 ) {
7538 // take nalimov path from the menu-changeable option first, if it is defined
7539 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7540 SendToProgram(buf,cps); // send egtbpath command for nalimov
7542 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7543 (s = StrStr(appData.egtFormats, name)) != NULL) {
7544 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7545 s = r = StrStr(s, ":") + 1; // beginning of path info
7546 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7547 c = *r; *r = 0; // temporarily null-terminate path info
7548 *--q = 0; // strip of trailig ':' from name
7549 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7551 SendToProgram(buf,cps); // send egtbpath command for this format
7553 if(*p == ',') p++; // read away comma to position for next format name
7558 InitChessProgram(cps, setup)
7559 ChessProgramState *cps;
7560 int setup; /* [HGM] needed to setup FRC opening position */
7562 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7563 if (appData.noChessProgram) return;
7564 hintRequested = FALSE;
7565 bookRequested = FALSE;
7567 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7568 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7569 if(cps->memSize) { /* [HGM] memory */
7570 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7571 SendToProgram(buf, cps);
7573 SendEgtPath(cps); /* [HGM] EGT */
7574 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7575 sprintf(buf, "cores %d\n", appData.smpCores);
7576 SendToProgram(buf, cps);
7579 SendToProgram(cps->initString, cps);
7580 if (gameInfo.variant != VariantNormal &&
7581 gameInfo.variant != VariantLoadable
7582 /* [HGM] also send variant if board size non-standard */
7583 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7585 char *v = VariantName(gameInfo.variant);
7586 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7587 /* [HGM] in protocol 1 we have to assume all variants valid */
7588 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7589 DisplayFatalError(buf, 0, 1);
7593 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7594 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7595 if( gameInfo.variant == VariantXiangqi )
7596 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7597 if( gameInfo.variant == VariantShogi )
7598 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7599 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7600 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7601 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7602 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7603 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7604 if( gameInfo.variant == VariantCourier )
7605 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7606 if( gameInfo.variant == VariantSuper )
7607 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7608 if( gameInfo.variant == VariantGreat )
7609 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7612 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7613 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7614 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7615 if(StrStr(cps->variants, b) == NULL) {
7616 // specific sized variant not known, check if general sizing allowed
7617 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7618 if(StrStr(cps->variants, "boardsize") == NULL) {
7619 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7620 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7621 DisplayFatalError(buf, 0, 1);
7624 /* [HGM] here we really should compare with the maximum supported board size */
7627 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7628 sprintf(buf, "variant %s\n", b);
7629 SendToProgram(buf, cps);
7631 currentlyInitializedVariant = gameInfo.variant;
7633 /* [HGM] send opening position in FRC to first engine */
7635 SendToProgram("force\n", cps);
7637 /* engine is now in force mode! Set flag to wake it up after first move. */
7638 setboardSpoiledMachineBlack = 1;
7642 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7643 SendToProgram(buf, cps);
7645 cps->maybeThinking = FALSE;
7646 cps->offeredDraw = 0;
7647 if (!appData.icsActive) {
7648 SendTimeControl(cps, movesPerSession, timeControl,
7649 timeIncrement, appData.searchDepth,
7652 if (appData.showThinking
7653 // [HGM] thinking: four options require thinking output to be sent
7654 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7656 SendToProgram("post\n", cps);
7658 SendToProgram("hard\n", cps);
7659 if (!appData.ponderNextMove) {
7660 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7661 it without being sure what state we are in first. "hard"
7662 is not a toggle, so that one is OK.
7664 SendToProgram("easy\n", cps);
7667 sprintf(buf, "ping %d\n", ++cps->lastPing);
7668 SendToProgram(buf, cps);
7670 cps->initDone = TRUE;
7675 StartChessProgram(cps)
7676 ChessProgramState *cps;
7681 if (appData.noChessProgram) return;
7682 cps->initDone = FALSE;
7684 if (strcmp(cps->host, "localhost") == 0) {
7685 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7686 } else if (*appData.remoteShell == NULLCHAR) {
7687 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7689 if (*appData.remoteUser == NULLCHAR) {
7690 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7693 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7694 cps->host, appData.remoteUser, cps->program);
7696 err = StartChildProcess(buf, "", &cps->pr);
7700 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7701 DisplayFatalError(buf, err, 1);
7707 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7708 if (cps->protocolVersion > 1) {
7709 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7710 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7711 cps->comboCnt = 0; // and values of combo boxes
7712 SendToProgram(buf, cps);
7714 SendToProgram("xboard\n", cps);
7720 TwoMachinesEventIfReady P((void))
7722 if (first.lastPing != first.lastPong) {
7723 DisplayMessage("", _("Waiting for first chess program"));
7724 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7727 if (second.lastPing != second.lastPong) {
7728 DisplayMessage("", _("Waiting for second chess program"));
7729 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7737 NextMatchGame P((void))
7739 int index; /* [HGM] autoinc: step lod index during match */
7741 if (*appData.loadGameFile != NULLCHAR) {
7742 index = appData.loadGameIndex;
7743 if(index < 0) { // [HGM] autoinc
7744 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7745 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7747 LoadGameFromFile(appData.loadGameFile,
7749 appData.loadGameFile, FALSE);
7750 } else if (*appData.loadPositionFile != NULLCHAR) {
7751 index = appData.loadPositionIndex;
7752 if(index < 0) { // [HGM] autoinc
7753 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7754 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7756 LoadPositionFromFile(appData.loadPositionFile,
7758 appData.loadPositionFile);
7760 TwoMachinesEventIfReady();
7763 void UserAdjudicationEvent( int result )
7765 ChessMove gameResult = GameIsDrawn;
7768 gameResult = WhiteWins;
7770 else if( result < 0 ) {
7771 gameResult = BlackWins;
7774 if( gameMode == TwoMachinesPlay ) {
7775 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7780 // [HGM] save: calculate checksum of game to make games easily identifiable
7781 int StringCheckSum(char *s)
7784 if(s==NULL) return 0;
7785 while(*s) i = i*259 + *s++;
7792 for(i=backwardMostMove; i<forwardMostMove; i++) {
7793 sum += pvInfoList[i].depth;
7794 sum += StringCheckSum(parseList[i]);
7795 sum += StringCheckSum(commentList[i]);
7798 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7799 return sum + StringCheckSum(commentList[i]);
7800 } // end of save patch
7803 GameEnds(result, resultDetails, whosays)
7805 char *resultDetails;
7808 GameMode nextGameMode;
7812 if(endingGame) return; /* [HGM] crash: forbid recursion */
7815 if (appData.debugMode) {
7816 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7817 result, resultDetails ? resultDetails : "(null)", whosays);
7820 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7821 /* If we are playing on ICS, the server decides when the
7822 game is over, but the engine can offer to draw, claim
7826 if (appData.zippyPlay && first.initDone) {
7827 if (result == GameIsDrawn) {
7828 /* In case draw still needs to be claimed */
7829 SendToICS(ics_prefix);
7830 SendToICS("draw\n");
7831 } else if (StrCaseStr(resultDetails, "resign")) {
7832 SendToICS(ics_prefix);
7833 SendToICS("resign\n");
7837 endingGame = 0; /* [HGM] crash */
7841 /* If we're loading the game from a file, stop */
7842 if (whosays == GE_FILE) {
7843 (void) StopLoadGameTimer();
7847 /* Cancel draw offers */
7848 first.offeredDraw = second.offeredDraw = 0;
7850 /* If this is an ICS game, only ICS can really say it's done;
7851 if not, anyone can. */
7852 isIcsGame = (gameMode == IcsPlayingWhite ||
7853 gameMode == IcsPlayingBlack ||
7854 gameMode == IcsObserving ||
7855 gameMode == IcsExamining);
7857 if (!isIcsGame || whosays == GE_ICS) {
7858 /* OK -- not an ICS game, or ICS said it was done */
7860 if (!isIcsGame && !appData.noChessProgram)
7861 SetUserThinkingEnables();
7863 /* [HGM] if a machine claims the game end we verify this claim */
7864 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7865 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7867 ChessMove trueResult = (ChessMove) -1;
7869 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7870 first.twoMachinesColor[0] :
7871 second.twoMachinesColor[0] ;
7873 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7874 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7875 /* [HGM] verify: engine mate claims accepted if they were flagged */
7876 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7878 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7879 /* [HGM] verify: engine mate claims accepted if they were flagged */
7880 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7882 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7883 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7886 // now verify win claims, but not in drop games, as we don't understand those yet
7887 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7888 || gameInfo.variant == VariantGreat) &&
7889 (result == WhiteWins && claimer == 'w' ||
7890 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7891 if (appData.debugMode) {
7892 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7893 result, epStatus[forwardMostMove], forwardMostMove);
7895 if(result != trueResult) {
7896 sprintf(buf, "False win claim: '%s'", resultDetails);
7897 result = claimer == 'w' ? BlackWins : WhiteWins;
7898 resultDetails = buf;
7901 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7902 && (forwardMostMove <= backwardMostMove ||
7903 epStatus[forwardMostMove-1] > EP_DRAWS ||
7904 (claimer=='b')==(forwardMostMove&1))
7906 /* [HGM] verify: draws that were not flagged are false claims */
7907 sprintf(buf, "False draw claim: '%s'", resultDetails);
7908 result = claimer == 'w' ? BlackWins : WhiteWins;
7909 resultDetails = buf;
7911 /* (Claiming a loss is accepted no questions asked!) */
7914 /* [HGM] bare: don't allow bare King to win */
7915 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7916 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7917 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7918 && result != GameIsDrawn)
7919 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7920 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7921 int p = (int)boards[forwardMostMove][i][j] - color;
7922 if(p >= 0 && p <= (int)WhiteKing) k++;
7924 if (appData.debugMode) {
7925 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7926 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7929 result = GameIsDrawn;
7930 sprintf(buf, "%s but bare king", resultDetails);
7931 resultDetails = buf;
7936 if(serverMoves != NULL && !loadFlag) { char c = '=';
7937 if(result==WhiteWins) c = '+';
7938 if(result==BlackWins) c = '-';
7939 if(resultDetails != NULL)
7940 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7942 if (resultDetails != NULL) {
7943 gameInfo.result = result;
7944 gameInfo.resultDetails = StrSave(resultDetails);
7946 /* display last move only if game was not loaded from file */
7947 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7948 DisplayMove(currentMove - 1);
7950 if (forwardMostMove != 0) {
7951 if (gameMode != PlayFromGameFile && gameMode != EditGame
7952 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7954 if (*appData.saveGameFile != NULLCHAR) {
7955 SaveGameToFile(appData.saveGameFile, TRUE);
7956 } else if (appData.autoSaveGames) {
7959 if (*appData.savePositionFile != NULLCHAR) {
7960 SavePositionToFile(appData.savePositionFile);
7965 /* Tell program how game ended in case it is learning */
7966 /* [HGM] Moved this to after saving the PGN, just in case */
7967 /* engine died and we got here through time loss. In that */
7968 /* case we will get a fatal error writing the pipe, which */
7969 /* would otherwise lose us the PGN. */
7970 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7971 /* output during GameEnds should never be fatal anymore */
7972 if (gameMode == MachinePlaysWhite ||
7973 gameMode == MachinePlaysBlack ||
7974 gameMode == TwoMachinesPlay ||
7975 gameMode == IcsPlayingWhite ||
7976 gameMode == IcsPlayingBlack ||
7977 gameMode == BeginningOfGame) {
7979 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7981 if (first.pr != NoProc) {
7982 SendToProgram(buf, &first);
7984 if (second.pr != NoProc &&
7985 gameMode == TwoMachinesPlay) {
7986 SendToProgram(buf, &second);
7991 if (appData.icsActive) {
7992 if (appData.quietPlay &&
7993 (gameMode == IcsPlayingWhite ||
7994 gameMode == IcsPlayingBlack)) {
7995 SendToICS(ics_prefix);
7996 SendToICS("set shout 1\n");
7998 nextGameMode = IcsIdle;
7999 ics_user_moved = FALSE;
8000 /* clean up premove. It's ugly when the game has ended and the
8001 * premove highlights are still on the board.
8005 ClearPremoveHighlights();
8006 DrawPosition(FALSE, boards[currentMove]);
8008 if (whosays == GE_ICS) {
8011 if (gameMode == IcsPlayingWhite)
8013 else if(gameMode == IcsPlayingBlack)
8017 if (gameMode == IcsPlayingBlack)
8019 else if(gameMode == IcsPlayingWhite)
8026 PlayIcsUnfinishedSound();
8029 } else if (gameMode == EditGame ||
8030 gameMode == PlayFromGameFile ||
8031 gameMode == AnalyzeMode ||
8032 gameMode == AnalyzeFile) {
8033 nextGameMode = gameMode;
8035 nextGameMode = EndOfGame;
8040 nextGameMode = gameMode;
8043 if (appData.noChessProgram) {
8044 gameMode = nextGameMode;
8046 endingGame = 0; /* [HGM] crash */
8051 /* Put first chess program into idle state */
8052 if (first.pr != NoProc &&
8053 (gameMode == MachinePlaysWhite ||
8054 gameMode == MachinePlaysBlack ||
8055 gameMode == TwoMachinesPlay ||
8056 gameMode == IcsPlayingWhite ||
8057 gameMode == IcsPlayingBlack ||
8058 gameMode == BeginningOfGame)) {
8059 SendToProgram("force\n", &first);
8060 if (first.usePing) {
8062 sprintf(buf, "ping %d\n", ++first.lastPing);
8063 SendToProgram(buf, &first);
8066 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8067 /* Kill off first chess program */
8068 if (first.isr != NULL)
8069 RemoveInputSource(first.isr);
8072 if (first.pr != NoProc) {
8074 DoSleep( appData.delayBeforeQuit );
8075 SendToProgram("quit\n", &first);
8076 DoSleep( appData.delayAfterQuit );
8077 DestroyChildProcess(first.pr, first.useSigterm);
8082 /* Put second chess program into idle state */
8083 if (second.pr != NoProc &&
8084 gameMode == TwoMachinesPlay) {
8085 SendToProgram("force\n", &second);
8086 if (second.usePing) {
8088 sprintf(buf, "ping %d\n", ++second.lastPing);
8089 SendToProgram(buf, &second);
8092 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8093 /* Kill off second chess program */
8094 if (second.isr != NULL)
8095 RemoveInputSource(second.isr);
8098 if (second.pr != NoProc) {
8099 DoSleep( appData.delayBeforeQuit );
8100 SendToProgram("quit\n", &second);
8101 DoSleep( appData.delayAfterQuit );
8102 DestroyChildProcess(second.pr, second.useSigterm);
8107 if (matchMode && gameMode == TwoMachinesPlay) {
8110 if (first.twoMachinesColor[0] == 'w') {
8117 if (first.twoMachinesColor[0] == 'b') {
8126 if (matchGame < appData.matchGames) {
8128 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8129 tmp = first.twoMachinesColor;
8130 first.twoMachinesColor = second.twoMachinesColor;
8131 second.twoMachinesColor = tmp;
8133 gameMode = nextGameMode;
8135 if(appData.matchPause>10000 || appData.matchPause<10)
8136 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8137 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8138 endingGame = 0; /* [HGM] crash */
8142 gameMode = nextGameMode;
8143 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8144 first.tidy, second.tidy,
8145 first.matchWins, second.matchWins,
8146 appData.matchGames - (first.matchWins + second.matchWins));
8147 DisplayFatalError(buf, 0, 0);
8150 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8151 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8153 gameMode = nextGameMode;
8155 endingGame = 0; /* [HGM] crash */
8158 /* Assumes program was just initialized (initString sent).
8159 Leaves program in force mode. */
8161 FeedMovesToProgram(cps, upto)
8162 ChessProgramState *cps;
8167 if (appData.debugMode)
8168 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8169 startedFromSetupPosition ? "position and " : "",
8170 backwardMostMove, upto, cps->which);
8171 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8172 // [HGM] variantswitch: make engine aware of new variant
8173 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8174 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8175 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8176 SendToProgram(buf, cps);
8177 currentlyInitializedVariant = gameInfo.variant;
8179 SendToProgram("force\n", cps);
8180 if (startedFromSetupPosition) {
8181 SendBoard(cps, backwardMostMove);
8182 if (appData.debugMode) {
8183 fprintf(debugFP, "feedMoves\n");
8186 for (i = backwardMostMove; i < upto; i++) {
8187 SendMoveToProgram(i, cps);
8193 ResurrectChessProgram()
8195 /* The chess program may have exited.
8196 If so, restart it and feed it all the moves made so far. */
8198 if (appData.noChessProgram || first.pr != NoProc) return;
8200 StartChessProgram(&first);
8201 InitChessProgram(&first, FALSE);
8202 FeedMovesToProgram(&first, currentMove);
8204 if (!first.sendTime) {
8205 /* can't tell gnuchess what its clock should read,
8206 so we bow to its notion. */
8208 timeRemaining[0][currentMove] = whiteTimeRemaining;
8209 timeRemaining[1][currentMove] = blackTimeRemaining;
8212 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8213 appData.icsEngineAnalyze) && first.analysisSupport) {
8214 SendToProgram("analyze\n", &first);
8215 first.analyzing = TRUE;
8228 if (appData.debugMode) {
8229 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8230 redraw, init, gameMode);
8232 pausing = pauseExamInvalid = FALSE;
8233 startedFromSetupPosition = blackPlaysFirst = FALSE;
8235 whiteFlag = blackFlag = FALSE;
8236 userOfferedDraw = FALSE;
8237 hintRequested = bookRequested = FALSE;
8238 first.maybeThinking = FALSE;
8239 second.maybeThinking = FALSE;
8240 first.bookSuspend = FALSE; // [HGM] book
8241 second.bookSuspend = FALSE;
8242 thinkOutput[0] = NULLCHAR;
8243 lastHint[0] = NULLCHAR;
8244 ClearGameInfo(&gameInfo);
8245 gameInfo.variant = StringToVariant(appData.variant);
8246 ics_user_moved = ics_clock_paused = FALSE;
8247 ics_getting_history = H_FALSE;
8249 white_holding[0] = black_holding[0] = NULLCHAR;
8250 ClearProgramStats();
8251 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8255 flipView = appData.flipView;
8256 ClearPremoveHighlights();
8258 alarmSounded = FALSE;
8260 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8261 if(appData.serverMovesName != NULL) {
8262 /* [HGM] prepare to make moves file for broadcasting */
8263 clock_t t = clock();
8264 if(serverMoves != NULL) fclose(serverMoves);
8265 serverMoves = fopen(appData.serverMovesName, "r");
8266 if(serverMoves != NULL) {
8267 fclose(serverMoves);
8268 /* delay 15 sec before overwriting, so all clients can see end */
8269 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8271 serverMoves = fopen(appData.serverMovesName, "w");
8275 gameMode = BeginningOfGame;
8278 if(appData.icsActive) gameInfo.variant = VariantNormal;
8279 InitPosition(redraw);
8280 for (i = 0; i < MAX_MOVES; i++) {
8281 if (commentList[i] != NULL) {
8282 free(commentList[i]);
8283 commentList[i] = NULL;
8288 timeRemaining[0][0] = whiteTimeRemaining;
8289 timeRemaining[1][0] = blackTimeRemaining;
8290 if (first.pr == NULL) {
8291 StartChessProgram(&first);
8294 InitChessProgram(&first, startedFromSetupPosition);
8298 DisplayMessage("", "");
8299 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8300 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8308 if (!AutoPlayOneMove())
8310 if (matchMode || appData.timeDelay == 0)
8312 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8314 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8323 int fromX, fromY, toX, toY;
8325 if (appData.debugMode) {
8326 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8329 if (gameMode != PlayFromGameFile)
8332 if (currentMove >= forwardMostMove) {
8333 gameMode = EditGame;
8336 /* [AS] Clear current move marker at the end of a game */
8337 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8342 toX = moveList[currentMove][2] - AAA;
8343 toY = moveList[currentMove][3] - ONE;
8345 if (moveList[currentMove][1] == '@') {
8346 if (appData.highlightLastMove) {
8347 SetHighlights(-1, -1, toX, toY);
8350 fromX = moveList[currentMove][0] - AAA;
8351 fromY = moveList[currentMove][1] - ONE;
8353 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8355 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8357 if (appData.highlightLastMove) {
8358 SetHighlights(fromX, fromY, toX, toY);
8361 DisplayMove(currentMove);
8362 SendMoveToProgram(currentMove++, &first);
8363 DisplayBothClocks();
8364 DrawPosition(FALSE, boards[currentMove]);
8365 // [HGM] PV info: always display, routine tests if empty
8366 DisplayComment(currentMove - 1, commentList[currentMove]);
8372 LoadGameOneMove(readAhead)
8373 ChessMove readAhead;
8375 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8376 char promoChar = NULLCHAR;
8381 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8382 gameMode != AnalyzeMode && gameMode != Training) {
8387 yyboardindex = forwardMostMove;
8388 if (readAhead != (ChessMove)0) {
8389 moveType = readAhead;
8391 if (gameFileFP == NULL)
8393 moveType = (ChessMove) yylex();
8399 if (appData.debugMode)
8400 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8402 if (*p == '{' || *p == '[' || *p == '(') {
8403 p[strlen(p) - 1] = NULLCHAR;
8407 /* append the comment but don't display it */
8408 while (*p == '\n') p++;
8409 AppendComment(currentMove, p);
8412 case WhiteCapturesEnPassant:
8413 case BlackCapturesEnPassant:
8414 case WhitePromotionChancellor:
8415 case BlackPromotionChancellor:
8416 case WhitePromotionArchbishop:
8417 case BlackPromotionArchbishop:
8418 case WhitePromotionCentaur:
8419 case BlackPromotionCentaur:
8420 case WhitePromotionQueen:
8421 case BlackPromotionQueen:
8422 case WhitePromotionRook:
8423 case BlackPromotionRook:
8424 case WhitePromotionBishop:
8425 case BlackPromotionBishop:
8426 case WhitePromotionKnight:
8427 case BlackPromotionKnight:
8428 case WhitePromotionKing:
8429 case BlackPromotionKing:
8431 case WhiteKingSideCastle:
8432 case WhiteQueenSideCastle:
8433 case BlackKingSideCastle:
8434 case BlackQueenSideCastle:
8435 case WhiteKingSideCastleWild:
8436 case WhiteQueenSideCastleWild:
8437 case BlackKingSideCastleWild:
8438 case BlackQueenSideCastleWild:
8440 case WhiteHSideCastleFR:
8441 case WhiteASideCastleFR:
8442 case BlackHSideCastleFR:
8443 case BlackASideCastleFR:
8445 if (appData.debugMode)
8446 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8447 fromX = currentMoveString[0] - AAA;
8448 fromY = currentMoveString[1] - ONE;
8449 toX = currentMoveString[2] - AAA;
8450 toY = currentMoveString[3] - ONE;
8451 promoChar = currentMoveString[4];
8456 if (appData.debugMode)
8457 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8458 fromX = moveType == WhiteDrop ?
8459 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8460 (int) CharToPiece(ToLower(currentMoveString[0]));
8462 toX = currentMoveString[2] - AAA;
8463 toY = currentMoveString[3] - ONE;
8469 case GameUnfinished:
8470 if (appData.debugMode)
8471 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8472 p = strchr(yy_text, '{');
8473 if (p == NULL) p = strchr(yy_text, '(');
8476 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8478 q = strchr(p, *p == '{' ? '}' : ')');
8479 if (q != NULL) *q = NULLCHAR;
8482 GameEnds(moveType, p, GE_FILE);
8484 if (cmailMsgLoaded) {
8486 flipView = WhiteOnMove(currentMove);
8487 if (moveType == GameUnfinished) flipView = !flipView;
8488 if (appData.debugMode)
8489 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8493 case (ChessMove) 0: /* end of file */
8494 if (appData.debugMode)
8495 fprintf(debugFP, "Parser hit end of file\n");
8496 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8497 EP_UNKNOWN, castlingRights[currentMove]) ) {
8503 if (WhiteOnMove(currentMove)) {
8504 GameEnds(BlackWins, "Black mates", GE_FILE);
8506 GameEnds(WhiteWins, "White mates", GE_FILE);
8510 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8517 if (lastLoadGameStart == GNUChessGame) {
8518 /* GNUChessGames have numbers, but they aren't move numbers */
8519 if (appData.debugMode)
8520 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8521 yy_text, (int) moveType);
8522 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8524 /* else fall thru */
8529 /* Reached start of next game in file */
8530 if (appData.debugMode)
8531 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8532 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8533 EP_UNKNOWN, castlingRights[currentMove]) ) {
8539 if (WhiteOnMove(currentMove)) {
8540 GameEnds(BlackWins, "Black mates", GE_FILE);
8542 GameEnds(WhiteWins, "White mates", GE_FILE);
8546 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8552 case PositionDiagram: /* should not happen; ignore */
8553 case ElapsedTime: /* ignore */
8554 case NAG: /* ignore */
8555 if (appData.debugMode)
8556 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8557 yy_text, (int) moveType);
8558 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8561 if (appData.testLegality) {
8562 if (appData.debugMode)
8563 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8564 sprintf(move, _("Illegal move: %d.%s%s"),
8565 (forwardMostMove / 2) + 1,
8566 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8567 DisplayError(move, 0);
8570 if (appData.debugMode)
8571 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8572 yy_text, currentMoveString);
8573 fromX = currentMoveString[0] - AAA;
8574 fromY = currentMoveString[1] - ONE;
8575 toX = currentMoveString[2] - AAA;
8576 toY = currentMoveString[3] - ONE;
8577 promoChar = currentMoveString[4];
8582 if (appData.debugMode)
8583 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8584 sprintf(move, _("Ambiguous move: %d.%s%s"),
8585 (forwardMostMove / 2) + 1,
8586 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8587 DisplayError(move, 0);
8592 case ImpossibleMove:
8593 if (appData.debugMode)
8594 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8595 sprintf(move, _("Illegal move: %d.%s%s"),
8596 (forwardMostMove / 2) + 1,
8597 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8598 DisplayError(move, 0);
8604 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8605 DrawPosition(FALSE, boards[currentMove]);
8606 DisplayBothClocks();
8607 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8608 DisplayComment(currentMove - 1, commentList[currentMove]);
8610 (void) StopLoadGameTimer();
8612 cmailOldMove = forwardMostMove;
8615 /* currentMoveString is set as a side-effect of yylex */
8616 strcat(currentMoveString, "\n");
8617 strcpy(moveList[forwardMostMove], currentMoveString);
8619 thinkOutput[0] = NULLCHAR;
8620 MakeMove(fromX, fromY, toX, toY, promoChar);
8621 currentMove = forwardMostMove;
8626 /* Load the nth game from the given file */
8628 LoadGameFromFile(filename, n, title, useList)
8632 /*Boolean*/ int useList;
8637 if (strcmp(filename, "-") == 0) {
8641 f = fopen(filename, "rb");
8643 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8644 DisplayError(buf, errno);
8648 if (fseek(f, 0, 0) == -1) {
8649 /* f is not seekable; probably a pipe */
8652 if (useList && n == 0) {
8653 int error = GameListBuild(f);
8655 DisplayError(_("Cannot build game list"), error);
8656 } else if (!ListEmpty(&gameList) &&
8657 ((ListGame *) gameList.tailPred)->number > 1) {
8658 GameListPopUp(f, title);
8665 return LoadGame(f, n, title, FALSE);
8670 MakeRegisteredMove()
8672 int fromX, fromY, toX, toY;
8674 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8675 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8678 if (appData.debugMode)
8679 fprintf(debugFP, "Restoring %s for game %d\n",
8680 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8682 thinkOutput[0] = NULLCHAR;
8683 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8684 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8685 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8686 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8687 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8688 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8689 MakeMove(fromX, fromY, toX, toY, promoChar);
8690 ShowMove(fromX, fromY, toX, toY);
8692 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8693 EP_UNKNOWN, castlingRights[currentMove]) ) {
8700 if (WhiteOnMove(currentMove)) {
8701 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8703 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8708 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8715 if (WhiteOnMove(currentMove)) {
8716 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8718 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8723 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8734 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8736 CmailLoadGame(f, gameNumber, title, useList)
8744 if (gameNumber > nCmailGames) {
8745 DisplayError(_("No more games in this message"), 0);
8748 if (f == lastLoadGameFP) {
8749 int offset = gameNumber - lastLoadGameNumber;
8751 cmailMsg[0] = NULLCHAR;
8752 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8753 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8754 nCmailMovesRegistered--;
8756 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8757 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8758 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8761 if (! RegisterMove()) return FALSE;
8765 retVal = LoadGame(f, gameNumber, title, useList);
8767 /* Make move registered during previous look at this game, if any */
8768 MakeRegisteredMove();
8770 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8771 commentList[currentMove]
8772 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8773 DisplayComment(currentMove - 1, commentList[currentMove]);
8779 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8784 int gameNumber = lastLoadGameNumber + offset;
8785 if (lastLoadGameFP == NULL) {
8786 DisplayError(_("No game has been loaded yet"), 0);
8789 if (gameNumber <= 0) {
8790 DisplayError(_("Can't back up any further"), 0);
8793 if (cmailMsgLoaded) {
8794 return CmailLoadGame(lastLoadGameFP, gameNumber,
8795 lastLoadGameTitle, lastLoadGameUseList);
8797 return LoadGame(lastLoadGameFP, gameNumber,
8798 lastLoadGameTitle, lastLoadGameUseList);
8804 /* Load the nth game from open file f */
8806 LoadGame(f, gameNumber, title, useList)
8814 int gn = gameNumber;
8815 ListGame *lg = NULL;
8818 GameMode oldGameMode;
8819 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8821 if (appData.debugMode)
8822 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8824 if (gameMode == Training )
8825 SetTrainingModeOff();
8827 oldGameMode = gameMode;
8828 if (gameMode != BeginningOfGame) {
8833 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8834 fclose(lastLoadGameFP);
8838 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8841 fseek(f, lg->offset, 0);
8842 GameListHighlight(gameNumber);
8846 DisplayError(_("Game number out of range"), 0);
8851 if (fseek(f, 0, 0) == -1) {
8852 if (f == lastLoadGameFP ?
8853 gameNumber == lastLoadGameNumber + 1 :
8857 DisplayError(_("Can't seek on game file"), 0);
8863 lastLoadGameNumber = gameNumber;
8864 strcpy(lastLoadGameTitle, title);
8865 lastLoadGameUseList = useList;
8869 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8870 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8871 lg->gameInfo.black);
8873 } else if (*title != NULLCHAR) {
8874 if (gameNumber > 1) {
8875 sprintf(buf, "%s %d", title, gameNumber);
8878 DisplayTitle(title);
8882 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8883 gameMode = PlayFromGameFile;
8887 currentMove = forwardMostMove = backwardMostMove = 0;
8888 CopyBoard(boards[0], initialPosition);
8892 * Skip the first gn-1 games in the file.
8893 * Also skip over anything that precedes an identifiable
8894 * start of game marker, to avoid being confused by
8895 * garbage at the start of the file. Currently
8896 * recognized start of game markers are the move number "1",
8897 * the pattern "gnuchess .* game", the pattern
8898 * "^[#;%] [^ ]* game file", and a PGN tag block.
8899 * A game that starts with one of the latter two patterns
8900 * will also have a move number 1, possibly
8901 * following a position diagram.
8902 * 5-4-02: Let's try being more lenient and allowing a game to
8903 * start with an unnumbered move. Does that break anything?
8905 cm = lastLoadGameStart = (ChessMove) 0;
8907 yyboardindex = forwardMostMove;
8908 cm = (ChessMove) yylex();
8911 if (cmailMsgLoaded) {
8912 nCmailGames = CMAIL_MAX_GAMES - gn;
8915 DisplayError(_("Game not found in file"), 0);
8922 lastLoadGameStart = cm;
8926 switch (lastLoadGameStart) {
8933 gn--; /* count this game */
8934 lastLoadGameStart = cm;
8943 switch (lastLoadGameStart) {
8948 gn--; /* count this game */
8949 lastLoadGameStart = cm;
8952 lastLoadGameStart = cm; /* game counted already */
8960 yyboardindex = forwardMostMove;
8961 cm = (ChessMove) yylex();
8962 } while (cm == PGNTag || cm == Comment);
8969 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8970 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8971 != CMAIL_OLD_RESULT) {
8973 cmailResult[ CMAIL_MAX_GAMES
8974 - gn - 1] = CMAIL_OLD_RESULT;
8980 /* Only a NormalMove can be at the start of a game
8981 * without a position diagram. */
8982 if (lastLoadGameStart == (ChessMove) 0) {
8984 lastLoadGameStart = MoveNumberOne;
8993 if (appData.debugMode)
8994 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8996 if (cm == XBoardGame) {
8997 /* Skip any header junk before position diagram and/or move 1 */
8999 yyboardindex = forwardMostMove;
9000 cm = (ChessMove) yylex();
9002 if (cm == (ChessMove) 0 ||
9003 cm == GNUChessGame || cm == XBoardGame) {
9004 /* Empty game; pretend end-of-file and handle later */
9009 if (cm == MoveNumberOne || cm == PositionDiagram ||
9010 cm == PGNTag || cm == Comment)
9013 } else if (cm == GNUChessGame) {
9014 if (gameInfo.event != NULL) {
9015 free(gameInfo.event);
9017 gameInfo.event = StrSave(yy_text);
9020 startedFromSetupPosition = FALSE;
9021 while (cm == PGNTag) {
9022 if (appData.debugMode)
9023 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9024 err = ParsePGNTag(yy_text, &gameInfo);
9025 if (!err) numPGNTags++;
9027 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9028 if(gameInfo.variant != oldVariant) {
9029 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9031 oldVariant = gameInfo.variant;
9032 if (appData.debugMode)
9033 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9037 if (gameInfo.fen != NULL) {
9038 Board initial_position;
9039 startedFromSetupPosition = TRUE;
9040 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9042 DisplayError(_("Bad FEN position in file"), 0);
9045 CopyBoard(boards[0], initial_position);
9046 if (blackPlaysFirst) {
9047 currentMove = forwardMostMove = backwardMostMove = 1;
9048 CopyBoard(boards[1], initial_position);
9049 strcpy(moveList[0], "");
9050 strcpy(parseList[0], "");
9051 timeRemaining[0][1] = whiteTimeRemaining;
9052 timeRemaining[1][1] = blackTimeRemaining;
9053 if (commentList[0] != NULL) {
9054 commentList[1] = commentList[0];
9055 commentList[0] = NULL;
9058 currentMove = forwardMostMove = backwardMostMove = 0;
9060 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9062 initialRulePlies = FENrulePlies;
9063 epStatus[forwardMostMove] = FENepStatus;
9064 for( i=0; i< nrCastlingRights; i++ )
9065 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9067 yyboardindex = forwardMostMove;
9069 gameInfo.fen = NULL;
9072 yyboardindex = forwardMostMove;
9073 cm = (ChessMove) yylex();
9075 /* Handle comments interspersed among the tags */
9076 while (cm == Comment) {
9078 if (appData.debugMode)
9079 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9081 if (*p == '{' || *p == '[' || *p == '(') {
9082 p[strlen(p) - 1] = NULLCHAR;
9085 while (*p == '\n') p++;
9086 AppendComment(currentMove, p);
9087 yyboardindex = forwardMostMove;
9088 cm = (ChessMove) yylex();
9092 /* don't rely on existence of Event tag since if game was
9093 * pasted from clipboard the Event tag may not exist
9095 if (numPGNTags > 0){
9097 if (gameInfo.variant == VariantNormal) {
9098 gameInfo.variant = StringToVariant(gameInfo.event);
9101 if( appData.autoDisplayTags ) {
9102 tags = PGNTags(&gameInfo);
9103 TagsPopUp(tags, CmailMsg());
9108 /* Make something up, but don't display it now */
9113 if (cm == PositionDiagram) {
9116 Board initial_position;
9118 if (appData.debugMode)
9119 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9121 if (!startedFromSetupPosition) {
9123 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9124 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9134 initial_position[i][j++] = CharToPiece(*p);
9137 while (*p == ' ' || *p == '\t' ||
9138 *p == '\n' || *p == '\r') p++;
9140 if (strncmp(p, "black", strlen("black"))==0)
9141 blackPlaysFirst = TRUE;
9143 blackPlaysFirst = FALSE;
9144 startedFromSetupPosition = TRUE;
9146 CopyBoard(boards[0], initial_position);
9147 if (blackPlaysFirst) {
9148 currentMove = forwardMostMove = backwardMostMove = 1;
9149 CopyBoard(boards[1], initial_position);
9150 strcpy(moveList[0], "");
9151 strcpy(parseList[0], "");
9152 timeRemaining[0][1] = whiteTimeRemaining;
9153 timeRemaining[1][1] = blackTimeRemaining;
9154 if (commentList[0] != NULL) {
9155 commentList[1] = commentList[0];
9156 commentList[0] = NULL;
9159 currentMove = forwardMostMove = backwardMostMove = 0;
9162 yyboardindex = forwardMostMove;
9163 cm = (ChessMove) yylex();
9166 if (first.pr == NoProc) {
9167 StartChessProgram(&first);
9169 InitChessProgram(&first, FALSE);
9170 SendToProgram("force\n", &first);
9171 if (startedFromSetupPosition) {
9172 SendBoard(&first, forwardMostMove);
9173 if (appData.debugMode) {
9174 fprintf(debugFP, "Load Game\n");
9176 DisplayBothClocks();
9179 /* [HGM] server: flag to write setup moves in broadcast file as one */
9180 loadFlag = appData.suppressLoadMoves;
9182 while (cm == Comment) {
9184 if (appData.debugMode)
9185 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9187 if (*p == '{' || *p == '[' || *p == '(') {
9188 p[strlen(p) - 1] = NULLCHAR;
9191 while (*p == '\n') p++;
9192 AppendComment(currentMove, p);
9193 yyboardindex = forwardMostMove;
9194 cm = (ChessMove) yylex();
9197 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9198 cm == WhiteWins || cm == BlackWins ||
9199 cm == GameIsDrawn || cm == GameUnfinished) {
9200 DisplayMessage("", _("No moves in game"));
9201 if (cmailMsgLoaded) {
9202 if (appData.debugMode)
9203 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9207 DrawPosition(FALSE, boards[currentMove]);
9208 DisplayBothClocks();
9209 gameMode = EditGame;
9216 // [HGM] PV info: routine tests if comment empty
9217 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9218 DisplayComment(currentMove - 1, commentList[currentMove]);
9220 if (!matchMode && appData.timeDelay != 0)
9221 DrawPosition(FALSE, boards[currentMove]);
9223 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9224 programStats.ok_to_send = 1;
9227 /* if the first token after the PGN tags is a move
9228 * and not move number 1, retrieve it from the parser
9230 if (cm != MoveNumberOne)
9231 LoadGameOneMove(cm);
9233 /* load the remaining moves from the file */
9234 while (LoadGameOneMove((ChessMove)0)) {
9235 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9236 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9239 /* rewind to the start of the game */
9240 currentMove = backwardMostMove;
9242 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9244 if (oldGameMode == AnalyzeFile ||
9245 oldGameMode == AnalyzeMode) {
9249 if (matchMode || appData.timeDelay == 0) {
9251 gameMode = EditGame;
9253 } else if (appData.timeDelay > 0) {
9257 if (appData.debugMode)
9258 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9260 loadFlag = 0; /* [HGM] true game starts */
9264 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9266 ReloadPosition(offset)
9269 int positionNumber = lastLoadPositionNumber + offset;
9270 if (lastLoadPositionFP == NULL) {
9271 DisplayError(_("No position has been loaded yet"), 0);
9274 if (positionNumber <= 0) {
9275 DisplayError(_("Can't back up any further"), 0);
9278 return LoadPosition(lastLoadPositionFP, positionNumber,
9279 lastLoadPositionTitle);
9282 /* Load the nth position from the given file */
9284 LoadPositionFromFile(filename, n, title)
9292 if (strcmp(filename, "-") == 0) {
9293 return LoadPosition(stdin, n, "stdin");
9295 f = fopen(filename, "rb");
9297 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9298 DisplayError(buf, errno);
9301 return LoadPosition(f, n, title);
9306 /* Load the nth position from the given open file, and close it */
9308 LoadPosition(f, positionNumber, title)
9313 char *p, line[MSG_SIZ];
9314 Board initial_position;
9315 int i, j, fenMode, pn;
9317 if (gameMode == Training )
9318 SetTrainingModeOff();
9320 if (gameMode != BeginningOfGame) {
9323 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9324 fclose(lastLoadPositionFP);
9326 if (positionNumber == 0) positionNumber = 1;
9327 lastLoadPositionFP = f;
9328 lastLoadPositionNumber = positionNumber;
9329 strcpy(lastLoadPositionTitle, title);
9330 if (first.pr == NoProc) {
9331 StartChessProgram(&first);
9332 InitChessProgram(&first, FALSE);
9334 pn = positionNumber;
9335 if (positionNumber < 0) {
9336 /* Negative position number means to seek to that byte offset */
9337 if (fseek(f, -positionNumber, 0) == -1) {
9338 DisplayError(_("Can't seek on position file"), 0);
9343 if (fseek(f, 0, 0) == -1) {
9344 if (f == lastLoadPositionFP ?
9345 positionNumber == lastLoadPositionNumber + 1 :
9346 positionNumber == 1) {
9349 DisplayError(_("Can't seek on position file"), 0);
9354 /* See if this file is FEN or old-style xboard */
9355 if (fgets(line, MSG_SIZ, f) == NULL) {
9356 DisplayError(_("Position not found in file"), 0);
9365 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9366 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9367 case '1': case '2': case '3': case '4': case '5': case '6':
9368 case '7': case '8': case '9':
9369 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9370 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9371 case 'C': case 'W': case 'c': case 'w':
9376 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9377 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9381 if (fenMode || line[0] == '#') pn--;
9383 /* skip positions before number pn */
9384 if (fgets(line, MSG_SIZ, f) == NULL) {
9386 DisplayError(_("Position not found in file"), 0);
9389 if (fenMode || line[0] == '#') pn--;
9394 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9395 DisplayError(_("Bad FEN position in file"), 0);
9399 (void) fgets(line, MSG_SIZ, f);
9400 (void) fgets(line, MSG_SIZ, f);
9402 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9403 (void) fgets(line, MSG_SIZ, f);
9404 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9407 initial_position[i][j++] = CharToPiece(*p);
9411 blackPlaysFirst = FALSE;
9413 (void) fgets(line, MSG_SIZ, f);
9414 if (strncmp(line, "black", strlen("black"))==0)
9415 blackPlaysFirst = TRUE;
9418 startedFromSetupPosition = TRUE;
9420 SendToProgram("force\n", &first);
9421 CopyBoard(boards[0], initial_position);
9422 if (blackPlaysFirst) {
9423 currentMove = forwardMostMove = backwardMostMove = 1;
9424 strcpy(moveList[0], "");
9425 strcpy(parseList[0], "");
9426 CopyBoard(boards[1], initial_position);
9427 DisplayMessage("", _("Black to play"));
9429 currentMove = forwardMostMove = backwardMostMove = 0;
9430 DisplayMessage("", _("White to play"));
9432 /* [HGM] copy FEN attributes as well */
9434 initialRulePlies = FENrulePlies;
9435 epStatus[forwardMostMove] = FENepStatus;
9436 for( i=0; i< nrCastlingRights; i++ )
9437 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9439 SendBoard(&first, forwardMostMove);
9440 if (appData.debugMode) {
9442 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9443 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9444 fprintf(debugFP, "Load Position\n");
9447 if (positionNumber > 1) {
9448 sprintf(line, "%s %d", title, positionNumber);
9451 DisplayTitle(title);
9453 gameMode = EditGame;
9456 timeRemaining[0][1] = whiteTimeRemaining;
9457 timeRemaining[1][1] = blackTimeRemaining;
9458 DrawPosition(FALSE, boards[currentMove]);
9465 CopyPlayerNameIntoFileName(dest, src)
9468 while (*src != NULLCHAR && *src != ',') {
9473 *(*dest)++ = *src++;
9478 char *DefaultFileName(ext)
9481 static char def[MSG_SIZ];
9484 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9486 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9488 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9497 /* Save the current game to the given file */
9499 SaveGameToFile(filename, append)
9506 if (strcmp(filename, "-") == 0) {
9507 return SaveGame(stdout, 0, NULL);
9509 f = fopen(filename, append ? "a" : "w");
9511 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9512 DisplayError(buf, errno);
9515 return SaveGame(f, 0, NULL);
9524 static char buf[MSG_SIZ];
9527 p = strchr(str, ' ');
9528 if (p == NULL) return str;
9529 strncpy(buf, str, p - str);
9530 buf[p - str] = NULLCHAR;
9534 #define PGN_MAX_LINE 75
9536 #define PGN_SIDE_WHITE 0
9537 #define PGN_SIDE_BLACK 1
9540 static int FindFirstMoveOutOfBook( int side )
9544 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9545 int index = backwardMostMove;
9546 int has_book_hit = 0;
9548 if( (index % 2) != side ) {
9552 while( index < forwardMostMove ) {
9553 /* Check to see if engine is in book */
9554 int depth = pvInfoList[index].depth;
9555 int score = pvInfoList[index].score;
9561 else if( score == 0 && depth == 63 ) {
9562 in_book = 1; /* Zappa */
9564 else if( score == 2 && depth == 99 ) {
9565 in_book = 1; /* Abrok */
9568 has_book_hit += in_book;
9584 void GetOutOfBookInfo( char * buf )
9588 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9590 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9591 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9595 if( oob[0] >= 0 || oob[1] >= 0 ) {
9596 for( i=0; i<2; i++ ) {
9600 if( i > 0 && oob[0] >= 0 ) {
9604 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9605 sprintf( buf+strlen(buf), "%s%.2f",
9606 pvInfoList[idx].score >= 0 ? "+" : "",
9607 pvInfoList[idx].score / 100.0 );
9613 /* Save game in PGN style and close the file */
9618 int i, offset, linelen, newblock;
9622 int movelen, numlen, blank;
9623 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9625 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9627 tm = time((time_t *) NULL);
9629 PrintPGNTags(f, &gameInfo);
9631 if (backwardMostMove > 0 || startedFromSetupPosition) {
9632 char *fen = PositionToFEN(backwardMostMove, NULL);
9633 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9634 fprintf(f, "\n{--------------\n");
9635 PrintPosition(f, backwardMostMove);
9636 fprintf(f, "--------------}\n");
9640 /* [AS] Out of book annotation */
9641 if( appData.saveOutOfBookInfo ) {
9644 GetOutOfBookInfo( buf );
9646 if( buf[0] != '\0' ) {
9647 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9654 i = backwardMostMove;
9658 while (i < forwardMostMove) {
9659 /* Print comments preceding this move */
9660 if (commentList[i] != NULL) {
9661 if (linelen > 0) fprintf(f, "\n");
9662 fprintf(f, "{\n%s}\n", commentList[i]);
9667 /* Format move number */
9669 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9672 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9674 numtext[0] = NULLCHAR;
9677 numlen = strlen(numtext);
9680 /* Print move number */
9681 blank = linelen > 0 && numlen > 0;
9682 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9691 fprintf(f, numtext);
9695 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9696 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9698 // SavePart already does this!
9699 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9700 int p = movelen - 1;
9701 if(move_buffer[p] == ' ') p--;
9702 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9703 while(p && move_buffer[--p] != '(');
9704 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9709 blank = linelen > 0 && movelen > 0;
9710 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9719 fprintf(f, move_buffer);
9722 /* [AS] Add PV info if present */
9723 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9724 /* [HGM] add time */
9725 char buf[MSG_SIZ]; int seconds = 0;
9728 if(i >= backwardMostMove) {
9730 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9731 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9733 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9734 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9736 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9738 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9741 if( seconds <= 0) buf[0] = 0; else
9742 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9743 seconds = (seconds + 4)/10; // round to full seconds
9744 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9745 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9748 sprintf( move_buffer, "{%s%.2f/%d%s}",
9749 pvInfoList[i].score >= 0 ? "+" : "",
9750 pvInfoList[i].score / 100.0,
9751 pvInfoList[i].depth,
9754 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9756 /* Print score/depth */
9757 blank = linelen > 0 && movelen > 0;
9758 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9767 fprintf(f, move_buffer);
9774 /* Start a new line */
9775 if (linelen > 0) fprintf(f, "\n");
9777 /* Print comments after last move */
9778 if (commentList[i] != NULL) {
9779 fprintf(f, "{\n%s}\n", commentList[i]);
9783 if (gameInfo.resultDetails != NULL &&
9784 gameInfo.resultDetails[0] != NULLCHAR) {
9785 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9786 PGNResult(gameInfo.result));
9788 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9792 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9796 /* Save game in old style and close the file */
9804 tm = time((time_t *) NULL);
9806 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9809 if (backwardMostMove > 0 || startedFromSetupPosition) {
9810 fprintf(f, "\n[--------------\n");
9811 PrintPosition(f, backwardMostMove);
9812 fprintf(f, "--------------]\n");
9817 i = backwardMostMove;
9818 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9820 while (i < forwardMostMove) {
9821 if (commentList[i] != NULL) {
9822 fprintf(f, "[%s]\n", commentList[i]);
9826 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9829 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9831 if (commentList[i] != NULL) {
9835 if (i >= forwardMostMove) {
9839 fprintf(f, "%s\n", parseList[i]);
9844 if (commentList[i] != NULL) {
9845 fprintf(f, "[%s]\n", commentList[i]);
9848 /* This isn't really the old style, but it's close enough */
9849 if (gameInfo.resultDetails != NULL &&
9850 gameInfo.resultDetails[0] != NULLCHAR) {
9851 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9852 gameInfo.resultDetails);
9854 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9861 /* Save the current game to open file f and close the file */
9863 SaveGame(f, dummy, dummy2)
9868 if (gameMode == EditPosition) EditPositionDone();
9869 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9870 if (appData.oldSaveStyle)
9871 return SaveGameOldStyle(f);
9873 return SaveGamePGN(f);
9876 /* Save the current position to the given file */
9878 SavePositionToFile(filename)
9884 if (strcmp(filename, "-") == 0) {
9885 return SavePosition(stdout, 0, NULL);
9887 f = fopen(filename, "a");
9889 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9890 DisplayError(buf, errno);
9893 SavePosition(f, 0, NULL);
9899 /* Save the current position to the given open file and close the file */
9901 SavePosition(f, dummy, dummy2)
9909 if (appData.oldSaveStyle) {
9910 tm = time((time_t *) NULL);
9912 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9914 fprintf(f, "[--------------\n");
9915 PrintPosition(f, currentMove);
9916 fprintf(f, "--------------]\n");
9918 fen = PositionToFEN(currentMove, NULL);
9919 fprintf(f, "%s\n", fen);
9927 ReloadCmailMsgEvent(unregister)
9931 static char *inFilename = NULL;
9932 static char *outFilename;
9934 struct stat inbuf, outbuf;
9937 /* Any registered moves are unregistered if unregister is set, */
9938 /* i.e. invoked by the signal handler */
9940 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9941 cmailMoveRegistered[i] = FALSE;
9942 if (cmailCommentList[i] != NULL) {
9943 free(cmailCommentList[i]);
9944 cmailCommentList[i] = NULL;
9947 nCmailMovesRegistered = 0;
9950 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9951 cmailResult[i] = CMAIL_NOT_RESULT;
9955 if (inFilename == NULL) {
9956 /* Because the filenames are static they only get malloced once */
9957 /* and they never get freed */
9958 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9959 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9961 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9962 sprintf(outFilename, "%s.out", appData.cmailGameName);
9965 status = stat(outFilename, &outbuf);
9967 cmailMailedMove = FALSE;
9969 status = stat(inFilename, &inbuf);
9970 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9973 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9974 counts the games, notes how each one terminated, etc.
9976 It would be nice to remove this kludge and instead gather all
9977 the information while building the game list. (And to keep it
9978 in the game list nodes instead of having a bunch of fixed-size
9979 parallel arrays.) Note this will require getting each game's
9980 termination from the PGN tags, as the game list builder does
9981 not process the game moves. --mann
9983 cmailMsgLoaded = TRUE;
9984 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9986 /* Load first game in the file or popup game menu */
9987 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9997 char string[MSG_SIZ];
9999 if ( cmailMailedMove
10000 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10001 return TRUE; /* Allow free viewing */
10004 /* Unregister move to ensure that we don't leave RegisterMove */
10005 /* with the move registered when the conditions for registering no */
10007 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10008 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10009 nCmailMovesRegistered --;
10011 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10013 free(cmailCommentList[lastLoadGameNumber - 1]);
10014 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10018 if (cmailOldMove == -1) {
10019 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10023 if (currentMove > cmailOldMove + 1) {
10024 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10028 if (currentMove < cmailOldMove) {
10029 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10033 if (forwardMostMove > currentMove) {
10034 /* Silently truncate extra moves */
10038 if ( (currentMove == cmailOldMove + 1)
10039 || ( (currentMove == cmailOldMove)
10040 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10041 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10042 if (gameInfo.result != GameUnfinished) {
10043 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10046 if (commentList[currentMove] != NULL) {
10047 cmailCommentList[lastLoadGameNumber - 1]
10048 = StrSave(commentList[currentMove]);
10050 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10052 if (appData.debugMode)
10053 fprintf(debugFP, "Saving %s for game %d\n",
10054 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10057 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10059 f = fopen(string, "w");
10060 if (appData.oldSaveStyle) {
10061 SaveGameOldStyle(f); /* also closes the file */
10063 sprintf(string, "%s.pos.out", appData.cmailGameName);
10064 f = fopen(string, "w");
10065 SavePosition(f, 0, NULL); /* also closes the file */
10067 fprintf(f, "{--------------\n");
10068 PrintPosition(f, currentMove);
10069 fprintf(f, "--------------}\n\n");
10071 SaveGame(f, 0, NULL); /* also closes the file*/
10074 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10075 nCmailMovesRegistered ++;
10076 } else if (nCmailGames == 1) {
10077 DisplayError(_("You have not made a move yet"), 0);
10088 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10089 FILE *commandOutput;
10090 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10091 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10097 if (! cmailMsgLoaded) {
10098 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10102 if (nCmailGames == nCmailResults) {
10103 DisplayError(_("No unfinished games"), 0);
10107 #if CMAIL_PROHIBIT_REMAIL
10108 if (cmailMailedMove) {
10109 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);
10110 DisplayError(msg, 0);
10115 if (! (cmailMailedMove || RegisterMove())) return;
10117 if ( cmailMailedMove
10118 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10119 sprintf(string, partCommandString,
10120 appData.debugMode ? " -v" : "", appData.cmailGameName);
10121 commandOutput = popen(string, "r");
10123 if (commandOutput == NULL) {
10124 DisplayError(_("Failed to invoke cmail"), 0);
10126 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10127 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10129 if (nBuffers > 1) {
10130 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10131 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10132 nBytes = MSG_SIZ - 1;
10134 (void) memcpy(msg, buffer, nBytes);
10136 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10138 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10139 cmailMailedMove = TRUE; /* Prevent >1 moves */
10142 for (i = 0; i < nCmailGames; i ++) {
10143 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10148 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10150 sprintf(buffer, "%s/%s.%s.archive",
10152 appData.cmailGameName,
10154 LoadGameFromFile(buffer, 1, buffer, FALSE);
10155 cmailMsgLoaded = FALSE;
10159 DisplayInformation(msg);
10160 pclose(commandOutput);
10163 if ((*cmailMsg) != '\0') {
10164 DisplayInformation(cmailMsg);
10169 #endif /* !WIN32 */
10178 int prependComma = 0;
10180 char string[MSG_SIZ]; /* Space for game-list */
10183 if (!cmailMsgLoaded) return "";
10185 if (cmailMailedMove) {
10186 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10188 /* Create a list of games left */
10189 sprintf(string, "[");
10190 for (i = 0; i < nCmailGames; i ++) {
10191 if (! ( cmailMoveRegistered[i]
10192 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10193 if (prependComma) {
10194 sprintf(number, ",%d", i + 1);
10196 sprintf(number, "%d", i + 1);
10200 strcat(string, number);
10203 strcat(string, "]");
10205 if (nCmailMovesRegistered + nCmailResults == 0) {
10206 switch (nCmailGames) {
10209 _("Still need to make move for game\n"));
10214 _("Still need to make moves for both games\n"));
10219 _("Still need to make moves for all %d games\n"),
10224 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10227 _("Still need to make a move for game %s\n"),
10232 if (nCmailResults == nCmailGames) {
10233 sprintf(cmailMsg, _("No unfinished games\n"));
10235 sprintf(cmailMsg, _("Ready to send mail\n"));
10241 _("Still need to make moves for games %s\n"),
10253 if (gameMode == Training)
10254 SetTrainingModeOff();
10257 cmailMsgLoaded = FALSE;
10258 if (appData.icsActive) {
10259 SendToICS(ics_prefix);
10260 SendToICS("refresh\n");
10270 /* Give up on clean exit */
10274 /* Keep trying for clean exit */
10278 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10280 if (telnetISR != NULL) {
10281 RemoveInputSource(telnetISR);
10283 if (icsPR != NoProc) {
10284 DestroyChildProcess(icsPR, TRUE);
10287 /* Save game if resource set and not already saved by GameEnds() */
10288 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10289 && forwardMostMove > 0) {
10290 if (*appData.saveGameFile != NULLCHAR) {
10291 SaveGameToFile(appData.saveGameFile, TRUE);
10292 } else if (appData.autoSaveGames) {
10295 if (*appData.savePositionFile != NULLCHAR) {
10296 SavePositionToFile(appData.savePositionFile);
10299 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10301 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10302 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10304 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10305 /* make sure this other one finishes before killing it! */
10306 if(endingGame) { int count = 0;
10307 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10308 while(endingGame && count++ < 10) DoSleep(1);
10309 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10312 /* Kill off chess programs */
10313 if (first.pr != NoProc) {
10316 DoSleep( appData.delayBeforeQuit );
10317 SendToProgram("quit\n", &first);
10318 DoSleep( appData.delayAfterQuit );
10319 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10321 if (second.pr != NoProc) {
10322 DoSleep( appData.delayBeforeQuit );
10323 SendToProgram("quit\n", &second);
10324 DoSleep( appData.delayAfterQuit );
10325 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10327 if (first.isr != NULL) {
10328 RemoveInputSource(first.isr);
10330 if (second.isr != NULL) {
10331 RemoveInputSource(second.isr);
10334 ShutDownFrontEnd();
10341 if (appData.debugMode)
10342 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10346 if (gameMode == MachinePlaysWhite ||
10347 gameMode == MachinePlaysBlack) {
10350 DisplayBothClocks();
10352 if (gameMode == PlayFromGameFile) {
10353 if (appData.timeDelay >= 0)
10354 AutoPlayGameLoop();
10355 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10356 Reset(FALSE, TRUE);
10357 SendToICS(ics_prefix);
10358 SendToICS("refresh\n");
10359 } else if (currentMove < forwardMostMove) {
10360 ForwardInner(forwardMostMove);
10362 pauseExamInvalid = FALSE;
10364 switch (gameMode) {
10368 pauseExamForwardMostMove = forwardMostMove;
10369 pauseExamInvalid = FALSE;
10372 case IcsPlayingWhite:
10373 case IcsPlayingBlack:
10377 case PlayFromGameFile:
10378 (void) StopLoadGameTimer();
10382 case BeginningOfGame:
10383 if (appData.icsActive) return;
10384 /* else fall through */
10385 case MachinePlaysWhite:
10386 case MachinePlaysBlack:
10387 case TwoMachinesPlay:
10388 if (forwardMostMove == 0)
10389 return; /* don't pause if no one has moved */
10390 if ((gameMode == MachinePlaysWhite &&
10391 !WhiteOnMove(forwardMostMove)) ||
10392 (gameMode == MachinePlaysBlack &&
10393 WhiteOnMove(forwardMostMove))) {
10406 char title[MSG_SIZ];
10408 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10409 strcpy(title, _("Edit comment"));
10411 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10412 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10413 parseList[currentMove - 1]);
10416 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10423 char *tags = PGNTags(&gameInfo);
10424 EditTagsPopUp(tags);
10431 if (appData.noChessProgram || gameMode == AnalyzeMode)
10434 if (gameMode != AnalyzeFile) {
10435 if (!appData.icsEngineAnalyze) {
10437 if (gameMode != EditGame) return;
10439 ResurrectChessProgram();
10440 SendToProgram("analyze\n", &first);
10441 first.analyzing = TRUE;
10442 /*first.maybeThinking = TRUE;*/
10443 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10444 AnalysisPopUp(_("Analysis"),
10445 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10447 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10452 StartAnalysisClock();
10453 GetTimeMark(&lastNodeCountTime);
10460 if (appData.noChessProgram || gameMode == AnalyzeFile)
10463 if (gameMode != AnalyzeMode) {
10465 if (gameMode != EditGame) return;
10466 ResurrectChessProgram();
10467 SendToProgram("analyze\n", &first);
10468 first.analyzing = TRUE;
10469 /*first.maybeThinking = TRUE;*/
10470 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10471 AnalysisPopUp(_("Analysis"),
10472 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10474 gameMode = AnalyzeFile;
10479 StartAnalysisClock();
10480 GetTimeMark(&lastNodeCountTime);
10485 MachineWhiteEvent()
10488 char *bookHit = NULL;
10490 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10494 if (gameMode == PlayFromGameFile ||
10495 gameMode == TwoMachinesPlay ||
10496 gameMode == Training ||
10497 gameMode == AnalyzeMode ||
10498 gameMode == EndOfGame)
10501 if (gameMode == EditPosition)
10502 EditPositionDone();
10504 if (!WhiteOnMove(currentMove)) {
10505 DisplayError(_("It is not White's turn"), 0);
10509 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10512 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10513 gameMode == AnalyzeFile)
10516 ResurrectChessProgram(); /* in case it isn't running */
10517 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10518 gameMode = MachinePlaysWhite;
10521 gameMode = MachinePlaysWhite;
10525 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10527 if (first.sendName) {
10528 sprintf(buf, "name %s\n", gameInfo.black);
10529 SendToProgram(buf, &first);
10531 if (first.sendTime) {
10532 if (first.useColors) {
10533 SendToProgram("black\n", &first); /*gnu kludge*/
10535 SendTimeRemaining(&first, TRUE);
10537 if (first.useColors) {
10538 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10540 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10541 SetMachineThinkingEnables();
10542 first.maybeThinking = TRUE;
10545 if (appData.autoFlipView && !flipView) {
10546 flipView = !flipView;
10547 DrawPosition(FALSE, NULL);
10548 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10551 if(bookHit) { // [HGM] book: simulate book reply
10552 static char bookMove[MSG_SIZ]; // a bit generous?
10554 programStats.nodes = programStats.depth = programStats.time =
10555 programStats.score = programStats.got_only_move = 0;
10556 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10558 strcpy(bookMove, "move ");
10559 strcat(bookMove, bookHit);
10560 HandleMachineMove(bookMove, &first);
10565 MachineBlackEvent()
10568 char *bookHit = NULL;
10570 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10574 if (gameMode == PlayFromGameFile ||
10575 gameMode == TwoMachinesPlay ||
10576 gameMode == Training ||
10577 gameMode == AnalyzeMode ||
10578 gameMode == EndOfGame)
10581 if (gameMode == EditPosition)
10582 EditPositionDone();
10584 if (WhiteOnMove(currentMove)) {
10585 DisplayError(_("It is not Black's turn"), 0);
10589 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10592 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10593 gameMode == AnalyzeFile)
10596 ResurrectChessProgram(); /* in case it isn't running */
10597 gameMode = MachinePlaysBlack;
10601 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10603 if (first.sendName) {
10604 sprintf(buf, "name %s\n", gameInfo.white);
10605 SendToProgram(buf, &first);
10607 if (first.sendTime) {
10608 if (first.useColors) {
10609 SendToProgram("white\n", &first); /*gnu kludge*/
10611 SendTimeRemaining(&first, FALSE);
10613 if (first.useColors) {
10614 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10616 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10617 SetMachineThinkingEnables();
10618 first.maybeThinking = TRUE;
10621 if (appData.autoFlipView && flipView) {
10622 flipView = !flipView;
10623 DrawPosition(FALSE, NULL);
10624 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10626 if(bookHit) { // [HGM] book: simulate book reply
10627 static char bookMove[MSG_SIZ]; // a bit generous?
10629 programStats.nodes = programStats.depth = programStats.time =
10630 programStats.score = programStats.got_only_move = 0;
10631 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10633 strcpy(bookMove, "move ");
10634 strcat(bookMove, bookHit);
10635 HandleMachineMove(bookMove, &first);
10641 DisplayTwoMachinesTitle()
10644 if (appData.matchGames > 0) {
10645 if (first.twoMachinesColor[0] == 'w') {
10646 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10647 gameInfo.white, gameInfo.black,
10648 first.matchWins, second.matchWins,
10649 matchGame - 1 - (first.matchWins + second.matchWins));
10651 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10652 gameInfo.white, gameInfo.black,
10653 second.matchWins, first.matchWins,
10654 matchGame - 1 - (first.matchWins + second.matchWins));
10657 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10663 TwoMachinesEvent P((void))
10667 ChessProgramState *onmove;
10668 char *bookHit = NULL;
10670 if (appData.noChessProgram) return;
10672 switch (gameMode) {
10673 case TwoMachinesPlay:
10675 case MachinePlaysWhite:
10676 case MachinePlaysBlack:
10677 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10678 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10682 case BeginningOfGame:
10683 case PlayFromGameFile:
10686 if (gameMode != EditGame) return;
10689 EditPositionDone();
10700 forwardMostMove = currentMove;
10701 ResurrectChessProgram(); /* in case first program isn't running */
10703 if (second.pr == NULL) {
10704 StartChessProgram(&second);
10705 if (second.protocolVersion == 1) {
10706 TwoMachinesEventIfReady();
10708 /* kludge: allow timeout for initial "feature" command */
10710 DisplayMessage("", _("Starting second chess program"));
10711 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10715 DisplayMessage("", "");
10716 InitChessProgram(&second, FALSE);
10717 SendToProgram("force\n", &second);
10718 if (startedFromSetupPosition) {
10719 SendBoard(&second, backwardMostMove);
10720 if (appData.debugMode) {
10721 fprintf(debugFP, "Two Machines\n");
10724 for (i = backwardMostMove; i < forwardMostMove; i++) {
10725 SendMoveToProgram(i, &second);
10728 gameMode = TwoMachinesPlay;
10732 DisplayTwoMachinesTitle();
10734 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10740 SendToProgram(first.computerString, &first);
10741 if (first.sendName) {
10742 sprintf(buf, "name %s\n", second.tidy);
10743 SendToProgram(buf, &first);
10745 SendToProgram(second.computerString, &second);
10746 if (second.sendName) {
10747 sprintf(buf, "name %s\n", first.tidy);
10748 SendToProgram(buf, &second);
10752 if (!first.sendTime || !second.sendTime) {
10753 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10754 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10756 if (onmove->sendTime) {
10757 if (onmove->useColors) {
10758 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10760 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10762 if (onmove->useColors) {
10763 SendToProgram(onmove->twoMachinesColor, onmove);
10765 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10766 // SendToProgram("go\n", onmove);
10767 onmove->maybeThinking = TRUE;
10768 SetMachineThinkingEnables();
10772 if(bookHit) { // [HGM] book: simulate book reply
10773 static char bookMove[MSG_SIZ]; // a bit generous?
10775 programStats.nodes = programStats.depth = programStats.time =
10776 programStats.score = programStats.got_only_move = 0;
10777 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10779 strcpy(bookMove, "move ");
10780 strcat(bookMove, bookHit);
10781 HandleMachineMove(bookMove, &first);
10788 if (gameMode == Training) {
10789 SetTrainingModeOff();
10790 gameMode = PlayFromGameFile;
10791 DisplayMessage("", _("Training mode off"));
10793 gameMode = Training;
10794 animateTraining = appData.animate;
10796 /* make sure we are not already at the end of the game */
10797 if (currentMove < forwardMostMove) {
10798 SetTrainingModeOn();
10799 DisplayMessage("", _("Training mode on"));
10801 gameMode = PlayFromGameFile;
10802 DisplayError(_("Already at end of game"), 0);
10811 if (!appData.icsActive) return;
10812 switch (gameMode) {
10813 case IcsPlayingWhite:
10814 case IcsPlayingBlack:
10817 case BeginningOfGame:
10825 EditPositionDone();
10838 gameMode = IcsIdle;
10849 switch (gameMode) {
10851 SetTrainingModeOff();
10853 case MachinePlaysWhite:
10854 case MachinePlaysBlack:
10855 case BeginningOfGame:
10856 SendToProgram("force\n", &first);
10857 SetUserThinkingEnables();
10859 case PlayFromGameFile:
10860 (void) StopLoadGameTimer();
10861 if (gameFileFP != NULL) {
10866 EditPositionDone();
10871 SendToProgram("force\n", &first);
10873 case TwoMachinesPlay:
10874 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10875 ResurrectChessProgram();
10876 SetUserThinkingEnables();
10879 ResurrectChessProgram();
10881 case IcsPlayingBlack:
10882 case IcsPlayingWhite:
10883 DisplayError(_("Warning: You are still playing a game"), 0);
10886 DisplayError(_("Warning: You are still observing a game"), 0);
10889 DisplayError(_("Warning: You are still examining a game"), 0);
10900 first.offeredDraw = second.offeredDraw = 0;
10902 if (gameMode == PlayFromGameFile) {
10903 whiteTimeRemaining = timeRemaining[0][currentMove];
10904 blackTimeRemaining = timeRemaining[1][currentMove];
10908 if (gameMode == MachinePlaysWhite ||
10909 gameMode == MachinePlaysBlack ||
10910 gameMode == TwoMachinesPlay ||
10911 gameMode == EndOfGame) {
10912 i = forwardMostMove;
10913 while (i > currentMove) {
10914 SendToProgram("undo\n", &first);
10917 whiteTimeRemaining = timeRemaining[0][currentMove];
10918 blackTimeRemaining = timeRemaining[1][currentMove];
10919 DisplayBothClocks();
10920 if (whiteFlag || blackFlag) {
10921 whiteFlag = blackFlag = 0;
10926 gameMode = EditGame;
10933 EditPositionEvent()
10935 if (gameMode == EditPosition) {
10941 if (gameMode != EditGame) return;
10943 gameMode = EditPosition;
10946 if (currentMove > 0)
10947 CopyBoard(boards[0], boards[currentMove]);
10949 blackPlaysFirst = !WhiteOnMove(currentMove);
10951 currentMove = forwardMostMove = backwardMostMove = 0;
10952 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10959 /* [DM] icsEngineAnalyze - possible call from other functions */
10960 if (appData.icsEngineAnalyze) {
10961 appData.icsEngineAnalyze = FALSE;
10963 DisplayMessage("",_("Close ICS engine analyze..."));
10965 if (first.analysisSupport && first.analyzing) {
10966 SendToProgram("exit\n", &first);
10967 first.analyzing = FALSE;
10970 thinkOutput[0] = NULLCHAR;
10976 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10978 startedFromSetupPosition = TRUE;
10979 InitChessProgram(&first, FALSE);
10980 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10981 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10982 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10983 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10984 } else castlingRights[0][2] = -1;
10985 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10986 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10987 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10988 } else castlingRights[0][5] = -1;
10989 SendToProgram("force\n", &first);
10990 if (blackPlaysFirst) {
10991 strcpy(moveList[0], "");
10992 strcpy(parseList[0], "");
10993 currentMove = forwardMostMove = backwardMostMove = 1;
10994 CopyBoard(boards[1], boards[0]);
10995 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10997 epStatus[1] = epStatus[0];
10998 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11001 currentMove = forwardMostMove = backwardMostMove = 0;
11003 SendBoard(&first, forwardMostMove);
11004 if (appData.debugMode) {
11005 fprintf(debugFP, "EditPosDone\n");
11008 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11009 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11010 gameMode = EditGame;
11012 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11013 ClearHighlights(); /* [AS] */
11016 /* Pause for `ms' milliseconds */
11017 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11027 } while (SubtractTimeMarks(&m2, &m1) < ms);
11030 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11032 SendMultiLineToICS(buf)
11035 char temp[MSG_SIZ+1], *p;
11042 strncpy(temp, buf, len);
11047 if (*p == '\n' || *p == '\r')
11052 strcat(temp, "\n");
11054 SendToPlayer(temp, strlen(temp));
11058 SetWhiteToPlayEvent()
11060 if (gameMode == EditPosition) {
11061 blackPlaysFirst = FALSE;
11062 DisplayBothClocks(); /* works because currentMove is 0 */
11063 } else if (gameMode == IcsExamining) {
11064 SendToICS(ics_prefix);
11065 SendToICS("tomove white\n");
11070 SetBlackToPlayEvent()
11072 if (gameMode == EditPosition) {
11073 blackPlaysFirst = TRUE;
11074 currentMove = 1; /* kludge */
11075 DisplayBothClocks();
11077 } else if (gameMode == IcsExamining) {
11078 SendToICS(ics_prefix);
11079 SendToICS("tomove black\n");
11084 EditPositionMenuEvent(selection, x, y)
11085 ChessSquare selection;
11089 ChessSquare piece = boards[0][y][x];
11091 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11093 switch (selection) {
11095 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11096 SendToICS(ics_prefix);
11097 SendToICS("bsetup clear\n");
11098 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11099 SendToICS(ics_prefix);
11100 SendToICS("clearboard\n");
11102 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11103 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11104 for (y = 0; y < BOARD_HEIGHT; y++) {
11105 if (gameMode == IcsExamining) {
11106 if (boards[currentMove][y][x] != EmptySquare) {
11107 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11112 boards[0][y][x] = p;
11117 if (gameMode == EditPosition) {
11118 DrawPosition(FALSE, boards[0]);
11123 SetWhiteToPlayEvent();
11127 SetBlackToPlayEvent();
11131 if (gameMode == IcsExamining) {
11132 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11135 boards[0][y][x] = EmptySquare;
11136 DrawPosition(FALSE, boards[0]);
11141 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11142 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11143 selection = (ChessSquare) (PROMOTED piece);
11144 } else if(piece == EmptySquare) selection = WhiteSilver;
11145 else selection = (ChessSquare)((int)piece - 1);
11149 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11150 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11151 selection = (ChessSquare) (DEMOTED piece);
11152 } else if(piece == EmptySquare) selection = BlackSilver;
11153 else selection = (ChessSquare)((int)piece + 1);
11158 if(gameInfo.variant == VariantShatranj ||
11159 gameInfo.variant == VariantXiangqi ||
11160 gameInfo.variant == VariantCourier )
11161 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11166 if(gameInfo.variant == VariantXiangqi)
11167 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11168 if(gameInfo.variant == VariantKnightmate)
11169 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11172 if (gameMode == IcsExamining) {
11173 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11174 PieceToChar(selection), AAA + x, ONE + y);
11177 boards[0][y][x] = selection;
11178 DrawPosition(FALSE, boards[0]);
11186 DropMenuEvent(selection, x, y)
11187 ChessSquare selection;
11190 ChessMove moveType;
11192 switch (gameMode) {
11193 case IcsPlayingWhite:
11194 case MachinePlaysBlack:
11195 if (!WhiteOnMove(currentMove)) {
11196 DisplayMoveError(_("It is Black's turn"));
11199 moveType = WhiteDrop;
11201 case IcsPlayingBlack:
11202 case MachinePlaysWhite:
11203 if (WhiteOnMove(currentMove)) {
11204 DisplayMoveError(_("It is White's turn"));
11207 moveType = BlackDrop;
11210 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11216 if (moveType == BlackDrop && selection < BlackPawn) {
11217 selection = (ChessSquare) ((int) selection
11218 + (int) BlackPawn - (int) WhitePawn);
11220 if (boards[currentMove][y][x] != EmptySquare) {
11221 DisplayMoveError(_("That square is occupied"));
11225 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11231 /* Accept a pending offer of any kind from opponent */
11233 if (appData.icsActive) {
11234 SendToICS(ics_prefix);
11235 SendToICS("accept\n");
11236 } else if (cmailMsgLoaded) {
11237 if (currentMove == cmailOldMove &&
11238 commentList[cmailOldMove] != NULL &&
11239 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11240 "Black offers a draw" : "White offers a draw")) {
11242 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11243 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11245 DisplayError(_("There is no pending offer on this move"), 0);
11246 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11249 /* Not used for offers from chess program */
11256 /* Decline a pending offer of any kind from opponent */
11258 if (appData.icsActive) {
11259 SendToICS(ics_prefix);
11260 SendToICS("decline\n");
11261 } else if (cmailMsgLoaded) {
11262 if (currentMove == cmailOldMove &&
11263 commentList[cmailOldMove] != NULL &&
11264 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11265 "Black offers a draw" : "White offers a draw")) {
11267 AppendComment(cmailOldMove, "Draw declined");
11268 DisplayComment(cmailOldMove - 1, "Draw declined");
11271 DisplayError(_("There is no pending offer on this move"), 0);
11274 /* Not used for offers from chess program */
11281 /* Issue ICS rematch command */
11282 if (appData.icsActive) {
11283 SendToICS(ics_prefix);
11284 SendToICS("rematch\n");
11291 /* Call your opponent's flag (claim a win on time) */
11292 if (appData.icsActive) {
11293 SendToICS(ics_prefix);
11294 SendToICS("flag\n");
11296 switch (gameMode) {
11299 case MachinePlaysWhite:
11302 GameEnds(GameIsDrawn, "Both players ran out of time",
11305 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11307 DisplayError(_("Your opponent is not out of time"), 0);
11310 case MachinePlaysBlack:
11313 GameEnds(GameIsDrawn, "Both players ran out of time",
11316 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11318 DisplayError(_("Your opponent is not out of time"), 0);
11328 /* Offer draw or accept pending draw offer from opponent */
11330 if (appData.icsActive) {
11331 /* Note: tournament rules require draw offers to be
11332 made after you make your move but before you punch
11333 your clock. Currently ICS doesn't let you do that;
11334 instead, you immediately punch your clock after making
11335 a move, but you can offer a draw at any time. */
11337 SendToICS(ics_prefix);
11338 SendToICS("draw\n");
11339 } else if (cmailMsgLoaded) {
11340 if (currentMove == cmailOldMove &&
11341 commentList[cmailOldMove] != NULL &&
11342 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11343 "Black offers a draw" : "White offers a draw")) {
11344 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11345 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11346 } else if (currentMove == cmailOldMove + 1) {
11347 char *offer = WhiteOnMove(cmailOldMove) ?
11348 "White offers a draw" : "Black offers a draw";
11349 AppendComment(currentMove, offer);
11350 DisplayComment(currentMove - 1, offer);
11351 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11353 DisplayError(_("You must make your move before offering a draw"), 0);
11354 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11356 } else if (first.offeredDraw) {
11357 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11359 if (first.sendDrawOffers) {
11360 SendToProgram("draw\n", &first);
11361 userOfferedDraw = TRUE;
11369 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11371 if (appData.icsActive) {
11372 SendToICS(ics_prefix);
11373 SendToICS("adjourn\n");
11375 /* Currently GNU Chess doesn't offer or accept Adjourns */
11383 /* Offer Abort or accept pending Abort offer from opponent */
11385 if (appData.icsActive) {
11386 SendToICS(ics_prefix);
11387 SendToICS("abort\n");
11389 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11396 /* Resign. You can do this even if it's not your turn. */
11398 if (appData.icsActive) {
11399 SendToICS(ics_prefix);
11400 SendToICS("resign\n");
11402 switch (gameMode) {
11403 case MachinePlaysWhite:
11404 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11406 case MachinePlaysBlack:
11407 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11410 if (cmailMsgLoaded) {
11412 if (WhiteOnMove(cmailOldMove)) {
11413 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11415 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11417 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11428 StopObservingEvent()
11430 /* Stop observing current games */
11431 SendToICS(ics_prefix);
11432 SendToICS("unobserve\n");
11436 StopExaminingEvent()
11438 /* Stop observing current game */
11439 SendToICS(ics_prefix);
11440 SendToICS("unexamine\n");
11444 ForwardInner(target)
11449 if (appData.debugMode)
11450 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11451 target, currentMove, forwardMostMove);
11453 if (gameMode == EditPosition)
11456 if (gameMode == PlayFromGameFile && !pausing)
11459 if (gameMode == IcsExamining && pausing)
11460 limit = pauseExamForwardMostMove;
11462 limit = forwardMostMove;
11464 if (target > limit) target = limit;
11466 if (target > 0 && moveList[target - 1][0]) {
11467 int fromX, fromY, toX, toY;
11468 toX = moveList[target - 1][2] - AAA;
11469 toY = moveList[target - 1][3] - ONE;
11470 if (moveList[target - 1][1] == '@') {
11471 if (appData.highlightLastMove) {
11472 SetHighlights(-1, -1, toX, toY);
11475 fromX = moveList[target - 1][0] - AAA;
11476 fromY = moveList[target - 1][1] - ONE;
11477 if (target == currentMove + 1) {
11478 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11480 if (appData.highlightLastMove) {
11481 SetHighlights(fromX, fromY, toX, toY);
11485 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11486 gameMode == Training || gameMode == PlayFromGameFile ||
11487 gameMode == AnalyzeFile) {
11488 while (currentMove < target) {
11489 SendMoveToProgram(currentMove++, &first);
11492 currentMove = target;
11495 if (gameMode == EditGame || gameMode == EndOfGame) {
11496 whiteTimeRemaining = timeRemaining[0][currentMove];
11497 blackTimeRemaining = timeRemaining[1][currentMove];
11499 DisplayBothClocks();
11500 DisplayMove(currentMove - 1);
11501 DrawPosition(FALSE, boards[currentMove]);
11502 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11503 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11504 DisplayComment(currentMove - 1, commentList[currentMove]);
11512 if (gameMode == IcsExamining && !pausing) {
11513 SendToICS(ics_prefix);
11514 SendToICS("forward\n");
11516 ForwardInner(currentMove + 1);
11523 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11524 /* to optimze, we temporarily turn off analysis mode while we feed
11525 * the remaining moves to the engine. Otherwise we get analysis output
11528 if (first.analysisSupport) {
11529 SendToProgram("exit\nforce\n", &first);
11530 first.analyzing = FALSE;
11534 if (gameMode == IcsExamining && !pausing) {
11535 SendToICS(ics_prefix);
11536 SendToICS("forward 999999\n");
11538 ForwardInner(forwardMostMove);
11541 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11542 /* we have fed all the moves, so reactivate analysis mode */
11543 SendToProgram("analyze\n", &first);
11544 first.analyzing = TRUE;
11545 /*first.maybeThinking = TRUE;*/
11546 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11551 BackwardInner(target)
11554 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11556 if (appData.debugMode)
11557 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11558 target, currentMove, forwardMostMove);
11560 if (gameMode == EditPosition) return;
11561 if (currentMove <= backwardMostMove) {
11563 DrawPosition(full_redraw, boards[currentMove]);
11566 if (gameMode == PlayFromGameFile && !pausing)
11569 if (moveList[target][0]) {
11570 int fromX, fromY, toX, toY;
11571 toX = moveList[target][2] - AAA;
11572 toY = moveList[target][3] - ONE;
11573 if (moveList[target][1] == '@') {
11574 if (appData.highlightLastMove) {
11575 SetHighlights(-1, -1, toX, toY);
11578 fromX = moveList[target][0] - AAA;
11579 fromY = moveList[target][1] - ONE;
11580 if (target == currentMove - 1) {
11581 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11583 if (appData.highlightLastMove) {
11584 SetHighlights(fromX, fromY, toX, toY);
11588 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11589 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11590 while (currentMove > target) {
11591 SendToProgram("undo\n", &first);
11595 currentMove = target;
11598 if (gameMode == EditGame || gameMode == EndOfGame) {
11599 whiteTimeRemaining = timeRemaining[0][currentMove];
11600 blackTimeRemaining = timeRemaining[1][currentMove];
11602 DisplayBothClocks();
11603 DisplayMove(currentMove - 1);
11604 DrawPosition(full_redraw, boards[currentMove]);
11605 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11606 // [HGM] PV info: routine tests if comment empty
11607 DisplayComment(currentMove - 1, commentList[currentMove]);
11613 if (gameMode == IcsExamining && !pausing) {
11614 SendToICS(ics_prefix);
11615 SendToICS("backward\n");
11617 BackwardInner(currentMove - 1);
11624 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11625 /* to optimze, we temporarily turn off analysis mode while we undo
11626 * all the moves. Otherwise we get analysis output after each undo.
11628 if (first.analysisSupport) {
11629 SendToProgram("exit\nforce\n", &first);
11630 first.analyzing = FALSE;
11634 if (gameMode == IcsExamining && !pausing) {
11635 SendToICS(ics_prefix);
11636 SendToICS("backward 999999\n");
11638 BackwardInner(backwardMostMove);
11641 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11642 /* we have fed all the moves, so reactivate analysis mode */
11643 SendToProgram("analyze\n", &first);
11644 first.analyzing = TRUE;
11645 /*first.maybeThinking = TRUE;*/
11646 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11653 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11654 if (to >= forwardMostMove) to = forwardMostMove;
11655 if (to <= backwardMostMove) to = backwardMostMove;
11656 if (to < currentMove) {
11666 if (gameMode != IcsExamining) {
11667 DisplayError(_("You are not examining a game"), 0);
11671 DisplayError(_("You can't revert while pausing"), 0);
11674 SendToICS(ics_prefix);
11675 SendToICS("revert\n");
11681 switch (gameMode) {
11682 case MachinePlaysWhite:
11683 case MachinePlaysBlack:
11684 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11685 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11688 if (forwardMostMove < 2) return;
11689 currentMove = forwardMostMove = forwardMostMove - 2;
11690 whiteTimeRemaining = timeRemaining[0][currentMove];
11691 blackTimeRemaining = timeRemaining[1][currentMove];
11692 DisplayBothClocks();
11693 DisplayMove(currentMove - 1);
11694 ClearHighlights();/*!! could figure this out*/
11695 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11696 SendToProgram("remove\n", &first);
11697 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11700 case BeginningOfGame:
11704 case IcsPlayingWhite:
11705 case IcsPlayingBlack:
11706 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11707 SendToICS(ics_prefix);
11708 SendToICS("takeback 2\n");
11710 SendToICS(ics_prefix);
11711 SendToICS("takeback 1\n");
11720 ChessProgramState *cps;
11722 switch (gameMode) {
11723 case MachinePlaysWhite:
11724 if (!WhiteOnMove(forwardMostMove)) {
11725 DisplayError(_("It is your turn"), 0);
11730 case MachinePlaysBlack:
11731 if (WhiteOnMove(forwardMostMove)) {
11732 DisplayError(_("It is your turn"), 0);
11737 case TwoMachinesPlay:
11738 if (WhiteOnMove(forwardMostMove) ==
11739 (first.twoMachinesColor[0] == 'w')) {
11745 case BeginningOfGame:
11749 SendToProgram("?\n", cps);
11753 TruncateGameEvent()
11756 if (gameMode != EditGame) return;
11763 if (forwardMostMove > currentMove) {
11764 if (gameInfo.resultDetails != NULL) {
11765 free(gameInfo.resultDetails);
11766 gameInfo.resultDetails = NULL;
11767 gameInfo.result = GameUnfinished;
11769 forwardMostMove = currentMove;
11770 HistorySet(parseList, backwardMostMove, forwardMostMove,
11778 if (appData.noChessProgram) return;
11779 switch (gameMode) {
11780 case MachinePlaysWhite:
11781 if (WhiteOnMove(forwardMostMove)) {
11782 DisplayError(_("Wait until your turn"), 0);
11786 case BeginningOfGame:
11787 case MachinePlaysBlack:
11788 if (!WhiteOnMove(forwardMostMove)) {
11789 DisplayError(_("Wait until your turn"), 0);
11794 DisplayError(_("No hint available"), 0);
11797 SendToProgram("hint\n", &first);
11798 hintRequested = TRUE;
11804 if (appData.noChessProgram) return;
11805 switch (gameMode) {
11806 case MachinePlaysWhite:
11807 if (WhiteOnMove(forwardMostMove)) {
11808 DisplayError(_("Wait until your turn"), 0);
11812 case BeginningOfGame:
11813 case MachinePlaysBlack:
11814 if (!WhiteOnMove(forwardMostMove)) {
11815 DisplayError(_("Wait until your turn"), 0);
11820 EditPositionDone();
11822 case TwoMachinesPlay:
11827 SendToProgram("bk\n", &first);
11828 bookOutput[0] = NULLCHAR;
11829 bookRequested = TRUE;
11835 char *tags = PGNTags(&gameInfo);
11836 TagsPopUp(tags, CmailMsg());
11840 /* end button procedures */
11843 PrintPosition(fp, move)
11849 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11850 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11851 char c = PieceToChar(boards[move][i][j]);
11852 fputc(c == 'x' ? '.' : c, fp);
11853 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11856 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11857 fprintf(fp, "white to play\n");
11859 fprintf(fp, "black to play\n");
11866 if (gameInfo.white != NULL) {
11867 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11873 /* Find last component of program's own name, using some heuristics */
11875 TidyProgramName(prog, host, buf)
11876 char *prog, *host, buf[MSG_SIZ];
11879 int local = (strcmp(host, "localhost") == 0);
11880 while (!local && (p = strchr(prog, ';')) != NULL) {
11882 while (*p == ' ') p++;
11885 if (*prog == '"' || *prog == '\'') {
11886 q = strchr(prog + 1, *prog);
11888 q = strchr(prog, ' ');
11890 if (q == NULL) q = prog + strlen(prog);
11892 while (p >= prog && *p != '/' && *p != '\\') p--;
11894 if(p == prog && *p == '"') p++;
11895 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11896 memcpy(buf, p, q - p);
11897 buf[q - p] = NULLCHAR;
11905 TimeControlTagValue()
11908 if (!appData.clockMode) {
11910 } else if (movesPerSession > 0) {
11911 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11912 } else if (timeIncrement == 0) {
11913 sprintf(buf, "%ld", timeControl/1000);
11915 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11917 return StrSave(buf);
11923 /* This routine is used only for certain modes */
11924 VariantClass v = gameInfo.variant;
11925 ClearGameInfo(&gameInfo);
11926 gameInfo.variant = v;
11928 switch (gameMode) {
11929 case MachinePlaysWhite:
11930 gameInfo.event = StrSave( appData.pgnEventHeader );
11931 gameInfo.site = StrSave(HostName());
11932 gameInfo.date = PGNDate();
11933 gameInfo.round = StrSave("-");
11934 gameInfo.white = StrSave(first.tidy);
11935 gameInfo.black = StrSave(UserName());
11936 gameInfo.timeControl = TimeControlTagValue();
11939 case MachinePlaysBlack:
11940 gameInfo.event = StrSave( appData.pgnEventHeader );
11941 gameInfo.site = StrSave(HostName());
11942 gameInfo.date = PGNDate();
11943 gameInfo.round = StrSave("-");
11944 gameInfo.white = StrSave(UserName());
11945 gameInfo.black = StrSave(first.tidy);
11946 gameInfo.timeControl = TimeControlTagValue();
11949 case TwoMachinesPlay:
11950 gameInfo.event = StrSave( appData.pgnEventHeader );
11951 gameInfo.site = StrSave(HostName());
11952 gameInfo.date = PGNDate();
11953 if (matchGame > 0) {
11955 sprintf(buf, "%d", matchGame);
11956 gameInfo.round = StrSave(buf);
11958 gameInfo.round = StrSave("-");
11960 if (first.twoMachinesColor[0] == 'w') {
11961 gameInfo.white = StrSave(first.tidy);
11962 gameInfo.black = StrSave(second.tidy);
11964 gameInfo.white = StrSave(second.tidy);
11965 gameInfo.black = StrSave(first.tidy);
11967 gameInfo.timeControl = TimeControlTagValue();
11971 gameInfo.event = StrSave("Edited game");
11972 gameInfo.site = StrSave(HostName());
11973 gameInfo.date = PGNDate();
11974 gameInfo.round = StrSave("-");
11975 gameInfo.white = StrSave("-");
11976 gameInfo.black = StrSave("-");
11980 gameInfo.event = StrSave("Edited position");
11981 gameInfo.site = StrSave(HostName());
11982 gameInfo.date = PGNDate();
11983 gameInfo.round = StrSave("-");
11984 gameInfo.white = StrSave("-");
11985 gameInfo.black = StrSave("-");
11988 case IcsPlayingWhite:
11989 case IcsPlayingBlack:
11994 case PlayFromGameFile:
11995 gameInfo.event = StrSave("Game from non-PGN file");
11996 gameInfo.site = StrSave(HostName());
11997 gameInfo.date = PGNDate();
11998 gameInfo.round = StrSave("-");
11999 gameInfo.white = StrSave("?");
12000 gameInfo.black = StrSave("?");
12009 ReplaceComment(index, text)
12015 while (*text == '\n') text++;
12016 len = strlen(text);
12017 while (len > 0 && text[len - 1] == '\n') len--;
12019 if (commentList[index] != NULL)
12020 free(commentList[index]);
12023 commentList[index] = NULL;
12026 commentList[index] = (char *) malloc(len + 2);
12027 strncpy(commentList[index], text, len);
12028 commentList[index][len] = '\n';
12029 commentList[index][len + 1] = NULLCHAR;
12042 if (ch == '\r') continue;
12044 } while (ch != '\0');
12048 AppendComment(index, text)
12055 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12058 while (*text == '\n') text++;
12059 len = strlen(text);
12060 while (len > 0 && text[len - 1] == '\n') len--;
12062 if (len == 0) return;
12064 if (commentList[index] != NULL) {
12065 old = commentList[index];
12066 oldlen = strlen(old);
12067 commentList[index] = (char *) malloc(oldlen + len + 2);
12068 strcpy(commentList[index], old);
12070 strncpy(&commentList[index][oldlen], text, len);
12071 commentList[index][oldlen + len] = '\n';
12072 commentList[index][oldlen + len + 1] = NULLCHAR;
12074 commentList[index] = (char *) malloc(len + 2);
12075 strncpy(commentList[index], text, len);
12076 commentList[index][len] = '\n';
12077 commentList[index][len + 1] = NULLCHAR;
12081 static char * FindStr( char * text, char * sub_text )
12083 char * result = strstr( text, sub_text );
12085 if( result != NULL ) {
12086 result += strlen( sub_text );
12092 /* [AS] Try to extract PV info from PGN comment */
12093 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12094 char *GetInfoFromComment( int index, char * text )
12098 if( text != NULL && index > 0 ) {
12101 int time = -1, sec = 0, deci;
12102 char * s_eval = FindStr( text, "[%eval " );
12103 char * s_emt = FindStr( text, "[%emt " );
12105 if( s_eval != NULL || s_emt != NULL ) {
12109 if( s_eval != NULL ) {
12110 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12114 if( delim != ']' ) {
12119 if( s_emt != NULL ) {
12123 /* We expect something like: [+|-]nnn.nn/dd */
12126 sep = strchr( text, '/' );
12127 if( sep == NULL || sep < (text+4) ) {
12131 time = -1; sec = -1; deci = -1;
12132 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12133 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12134 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12135 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12139 if( score_lo < 0 || score_lo >= 100 ) {
12143 if(sec >= 0) time = 600*time + 10*sec; else
12144 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12146 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12148 /* [HGM] PV time: now locate end of PV info */
12149 while( *++sep >= '0' && *sep <= '9'); // strip depth
12151 while( *++sep >= '0' && *sep <= '9'); // strip time
12153 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12155 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12156 while(*sep == ' ') sep++;
12167 pvInfoList[index-1].depth = depth;
12168 pvInfoList[index-1].score = score;
12169 pvInfoList[index-1].time = 10*time; // centi-sec
12175 SendToProgram(message, cps)
12177 ChessProgramState *cps;
12179 int count, outCount, error;
12182 if (cps->pr == NULL) return;
12185 if (appData.debugMode) {
12188 fprintf(debugFP, "%ld >%-6s: %s",
12189 SubtractTimeMarks(&now, &programStartTime),
12190 cps->which, message);
12193 count = strlen(message);
12194 outCount = OutputToProcess(cps->pr, message, count, &error);
12195 if (outCount < count && !exiting
12196 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12197 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12198 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12199 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12200 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12201 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12203 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12205 gameInfo.resultDetails = buf;
12207 DisplayFatalError(buf, error, 1);
12212 ReceiveFromProgram(isr, closure, message, count, error)
12213 InputSourceRef isr;
12221 ChessProgramState *cps = (ChessProgramState *)closure;
12223 if (isr != cps->isr) return; /* Killed intentionally */
12227 _("Error: %s chess program (%s) exited unexpectedly"),
12228 cps->which, cps->program);
12229 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12230 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12231 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12232 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12234 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12236 gameInfo.resultDetails = buf;
12238 RemoveInputSource(cps->isr);
12239 DisplayFatalError(buf, 0, 1);
12242 _("Error reading from %s chess program (%s)"),
12243 cps->which, cps->program);
12244 RemoveInputSource(cps->isr);
12246 /* [AS] Program is misbehaving badly... kill it */
12247 if( count == -2 ) {
12248 DestroyChildProcess( cps->pr, 9 );
12252 DisplayFatalError(buf, error, 1);
12257 if ((end_str = strchr(message, '\r')) != NULL)
12258 *end_str = NULLCHAR;
12259 if ((end_str = strchr(message, '\n')) != NULL)
12260 *end_str = NULLCHAR;
12262 if (appData.debugMode) {
12263 TimeMark now; int print = 1;
12264 char *quote = ""; char c; int i;
12266 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12267 char start = message[0];
12268 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12269 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12270 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12271 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12272 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12273 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12274 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12275 { quote = "# "; print = (appData.engineComments == 2); }
12276 message[0] = start; // restore original message
12280 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12281 SubtractTimeMarks(&now, &programStartTime), cps->which,
12287 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12288 if (appData.icsEngineAnalyze) {
12289 if (strstr(message, "whisper") != NULL ||
12290 strstr(message, "kibitz") != NULL ||
12291 strstr(message, "tellics") != NULL) return;
12294 HandleMachineMove(message, cps);
12299 SendTimeControl(cps, mps, tc, inc, sd, st)
12300 ChessProgramState *cps;
12301 int mps, inc, sd, st;
12307 if( timeControl_2 > 0 ) {
12308 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12309 tc = timeControl_2;
12312 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12313 inc /= cps->timeOdds;
12314 st /= cps->timeOdds;
12316 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12319 /* Set exact time per move, normally using st command */
12320 if (cps->stKludge) {
12321 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12323 if (seconds == 0) {
12324 sprintf(buf, "level 1 %d\n", st/60);
12326 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12329 sprintf(buf, "st %d\n", st);
12332 /* Set conventional or incremental time control, using level command */
12333 if (seconds == 0) {
12334 /* Note old gnuchess bug -- minutes:seconds used to not work.
12335 Fixed in later versions, but still avoid :seconds
12336 when seconds is 0. */
12337 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12339 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12340 seconds, inc/1000);
12343 SendToProgram(buf, cps);
12345 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12346 /* Orthogonally, limit search to given depth */
12348 if (cps->sdKludge) {
12349 sprintf(buf, "depth\n%d\n", sd);
12351 sprintf(buf, "sd %d\n", sd);
12353 SendToProgram(buf, cps);
12356 if(cps->nps > 0) { /* [HGM] nps */
12357 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12359 sprintf(buf, "nps %d\n", cps->nps);
12360 SendToProgram(buf, cps);
12365 ChessProgramState *WhitePlayer()
12366 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12368 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12369 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12375 SendTimeRemaining(cps, machineWhite)
12376 ChessProgramState *cps;
12377 int /*boolean*/ machineWhite;
12379 char message[MSG_SIZ];
12382 /* Note: this routine must be called when the clocks are stopped
12383 or when they have *just* been set or switched; otherwise
12384 it will be off by the time since the current tick started.
12386 if (machineWhite) {
12387 time = whiteTimeRemaining / 10;
12388 otime = blackTimeRemaining / 10;
12390 time = blackTimeRemaining / 10;
12391 otime = whiteTimeRemaining / 10;
12393 /* [HGM] translate opponent's time by time-odds factor */
12394 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12395 if (appData.debugMode) {
12396 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12399 if (time <= 0) time = 1;
12400 if (otime <= 0) otime = 1;
12402 sprintf(message, "time %ld\n", time);
12403 SendToProgram(message, cps);
12405 sprintf(message, "otim %ld\n", otime);
12406 SendToProgram(message, cps);
12410 BoolFeature(p, name, loc, cps)
12414 ChessProgramState *cps;
12417 int len = strlen(name);
12419 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12421 sscanf(*p, "%d", &val);
12423 while (**p && **p != ' ') (*p)++;
12424 sprintf(buf, "accepted %s\n", name);
12425 SendToProgram(buf, cps);
12432 IntFeature(p, name, loc, cps)
12436 ChessProgramState *cps;
12439 int len = strlen(name);
12440 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12442 sscanf(*p, "%d", loc);
12443 while (**p && **p != ' ') (*p)++;
12444 sprintf(buf, "accepted %s\n", name);
12445 SendToProgram(buf, cps);
12452 StringFeature(p, name, loc, cps)
12456 ChessProgramState *cps;
12459 int len = strlen(name);
12460 if (strncmp((*p), name, len) == 0
12461 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12463 sscanf(*p, "%[^\"]", loc);
12464 while (**p && **p != '\"') (*p)++;
12465 if (**p == '\"') (*p)++;
12466 sprintf(buf, "accepted %s\n", name);
12467 SendToProgram(buf, cps);
12474 ParseOption(Option *opt, ChessProgramState *cps)
12475 // [HGM] options: process the string that defines an engine option, and determine
12476 // name, type, default value, and allowed value range
12478 char *p, *q, buf[MSG_SIZ];
12479 int n, min = (-1)<<31, max = 1<<31, def;
12481 if(p = strstr(opt->name, " -spin ")) {
12482 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12483 if(max < min) max = min; // enforce consistency
12484 if(def < min) def = min;
12485 if(def > max) def = max;
12490 } else if((p = strstr(opt->name, " -slider "))) {
12491 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12492 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12493 if(max < min) max = min; // enforce consistency
12494 if(def < min) def = min;
12495 if(def > max) def = max;
12499 opt->type = Spin; // Slider;
12500 } else if((p = strstr(opt->name, " -string "))) {
12501 opt->textValue = p+9;
12502 opt->type = TextBox;
12503 } else if((p = strstr(opt->name, " -file "))) {
12504 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12505 opt->textValue = p+7;
12506 opt->type = TextBox; // FileName;
12507 } else if((p = strstr(opt->name, " -path "))) {
12508 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12509 opt->textValue = p+7;
12510 opt->type = TextBox; // PathName;
12511 } else if(p = strstr(opt->name, " -check ")) {
12512 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12513 opt->value = (def != 0);
12514 opt->type = CheckBox;
12515 } else if(p = strstr(opt->name, " -combo ")) {
12516 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12517 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12518 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12519 opt->value = n = 0;
12520 while(q = StrStr(q, " /// ")) {
12521 n++; *q = 0; // count choices, and null-terminate each of them
12523 if(*q == '*') { // remember default, which is marked with * prefix
12527 cps->comboList[cps->comboCnt++] = q;
12529 cps->comboList[cps->comboCnt++] = NULL;
12531 opt->type = ComboBox;
12532 } else if(p = strstr(opt->name, " -button")) {
12533 opt->type = Button;
12534 } else if(p = strstr(opt->name, " -save")) {
12535 opt->type = SaveButton;
12536 } else return FALSE;
12537 *p = 0; // terminate option name
12538 // now look if the command-line options define a setting for this engine option.
12539 if(cps->optionSettings && cps->optionSettings[0])
12540 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12541 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12542 sprintf(buf, "option %s", p);
12543 if(p = strstr(buf, ",")) *p = 0;
12545 SendToProgram(buf, cps);
12551 FeatureDone(cps, val)
12552 ChessProgramState* cps;
12555 DelayedEventCallback cb = GetDelayedEvent();
12556 if ((cb == InitBackEnd3 && cps == &first) ||
12557 (cb == TwoMachinesEventIfReady && cps == &second)) {
12558 CancelDelayedEvent();
12559 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12561 cps->initDone = val;
12564 /* Parse feature command from engine */
12566 ParseFeatures(args, cps)
12568 ChessProgramState *cps;
12576 while (*p == ' ') p++;
12577 if (*p == NULLCHAR) return;
12579 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12580 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12581 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12582 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12583 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12584 if (BoolFeature(&p, "reuse", &val, cps)) {
12585 /* Engine can disable reuse, but can't enable it if user said no */
12586 if (!val) cps->reuse = FALSE;
12589 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12590 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12591 if (gameMode == TwoMachinesPlay) {
12592 DisplayTwoMachinesTitle();
12598 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12599 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12600 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12601 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12602 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12603 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12604 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12605 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12606 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12607 if (IntFeature(&p, "done", &val, cps)) {
12608 FeatureDone(cps, val);
12611 /* Added by Tord: */
12612 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12613 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12614 /* End of additions by Tord */
12616 /* [HGM] added features: */
12617 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12618 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12619 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12620 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12621 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12622 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12623 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12624 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12625 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12626 SendToProgram(buf, cps);
12629 if(cps->nrOptions >= MAX_OPTIONS) {
12631 sprintf(buf, "%s engine has too many options\n", cps->which);
12632 DisplayError(buf, 0);
12636 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12637 /* End of additions by HGM */
12639 /* unknown feature: complain and skip */
12641 while (*q && *q != '=') q++;
12642 sprintf(buf, "rejected %.*s\n", q-p, p);
12643 SendToProgram(buf, cps);
12649 while (*p && *p != '\"') p++;
12650 if (*p == '\"') p++;
12652 while (*p && *p != ' ') p++;
12660 PeriodicUpdatesEvent(newState)
12663 if (newState == appData.periodicUpdates)
12666 appData.periodicUpdates=newState;
12668 /* Display type changes, so update it now */
12671 /* Get the ball rolling again... */
12673 AnalysisPeriodicEvent(1);
12674 StartAnalysisClock();
12679 PonderNextMoveEvent(newState)
12682 if (newState == appData.ponderNextMove) return;
12683 if (gameMode == EditPosition) EditPositionDone();
12685 SendToProgram("hard\n", &first);
12686 if (gameMode == TwoMachinesPlay) {
12687 SendToProgram("hard\n", &second);
12690 SendToProgram("easy\n", &first);
12691 thinkOutput[0] = NULLCHAR;
12692 if (gameMode == TwoMachinesPlay) {
12693 SendToProgram("easy\n", &second);
12696 appData.ponderNextMove = newState;
12700 NewSettingEvent(option, command, value)
12706 if (gameMode == EditPosition) EditPositionDone();
12707 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12708 SendToProgram(buf, &first);
12709 if (gameMode == TwoMachinesPlay) {
12710 SendToProgram(buf, &second);
12715 ShowThinkingEvent()
12716 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12718 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12719 int newState = appData.showThinking
12720 // [HGM] thinking: other features now need thinking output as well
12721 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12723 if (oldState == newState) return;
12724 oldState = newState;
12725 if (gameMode == EditPosition) EditPositionDone();
12727 SendToProgram("post\n", &first);
12728 if (gameMode == TwoMachinesPlay) {
12729 SendToProgram("post\n", &second);
12732 SendToProgram("nopost\n", &first);
12733 thinkOutput[0] = NULLCHAR;
12734 if (gameMode == TwoMachinesPlay) {
12735 SendToProgram("nopost\n", &second);
12738 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12742 AskQuestionEvent(title, question, replyPrefix, which)
12743 char *title; char *question; char *replyPrefix; char *which;
12745 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12746 if (pr == NoProc) return;
12747 AskQuestion(title, question, replyPrefix, pr);
12751 DisplayMove(moveNumber)
12754 char message[MSG_SIZ];
12756 char cpThinkOutput[MSG_SIZ];
12758 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12760 if (moveNumber == forwardMostMove - 1 ||
12761 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12763 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12765 if (strchr(cpThinkOutput, '\n')) {
12766 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12769 *cpThinkOutput = NULLCHAR;
12772 /* [AS] Hide thinking from human user */
12773 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12774 *cpThinkOutput = NULLCHAR;
12775 if( thinkOutput[0] != NULLCHAR ) {
12778 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12779 cpThinkOutput[i] = '.';
12781 cpThinkOutput[i] = NULLCHAR;
12782 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12786 if (moveNumber == forwardMostMove - 1 &&
12787 gameInfo.resultDetails != NULL) {
12788 if (gameInfo.resultDetails[0] == NULLCHAR) {
12789 sprintf(res, " %s", PGNResult(gameInfo.result));
12791 sprintf(res, " {%s} %s",
12792 gameInfo.resultDetails, PGNResult(gameInfo.result));
12798 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12799 DisplayMessage(res, cpThinkOutput);
12801 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12802 WhiteOnMove(moveNumber) ? " " : ".. ",
12803 parseList[moveNumber], res);
12804 DisplayMessage(message, cpThinkOutput);
12809 DisplayAnalysisText(text)
12814 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12815 || appData.icsEngineAnalyze) {
12816 sprintf(buf, "Analysis (%s)", first.tidy);
12817 AnalysisPopUp(buf, text);
12825 while (*str && isspace(*str)) ++str;
12826 while (*str && !isspace(*str)) ++str;
12827 if (!*str) return 1;
12828 while (*str && isspace(*str)) ++str;
12829 if (!*str) return 1;
12837 char lst[MSG_SIZ / 2];
12839 static char *xtra[] = { "", " (--)", " (++)" };
12842 if (programStats.time == 0) {
12843 programStats.time = 1;
12846 if (programStats.got_only_move) {
12847 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12849 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12851 nps = (u64ToDouble(programStats.nodes) /
12852 ((double)programStats.time /100.0));
12854 cs = programStats.time % 100;
12855 s = programStats.time / 100;
12861 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12862 if (programStats.move_name[0] != NULLCHAR) {
12863 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12864 programStats.depth,
12865 programStats.nr_moves-programStats.moves_left,
12866 programStats.nr_moves, programStats.move_name,
12867 ((float)programStats.score)/100.0, lst,
12868 only_one_move(lst)?
12869 xtra[programStats.got_fail] : "",
12870 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12872 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12873 programStats.depth,
12874 programStats.nr_moves-programStats.moves_left,
12875 programStats.nr_moves, ((float)programStats.score)/100.0,
12877 only_one_move(lst)?
12878 xtra[programStats.got_fail] : "",
12879 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12882 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12883 programStats.depth,
12884 ((float)programStats.score)/100.0,
12886 only_one_move(lst)?
12887 xtra[programStats.got_fail] : "",
12888 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12891 DisplayAnalysisText(buf);
12895 DisplayComment(moveNumber, text)
12899 char title[MSG_SIZ];
12900 char buf[8000]; // comment can be long!
12903 if( appData.autoDisplayComment ) {
12904 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12905 strcpy(title, "Comment");
12907 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12908 WhiteOnMove(moveNumber) ? " " : ".. ",
12909 parseList[moveNumber]);
12911 // [HGM] PV info: display PV info together with (or as) comment
12912 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12913 if(text == NULL) text = "";
12914 score = pvInfoList[moveNumber].score;
12915 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12916 depth, (pvInfoList[moveNumber].time+50)/100, text);
12919 } else title[0] = 0;
12922 CommentPopUp(title, text);
12925 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12926 * might be busy thinking or pondering. It can be omitted if your
12927 * gnuchess is configured to stop thinking immediately on any user
12928 * input. However, that gnuchess feature depends on the FIONREAD
12929 * ioctl, which does not work properly on some flavors of Unix.
12933 ChessProgramState *cps;
12936 if (!cps->useSigint) return;
12937 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12938 switch (gameMode) {
12939 case MachinePlaysWhite:
12940 case MachinePlaysBlack:
12941 case TwoMachinesPlay:
12942 case IcsPlayingWhite:
12943 case IcsPlayingBlack:
12946 /* Skip if we know it isn't thinking */
12947 if (!cps->maybeThinking) return;
12948 if (appData.debugMode)
12949 fprintf(debugFP, "Interrupting %s\n", cps->which);
12950 InterruptChildProcess(cps->pr);
12951 cps->maybeThinking = FALSE;
12956 #endif /*ATTENTION*/
12962 if (whiteTimeRemaining <= 0) {
12965 if (appData.icsActive) {
12966 if (appData.autoCallFlag &&
12967 gameMode == IcsPlayingBlack && !blackFlag) {
12968 SendToICS(ics_prefix);
12969 SendToICS("flag\n");
12973 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12975 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12976 if (appData.autoCallFlag) {
12977 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12984 if (blackTimeRemaining <= 0) {
12987 if (appData.icsActive) {
12988 if (appData.autoCallFlag &&
12989 gameMode == IcsPlayingWhite && !whiteFlag) {
12990 SendToICS(ics_prefix);
12991 SendToICS("flag\n");
12995 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12997 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12998 if (appData.autoCallFlag) {
12999 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13012 if (!appData.clockMode || appData.icsActive ||
13013 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13016 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13018 if ( !WhiteOnMove(forwardMostMove) )
13019 /* White made time control */
13020 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13021 /* [HGM] time odds: correct new time quota for time odds! */
13022 / WhitePlayer()->timeOdds;
13024 /* Black made time control */
13025 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13026 / WhitePlayer()->other->timeOdds;
13030 DisplayBothClocks()
13032 int wom = gameMode == EditPosition ?
13033 !blackPlaysFirst : WhiteOnMove(currentMove);
13034 DisplayWhiteClock(whiteTimeRemaining, wom);
13035 DisplayBlackClock(blackTimeRemaining, !wom);
13039 /* Timekeeping seems to be a portability nightmare. I think everyone
13040 has ftime(), but I'm really not sure, so I'm including some ifdefs
13041 to use other calls if you don't. Clocks will be less accurate if
13042 you have neither ftime nor gettimeofday.
13045 /* VS 2008 requires the #include outside of the function */
13046 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13047 #include <sys/timeb.h>
13050 /* Get the current time as a TimeMark */
13055 #if HAVE_GETTIMEOFDAY
13057 struct timeval timeVal;
13058 struct timezone timeZone;
13060 gettimeofday(&timeVal, &timeZone);
13061 tm->sec = (long) timeVal.tv_sec;
13062 tm->ms = (int) (timeVal.tv_usec / 1000L);
13064 #else /*!HAVE_GETTIMEOFDAY*/
13067 // include <sys/timeb.h> / moved to just above start of function
13068 struct timeb timeB;
13071 tm->sec = (long) timeB.time;
13072 tm->ms = (int) timeB.millitm;
13074 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13075 tm->sec = (long) time(NULL);
13081 /* Return the difference in milliseconds between two
13082 time marks. We assume the difference will fit in a long!
13085 SubtractTimeMarks(tm2, tm1)
13086 TimeMark *tm2, *tm1;
13088 return 1000L*(tm2->sec - tm1->sec) +
13089 (long) (tm2->ms - tm1->ms);
13094 * Code to manage the game clocks.
13096 * In tournament play, black starts the clock and then white makes a move.
13097 * We give the human user a slight advantage if he is playing white---the
13098 * clocks don't run until he makes his first move, so it takes zero time.
13099 * Also, we don't account for network lag, so we could get out of sync
13100 * with GNU Chess's clock -- but then, referees are always right.
13103 static TimeMark tickStartTM;
13104 static long intendedTickLength;
13107 NextTickLength(timeRemaining)
13108 long timeRemaining;
13110 long nominalTickLength, nextTickLength;
13112 if (timeRemaining > 0L && timeRemaining <= 10000L)
13113 nominalTickLength = 100L;
13115 nominalTickLength = 1000L;
13116 nextTickLength = timeRemaining % nominalTickLength;
13117 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13119 return nextTickLength;
13122 /* Adjust clock one minute up or down */
13124 AdjustClock(Boolean which, int dir)
13126 if(which) blackTimeRemaining += 60000*dir;
13127 else whiteTimeRemaining += 60000*dir;
13128 DisplayBothClocks();
13131 /* Stop clocks and reset to a fresh time control */
13135 (void) StopClockTimer();
13136 if (appData.icsActive) {
13137 whiteTimeRemaining = blackTimeRemaining = 0;
13138 } else { /* [HGM] correct new time quote for time odds */
13139 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13140 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13142 if (whiteFlag || blackFlag) {
13144 whiteFlag = blackFlag = FALSE;
13146 DisplayBothClocks();
13149 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13151 /* Decrement running clock by amount of time that has passed */
13155 long timeRemaining;
13156 long lastTickLength, fudge;
13159 if (!appData.clockMode) return;
13160 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13164 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13166 /* Fudge if we woke up a little too soon */
13167 fudge = intendedTickLength - lastTickLength;
13168 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13170 if (WhiteOnMove(forwardMostMove)) {
13171 if(whiteNPS >= 0) lastTickLength = 0;
13172 timeRemaining = whiteTimeRemaining -= lastTickLength;
13173 DisplayWhiteClock(whiteTimeRemaining - fudge,
13174 WhiteOnMove(currentMove));
13176 if(blackNPS >= 0) lastTickLength = 0;
13177 timeRemaining = blackTimeRemaining -= lastTickLength;
13178 DisplayBlackClock(blackTimeRemaining - fudge,
13179 !WhiteOnMove(currentMove));
13182 if (CheckFlags()) return;
13185 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13186 StartClockTimer(intendedTickLength);
13188 /* if the time remaining has fallen below the alarm threshold, sound the
13189 * alarm. if the alarm has sounded and (due to a takeback or time control
13190 * with increment) the time remaining has increased to a level above the
13191 * threshold, reset the alarm so it can sound again.
13194 if (appData.icsActive && appData.icsAlarm) {
13196 /* make sure we are dealing with the user's clock */
13197 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13198 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13201 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13202 alarmSounded = FALSE;
13203 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13205 alarmSounded = TRUE;
13211 /* A player has just moved, so stop the previously running
13212 clock and (if in clock mode) start the other one.
13213 We redisplay both clocks in case we're in ICS mode, because
13214 ICS gives us an update to both clocks after every move.
13215 Note that this routine is called *after* forwardMostMove
13216 is updated, so the last fractional tick must be subtracted
13217 from the color that is *not* on move now.
13222 long lastTickLength;
13224 int flagged = FALSE;
13228 if (StopClockTimer() && appData.clockMode) {
13229 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13230 if (WhiteOnMove(forwardMostMove)) {
13231 if(blackNPS >= 0) lastTickLength = 0;
13232 blackTimeRemaining -= lastTickLength;
13233 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13234 // if(pvInfoList[forwardMostMove-1].time == -1)
13235 pvInfoList[forwardMostMove-1].time = // use GUI time
13236 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13238 if(whiteNPS >= 0) lastTickLength = 0;
13239 whiteTimeRemaining -= lastTickLength;
13240 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13241 // if(pvInfoList[forwardMostMove-1].time == -1)
13242 pvInfoList[forwardMostMove-1].time =
13243 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13245 flagged = CheckFlags();
13247 CheckTimeControl();
13249 if (flagged || !appData.clockMode) return;
13251 switch (gameMode) {
13252 case MachinePlaysBlack:
13253 case MachinePlaysWhite:
13254 case BeginningOfGame:
13255 if (pausing) return;
13259 case PlayFromGameFile:
13268 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13269 whiteTimeRemaining : blackTimeRemaining);
13270 StartClockTimer(intendedTickLength);
13274 /* Stop both clocks */
13278 long lastTickLength;
13281 if (!StopClockTimer()) return;
13282 if (!appData.clockMode) return;
13286 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13287 if (WhiteOnMove(forwardMostMove)) {
13288 if(whiteNPS >= 0) lastTickLength = 0;
13289 whiteTimeRemaining -= lastTickLength;
13290 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13292 if(blackNPS >= 0) lastTickLength = 0;
13293 blackTimeRemaining -= lastTickLength;
13294 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13299 /* Start clock of player on move. Time may have been reset, so
13300 if clock is already running, stop and restart it. */
13304 (void) StopClockTimer(); /* in case it was running already */
13305 DisplayBothClocks();
13306 if (CheckFlags()) return;
13308 if (!appData.clockMode) return;
13309 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13311 GetTimeMark(&tickStartTM);
13312 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13313 whiteTimeRemaining : blackTimeRemaining);
13315 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13316 whiteNPS = blackNPS = -1;
13317 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13318 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13319 whiteNPS = first.nps;
13320 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13321 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13322 blackNPS = first.nps;
13323 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13324 whiteNPS = second.nps;
13325 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13326 blackNPS = second.nps;
13327 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13329 StartClockTimer(intendedTickLength);
13336 long second, minute, hour, day;
13338 static char buf[32];
13340 if (ms > 0 && ms <= 9900) {
13341 /* convert milliseconds to tenths, rounding up */
13342 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13344 sprintf(buf, " %03.1f ", tenths/10.0);
13348 /* convert milliseconds to seconds, rounding up */
13349 /* use floating point to avoid strangeness of integer division
13350 with negative dividends on many machines */
13351 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13358 day = second / (60 * 60 * 24);
13359 second = second % (60 * 60 * 24);
13360 hour = second / (60 * 60);
13361 second = second % (60 * 60);
13362 minute = second / 60;
13363 second = second % 60;
13366 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13367 sign, day, hour, minute, second);
13369 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13371 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13378 * This is necessary because some C libraries aren't ANSI C compliant yet.
13381 StrStr(string, match)
13382 char *string, *match;
13386 length = strlen(match);
13388 for (i = strlen(string) - length; i >= 0; i--, string++)
13389 if (!strncmp(match, string, length))
13396 StrCaseStr(string, match)
13397 char *string, *match;
13401 length = strlen(match);
13403 for (i = strlen(string) - length; i >= 0; i--, string++) {
13404 for (j = 0; j < length; j++) {
13405 if (ToLower(match[j]) != ToLower(string[j]))
13408 if (j == length) return string;
13422 c1 = ToLower(*s1++);
13423 c2 = ToLower(*s2++);
13424 if (c1 > c2) return 1;
13425 if (c1 < c2) return -1;
13426 if (c1 == NULLCHAR) return 0;
13435 return isupper(c) ? tolower(c) : c;
13443 return islower(c) ? toupper(c) : c;
13445 #endif /* !_amigados */
13453 if ((ret = (char *) malloc(strlen(s) + 1))) {
13460 StrSavePtr(s, savePtr)
13461 char *s, **savePtr;
13466 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13467 strcpy(*savePtr, s);
13479 clock = time((time_t *)NULL);
13480 tm = localtime(&clock);
13481 sprintf(buf, "%04d.%02d.%02d",
13482 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13483 return StrSave(buf);
13488 PositionToFEN(move, overrideCastling)
13490 char *overrideCastling;
13492 int i, j, fromX, fromY, toX, toY;
13499 whiteToPlay = (gameMode == EditPosition) ?
13500 !blackPlaysFirst : (move % 2 == 0);
13503 /* Piece placement data */
13504 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13506 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13507 if (boards[move][i][j] == EmptySquare) {
13509 } else { ChessSquare piece = boards[move][i][j];
13510 if (emptycount > 0) {
13511 if(emptycount<10) /* [HGM] can be >= 10 */
13512 *p++ = '0' + emptycount;
13513 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13516 if(PieceToChar(piece) == '+') {
13517 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13519 piece = (ChessSquare)(DEMOTED piece);
13521 *p++ = PieceToChar(piece);
13523 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13524 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13529 if (emptycount > 0) {
13530 if(emptycount<10) /* [HGM] can be >= 10 */
13531 *p++ = '0' + emptycount;
13532 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13539 /* [HGM] print Crazyhouse or Shogi holdings */
13540 if( gameInfo.holdingsWidth ) {
13541 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13543 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13544 piece = boards[move][i][BOARD_WIDTH-1];
13545 if( piece != EmptySquare )
13546 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13547 *p++ = PieceToChar(piece);
13549 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13550 piece = boards[move][BOARD_HEIGHT-i-1][0];
13551 if( piece != EmptySquare )
13552 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13553 *p++ = PieceToChar(piece);
13556 if( q == p ) *p++ = '-';
13562 *p++ = whiteToPlay ? 'w' : 'b';
13565 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13566 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13568 if(nrCastlingRights) {
13570 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13571 /* [HGM] write directly from rights */
13572 if(castlingRights[move][2] >= 0 &&
13573 castlingRights[move][0] >= 0 )
13574 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13575 if(castlingRights[move][2] >= 0 &&
13576 castlingRights[move][1] >= 0 )
13577 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13578 if(castlingRights[move][5] >= 0 &&
13579 castlingRights[move][3] >= 0 )
13580 *p++ = castlingRights[move][3] + AAA;
13581 if(castlingRights[move][5] >= 0 &&
13582 castlingRights[move][4] >= 0 )
13583 *p++ = castlingRights[move][4] + AAA;
13586 /* [HGM] write true castling rights */
13587 if( nrCastlingRights == 6 ) {
13588 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13589 castlingRights[move][2] >= 0 ) *p++ = 'K';
13590 if(castlingRights[move][1] == BOARD_LEFT &&
13591 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13592 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13593 castlingRights[move][5] >= 0 ) *p++ = 'k';
13594 if(castlingRights[move][4] == BOARD_LEFT &&
13595 castlingRights[move][5] >= 0 ) *p++ = 'q';
13598 if (q == p) *p++ = '-'; /* No castling rights */
13602 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13603 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13604 /* En passant target square */
13605 if (move > backwardMostMove) {
13606 fromX = moveList[move - 1][0] - AAA;
13607 fromY = moveList[move - 1][1] - ONE;
13608 toX = moveList[move - 1][2] - AAA;
13609 toY = moveList[move - 1][3] - ONE;
13610 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13611 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13612 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13614 /* 2-square pawn move just happened */
13616 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13627 /* [HGM] find reversible plies */
13628 { int i = 0, j=move;
13630 if (appData.debugMode) { int k;
13631 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13632 for(k=backwardMostMove; k<=forwardMostMove; k++)
13633 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13637 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13638 if( j == backwardMostMove ) i += initialRulePlies;
13639 sprintf(p, "%d ", i);
13640 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13642 /* Fullmove number */
13643 sprintf(p, "%d", (move / 2) + 1);
13645 return StrSave(buf);
13649 ParseFEN(board, blackPlaysFirst, fen)
13651 int *blackPlaysFirst;
13661 /* [HGM] by default clear Crazyhouse holdings, if present */
13662 if(gameInfo.holdingsWidth) {
13663 for(i=0; i<BOARD_HEIGHT; i++) {
13664 board[i][0] = EmptySquare; /* black holdings */
13665 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13666 board[i][1] = (ChessSquare) 0; /* black counts */
13667 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13671 /* Piece placement data */
13672 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13675 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13676 if (*p == '/') p++;
13677 emptycount = gameInfo.boardWidth - j;
13678 while (emptycount--)
13679 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13681 #if(BOARD_SIZE >= 10)
13682 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13683 p++; emptycount=10;
13684 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13685 while (emptycount--)
13686 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13688 } else if (isdigit(*p)) {
13689 emptycount = *p++ - '0';
13690 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13691 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13692 while (emptycount--)
13693 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13694 } else if (*p == '+' || isalpha(*p)) {
13695 if (j >= gameInfo.boardWidth) return FALSE;
13697 piece = CharToPiece(*++p);
13698 if(piece == EmptySquare) return FALSE; /* unknown piece */
13699 piece = (ChessSquare) (PROMOTED piece ); p++;
13700 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13701 } else piece = CharToPiece(*p++);
13703 if(piece==EmptySquare) return FALSE; /* unknown piece */
13704 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13705 piece = (ChessSquare) (PROMOTED piece);
13706 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13709 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13715 while (*p == '/' || *p == ' ') p++;
13717 /* [HGM] look for Crazyhouse holdings here */
13718 while(*p==' ') p++;
13719 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13721 if(*p == '-' ) *p++; /* empty holdings */ else {
13722 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13723 /* if we would allow FEN reading to set board size, we would */
13724 /* have to add holdings and shift the board read so far here */
13725 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13727 if((int) piece >= (int) BlackPawn ) {
13728 i = (int)piece - (int)BlackPawn;
13729 i = PieceToNumber((ChessSquare)i);
13730 if( i >= gameInfo.holdingsSize ) return FALSE;
13731 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13732 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13734 i = (int)piece - (int)WhitePawn;
13735 i = PieceToNumber((ChessSquare)i);
13736 if( i >= gameInfo.holdingsSize ) return FALSE;
13737 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13738 board[i][BOARD_WIDTH-2]++; /* black holdings */
13742 if(*p == ']') *p++;
13745 while(*p == ' ') p++;
13750 *blackPlaysFirst = FALSE;
13753 *blackPlaysFirst = TRUE;
13759 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13760 /* return the extra info in global variiables */
13762 /* set defaults in case FEN is incomplete */
13763 FENepStatus = EP_UNKNOWN;
13764 for(i=0; i<nrCastlingRights; i++ ) {
13765 FENcastlingRights[i] =
13766 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13767 } /* assume possible unless obviously impossible */
13768 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13769 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13770 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13771 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13772 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13773 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13776 while(*p==' ') p++;
13777 if(nrCastlingRights) {
13778 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13779 /* castling indicator present, so default becomes no castlings */
13780 for(i=0; i<nrCastlingRights; i++ ) {
13781 FENcastlingRights[i] = -1;
13784 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13785 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13786 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13787 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13788 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13790 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13791 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13792 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13796 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13797 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13798 FENcastlingRights[2] = whiteKingFile;
13801 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13802 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13803 FENcastlingRights[2] = whiteKingFile;
13806 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13807 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13808 FENcastlingRights[5] = blackKingFile;
13811 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13812 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13813 FENcastlingRights[5] = blackKingFile;
13816 default: /* FRC castlings */
13817 if(c >= 'a') { /* black rights */
13818 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13819 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13820 if(i == BOARD_RGHT) break;
13821 FENcastlingRights[5] = i;
13823 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13824 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13826 FENcastlingRights[3] = c;
13828 FENcastlingRights[4] = c;
13829 } else { /* white rights */
13830 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13831 if(board[0][i] == WhiteKing) break;
13832 if(i == BOARD_RGHT) break;
13833 FENcastlingRights[2] = i;
13834 c -= AAA - 'a' + 'A';
13835 if(board[0][c] >= WhiteKing) break;
13837 FENcastlingRights[0] = c;
13839 FENcastlingRights[1] = c;
13843 if (appData.debugMode) {
13844 fprintf(debugFP, "FEN castling rights:");
13845 for(i=0; i<nrCastlingRights; i++)
13846 fprintf(debugFP, " %d", FENcastlingRights[i]);
13847 fprintf(debugFP, "\n");
13850 while(*p==' ') p++;
13853 /* read e.p. field in games that know e.p. capture */
13854 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13855 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13857 p++; FENepStatus = EP_NONE;
13859 char c = *p++ - AAA;
13861 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13862 if(*p >= '0' && *p <='9') *p++;
13868 if(sscanf(p, "%d", &i) == 1) {
13869 FENrulePlies = i; /* 50-move ply counter */
13870 /* (The move number is still ignored) */
13877 EditPositionPasteFEN(char *fen)
13880 Board initial_position;
13882 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13883 DisplayError(_("Bad FEN position in clipboard"), 0);
13886 int savedBlackPlaysFirst = blackPlaysFirst;
13887 EditPositionEvent();
13888 blackPlaysFirst = savedBlackPlaysFirst;
13889 CopyBoard(boards[0], initial_position);
13890 /* [HGM] copy FEN attributes as well */
13892 initialRulePlies = FENrulePlies;
13893 epStatus[0] = FENepStatus;
13894 for( i=0; i<nrCastlingRights; i++ )
13895 castlingRights[0][i] = FENcastlingRights[i];
13897 EditPositionDone();
13898 DisplayBothClocks();
13899 DrawPosition(FALSE, boards[currentMove]);