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, 2010 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>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163 Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void KeepAlive P((void));
191 void StartClocks P((void));
192 void SwitchClocks P((int nr));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
223 extern void ConsoleCreate();
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
242 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
246 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
253 /* States for ics_getting_history */
255 #define H_REQUESTED 1
256 #define H_GOT_REQ_HEADER 2
257 #define H_GOT_UNREQ_HEADER 3
258 #define H_GETTING_MOVES 4
259 #define H_GOT_UNWANTED_HEADER 5
261 /* whosays values for GameEnds */
270 /* Maximum number of games in a cmail message */
271 #define CMAIL_MAX_GAMES 20
273 /* Different types of move when calling RegisterMove */
275 #define CMAIL_RESIGN 1
277 #define CMAIL_ACCEPT 3
279 /* Different types of result to remember for each game */
280 #define CMAIL_NOT_RESULT 0
281 #define CMAIL_OLD_RESULT 1
282 #define CMAIL_NEW_RESULT 2
284 /* Telnet protocol constants */
295 static char * safeStrCpy( char * dst, const char * src, size_t count )
297 assert( dst != NULL );
298 assert( src != NULL );
301 strncpy( dst, src, count );
302 dst[ count-1 ] = '\0';
306 /* Some compiler can't cast u64 to double
307 * This function do the job for us:
309 * We use the highest bit for cast, this only
310 * works if the highest bit is not
311 * in use (This should not happen)
313 * We used this for all compiler
316 u64ToDouble(u64 value)
319 u64 tmp = value & u64Const(0x7fffffffffffffff);
320 r = (double)(s64)tmp;
321 if (value & u64Const(0x8000000000000000))
322 r += 9.2233720368547758080e18; /* 2^63 */
326 /* Fake up flags for now, as we aren't keeping track of castling
327 availability yet. [HGM] Change of logic: the flag now only
328 indicates the type of castlings allowed by the rule of the game.
329 The actual rights themselves are maintained in the array
330 castlingRights, as part of the game history, and are not probed
336 int flags = F_ALL_CASTLE_OK;
337 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
338 switch (gameInfo.variant) {
340 flags &= ~F_ALL_CASTLE_OK;
341 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
342 flags |= F_IGNORE_CHECK;
344 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
347 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
349 case VariantKriegspiel:
350 flags |= F_KRIEGSPIEL_CAPTURE;
352 case VariantCapaRandom:
353 case VariantFischeRandom:
354 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
355 case VariantNoCastle:
356 case VariantShatranj:
358 flags &= ~F_ALL_CASTLE_OK;
366 FILE *gameFileFP, *debugFP;
369 [AS] Note: sometimes, the sscanf() function is used to parse the input
370 into a fixed-size buffer. Because of this, we must be prepared to
371 receive strings as long as the size of the input buffer, which is currently
372 set to 4K for Windows and 8K for the rest.
373 So, we must either allocate sufficiently large buffers here, or
374 reduce the size of the input buffer in the input reading part.
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
379 char thinkOutput1[MSG_SIZ*10];
381 ChessProgramState first, second;
383 /* premove variables */
386 int premoveFromX = 0;
387 int premoveFromY = 0;
388 int premovePromoChar = 0;
390 Boolean alarmSounded;
391 /* end premove variables */
393 char *ics_prefix = "$";
394 int ics_type = ICS_GENERIC;
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
397 int pauseExamForwardMostMove = 0;
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
404 int whiteFlag = FALSE, blackFlag = FALSE;
405 int userOfferedDraw = FALSE;
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
408 int cmailMoveType[CMAIL_MAX_GAMES];
409 long ics_clock_paused = 0;
410 ProcRef icsPR = NoProc, cmailPR = NoProc;
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
412 GameMode gameMode = BeginningOfGame;
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
416 int hiddenThinkOutputState = 0; /* [AS] */
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
418 int adjudicateLossPlies = 6;
419 char white_holding[64], black_holding[64];
420 TimeMark lastNodeCountTime;
421 long lastNodeCount=0;
422 int have_sent_ICS_logon = 0;
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
425 long timeControl_2; /* [AS] Allow separate time controls */
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
427 long timeRemaining[2][MAX_MOVES];
429 TimeMark programStartTime;
430 char ics_handle[MSG_SIZ];
431 int have_set_title = 0;
433 /* animateTraining preserves the state of appData.animate
434 * when Training mode is activated. This allows the
435 * response to be animated when appData.animate == TRUE and
436 * appData.animateDragging == TRUE.
438 Boolean animateTraining;
444 Board boards[MAX_MOVES];
445 /* [HGM] Following 7 needed for accurate legality tests: */
446 signed char epStatus[MAX_MOVES];
447 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
448 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
449 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
450 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
451 int initialRulePlies, FENrulePlies;
453 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int mute; // mute all sounds
458 ChessSquare FIDEArray[2][BOARD_SIZE] = {
459 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
460 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
461 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
462 BlackKing, BlackBishop, BlackKnight, BlackRook }
465 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
466 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
467 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
468 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
469 BlackKing, BlackKing, BlackKnight, BlackRook }
472 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
473 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
474 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
475 { BlackRook, BlackMan, BlackBishop, BlackQueen,
476 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
479 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
480 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
481 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
482 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
483 BlackKing, BlackBishop, BlackKnight, BlackRook }
486 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
487 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
488 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
489 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
490 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
495 ChessSquare ShogiArray[2][BOARD_SIZE] = {
496 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
497 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
498 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
499 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
502 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
504 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
506 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
510 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
511 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
513 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
516 ChessSquare GreatArray[2][BOARD_SIZE] = {
517 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
518 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
519 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
520 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
523 ChessSquare JanusArray[2][BOARD_SIZE] = {
524 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
525 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
526 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
527 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
531 ChessSquare GothicArray[2][BOARD_SIZE] = {
532 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
533 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
534 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
535 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
538 #define GothicArray CapablancaArray
542 ChessSquare FalconArray[2][BOARD_SIZE] = {
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
544 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
546 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
549 #define FalconArray CapablancaArray
552 #else // !(BOARD_SIZE>=10)
553 #define XiangqiPosition FIDEArray
554 #define CapablancaArray FIDEArray
555 #define GothicArray FIDEArray
556 #define GreatArray FIDEArray
557 #endif // !(BOARD_SIZE>=10)
560 ChessSquare CourierArray[2][BOARD_SIZE] = {
561 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
562 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
563 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
564 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
566 #else // !(BOARD_SIZE>=12)
567 #define CourierArray CapablancaArray
568 #endif // !(BOARD_SIZE>=12)
571 Board initialPosition;
574 /* Convert str to a rating. Checks for special cases of "----",
576 "++++", etc. Also strips ()'s */
578 string_to_rating(str)
581 while(*str && !isdigit(*str)) ++str;
583 return 0; /* One of the special "no rating" cases */
591 /* Init programStats */
592 programStats.movelist[0] = 0;
593 programStats.depth = 0;
594 programStats.nr_moves = 0;
595 programStats.moves_left = 0;
596 programStats.nodes = 0;
597 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
598 programStats.score = 0;
599 programStats.got_only_move = 0;
600 programStats.got_fail = 0;
601 programStats.line_is_book = 0;
607 int matched, min, sec;
609 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
611 GetTimeMark(&programStartTime);
612 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
615 programStats.ok_to_send = 1;
616 programStats.seen_stat = 0;
619 * Initialize game list
625 * Internet chess server status
627 if (appData.icsActive) {
628 appData.matchMode = FALSE;
629 appData.matchGames = 0;
631 appData.noChessProgram = !appData.zippyPlay;
633 appData.zippyPlay = FALSE;
634 appData.zippyTalk = FALSE;
635 appData.noChessProgram = TRUE;
637 if (*appData.icsHelper != NULLCHAR) {
638 appData.useTelnet = TRUE;
639 appData.telnetProgram = appData.icsHelper;
642 appData.zippyTalk = appData.zippyPlay = FALSE;
645 /* [AS] Initialize pv info list [HGM] and game state */
649 for( i=0; i<MAX_MOVES; i++ ) {
650 pvInfoList[i].depth = -1;
652 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
657 * Parse timeControl resource
659 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
660 appData.movesPerSession)) {
662 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
663 DisplayFatalError(buf, 0, 2);
667 * Parse searchTime resource
669 if (*appData.searchTime != NULLCHAR) {
670 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
672 searchTime = min * 60;
673 } else if (matched == 2) {
674 searchTime = min * 60 + sec;
677 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
678 DisplayFatalError(buf, 0, 2);
682 /* [AS] Adjudication threshold */
683 adjudicateLossThreshold = appData.adjudicateLossThreshold;
685 first.which = "first";
686 second.which = "second";
687 first.maybeThinking = second.maybeThinking = FALSE;
688 first.pr = second.pr = NoProc;
689 first.isr = second.isr = NULL;
690 first.sendTime = second.sendTime = 2;
691 first.sendDrawOffers = 1;
692 if (appData.firstPlaysBlack) {
693 first.twoMachinesColor = "black\n";
694 second.twoMachinesColor = "white\n";
696 first.twoMachinesColor = "white\n";
697 second.twoMachinesColor = "black\n";
699 first.program = appData.firstChessProgram;
700 second.program = appData.secondChessProgram;
701 first.host = appData.firstHost;
702 second.host = appData.secondHost;
703 first.dir = appData.firstDirectory;
704 second.dir = appData.secondDirectory;
705 first.other = &second;
706 second.other = &first;
707 first.initString = appData.initString;
708 second.initString = appData.secondInitString;
709 first.computerString = appData.firstComputerString;
710 second.computerString = appData.secondComputerString;
711 first.useSigint = second.useSigint = TRUE;
712 first.useSigterm = second.useSigterm = TRUE;
713 first.reuse = appData.reuseFirst;
714 second.reuse = appData.reuseSecond;
715 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
716 second.nps = appData.secondNPS;
717 first.useSetboard = second.useSetboard = FALSE;
718 first.useSAN = second.useSAN = FALSE;
719 first.usePing = second.usePing = FALSE;
720 first.lastPing = second.lastPing = 0;
721 first.lastPong = second.lastPong = 0;
722 first.usePlayother = second.usePlayother = FALSE;
723 first.useColors = second.useColors = TRUE;
724 first.useUsermove = second.useUsermove = FALSE;
725 first.sendICS = second.sendICS = FALSE;
726 first.sendName = second.sendName = appData.icsActive;
727 first.sdKludge = second.sdKludge = FALSE;
728 first.stKludge = second.stKludge = FALSE;
729 TidyProgramName(first.program, first.host, first.tidy);
730 TidyProgramName(second.program, second.host, second.tidy);
731 first.matchWins = second.matchWins = 0;
732 strcpy(first.variants, appData.variant);
733 strcpy(second.variants, appData.variant);
734 first.analysisSupport = second.analysisSupport = 2; /* detect */
735 first.analyzing = second.analyzing = FALSE;
736 first.initDone = second.initDone = FALSE;
738 /* New features added by Tord: */
739 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
740 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
741 /* End of new features added by Tord. */
742 first.fenOverride = appData.fenOverride1;
743 second.fenOverride = appData.fenOverride2;
745 /* [HGM] time odds: set factor for each machine */
746 first.timeOdds = appData.firstTimeOdds;
747 second.timeOdds = appData.secondTimeOdds;
749 if(appData.timeOddsMode) {
750 norm = first.timeOdds;
751 if(norm > second.timeOdds) norm = second.timeOdds;
753 first.timeOdds /= norm;
754 second.timeOdds /= norm;
757 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
758 first.accumulateTC = appData.firstAccumulateTC;
759 second.accumulateTC = appData.secondAccumulateTC;
760 first.maxNrOfSessions = second.maxNrOfSessions = 1;
763 first.debug = second.debug = FALSE;
764 first.supportsNPS = second.supportsNPS = UNKNOWN;
767 first.optionSettings = appData.firstOptions;
768 second.optionSettings = appData.secondOptions;
770 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
771 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
772 first.isUCI = appData.firstIsUCI; /* [AS] */
773 second.isUCI = appData.secondIsUCI; /* [AS] */
774 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
775 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
777 if (appData.firstProtocolVersion > PROTOVER ||
778 appData.firstProtocolVersion < 1) {
780 sprintf(buf, _("protocol version %d not supported"),
781 appData.firstProtocolVersion);
782 DisplayFatalError(buf, 0, 2);
784 first.protocolVersion = appData.firstProtocolVersion;
787 if (appData.secondProtocolVersion > PROTOVER ||
788 appData.secondProtocolVersion < 1) {
790 sprintf(buf, _("protocol version %d not supported"),
791 appData.secondProtocolVersion);
792 DisplayFatalError(buf, 0, 2);
794 second.protocolVersion = appData.secondProtocolVersion;
797 if (appData.icsActive) {
798 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
799 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
800 appData.clockMode = FALSE;
801 first.sendTime = second.sendTime = 0;
805 /* Override some settings from environment variables, for backward
806 compatibility. Unfortunately it's not feasible to have the env
807 vars just set defaults, at least in xboard. Ugh.
809 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
814 if (appData.noChessProgram) {
815 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
816 sprintf(programVersion, "%s", PACKAGE_STRING);
818 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
819 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
820 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
823 if (!appData.icsActive) {
825 /* Check for variants that are supported only in ICS mode,
826 or not at all. Some that are accepted here nevertheless
827 have bugs; see comments below.
829 VariantClass variant = StringToVariant(appData.variant);
831 case VariantBughouse: /* need four players and two boards */
832 case VariantKriegspiel: /* need to hide pieces and move details */
833 /* case VariantFischeRandom: (Fabien: moved below) */
834 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
835 DisplayFatalError(buf, 0, 2);
839 case VariantLoadable:
849 sprintf(buf, _("Unknown variant name %s"), appData.variant);
850 DisplayFatalError(buf, 0, 2);
853 case VariantXiangqi: /* [HGM] repetition rules not implemented */
854 case VariantFairy: /* [HGM] TestLegality definitely off! */
855 case VariantGothic: /* [HGM] should work */
856 case VariantCapablanca: /* [HGM] should work */
857 case VariantCourier: /* [HGM] initial forced moves not implemented */
858 case VariantShogi: /* [HGM] drops not tested for legality */
859 case VariantKnightmate: /* [HGM] should work */
860 case VariantCylinder: /* [HGM] untested */
861 case VariantFalcon: /* [HGM] untested */
862 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
863 offboard interposition not understood */
864 case VariantNormal: /* definitely works! */
865 case VariantWildCastle: /* pieces not automatically shuffled */
866 case VariantNoCastle: /* pieces not automatically shuffled */
867 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
868 case VariantLosers: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantSuicide: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantGiveaway: /* should work except for win condition,
873 and doesn't know captures are mandatory */
874 case VariantTwoKings: /* should work */
875 case VariantAtomic: /* should work except for win condition */
876 case Variant3Check: /* should work except for win condition */
877 case VariantShatranj: /* should work except for all win conditions */
878 case VariantBerolina: /* might work if TestLegality is off */
879 case VariantCapaRandom: /* should work */
880 case VariantJanus: /* should work */
881 case VariantSuper: /* experimental */
882 case VariantGreat: /* experimental, requires legality testing to be off */
887 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
888 InitEngineUCI( installDir, &second );
891 int NextIntegerFromString( char ** str, long * value )
896 while( *s == ' ' || *s == '\t' ) {
902 if( *s >= '0' && *s <= '9' ) {
903 while( *s >= '0' && *s <= '9' ) {
904 *value = *value * 10 + (*s - '0');
916 int NextTimeControlFromString( char ** str, long * value )
919 int result = NextIntegerFromString( str, &temp );
922 *value = temp * 60; /* Minutes */
925 result = NextIntegerFromString( str, &temp );
926 *value += temp; /* Seconds */
933 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
934 { /* [HGM] routine added to read '+moves/time' for secondary time control */
935 int result = -1; long temp, temp2;
937 if(**str != '+') return -1; // old params remain in force!
939 if( NextTimeControlFromString( str, &temp ) ) return -1;
942 /* time only: incremental or sudden-death time control */
943 if(**str == '+') { /* increment follows; read it */
945 if(result = NextIntegerFromString( str, &temp2)) return -1;
948 *moves = 0; *tc = temp * 1000;
950 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
952 (*str)++; /* classical time control */
953 result = NextTimeControlFromString( str, &temp2);
962 int GetTimeQuota(int movenr)
963 { /* [HGM] get time to add from the multi-session time-control string */
964 int moves=1; /* kludge to force reading of first session */
965 long time, increment;
966 char *s = fullTimeControlString;
968 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
970 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
971 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
972 if(movenr == -1) return time; /* last move before new session */
973 if(!moves) return increment; /* current session is incremental */
974 if(movenr >= 0) movenr -= moves; /* we already finished this session */
975 } while(movenr >= -1); /* try again for next session */
977 return 0; // no new time quota on this move
981 ParseTimeControl(tc, ti, mps)
990 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
993 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
994 else sprintf(buf, "+%s+%d", tc, ti);
997 sprintf(buf, "+%d/%s", mps, tc);
998 else sprintf(buf, "+%s", tc);
1000 fullTimeControlString = StrSave(buf);
1002 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1007 /* Parse second time control */
1010 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1018 timeControl_2 = tc2 * 1000;
1028 timeControl = tc1 * 1000;
1031 timeIncrement = ti * 1000; /* convert to ms */
1032 movesPerSession = 0;
1035 movesPerSession = mps;
1043 if (appData.debugMode) {
1044 fprintf(debugFP, "%s\n", programVersion);
1047 set_cont_sequence(appData.wrapContSeq);
1048 if (appData.matchGames > 0) {
1049 appData.matchMode = TRUE;
1050 } else if (appData.matchMode) {
1051 appData.matchGames = 1;
1053 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1054 appData.matchGames = appData.sameColorGames;
1055 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1056 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1057 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1060 if (appData.noChessProgram || first.protocolVersion == 1) {
1063 /* kludge: allow timeout for initial "feature" commands */
1065 DisplayMessage("", _("Starting chess program"));
1066 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1071 InitBackEnd3 P((void))
1073 GameMode initialMode;
1077 InitChessProgram(&first, startedFromSetupPosition);
1080 if (appData.icsActive) {
1082 /* [DM] Make a console window if needed [HGM] merged ifs */
1087 if (*appData.icsCommPort != NULLCHAR) {
1088 sprintf(buf, _("Could not open comm port %s"),
1089 appData.icsCommPort);
1091 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1092 appData.icsHost, appData.icsPort);
1094 DisplayFatalError(buf, err, 1);
1099 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1101 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1102 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1103 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1104 } else if (appData.noChessProgram) {
1110 if (*appData.cmailGameName != NULLCHAR) {
1112 OpenLoopback(&cmailPR);
1114 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1118 DisplayMessage("", "");
1119 if (StrCaseCmp(appData.initialMode, "") == 0) {
1120 initialMode = BeginningOfGame;
1121 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1122 initialMode = TwoMachinesPlay;
1123 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1124 initialMode = AnalyzeFile;
1125 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1126 initialMode = AnalyzeMode;
1127 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1128 initialMode = MachinePlaysWhite;
1129 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1130 initialMode = MachinePlaysBlack;
1131 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1132 initialMode = EditGame;
1133 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1134 initialMode = EditPosition;
1135 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1136 initialMode = Training;
1138 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1139 DisplayFatalError(buf, 0, 2);
1143 if (appData.matchMode) {
1144 /* Set up machine vs. machine match */
1145 if (appData.noChessProgram) {
1146 DisplayFatalError(_("Can't have a match with no chess programs"),
1152 if (*appData.loadGameFile != NULLCHAR) {
1153 int index = appData.loadGameIndex; // [HGM] autoinc
1154 if(index<0) lastIndex = index = 1;
1155 if (!LoadGameFromFile(appData.loadGameFile,
1157 appData.loadGameFile, FALSE)) {
1158 DisplayFatalError(_("Bad game file"), 0, 1);
1161 } else if (*appData.loadPositionFile != NULLCHAR) {
1162 int index = appData.loadPositionIndex; // [HGM] autoinc
1163 if(index<0) lastIndex = index = 1;
1164 if (!LoadPositionFromFile(appData.loadPositionFile,
1166 appData.loadPositionFile)) {
1167 DisplayFatalError(_("Bad position file"), 0, 1);
1172 } else if (*appData.cmailGameName != NULLCHAR) {
1173 /* Set up cmail mode */
1174 ReloadCmailMsgEvent(TRUE);
1176 /* Set up other modes */
1177 if (initialMode == AnalyzeFile) {
1178 if (*appData.loadGameFile == NULLCHAR) {
1179 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1183 if (*appData.loadGameFile != NULLCHAR) {
1184 (void) LoadGameFromFile(appData.loadGameFile,
1185 appData.loadGameIndex,
1186 appData.loadGameFile, TRUE);
1187 } else if (*appData.loadPositionFile != NULLCHAR) {
1188 (void) LoadPositionFromFile(appData.loadPositionFile,
1189 appData.loadPositionIndex,
1190 appData.loadPositionFile);
1191 /* [HGM] try to make self-starting even after FEN load */
1192 /* to allow automatic setup of fairy variants with wtm */
1193 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1194 gameMode = BeginningOfGame;
1195 setboardSpoiledMachineBlack = 1;
1197 /* [HGM] loadPos: make that every new game uses the setup */
1198 /* from file as long as we do not switch variant */
1199 if(!blackPlaysFirst) { int i;
1200 startedFromPositionFile = TRUE;
1201 CopyBoard(filePosition, boards[0]);
1202 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1205 if (initialMode == AnalyzeMode) {
1206 if (appData.noChessProgram) {
1207 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1210 if (appData.icsActive) {
1211 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1215 } else if (initialMode == AnalyzeFile) {
1216 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1217 ShowThinkingEvent();
1219 AnalysisPeriodicEvent(1);
1220 } else if (initialMode == MachinePlaysWhite) {
1221 if (appData.noChessProgram) {
1222 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1226 if (appData.icsActive) {
1227 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1231 MachineWhiteEvent();
1232 } else if (initialMode == MachinePlaysBlack) {
1233 if (appData.noChessProgram) {
1234 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1238 if (appData.icsActive) {
1239 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1243 MachineBlackEvent();
1244 } else if (initialMode == TwoMachinesPlay) {
1245 if (appData.noChessProgram) {
1246 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1250 if (appData.icsActive) {
1251 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1256 } else if (initialMode == EditGame) {
1258 } else if (initialMode == EditPosition) {
1259 EditPositionEvent();
1260 } else if (initialMode == Training) {
1261 if (*appData.loadGameFile == NULLCHAR) {
1262 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1271 * Establish will establish a contact to a remote host.port.
1272 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1273 * used to talk to the host.
1274 * Returns 0 if okay, error code if not.
1281 if (*appData.icsCommPort != NULLCHAR) {
1282 /* Talk to the host through a serial comm port */
1283 return OpenCommPort(appData.icsCommPort, &icsPR);
1285 } else if (*appData.gateway != NULLCHAR) {
1286 if (*appData.remoteShell == NULLCHAR) {
1287 /* Use the rcmd protocol to run telnet program on a gateway host */
1288 snprintf(buf, sizeof(buf), "%s %s %s",
1289 appData.telnetProgram, appData.icsHost, appData.icsPort);
1290 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1293 /* Use the rsh program to run telnet program on a gateway host */
1294 if (*appData.remoteUser == NULLCHAR) {
1295 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1296 appData.gateway, appData.telnetProgram,
1297 appData.icsHost, appData.icsPort);
1299 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1300 appData.remoteShell, appData.gateway,
1301 appData.remoteUser, appData.telnetProgram,
1302 appData.icsHost, appData.icsPort);
1304 return StartChildProcess(buf, "", &icsPR);
1307 } else if (appData.useTelnet) {
1308 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1311 /* TCP socket interface differs somewhat between
1312 Unix and NT; handle details in the front end.
1314 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1319 show_bytes(fp, buf, count)
1325 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1326 fprintf(fp, "\\%03o", *buf & 0xff);
1335 /* Returns an errno value */
1337 OutputMaybeTelnet(pr, message, count, outError)
1343 char buf[8192], *p, *q, *buflim;
1344 int left, newcount, outcount;
1346 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1347 *appData.gateway != NULLCHAR) {
1348 if (appData.debugMode) {
1349 fprintf(debugFP, ">ICS: ");
1350 show_bytes(debugFP, message, count);
1351 fprintf(debugFP, "\n");
1353 return OutputToProcess(pr, message, count, outError);
1356 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1363 if (appData.debugMode) {
1364 fprintf(debugFP, ">ICS: ");
1365 show_bytes(debugFP, buf, newcount);
1366 fprintf(debugFP, "\n");
1368 outcount = OutputToProcess(pr, buf, newcount, outError);
1369 if (outcount < newcount) return -1; /* to be sure */
1376 } else if (((unsigned char) *p) == TN_IAC) {
1377 *q++ = (char) TN_IAC;
1384 if (appData.debugMode) {
1385 fprintf(debugFP, ">ICS: ");
1386 show_bytes(debugFP, buf, newcount);
1387 fprintf(debugFP, "\n");
1389 outcount = OutputToProcess(pr, buf, newcount, outError);
1390 if (outcount < newcount) return -1; /* to be sure */
1395 read_from_player(isr, closure, message, count, error)
1402 int outError, outCount;
1403 static int gotEof = 0;
1405 /* Pass data read from player on to ICS */
1408 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1409 if (outCount < count) {
1410 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1412 } else if (count < 0) {
1413 RemoveInputSource(isr);
1414 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1415 } else if (gotEof++ > 0) {
1416 RemoveInputSource(isr);
1417 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1423 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1424 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1425 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1426 SendToICS("date\n");
1427 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1430 /* added routine for printf style output to ics */
1431 void ics_printf(char *format, ...)
1433 char buffer[MSG_SIZ];
1436 va_start(args, format);
1437 vsnprintf(buffer, sizeof(buffer), format, args);
1438 buffer[sizeof(buffer)-1] = '\0';
1447 int count, outCount, outError;
1449 if (icsPR == NULL) return;
1452 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1453 if (outCount < count) {
1454 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1458 /* This is used for sending logon scripts to the ICS. Sending
1459 without a delay causes problems when using timestamp on ICC
1460 (at least on my machine). */
1462 SendToICSDelayed(s,msdelay)
1466 int count, outCount, outError;
1468 if (icsPR == NULL) return;
1471 if (appData.debugMode) {
1472 fprintf(debugFP, ">ICS: ");
1473 show_bytes(debugFP, s, count);
1474 fprintf(debugFP, "\n");
1476 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1478 if (outCount < count) {
1479 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1484 /* Remove all highlighting escape sequences in s
1485 Also deletes any suffix starting with '('
1488 StripHighlightAndTitle(s)
1491 static char retbuf[MSG_SIZ];
1494 while (*s != NULLCHAR) {
1495 while (*s == '\033') {
1496 while (*s != NULLCHAR && !isalpha(*s)) s++;
1497 if (*s != NULLCHAR) s++;
1499 while (*s != NULLCHAR && *s != '\033') {
1500 if (*s == '(' || *s == '[') {
1511 /* Remove all highlighting escape sequences in s */
1516 static char retbuf[MSG_SIZ];
1519 while (*s != NULLCHAR) {
1520 while (*s == '\033') {
1521 while (*s != NULLCHAR && !isalpha(*s)) s++;
1522 if (*s != NULLCHAR) s++;
1524 while (*s != NULLCHAR && *s != '\033') {
1532 char *variantNames[] = VARIANT_NAMES;
1537 return variantNames[v];
1541 /* Identify a variant from the strings the chess servers use or the
1542 PGN Variant tag names we use. */
1549 VariantClass v = VariantNormal;
1550 int i, found = FALSE;
1555 /* [HGM] skip over optional board-size prefixes */
1556 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1557 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1558 while( *e++ != '_');
1561 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1565 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1566 if (StrCaseStr(e, variantNames[i])) {
1567 v = (VariantClass) i;
1574 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1575 || StrCaseStr(e, "wild/fr")
1576 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1577 v = VariantFischeRandom;
1578 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1579 (i = 1, p = StrCaseStr(e, "w"))) {
1581 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1588 case 0: /* FICS only, actually */
1590 /* Castling legal even if K starts on d-file */
1591 v = VariantWildCastle;
1596 /* Castling illegal even if K & R happen to start in
1597 normal positions. */
1598 v = VariantNoCastle;
1611 /* Castling legal iff K & R start in normal positions */
1617 /* Special wilds for position setup; unclear what to do here */
1618 v = VariantLoadable;
1621 /* Bizarre ICC game */
1622 v = VariantTwoKings;
1625 v = VariantKriegspiel;
1631 v = VariantFischeRandom;
1634 v = VariantCrazyhouse;
1637 v = VariantBughouse;
1643 /* Not quite the same as FICS suicide! */
1644 v = VariantGiveaway;
1650 v = VariantShatranj;
1653 /* Temporary names for future ICC types. The name *will* change in
1654 the next xboard/WinBoard release after ICC defines it. */
1692 v = VariantCapablanca;
1695 v = VariantKnightmate;
1701 v = VariantCylinder;
1707 v = VariantCapaRandom;
1710 v = VariantBerolina;
1722 /* Found "wild" or "w" in the string but no number;
1723 must assume it's normal chess. */
1727 sprintf(buf, _("Unknown wild type %d"), wnum);
1728 DisplayError(buf, 0);
1734 if (appData.debugMode) {
1735 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1736 e, wnum, VariantName(v));
1741 static int leftover_start = 0, leftover_len = 0;
1742 char star_match[STAR_MATCH_N][MSG_SIZ];
1744 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1745 advance *index beyond it, and set leftover_start to the new value of
1746 *index; else return FALSE. If pattern contains the character '*', it
1747 matches any sequence of characters not containing '\r', '\n', or the
1748 character following the '*' (if any), and the matched sequence(s) are
1749 copied into star_match.
1752 looking_at(buf, index, pattern)
1757 char *bufp = &buf[*index], *patternp = pattern;
1759 char *matchp = star_match[0];
1762 if (*patternp == NULLCHAR) {
1763 *index = leftover_start = bufp - buf;
1767 if (*bufp == NULLCHAR) return FALSE;
1768 if (*patternp == '*') {
1769 if (*bufp == *(patternp + 1)) {
1771 matchp = star_match[++star_count];
1775 } else if (*bufp == '\n' || *bufp == '\r') {
1777 if (*patternp == NULLCHAR)
1782 *matchp++ = *bufp++;
1786 if (*patternp != *bufp) return FALSE;
1793 SendToPlayer(data, length)
1797 int error, outCount;
1798 outCount = OutputToProcess(NoProc, data, length, &error);
1799 if (outCount < length) {
1800 DisplayFatalError(_("Error writing to display"), error, 1);
1805 PackHolding(packed, holding)
1817 switch (runlength) {
1828 sprintf(q, "%d", runlength);
1840 /* Telnet protocol requests from the front end */
1842 TelnetRequest(ddww, option)
1843 unsigned char ddww, option;
1845 unsigned char msg[3];
1846 int outCount, outError;
1848 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1850 if (appData.debugMode) {
1851 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1867 sprintf(buf1, "%d", ddww);
1876 sprintf(buf2, "%d", option);
1879 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1884 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1886 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1893 if (!appData.icsActive) return;
1894 TelnetRequest(TN_DO, TN_ECHO);
1900 if (!appData.icsActive) return;
1901 TelnetRequest(TN_DONT, TN_ECHO);
1905 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1907 /* put the holdings sent to us by the server on the board holdings area */
1908 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1912 if(gameInfo.holdingsWidth < 2) return;
1913 if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1914 return; // prevent overwriting by pre-board holdings
1916 if( (int)lowestPiece >= BlackPawn ) {
1919 holdingsStartRow = BOARD_HEIGHT-1;
1922 holdingsColumn = BOARD_WIDTH-1;
1923 countsColumn = BOARD_WIDTH-2;
1924 holdingsStartRow = 0;
1928 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1929 board[i][holdingsColumn] = EmptySquare;
1930 board[i][countsColumn] = (ChessSquare) 0;
1932 while( (p=*holdings++) != NULLCHAR ) {
1933 piece = CharToPiece( ToUpper(p) );
1934 if(piece == EmptySquare) continue;
1935 /*j = (int) piece - (int) WhitePawn;*/
1936 j = PieceToNumber(piece);
1937 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1938 if(j < 0) continue; /* should not happen */
1939 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1940 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1941 board[holdingsStartRow+j*direction][countsColumn]++;
1947 VariantSwitch(Board board, VariantClass newVariant)
1949 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1952 startedFromPositionFile = FALSE;
1953 if(gameInfo.variant == newVariant) return;
1955 /* [HGM] This routine is called each time an assignment is made to
1956 * gameInfo.variant during a game, to make sure the board sizes
1957 * are set to match the new variant. If that means adding or deleting
1958 * holdings, we shift the playing board accordingly
1959 * This kludge is needed because in ICS observe mode, we get boards
1960 * of an ongoing game without knowing the variant, and learn about the
1961 * latter only later. This can be because of the move list we requested,
1962 * in which case the game history is refilled from the beginning anyway,
1963 * but also when receiving holdings of a crazyhouse game. In the latter
1964 * case we want to add those holdings to the already received position.
1968 if (appData.debugMode) {
1969 fprintf(debugFP, "Switch board from %s to %s\n",
1970 VariantName(gameInfo.variant), VariantName(newVariant));
1971 setbuf(debugFP, NULL);
1973 shuffleOpenings = 0; /* [HGM] shuffle */
1974 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1978 newWidth = 9; newHeight = 9;
1979 gameInfo.holdingsSize = 7;
1980 case VariantBughouse:
1981 case VariantCrazyhouse:
1982 newHoldingsWidth = 2; break;
1986 newHoldingsWidth = 2;
1987 gameInfo.holdingsSize = 8;
1990 case VariantCapablanca:
1991 case VariantCapaRandom:
1994 newHoldingsWidth = gameInfo.holdingsSize = 0;
1997 if(newWidth != gameInfo.boardWidth ||
1998 newHeight != gameInfo.boardHeight ||
1999 newHoldingsWidth != gameInfo.holdingsWidth ) {
2001 /* shift position to new playing area, if needed */
2002 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2003 for(i=0; i<BOARD_HEIGHT; i++)
2004 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2005 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2007 for(i=0; i<newHeight; i++) {
2008 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2009 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2011 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2012 for(i=0; i<BOARD_HEIGHT; i++)
2013 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2014 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2017 gameInfo.boardWidth = newWidth;
2018 gameInfo.boardHeight = newHeight;
2019 gameInfo.holdingsWidth = newHoldingsWidth;
2020 gameInfo.variant = newVariant;
2021 InitDrawingSizes(-2, 0);
2022 } else gameInfo.variant = newVariant;
2023 CopyBoard(oldBoard, board); // remember correctly formatted board
2024 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2025 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2028 static int loggedOn = FALSE;
2030 /*-- Game start info cache: --*/
2032 char gs_kind[MSG_SIZ];
2033 static char player1Name[128] = "";
2034 static char player2Name[128] = "";
2035 static char cont_seq[] = "\n\\ ";
2036 static int player1Rating = -1;
2037 static int player2Rating = -1;
2038 /*----------------------------*/
2040 ColorClass curColor = ColorNormal;
2041 int suppressKibitz = 0;
2044 read_from_ics(isr, closure, data, count, error)
2051 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2052 #define STARTED_NONE 0
2053 #define STARTED_MOVES 1
2054 #define STARTED_BOARD 2
2055 #define STARTED_OBSERVE 3
2056 #define STARTED_HOLDINGS 4
2057 #define STARTED_CHATTER 5
2058 #define STARTED_COMMENT 6
2059 #define STARTED_MOVES_NOHIDE 7
2061 static int started = STARTED_NONE;
2062 static char parse[20000];
2063 static int parse_pos = 0;
2064 static char buf[BUF_SIZE + 1];
2065 static int firstTime = TRUE, intfSet = FALSE;
2066 static ColorClass prevColor = ColorNormal;
2067 static int savingComment = FALSE;
2068 static int cmatch = 0; // continuation sequence match
2075 int backup; /* [DM] For zippy color lines */
2077 char talker[MSG_SIZ]; // [HGM] chat
2080 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2082 if (appData.debugMode) {
2084 fprintf(debugFP, "<ICS: ");
2085 show_bytes(debugFP, data, count);
2086 fprintf(debugFP, "\n");
2090 if (appData.debugMode) { int f = forwardMostMove;
2091 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2092 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2095 /* If last read ended with a partial line that we couldn't parse,
2096 prepend it to the new read and try again. */
2097 if (leftover_len > 0) {
2098 for (i=0; i<leftover_len; i++)
2099 buf[i] = buf[leftover_start + i];
2102 /* copy new characters into the buffer */
2103 bp = buf + leftover_len;
2104 buf_len=leftover_len;
2105 for (i=0; i<count; i++)
2108 if (data[i] == '\r')
2111 // join lines split by ICS?
2112 if (!appData.noJoin)
2115 Joining just consists of finding matches against the
2116 continuation sequence, and discarding that sequence
2117 if found instead of copying it. So, until a match
2118 fails, there's nothing to do since it might be the
2119 complete sequence, and thus, something we don't want
2122 if (data[i] == cont_seq[cmatch])
2125 if (cmatch == strlen(cont_seq))
2127 cmatch = 0; // complete match. just reset the counter
2130 it's possible for the ICS to not include the space
2131 at the end of the last word, making our [correct]
2132 join operation fuse two separate words. the server
2133 does this when the space occurs at the width setting.
2135 if (!buf_len || buf[buf_len-1] != ' ')
2146 match failed, so we have to copy what matched before
2147 falling through and copying this character. In reality,
2148 this will only ever be just the newline character, but
2149 it doesn't hurt to be precise.
2151 strncpy(bp, cont_seq, cmatch);
2163 buf[buf_len] = NULLCHAR;
2164 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2169 while (i < buf_len) {
2170 /* Deal with part of the TELNET option negotiation
2171 protocol. We refuse to do anything beyond the
2172 defaults, except that we allow the WILL ECHO option,
2173 which ICS uses to turn off password echoing when we are
2174 directly connected to it. We reject this option
2175 if localLineEditing mode is on (always on in xboard)
2176 and we are talking to port 23, which might be a real
2177 telnet server that will try to keep WILL ECHO on permanently.
2179 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2180 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2181 unsigned char option;
2183 switch ((unsigned char) buf[++i]) {
2185 if (appData.debugMode)
2186 fprintf(debugFP, "\n<WILL ");
2187 switch (option = (unsigned char) buf[++i]) {
2189 if (appData.debugMode)
2190 fprintf(debugFP, "ECHO ");
2191 /* Reply only if this is a change, according
2192 to the protocol rules. */
2193 if (remoteEchoOption) break;
2194 if (appData.localLineEditing &&
2195 atoi(appData.icsPort) == TN_PORT) {
2196 TelnetRequest(TN_DONT, TN_ECHO);
2199 TelnetRequest(TN_DO, TN_ECHO);
2200 remoteEchoOption = TRUE;
2204 if (appData.debugMode)
2205 fprintf(debugFP, "%d ", option);
2206 /* Whatever this is, we don't want it. */
2207 TelnetRequest(TN_DONT, option);
2212 if (appData.debugMode)
2213 fprintf(debugFP, "\n<WONT ");
2214 switch (option = (unsigned char) buf[++i]) {
2216 if (appData.debugMode)
2217 fprintf(debugFP, "ECHO ");
2218 /* Reply only if this is a change, according
2219 to the protocol rules. */
2220 if (!remoteEchoOption) break;
2222 TelnetRequest(TN_DONT, TN_ECHO);
2223 remoteEchoOption = FALSE;
2226 if (appData.debugMode)
2227 fprintf(debugFP, "%d ", (unsigned char) option);
2228 /* Whatever this is, it must already be turned
2229 off, because we never agree to turn on
2230 anything non-default, so according to the
2231 protocol rules, we don't reply. */
2236 if (appData.debugMode)
2237 fprintf(debugFP, "\n<DO ");
2238 switch (option = (unsigned char) buf[++i]) {
2240 /* Whatever this is, we refuse to do it. */
2241 if (appData.debugMode)
2242 fprintf(debugFP, "%d ", option);
2243 TelnetRequest(TN_WONT, option);
2248 if (appData.debugMode)
2249 fprintf(debugFP, "\n<DONT ");
2250 switch (option = (unsigned char) buf[++i]) {
2252 if (appData.debugMode)
2253 fprintf(debugFP, "%d ", option);
2254 /* Whatever this is, we are already not doing
2255 it, because we never agree to do anything
2256 non-default, so according to the protocol
2257 rules, we don't reply. */
2262 if (appData.debugMode)
2263 fprintf(debugFP, "\n<IAC ");
2264 /* Doubled IAC; pass it through */
2268 if (appData.debugMode)
2269 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2270 /* Drop all other telnet commands on the floor */
2273 if (oldi > next_out)
2274 SendToPlayer(&buf[next_out], oldi - next_out);
2280 /* OK, this at least will *usually* work */
2281 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2285 if (loggedOn && !intfSet) {
2286 if (ics_type == ICS_ICC) {
2288 "/set-quietly interface %s\n/set-quietly style 12\n",
2290 } else if (ics_type == ICS_CHESSNET) {
2291 sprintf(str, "/style 12\n");
2293 strcpy(str, "alias $ @\n$set interface ");
2294 strcat(str, programVersion);
2295 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2297 strcat(str, "$iset nohighlight 1\n");
2299 strcat(str, "$iset lock 1\n$style 12\n");
2302 NotifyFrontendLogin();
2306 if (started == STARTED_COMMENT) {
2307 /* Accumulate characters in comment */
2308 parse[parse_pos++] = buf[i];
2309 if (buf[i] == '\n') {
2310 parse[parse_pos] = NULLCHAR;
2311 if(chattingPartner>=0) {
2313 sprintf(mess, "%s%s", talker, parse);
2314 OutputChatMessage(chattingPartner, mess);
2315 chattingPartner = -1;
2317 if(!suppressKibitz) // [HGM] kibitz
2318 AppendComment(forwardMostMove, StripHighlight(parse));
2319 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2320 int nrDigit = 0, nrAlph = 0, j;
2321 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2322 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2323 parse[parse_pos] = NULLCHAR;
2324 // try to be smart: if it does not look like search info, it should go to
2325 // ICS interaction window after all, not to engine-output window.
2326 for(j=0; j<parse_pos; j++) { // count letters and digits
2327 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2328 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2329 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2331 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2332 int depth=0; float score;
2333 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2334 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2335 pvInfoList[forwardMostMove-1].depth = depth;
2336 pvInfoList[forwardMostMove-1].score = 100*score;
2338 OutputKibitz(suppressKibitz, parse);
2339 next_out = i+1; // [HGM] suppress printing in ICS window
2342 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2343 SendToPlayer(tmp, strlen(tmp));
2346 started = STARTED_NONE;
2348 /* Don't match patterns against characters in comment */
2353 if (started == STARTED_CHATTER) {
2354 if (buf[i] != '\n') {
2355 /* Don't match patterns against characters in chatter */
2359 started = STARTED_NONE;
2362 /* Kludge to deal with rcmd protocol */
2363 if (firstTime && looking_at(buf, &i, "\001*")) {
2364 DisplayFatalError(&buf[1], 0, 1);
2370 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2373 if (appData.debugMode)
2374 fprintf(debugFP, "ics_type %d\n", ics_type);
2377 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2378 ics_type = ICS_FICS;
2380 if (appData.debugMode)
2381 fprintf(debugFP, "ics_type %d\n", ics_type);
2384 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2385 ics_type = ICS_CHESSNET;
2387 if (appData.debugMode)
2388 fprintf(debugFP, "ics_type %d\n", ics_type);
2393 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2394 looking_at(buf, &i, "Logging you in as \"*\"") ||
2395 looking_at(buf, &i, "will be \"*\""))) {
2396 strcpy(ics_handle, star_match[0]);
2400 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2402 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2403 DisplayIcsInteractionTitle(buf);
2404 have_set_title = TRUE;
2407 /* skip finger notes */
2408 if (started == STARTED_NONE &&
2409 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2410 (buf[i] == '1' && buf[i+1] == '0')) &&
2411 buf[i+2] == ':' && buf[i+3] == ' ') {
2412 started = STARTED_CHATTER;
2417 /* skip formula vars */
2418 if (started == STARTED_NONE &&
2419 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2420 started = STARTED_CHATTER;
2426 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2427 if (appData.autoKibitz && started == STARTED_NONE &&
2428 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2429 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2430 if(looking_at(buf, &i, "* kibitzes: ") &&
2431 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2432 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2433 suppressKibitz = TRUE;
2434 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2435 && (gameMode == IcsPlayingWhite)) ||
2436 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2437 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2438 started = STARTED_CHATTER; // own kibitz we simply discard
2440 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2441 parse_pos = 0; parse[0] = NULLCHAR;
2442 savingComment = TRUE;
2443 suppressKibitz = gameMode != IcsObserving ? 2 :
2444 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2448 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2449 // suppress the acknowledgements of our own autoKibitz
2451 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2452 SendToPlayer(star_match[0], strlen(star_match[0]));
2453 looking_at(buf, &i, "*% "); // eat prompt
2456 } // [HGM] kibitz: end of patch
2458 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2460 // [HGM] chat: intercept tells by users for which we have an open chat window
2462 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2463 looking_at(buf, &i, "* whispers:") ||
2464 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2465 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2467 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2468 chattingPartner = -1;
2470 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2471 for(p=0; p<MAX_CHAT; p++) {
2472 if(channel == atoi(chatPartner[p])) {
2473 talker[0] = '['; strcat(talker, "] ");
2474 chattingPartner = p; break;
2477 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2478 for(p=0; p<MAX_CHAT; p++) {
2479 if(!strcmp("WHISPER", chatPartner[p])) {
2480 talker[0] = '['; strcat(talker, "] ");
2481 chattingPartner = p; break;
2484 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2485 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2487 chattingPartner = p; break;
2489 if(chattingPartner<0) i = oldi; else {
2490 started = STARTED_COMMENT;
2491 parse_pos = 0; parse[0] = NULLCHAR;
2492 savingComment = 3 + chattingPartner; // counts as TRUE
2493 suppressKibitz = TRUE;
2495 } // [HGM] chat: end of patch
2497 if (appData.zippyTalk || appData.zippyPlay) {
2498 /* [DM] Backup address for color zippy lines */
2502 if (loggedOn == TRUE)
2503 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2504 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2506 if (ZippyControl(buf, &i) ||
2507 ZippyConverse(buf, &i) ||
2508 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2510 if (!appData.colorize) continue;
2514 } // [DM] 'else { ' deleted
2516 /* Regular tells and says */
2517 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2518 looking_at(buf, &i, "* (your partner) tells you: ") ||
2519 looking_at(buf, &i, "* says: ") ||
2520 /* Don't color "message" or "messages" output */
2521 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2522 looking_at(buf, &i, "*. * at *:*: ") ||
2523 looking_at(buf, &i, "--* (*:*): ") ||
2524 /* Message notifications (same color as tells) */
2525 looking_at(buf, &i, "* has left a message ") ||
2526 looking_at(buf, &i, "* just sent you a message:\n") ||
2527 /* Whispers and kibitzes */
2528 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2529 looking_at(buf, &i, "* kibitzes: ") ||
2531 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2533 if (tkind == 1 && strchr(star_match[0], ':')) {
2534 /* Avoid "tells you:" spoofs in channels */
2537 if (star_match[0][0] == NULLCHAR ||
2538 strchr(star_match[0], ' ') ||
2539 (tkind == 3 && strchr(star_match[1], ' '))) {
2540 /* Reject bogus matches */
2543 if (appData.colorize) {
2544 if (oldi > next_out) {
2545 SendToPlayer(&buf[next_out], oldi - next_out);
2550 Colorize(ColorTell, FALSE);
2551 curColor = ColorTell;
2554 Colorize(ColorKibitz, FALSE);
2555 curColor = ColorKibitz;
2558 p = strrchr(star_match[1], '(');
2565 Colorize(ColorChannel1, FALSE);
2566 curColor = ColorChannel1;
2568 Colorize(ColorChannel, FALSE);
2569 curColor = ColorChannel;
2573 curColor = ColorNormal;
2577 if (started == STARTED_NONE && appData.autoComment &&
2578 (gameMode == IcsObserving ||
2579 gameMode == IcsPlayingWhite ||
2580 gameMode == IcsPlayingBlack)) {
2581 parse_pos = i - oldi;
2582 memcpy(parse, &buf[oldi], parse_pos);
2583 parse[parse_pos] = NULLCHAR;
2584 started = STARTED_COMMENT;
2585 savingComment = TRUE;
2587 started = STARTED_CHATTER;
2588 savingComment = FALSE;
2595 if (looking_at(buf, &i, "* s-shouts: ") ||
2596 looking_at(buf, &i, "* c-shouts: ")) {
2597 if (appData.colorize) {
2598 if (oldi > next_out) {
2599 SendToPlayer(&buf[next_out], oldi - next_out);
2602 Colorize(ColorSShout, FALSE);
2603 curColor = ColorSShout;
2606 started = STARTED_CHATTER;
2610 if (looking_at(buf, &i, "--->")) {
2615 if (looking_at(buf, &i, "* shouts: ") ||
2616 looking_at(buf, &i, "--> ")) {
2617 if (appData.colorize) {
2618 if (oldi > next_out) {
2619 SendToPlayer(&buf[next_out], oldi - next_out);
2622 Colorize(ColorShout, FALSE);
2623 curColor = ColorShout;
2626 started = STARTED_CHATTER;
2630 if (looking_at( buf, &i, "Challenge:")) {
2631 if (appData.colorize) {
2632 if (oldi > next_out) {
2633 SendToPlayer(&buf[next_out], oldi - next_out);
2636 Colorize(ColorChallenge, FALSE);
2637 curColor = ColorChallenge;
2643 if (looking_at(buf, &i, "* offers you") ||
2644 looking_at(buf, &i, "* offers to be") ||
2645 looking_at(buf, &i, "* would like to") ||
2646 looking_at(buf, &i, "* requests to") ||
2647 looking_at(buf, &i, "Your opponent offers") ||
2648 looking_at(buf, &i, "Your opponent requests")) {
2650 if (appData.colorize) {
2651 if (oldi > next_out) {
2652 SendToPlayer(&buf[next_out], oldi - next_out);
2655 Colorize(ColorRequest, FALSE);
2656 curColor = ColorRequest;
2661 if (looking_at(buf, &i, "* (*) seeking")) {
2662 if (appData.colorize) {
2663 if (oldi > next_out) {
2664 SendToPlayer(&buf[next_out], oldi - next_out);
2667 Colorize(ColorSeek, FALSE);
2668 curColor = ColorSeek;
2673 if (looking_at(buf, &i, "\\ ")) {
2674 if (prevColor != ColorNormal) {
2675 if (oldi > next_out) {
2676 SendToPlayer(&buf[next_out], oldi - next_out);
2679 Colorize(prevColor, TRUE);
2680 curColor = prevColor;
2682 if (savingComment) {
2683 parse_pos = i - oldi;
2684 memcpy(parse, &buf[oldi], parse_pos);
2685 parse[parse_pos] = NULLCHAR;
2686 started = STARTED_COMMENT;
2687 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2688 chattingPartner = savingComment - 3; // kludge to remember the box
2690 started = STARTED_CHATTER;
2695 if (looking_at(buf, &i, "Black Strength :") ||
2696 looking_at(buf, &i, "<<< style 10 board >>>") ||
2697 looking_at(buf, &i, "<10>") ||
2698 looking_at(buf, &i, "#@#")) {
2699 /* Wrong board style */
2701 SendToICS(ics_prefix);
2702 SendToICS("set style 12\n");
2703 SendToICS(ics_prefix);
2704 SendToICS("refresh\n");
2708 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2710 have_sent_ICS_logon = 1;
2714 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2715 (looking_at(buf, &i, "\n<12> ") ||
2716 looking_at(buf, &i, "<12> "))) {
2718 if (oldi > next_out) {
2719 SendToPlayer(&buf[next_out], oldi - next_out);
2722 started = STARTED_BOARD;
2727 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2728 looking_at(buf, &i, "<b1> ")) {
2729 if (oldi > next_out) {
2730 SendToPlayer(&buf[next_out], oldi - next_out);
2733 started = STARTED_HOLDINGS;
2738 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2740 /* Header for a move list -- first line */
2742 switch (ics_getting_history) {
2746 case BeginningOfGame:
2747 /* User typed "moves" or "oldmoves" while we
2748 were idle. Pretend we asked for these
2749 moves and soak them up so user can step
2750 through them and/or save them.
2753 gameMode = IcsObserving;
2756 ics_getting_history = H_GOT_UNREQ_HEADER;
2758 case EditGame: /*?*/
2759 case EditPosition: /*?*/
2760 /* Should above feature work in these modes too? */
2761 /* For now it doesn't */
2762 ics_getting_history = H_GOT_UNWANTED_HEADER;
2765 ics_getting_history = H_GOT_UNWANTED_HEADER;
2770 /* Is this the right one? */
2771 if (gameInfo.white && gameInfo.black &&
2772 strcmp(gameInfo.white, star_match[0]) == 0 &&
2773 strcmp(gameInfo.black, star_match[2]) == 0) {
2775 ics_getting_history = H_GOT_REQ_HEADER;
2778 case H_GOT_REQ_HEADER:
2779 case H_GOT_UNREQ_HEADER:
2780 case H_GOT_UNWANTED_HEADER:
2781 case H_GETTING_MOVES:
2782 /* Should not happen */
2783 DisplayError(_("Error gathering move list: two headers"), 0);
2784 ics_getting_history = H_FALSE;
2788 /* Save player ratings into gameInfo if needed */
2789 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2790 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2791 (gameInfo.whiteRating == -1 ||
2792 gameInfo.blackRating == -1)) {
2794 gameInfo.whiteRating = string_to_rating(star_match[1]);
2795 gameInfo.blackRating = string_to_rating(star_match[3]);
2796 if (appData.debugMode)
2797 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2798 gameInfo.whiteRating, gameInfo.blackRating);
2803 if (looking_at(buf, &i,
2804 "* * match, initial time: * minute*, increment: * second")) {
2805 /* Header for a move list -- second line */
2806 /* Initial board will follow if this is a wild game */
2807 if (gameInfo.event != NULL) free(gameInfo.event);
2808 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2809 gameInfo.event = StrSave(str);
2810 /* [HGM] we switched variant. Translate boards if needed. */
2811 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2815 if (looking_at(buf, &i, "Move ")) {
2816 /* Beginning of a move list */
2817 switch (ics_getting_history) {
2819 /* Normally should not happen */
2820 /* Maybe user hit reset while we were parsing */
2823 /* Happens if we are ignoring a move list that is not
2824 * the one we just requested. Common if the user
2825 * tries to observe two games without turning off
2828 case H_GETTING_MOVES:
2829 /* Should not happen */
2830 DisplayError(_("Error gathering move list: nested"), 0);
2831 ics_getting_history = H_FALSE;
2833 case H_GOT_REQ_HEADER:
2834 ics_getting_history = H_GETTING_MOVES;
2835 started = STARTED_MOVES;
2837 if (oldi > next_out) {
2838 SendToPlayer(&buf[next_out], oldi - next_out);
2841 case H_GOT_UNREQ_HEADER:
2842 ics_getting_history = H_GETTING_MOVES;
2843 started = STARTED_MOVES_NOHIDE;
2846 case H_GOT_UNWANTED_HEADER:
2847 ics_getting_history = H_FALSE;
2853 if (looking_at(buf, &i, "% ") ||
2854 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2855 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2856 if(suppressKibitz) next_out = i;
2857 savingComment = FALSE;
2861 case STARTED_MOVES_NOHIDE:
2862 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2863 parse[parse_pos + i - oldi] = NULLCHAR;
2864 ParseGameHistory(parse);
2866 if (appData.zippyPlay && first.initDone) {
2867 FeedMovesToProgram(&first, forwardMostMove);
2868 if (gameMode == IcsPlayingWhite) {
2869 if (WhiteOnMove(forwardMostMove)) {
2870 if (first.sendTime) {
2871 if (first.useColors) {
2872 SendToProgram("black\n", &first);
2874 SendTimeRemaining(&first, TRUE);
2876 if (first.useColors) {
2877 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2879 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2880 first.maybeThinking = TRUE;
2882 if (first.usePlayother) {
2883 if (first.sendTime) {
2884 SendTimeRemaining(&first, TRUE);
2886 SendToProgram("playother\n", &first);
2892 } else if (gameMode == IcsPlayingBlack) {
2893 if (!WhiteOnMove(forwardMostMove)) {
2894 if (first.sendTime) {
2895 if (first.useColors) {
2896 SendToProgram("white\n", &first);
2898 SendTimeRemaining(&first, FALSE);
2900 if (first.useColors) {
2901 SendToProgram("black\n", &first);
2903 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2904 first.maybeThinking = TRUE;
2906 if (first.usePlayother) {
2907 if (first.sendTime) {
2908 SendTimeRemaining(&first, FALSE);
2910 SendToProgram("playother\n", &first);
2919 if (gameMode == IcsObserving && ics_gamenum == -1) {
2920 /* Moves came from oldmoves or moves command
2921 while we weren't doing anything else.
2923 currentMove = forwardMostMove;
2924 ClearHighlights();/*!!could figure this out*/
2925 flipView = appData.flipView;
2926 DrawPosition(TRUE, boards[currentMove]);
2927 DisplayBothClocks();
2928 sprintf(str, "%s vs. %s",
2929 gameInfo.white, gameInfo.black);
2933 /* Moves were history of an active game */
2934 if (gameInfo.resultDetails != NULL) {
2935 free(gameInfo.resultDetails);
2936 gameInfo.resultDetails = NULL;
2939 HistorySet(parseList, backwardMostMove,
2940 forwardMostMove, currentMove-1);
2941 DisplayMove(currentMove - 1);
2942 if (started == STARTED_MOVES) next_out = i;
2943 started = STARTED_NONE;
2944 ics_getting_history = H_FALSE;
2947 case STARTED_OBSERVE:
2948 started = STARTED_NONE;
2949 SendToICS(ics_prefix);
2950 SendToICS("refresh\n");
2956 if(bookHit) { // [HGM] book: simulate book reply
2957 static char bookMove[MSG_SIZ]; // a bit generous?
2959 programStats.nodes = programStats.depth = programStats.time =
2960 programStats.score = programStats.got_only_move = 0;
2961 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2963 strcpy(bookMove, "move ");
2964 strcat(bookMove, bookHit);
2965 HandleMachineMove(bookMove, &first);
2970 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2971 started == STARTED_HOLDINGS ||
2972 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2973 /* Accumulate characters in move list or board */
2974 parse[parse_pos++] = buf[i];
2977 /* Start of game messages. Mostly we detect start of game
2978 when the first board image arrives. On some versions
2979 of the ICS, though, we need to do a "refresh" after starting
2980 to observe in order to get the current board right away. */
2981 if (looking_at(buf, &i, "Adding game * to observation list")) {
2982 started = STARTED_OBSERVE;
2986 /* Handle auto-observe */
2987 if (appData.autoObserve &&
2988 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2989 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2991 /* Choose the player that was highlighted, if any. */
2992 if (star_match[0][0] == '\033' ||
2993 star_match[1][0] != '\033') {
2994 player = star_match[0];
2996 player = star_match[2];
2998 sprintf(str, "%sobserve %s\n",
2999 ics_prefix, StripHighlightAndTitle(player));
3002 /* Save ratings from notify string */
3003 strcpy(player1Name, star_match[0]);
3004 player1Rating = string_to_rating(star_match[1]);
3005 strcpy(player2Name, star_match[2]);
3006 player2Rating = string_to_rating(star_match[3]);
3008 if (appData.debugMode)
3010 "Ratings from 'Game notification:' %s %d, %s %d\n",
3011 player1Name, player1Rating,
3012 player2Name, player2Rating);
3017 /* Deal with automatic examine mode after a game,
3018 and with IcsObserving -> IcsExamining transition */
3019 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3020 looking_at(buf, &i, "has made you an examiner of game *")) {
3022 int gamenum = atoi(star_match[0]);
3023 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3024 gamenum == ics_gamenum) {
3025 /* We were already playing or observing this game;
3026 no need to refetch history */
3027 gameMode = IcsExamining;
3029 pauseExamForwardMostMove = forwardMostMove;
3030 } else if (currentMove < forwardMostMove) {
3031 ForwardInner(forwardMostMove);
3034 /* I don't think this case really can happen */
3035 SendToICS(ics_prefix);
3036 SendToICS("refresh\n");
3041 /* Error messages */
3042 // if (ics_user_moved) {
3043 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3044 if (looking_at(buf, &i, "Illegal move") ||
3045 looking_at(buf, &i, "Not a legal move") ||
3046 looking_at(buf, &i, "Your king is in check") ||
3047 looking_at(buf, &i, "It isn't your turn") ||
3048 looking_at(buf, &i, "It is not your move")) {
3050 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3051 currentMove = forwardMostMove-1;
3052 DisplayMove(currentMove - 1); /* before DMError */
3053 DrawPosition(FALSE, boards[currentMove]);
3054 SwitchClocks(forwardMostMove-1); // [HGM] race
3055 DisplayBothClocks();
3057 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3063 if (looking_at(buf, &i, "still have time") ||
3064 looking_at(buf, &i, "not out of time") ||
3065 looking_at(buf, &i, "either player is out of time") ||
3066 looking_at(buf, &i, "has timeseal; checking")) {
3067 /* We must have called his flag a little too soon */
3068 whiteFlag = blackFlag = FALSE;
3072 if (looking_at(buf, &i, "added * seconds to") ||
3073 looking_at(buf, &i, "seconds were added to")) {
3074 /* Update the clocks */
3075 SendToICS(ics_prefix);
3076 SendToICS("refresh\n");
3080 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3081 ics_clock_paused = TRUE;
3086 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3087 ics_clock_paused = FALSE;
3092 /* Grab player ratings from the Creating: message.
3093 Note we have to check for the special case when
3094 the ICS inserts things like [white] or [black]. */
3095 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3096 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3098 0 player 1 name (not necessarily white)
3100 2 empty, white, or black (IGNORED)
3101 3 player 2 name (not necessarily black)
3104 The names/ratings are sorted out when the game
3105 actually starts (below).
3107 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3108 player1Rating = string_to_rating(star_match[1]);
3109 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3110 player2Rating = string_to_rating(star_match[4]);
3112 if (appData.debugMode)
3114 "Ratings from 'Creating:' %s %d, %s %d\n",
3115 player1Name, player1Rating,
3116 player2Name, player2Rating);
3121 /* Improved generic start/end-of-game messages */
3122 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3123 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3124 /* If tkind == 0: */
3125 /* star_match[0] is the game number */
3126 /* [1] is the white player's name */
3127 /* [2] is the black player's name */
3128 /* For end-of-game: */
3129 /* [3] is the reason for the game end */
3130 /* [4] is a PGN end game-token, preceded by " " */
3131 /* For start-of-game: */
3132 /* [3] begins with "Creating" or "Continuing" */
3133 /* [4] is " *" or empty (don't care). */
3134 int gamenum = atoi(star_match[0]);
3135 char *whitename, *blackname, *why, *endtoken;
3136 ChessMove endtype = (ChessMove) 0;
3139 whitename = star_match[1];
3140 blackname = star_match[2];
3141 why = star_match[3];
3142 endtoken = star_match[4];
3144 whitename = star_match[1];
3145 blackname = star_match[3];
3146 why = star_match[5];
3147 endtoken = star_match[6];
3150 /* Game start messages */
3151 if (strncmp(why, "Creating ", 9) == 0 ||
3152 strncmp(why, "Continuing ", 11) == 0) {
3153 gs_gamenum = gamenum;
3154 strcpy(gs_kind, strchr(why, ' ') + 1);
3155 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3157 if (appData.zippyPlay) {
3158 ZippyGameStart(whitename, blackname);
3164 /* Game end messages */
3165 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3166 ics_gamenum != gamenum) {
3169 while (endtoken[0] == ' ') endtoken++;
3170 switch (endtoken[0]) {
3173 endtype = GameUnfinished;
3176 endtype = BlackWins;
3179 if (endtoken[1] == '/')
3180 endtype = GameIsDrawn;
3182 endtype = WhiteWins;
3185 GameEnds(endtype, why, GE_ICS);
3187 if (appData.zippyPlay && first.initDone) {
3188 ZippyGameEnd(endtype, why);
3189 if (first.pr == NULL) {
3190 /* Start the next process early so that we'll
3191 be ready for the next challenge */
3192 StartChessProgram(&first);
3194 /* Send "new" early, in case this command takes
3195 a long time to finish, so that we'll be ready
3196 for the next challenge. */
3197 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3204 if (looking_at(buf, &i, "Removing game * from observation") ||
3205 looking_at(buf, &i, "no longer observing game *") ||
3206 looking_at(buf, &i, "Game * (*) has no examiners")) {
3207 if (gameMode == IcsObserving &&
3208 atoi(star_match[0]) == ics_gamenum)
3210 /* icsEngineAnalyze */
3211 if (appData.icsEngineAnalyze) {
3218 ics_user_moved = FALSE;
3223 if (looking_at(buf, &i, "no longer examining game *")) {
3224 if (gameMode == IcsExamining &&
3225 atoi(star_match[0]) == ics_gamenum)
3229 ics_user_moved = FALSE;
3234 /* Advance leftover_start past any newlines we find,
3235 so only partial lines can get reparsed */
3236 if (looking_at(buf, &i, "\n")) {
3237 prevColor = curColor;
3238 if (curColor != ColorNormal) {
3239 if (oldi > next_out) {
3240 SendToPlayer(&buf[next_out], oldi - next_out);
3243 Colorize(ColorNormal, FALSE);
3244 curColor = ColorNormal;
3246 if (started == STARTED_BOARD) {
3247 started = STARTED_NONE;
3248 parse[parse_pos] = NULLCHAR;
3249 ParseBoard12(parse);
3252 /* Send premove here */
3253 if (appData.premove) {
3255 if (currentMove == 0 &&
3256 gameMode == IcsPlayingWhite &&
3257 appData.premoveWhite) {
3258 sprintf(str, "%s\n", appData.premoveWhiteText);
3259 if (appData.debugMode)
3260 fprintf(debugFP, "Sending premove:\n");
3262 } else if (currentMove == 1 &&
3263 gameMode == IcsPlayingBlack &&
3264 appData.premoveBlack) {
3265 sprintf(str, "%s\n", appData.premoveBlackText);
3266 if (appData.debugMode)
3267 fprintf(debugFP, "Sending premove:\n");
3269 } else if (gotPremove) {
3271 ClearPremoveHighlights();
3272 if (appData.debugMode)
3273 fprintf(debugFP, "Sending premove:\n");
3274 UserMoveEvent(premoveFromX, premoveFromY,
3275 premoveToX, premoveToY,
3280 /* Usually suppress following prompt */
3281 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3282 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3283 if (looking_at(buf, &i, "*% ")) {
3284 savingComment = FALSE;
3289 } else if (started == STARTED_HOLDINGS) {
3291 char new_piece[MSG_SIZ];
3292 started = STARTED_NONE;
3293 parse[parse_pos] = NULLCHAR;
3294 if (appData.debugMode)
3295 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3296 parse, currentMove);
3297 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3298 gamenum == ics_gamenum) {
3299 if (gameInfo.variant == VariantNormal) {
3300 /* [HGM] We seem to switch variant during a game!
3301 * Presumably no holdings were displayed, so we have
3302 * to move the position two files to the right to
3303 * create room for them!
3305 VariantClass newVariant;
3306 switch(gameInfo.boardWidth) { // base guess on board width
3307 case 9: newVariant = VariantShogi; break;
3308 case 10: newVariant = VariantGreat; break;
3309 default: newVariant = VariantCrazyhouse; break;
3311 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3312 /* Get a move list just to see the header, which
3313 will tell us whether this is really bug or zh */
3314 if (ics_getting_history == H_FALSE) {
3315 ics_getting_history = H_REQUESTED;
3316 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3320 new_piece[0] = NULLCHAR;
3321 sscanf(parse, "game %d white [%s black [%s <- %s",
3322 &gamenum, white_holding, black_holding,
3324 white_holding[strlen(white_holding)-1] = NULLCHAR;
3325 black_holding[strlen(black_holding)-1] = NULLCHAR;
3326 /* [HGM] copy holdings to board holdings area */
3327 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3328 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3329 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3331 if (appData.zippyPlay && first.initDone) {
3332 ZippyHoldings(white_holding, black_holding,
3336 if (tinyLayout || smallLayout) {
3337 char wh[16], bh[16];
3338 PackHolding(wh, white_holding);
3339 PackHolding(bh, black_holding);
3340 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3341 gameInfo.white, gameInfo.black);
3343 sprintf(str, "%s [%s] vs. %s [%s]",
3344 gameInfo.white, white_holding,
3345 gameInfo.black, black_holding);
3348 DrawPosition(FALSE, boards[currentMove]);
3351 /* Suppress following prompt */
3352 if (looking_at(buf, &i, "*% ")) {
3353 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3354 savingComment = FALSE;
3362 i++; /* skip unparsed character and loop back */
3365 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3366 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3367 // SendToPlayer(&buf[next_out], i - next_out);
3368 started != STARTED_HOLDINGS && leftover_start > next_out) {
3369 SendToPlayer(&buf[next_out], leftover_start - next_out);
3373 leftover_len = buf_len - leftover_start;
3374 /* if buffer ends with something we couldn't parse,
3375 reparse it after appending the next read */
3377 } else if (count == 0) {
3378 RemoveInputSource(isr);
3379 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3381 DisplayFatalError(_("Error reading from ICS"), error, 1);
3386 /* Board style 12 looks like this:
3388 <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
3390 * The "<12> " is stripped before it gets to this routine. The two
3391 * trailing 0's (flip state and clock ticking) are later addition, and
3392 * some chess servers may not have them, or may have only the first.
3393 * Additional trailing fields may be added in the future.
3396 #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"
3398 #define RELATION_OBSERVING_PLAYED 0
3399 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3400 #define RELATION_PLAYING_MYMOVE 1
3401 #define RELATION_PLAYING_NOTMYMOVE -1
3402 #define RELATION_EXAMINING 2
3403 #define RELATION_ISOLATED_BOARD -3
3404 #define RELATION_STARTING_POSITION -4 /* FICS only */
3407 ParseBoard12(string)
3410 GameMode newGameMode;
3411 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3412 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3413 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3414 char to_play, board_chars[200];
3415 char move_str[500], str[500], elapsed_time[500];
3416 char black[32], white[32];
3418 int prevMove = currentMove;
3421 int fromX, fromY, toX, toY;
3423 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3424 char *bookHit = NULL; // [HGM] book
3425 Boolean weird = FALSE, reqFlag = FALSE;
3427 fromX = fromY = toX = toY = -1;
3431 if (appData.debugMode)
3432 fprintf(debugFP, _("Parsing board: %s\n"), string);
3434 move_str[0] = NULLCHAR;
3435 elapsed_time[0] = NULLCHAR;
3436 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3438 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3439 if(string[i] == ' ') { ranks++; files = 0; }
3441 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3444 for(j = 0; j <i; j++) board_chars[j] = string[j];
3445 board_chars[i] = '\0';
3448 n = sscanf(string, PATTERN, &to_play, &double_push,
3449 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3450 &gamenum, white, black, &relation, &basetime, &increment,
3451 &white_stren, &black_stren, &white_time, &black_time,
3452 &moveNum, str, elapsed_time, move_str, &ics_flip,
3456 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3457 DisplayError(str, 0);
3461 /* Convert the move number to internal form */
3462 moveNum = (moveNum - 1) * 2;
3463 if (to_play == 'B') moveNum++;
3464 if (moveNum >= MAX_MOVES) {
3465 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3471 case RELATION_OBSERVING_PLAYED:
3472 case RELATION_OBSERVING_STATIC:
3473 if (gamenum == -1) {
3474 /* Old ICC buglet */
3475 relation = RELATION_OBSERVING_STATIC;
3477 newGameMode = IcsObserving;
3479 case RELATION_PLAYING_MYMOVE:
3480 case RELATION_PLAYING_NOTMYMOVE:
3482 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3483 IcsPlayingWhite : IcsPlayingBlack;
3485 case RELATION_EXAMINING:
3486 newGameMode = IcsExamining;
3488 case RELATION_ISOLATED_BOARD:
3490 /* Just display this board. If user was doing something else,
3491 we will forget about it until the next board comes. */
3492 newGameMode = IcsIdle;
3494 case RELATION_STARTING_POSITION:
3495 newGameMode = gameMode;
3499 /* Modify behavior for initial board display on move listing
3502 switch (ics_getting_history) {
3506 case H_GOT_REQ_HEADER:
3507 case H_GOT_UNREQ_HEADER:
3508 /* This is the initial position of the current game */
3509 gamenum = ics_gamenum;
3510 moveNum = 0; /* old ICS bug workaround */
3511 if (to_play == 'B') {
3512 startedFromSetupPosition = TRUE;
3513 blackPlaysFirst = TRUE;
3515 if (forwardMostMove == 0) forwardMostMove = 1;
3516 if (backwardMostMove == 0) backwardMostMove = 1;
3517 if (currentMove == 0) currentMove = 1;
3519 newGameMode = gameMode;
3520 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3522 case H_GOT_UNWANTED_HEADER:
3523 /* This is an initial board that we don't want */
3525 case H_GETTING_MOVES:
3526 /* Should not happen */
3527 DisplayError(_("Error gathering move list: extra board"), 0);
3528 ics_getting_history = H_FALSE;
3532 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3533 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3534 /* [HGM] We seem to have switched variant unexpectedly
3535 * Try to guess new variant from board size
3537 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3538 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3539 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3540 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3541 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3542 if(!weird) newVariant = VariantNormal;
3543 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3544 /* Get a move list just to see the header, which
3545 will tell us whether this is really bug or zh */
3546 if (ics_getting_history == H_FALSE) {
3547 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3548 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3553 /* Take action if this is the first board of a new game, or of a
3554 different game than is currently being displayed. */
3555 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3556 relation == RELATION_ISOLATED_BOARD) {
3558 /* Forget the old game and get the history (if any) of the new one */
3559 if (gameMode != BeginningOfGame) {
3563 if (appData.autoRaiseBoard) BoardToTop();
3565 if (gamenum == -1) {
3566 newGameMode = IcsIdle;
3567 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3568 appData.getMoveList && !reqFlag) {
3569 /* Need to get game history */
3570 ics_getting_history = H_REQUESTED;
3571 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3575 /* Initially flip the board to have black on the bottom if playing
3576 black or if the ICS flip flag is set, but let the user change
3577 it with the Flip View button. */
3578 flipView = appData.autoFlipView ?
3579 (newGameMode == IcsPlayingBlack) || ics_flip :
3582 /* Done with values from previous mode; copy in new ones */
3583 gameMode = newGameMode;
3585 ics_gamenum = gamenum;
3586 if (gamenum == gs_gamenum) {
3587 int klen = strlen(gs_kind);
3588 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3589 sprintf(str, "ICS %s", gs_kind);
3590 gameInfo.event = StrSave(str);
3592 gameInfo.event = StrSave("ICS game");
3594 gameInfo.site = StrSave(appData.icsHost);
3595 gameInfo.date = PGNDate();
3596 gameInfo.round = StrSave("-");
3597 gameInfo.white = StrSave(white);
3598 gameInfo.black = StrSave(black);
3599 timeControl = basetime * 60 * 1000;
3601 timeIncrement = increment * 1000;
3602 movesPerSession = 0;
3603 gameInfo.timeControl = TimeControlTagValue();
3604 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3605 if (appData.debugMode) {
3606 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3607 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3608 setbuf(debugFP, NULL);
3611 gameInfo.outOfBook = NULL;
3613 /* Do we have the ratings? */
3614 if (strcmp(player1Name, white) == 0 &&
3615 strcmp(player2Name, black) == 0) {
3616 if (appData.debugMode)
3617 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3618 player1Rating, player2Rating);
3619 gameInfo.whiteRating = player1Rating;
3620 gameInfo.blackRating = player2Rating;
3621 } else if (strcmp(player2Name, white) == 0 &&
3622 strcmp(player1Name, black) == 0) {
3623 if (appData.debugMode)
3624 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3625 player2Rating, player1Rating);
3626 gameInfo.whiteRating = player2Rating;
3627 gameInfo.blackRating = player1Rating;
3629 player1Name[0] = player2Name[0] = NULLCHAR;
3631 /* Silence shouts if requested */
3632 if (appData.quietPlay &&
3633 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3634 SendToICS(ics_prefix);
3635 SendToICS("set shout 0\n");
3639 /* Deal with midgame name changes */
3641 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3642 if (gameInfo.white) free(gameInfo.white);
3643 gameInfo.white = StrSave(white);
3645 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3646 if (gameInfo.black) free(gameInfo.black);
3647 gameInfo.black = StrSave(black);
3651 /* Throw away game result if anything actually changes in examine mode */
3652 if (gameMode == IcsExamining && !newGame) {
3653 gameInfo.result = GameUnfinished;
3654 if (gameInfo.resultDetails != NULL) {
3655 free(gameInfo.resultDetails);
3656 gameInfo.resultDetails = NULL;
3660 /* In pausing && IcsExamining mode, we ignore boards coming
3661 in if they are in a different variation than we are. */
3662 if (pauseExamInvalid) return;
3663 if (pausing && gameMode == IcsExamining) {
3664 if (moveNum <= pauseExamForwardMostMove) {
3665 pauseExamInvalid = TRUE;
3666 forwardMostMove = pauseExamForwardMostMove;
3671 if (appData.debugMode) {
3672 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3674 /* Parse the board */
3675 for (k = 0; k < ranks; k++) {
3676 for (j = 0; j < files; j++)
3677 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3678 if(gameInfo.holdingsWidth > 1) {
3679 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3680 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3683 CopyBoard(boards[moveNum], board);
3684 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3686 startedFromSetupPosition =
3687 !CompareBoards(board, initialPosition);
3688 if(startedFromSetupPosition)
3689 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3692 /* [HGM] Set castling rights. Take the outermost Rooks,
3693 to make it also work for FRC opening positions. Note that board12
3694 is really defective for later FRC positions, as it has no way to
3695 indicate which Rook can castle if they are on the same side of King.
3696 For the initial position we grant rights to the outermost Rooks,
3697 and remember thos rights, and we then copy them on positions
3698 later in an FRC game. This means WB might not recognize castlings with
3699 Rooks that have moved back to their original position as illegal,
3700 but in ICS mode that is not its job anyway.
3702 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3703 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3705 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3706 if(board[0][i] == WhiteRook) j = i;
3707 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3708 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3709 if(board[0][i] == WhiteRook) j = i;
3710 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3711 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3712 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3713 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3714 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3715 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3716 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3718 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3719 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3720 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3721 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3722 if(board[BOARD_HEIGHT-1][k] == bKing)
3723 initialRights[5] = castlingRights[moveNum][5] = k;
3724 if(gameInfo.variant == VariantTwoKings) {
3725 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3726 if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3727 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3730 r = castlingRights[moveNum][0] = initialRights[0];
3731 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3732 r = castlingRights[moveNum][1] = initialRights[1];
3733 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3734 r = castlingRights[moveNum][3] = initialRights[3];
3735 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3736 r = castlingRights[moveNum][4] = initialRights[4];
3737 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3738 /* wildcastle kludge: always assume King has rights */
3739 r = castlingRights[moveNum][2] = initialRights[2];
3740 r = castlingRights[moveNum][5] = initialRights[5];
3742 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3743 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3746 if (ics_getting_history == H_GOT_REQ_HEADER ||
3747 ics_getting_history == H_GOT_UNREQ_HEADER) {
3748 /* This was an initial position from a move list, not
3749 the current position */
3753 /* Update currentMove and known move number limits */
3754 newMove = newGame || moveNum > forwardMostMove;
3757 forwardMostMove = backwardMostMove = currentMove = moveNum;
3758 if (gameMode == IcsExamining && moveNum == 0) {
3759 /* Workaround for ICS limitation: we are not told the wild
3760 type when starting to examine a game. But if we ask for
3761 the move list, the move list header will tell us */
3762 ics_getting_history = H_REQUESTED;
3763 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3766 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3767 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3769 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3770 /* [HGM] applied this also to an engine that is silently watching */
3771 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3772 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3773 gameInfo.variant == currentlyInitializedVariant) {
3774 takeback = forwardMostMove - moveNum;
3775 for (i = 0; i < takeback; i++) {
3776 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3777 SendToProgram("undo\n", &first);
3782 forwardMostMove = moveNum;
3783 if (!pausing || currentMove > forwardMostMove)
3784 currentMove = forwardMostMove;
3786 /* New part of history that is not contiguous with old part */
3787 if (pausing && gameMode == IcsExamining) {
3788 pauseExamInvalid = TRUE;
3789 forwardMostMove = pauseExamForwardMostMove;
3792 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3794 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3795 // [HGM] when we will receive the move list we now request, it will be
3796 // fed to the engine from the first move on. So if the engine is not
3797 // in the initial position now, bring it there.
3798 InitChessProgram(&first, 0);
3801 ics_getting_history = H_REQUESTED;
3802 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3805 forwardMostMove = backwardMostMove = currentMove = moveNum;
3808 /* Update the clocks */
3809 if (strchr(elapsed_time, '.')) {
3811 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3812 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3814 /* Time is in seconds */
3815 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3816 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3821 if (appData.zippyPlay && newGame &&
3822 gameMode != IcsObserving && gameMode != IcsIdle &&
3823 gameMode != IcsExamining)
3824 ZippyFirstBoard(moveNum, basetime, increment);
3827 /* Put the move on the move list, first converting
3828 to canonical algebraic form. */
3830 if (appData.debugMode) {
3831 if (appData.debugMode) { int f = forwardMostMove;
3832 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3833 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3835 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3836 fprintf(debugFP, "moveNum = %d\n", moveNum);
3837 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3838 setbuf(debugFP, NULL);
3840 if (moveNum <= backwardMostMove) {
3841 /* We don't know what the board looked like before
3843 strcpy(parseList[moveNum - 1], move_str);
3844 strcat(parseList[moveNum - 1], " ");
3845 strcat(parseList[moveNum - 1], elapsed_time);
3846 moveList[moveNum - 1][0] = NULLCHAR;
3847 } else if (strcmp(move_str, "none") == 0) {
3848 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3849 /* Again, we don't know what the board looked like;
3850 this is really the start of the game. */
3851 parseList[moveNum - 1][0] = NULLCHAR;
3852 moveList[moveNum - 1][0] = NULLCHAR;
3853 backwardMostMove = moveNum;
3854 startedFromSetupPosition = TRUE;
3855 fromX = fromY = toX = toY = -1;
3857 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3858 // So we parse the long-algebraic move string in stead of the SAN move
3859 int valid; char buf[MSG_SIZ], *prom;
3861 // str looks something like "Q/a1-a2"; kill the slash
3863 sprintf(buf, "%c%s", str[0], str+2);
3864 else strcpy(buf, str); // might be castling
3865 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3866 strcat(buf, prom); // long move lacks promo specification!
3867 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3868 if(appData.debugMode)
3869 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3870 strcpy(move_str, buf);
3872 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3873 &fromX, &fromY, &toX, &toY, &promoChar)
3874 || ParseOneMove(buf, moveNum - 1, &moveType,
3875 &fromX, &fromY, &toX, &toY, &promoChar);
3876 // end of long SAN patch
3878 (void) CoordsToAlgebraic(boards[moveNum - 1],
3879 PosFlags(moveNum - 1), EP_UNKNOWN,
3880 fromY, fromX, toY, toX, promoChar,
3881 parseList[moveNum-1]);
3882 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3883 castlingRights[moveNum]) ) {
3889 if(gameInfo.variant != VariantShogi)
3890 strcat(parseList[moveNum - 1], "+");
3893 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3894 strcat(parseList[moveNum - 1], "#");
3897 strcat(parseList[moveNum - 1], " ");
3898 strcat(parseList[moveNum - 1], elapsed_time);
3899 /* currentMoveString is set as a side-effect of ParseOneMove */
3900 strcpy(moveList[moveNum - 1], currentMoveString);
3901 strcat(moveList[moveNum - 1], "\n");
3903 /* Move from ICS was illegal!? Punt. */
3904 if (appData.debugMode) {
3905 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3906 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3908 strcpy(parseList[moveNum - 1], move_str);
3909 strcat(parseList[moveNum - 1], " ");
3910 strcat(parseList[moveNum - 1], elapsed_time);
3911 moveList[moveNum - 1][0] = NULLCHAR;
3912 fromX = fromY = toX = toY = -1;
3915 if (appData.debugMode) {
3916 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3917 setbuf(debugFP, NULL);
3921 /* Send move to chess program (BEFORE animating it). */
3922 if (appData.zippyPlay && !newGame && newMove &&
3923 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3925 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3926 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3927 if (moveList[moveNum - 1][0] == NULLCHAR) {
3928 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3930 DisplayError(str, 0);
3932 if (first.sendTime) {
3933 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3935 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3936 if (firstMove && !bookHit) {
3938 if (first.useColors) {
3939 SendToProgram(gameMode == IcsPlayingWhite ?
3941 "black\ngo\n", &first);
3943 SendToProgram("go\n", &first);
3945 first.maybeThinking = TRUE;
3948 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3949 if (moveList[moveNum - 1][0] == NULLCHAR) {
3950 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3951 DisplayError(str, 0);
3953 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3954 SendMoveToProgram(moveNum - 1, &first);
3961 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3962 /* If move comes from a remote source, animate it. If it
3963 isn't remote, it will have already been animated. */
3964 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3965 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3967 if (!pausing && appData.highlightLastMove) {
3968 SetHighlights(fromX, fromY, toX, toY);
3972 /* Start the clocks */
3973 whiteFlag = blackFlag = FALSE;
3974 appData.clockMode = !(basetime == 0 && increment == 0);
3976 ics_clock_paused = TRUE;
3978 } else if (ticking == 1) {
3979 ics_clock_paused = FALSE;
3981 if (gameMode == IcsIdle ||
3982 relation == RELATION_OBSERVING_STATIC ||
3983 relation == RELATION_EXAMINING ||
3985 DisplayBothClocks();
3989 /* Display opponents and material strengths */
3990 if (gameInfo.variant != VariantBughouse &&
3991 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3992 if (tinyLayout || smallLayout) {
3993 if(gameInfo.variant == VariantNormal)
3994 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3995 gameInfo.white, white_stren, gameInfo.black, black_stren,
3996 basetime, increment);
3998 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3999 gameInfo.white, white_stren, gameInfo.black, black_stren,
4000 basetime, increment, (int) gameInfo.variant);
4002 if(gameInfo.variant == VariantNormal)
4003 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4004 gameInfo.white, white_stren, gameInfo.black, black_stren,
4005 basetime, increment);
4007 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4008 gameInfo.white, white_stren, gameInfo.black, black_stren,
4009 basetime, increment, VariantName(gameInfo.variant));
4012 if (appData.debugMode) {
4013 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4018 /* Display the board */
4019 if (!pausing && !appData.noGUI) {
4021 if (appData.premove)
4023 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4024 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4025 ClearPremoveHighlights();
4027 DrawPosition(FALSE, boards[currentMove]);
4028 DisplayMove(moveNum - 1);
4029 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4030 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4031 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4032 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4036 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4038 if(bookHit) { // [HGM] book: simulate book reply
4039 static char bookMove[MSG_SIZ]; // a bit generous?
4041 programStats.nodes = programStats.depth = programStats.time =
4042 programStats.score = programStats.got_only_move = 0;
4043 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4045 strcpy(bookMove, "move ");
4046 strcat(bookMove, bookHit);
4047 HandleMachineMove(bookMove, &first);
4056 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4057 ics_getting_history = H_REQUESTED;
4058 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4064 AnalysisPeriodicEvent(force)
4067 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4068 && !force) || !appData.periodicUpdates)
4071 /* Send . command to Crafty to collect stats */
4072 SendToProgram(".\n", &first);
4074 /* Don't send another until we get a response (this makes
4075 us stop sending to old Crafty's which don't understand
4076 the "." command (sending illegal cmds resets node count & time,
4077 which looks bad)) */
4078 programStats.ok_to_send = 0;
4081 void ics_update_width(new_width)
4084 ics_printf("set width %d\n", new_width);
4088 SendMoveToProgram(moveNum, cps)
4090 ChessProgramState *cps;
4094 if (cps->useUsermove) {
4095 SendToProgram("usermove ", cps);
4099 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4100 int len = space - parseList[moveNum];
4101 memcpy(buf, parseList[moveNum], len);
4103 buf[len] = NULLCHAR;
4105 sprintf(buf, "%s\n", parseList[moveNum]);
4107 SendToProgram(buf, cps);
4109 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4110 AlphaRank(moveList[moveNum], 4);
4111 SendToProgram(moveList[moveNum], cps);
4112 AlphaRank(moveList[moveNum], 4); // and back
4114 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4115 * the engine. It would be nice to have a better way to identify castle
4117 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4118 && cps->useOOCastle) {
4119 int fromX = moveList[moveNum][0] - AAA;
4120 int fromY = moveList[moveNum][1] - ONE;
4121 int toX = moveList[moveNum][2] - AAA;
4122 int toY = moveList[moveNum][3] - ONE;
4123 if((boards[moveNum][fromY][fromX] == WhiteKing
4124 && boards[moveNum][toY][toX] == WhiteRook)
4125 || (boards[moveNum][fromY][fromX] == BlackKing
4126 && boards[moveNum][toY][toX] == BlackRook)) {
4127 if(toX > fromX) SendToProgram("O-O\n", cps);
4128 else SendToProgram("O-O-O\n", cps);
4130 else SendToProgram(moveList[moveNum], cps);
4132 else SendToProgram(moveList[moveNum], cps);
4133 /* End of additions by Tord */
4136 /* [HGM] setting up the opening has brought engine in force mode! */
4137 /* Send 'go' if we are in a mode where machine should play. */
4138 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4139 (gameMode == TwoMachinesPlay ||
4141 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4143 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4144 SendToProgram("go\n", cps);
4145 if (appData.debugMode) {
4146 fprintf(debugFP, "(extra)\n");
4149 setboardSpoiledMachineBlack = 0;
4153 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4155 int fromX, fromY, toX, toY;
4157 char user_move[MSG_SIZ];
4161 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4162 (int)moveType, fromX, fromY, toX, toY);
4163 DisplayError(user_move + strlen("say "), 0);
4165 case WhiteKingSideCastle:
4166 case BlackKingSideCastle:
4167 case WhiteQueenSideCastleWild:
4168 case BlackQueenSideCastleWild:
4170 case WhiteHSideCastleFR:
4171 case BlackHSideCastleFR:
4173 sprintf(user_move, "o-o\n");
4175 case WhiteQueenSideCastle:
4176 case BlackQueenSideCastle:
4177 case WhiteKingSideCastleWild:
4178 case BlackKingSideCastleWild:
4180 case WhiteASideCastleFR:
4181 case BlackASideCastleFR:
4183 sprintf(user_move, "o-o-o\n");
4185 case WhitePromotionQueen:
4186 case BlackPromotionQueen:
4187 case WhitePromotionRook:
4188 case BlackPromotionRook:
4189 case WhitePromotionBishop:
4190 case BlackPromotionBishop:
4191 case WhitePromotionKnight:
4192 case BlackPromotionKnight:
4193 case WhitePromotionKing:
4194 case BlackPromotionKing:
4195 case WhitePromotionChancellor:
4196 case BlackPromotionChancellor:
4197 case WhitePromotionArchbishop:
4198 case BlackPromotionArchbishop:
4199 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4200 sprintf(user_move, "%c%c%c%c=%c\n",
4201 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4202 PieceToChar(WhiteFerz));
4203 else if(gameInfo.variant == VariantGreat)
4204 sprintf(user_move, "%c%c%c%c=%c\n",
4205 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4206 PieceToChar(WhiteMan));
4208 sprintf(user_move, "%c%c%c%c=%c\n",
4209 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4210 PieceToChar(PromoPiece(moveType)));
4214 sprintf(user_move, "%c@%c%c\n",
4215 ToUpper(PieceToChar((ChessSquare) fromX)),
4216 AAA + toX, ONE + toY);
4219 case WhiteCapturesEnPassant:
4220 case BlackCapturesEnPassant:
4221 case IllegalMove: /* could be a variant we don't quite understand */
4222 sprintf(user_move, "%c%c%c%c\n",
4223 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4226 SendToICS(user_move);
4227 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4228 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4232 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4237 if (rf == DROP_RANK) {
4238 sprintf(move, "%c@%c%c\n",
4239 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4241 if (promoChar == 'x' || promoChar == NULLCHAR) {
4242 sprintf(move, "%c%c%c%c\n",
4243 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4245 sprintf(move, "%c%c%c%c%c\n",
4246 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4252 ProcessICSInitScript(f)
4257 while (fgets(buf, MSG_SIZ, f)) {
4258 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4265 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4267 AlphaRank(char *move, int n)
4269 // char *p = move, c; int x, y;
4271 if (appData.debugMode) {
4272 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4276 move[2]>='0' && move[2]<='9' &&
4277 move[3]>='a' && move[3]<='x' ) {
4279 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4280 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4282 if(move[0]>='0' && move[0]<='9' &&
4283 move[1]>='a' && move[1]<='x' &&
4284 move[2]>='0' && move[2]<='9' &&
4285 move[3]>='a' && move[3]<='x' ) {
4286 /* input move, Shogi -> normal */
4287 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4288 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4289 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4290 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4293 move[3]>='0' && move[3]<='9' &&
4294 move[2]>='a' && move[2]<='x' ) {
4296 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4297 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4300 move[0]>='a' && move[0]<='x' &&
4301 move[3]>='0' && move[3]<='9' &&
4302 move[2]>='a' && move[2]<='x' ) {
4303 /* output move, normal -> Shogi */
4304 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4305 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4306 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4307 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4308 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4310 if (appData.debugMode) {
4311 fprintf(debugFP, " out = '%s'\n", move);
4315 /* Parser for moves from gnuchess, ICS, or user typein box */
4317 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4320 ChessMove *moveType;
4321 int *fromX, *fromY, *toX, *toY;
4324 if (appData.debugMode) {
4325 fprintf(debugFP, "move to parse: %s\n", move);
4327 *moveType = yylexstr(moveNum, move);
4329 switch (*moveType) {
4330 case WhitePromotionChancellor:
4331 case BlackPromotionChancellor:
4332 case WhitePromotionArchbishop:
4333 case BlackPromotionArchbishop:
4334 case WhitePromotionQueen:
4335 case BlackPromotionQueen:
4336 case WhitePromotionRook:
4337 case BlackPromotionRook:
4338 case WhitePromotionBishop:
4339 case BlackPromotionBishop:
4340 case WhitePromotionKnight:
4341 case BlackPromotionKnight:
4342 case WhitePromotionKing:
4343 case BlackPromotionKing:
4345 case WhiteCapturesEnPassant:
4346 case BlackCapturesEnPassant:
4347 case WhiteKingSideCastle:
4348 case WhiteQueenSideCastle:
4349 case BlackKingSideCastle:
4350 case BlackQueenSideCastle:
4351 case WhiteKingSideCastleWild:
4352 case WhiteQueenSideCastleWild:
4353 case BlackKingSideCastleWild:
4354 case BlackQueenSideCastleWild:
4355 /* Code added by Tord: */
4356 case WhiteHSideCastleFR:
4357 case WhiteASideCastleFR:
4358 case BlackHSideCastleFR:
4359 case BlackASideCastleFR:
4360 /* End of code added by Tord */
4361 case IllegalMove: /* bug or odd chess variant */
4362 *fromX = currentMoveString[0] - AAA;
4363 *fromY = currentMoveString[1] - ONE;
4364 *toX = currentMoveString[2] - AAA;
4365 *toY = currentMoveString[3] - ONE;
4366 *promoChar = currentMoveString[4];
4367 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4368 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4369 if (appData.debugMode) {
4370 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4372 *fromX = *fromY = *toX = *toY = 0;
4375 if (appData.testLegality) {
4376 return (*moveType != IllegalMove);
4378 return !(*fromX == *toX && *fromY == *toY);
4383 *fromX = *moveType == WhiteDrop ?
4384 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4385 (int) CharToPiece(ToLower(currentMoveString[0]));
4387 *toX = currentMoveString[2] - AAA;
4388 *toY = currentMoveString[3] - ONE;
4389 *promoChar = NULLCHAR;
4393 case ImpossibleMove:
4394 case (ChessMove) 0: /* end of file */
4403 if (appData.debugMode) {
4404 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4407 *fromX = *fromY = *toX = *toY = 0;
4408 *promoChar = NULLCHAR;
4413 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4414 // All positions will have equal probability, but the current method will not provide a unique
4415 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4421 int piecesLeft[(int)BlackPawn];
4422 int seed, nrOfShuffles;
4424 void GetPositionNumber()
4425 { // sets global variable seed
4428 seed = appData.defaultFrcPosition;
4429 if(seed < 0) { // randomize based on time for negative FRC position numbers
4430 for(i=0; i<50; i++) seed += random();
4431 seed = random() ^ random() >> 8 ^ random() << 8;
4432 if(seed<0) seed = -seed;
4436 int put(Board board, int pieceType, int rank, int n, int shade)
4437 // put the piece on the (n-1)-th empty squares of the given shade
4441 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4442 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4443 board[rank][i] = (ChessSquare) pieceType;
4444 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4446 piecesLeft[pieceType]--;
4454 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4455 // calculate where the next piece goes, (any empty square), and put it there
4459 i = seed % squaresLeft[shade];
4460 nrOfShuffles *= squaresLeft[shade];
4461 seed /= squaresLeft[shade];
4462 put(board, pieceType, rank, i, shade);
4465 void AddTwoPieces(Board board, int pieceType, int rank)
4466 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4468 int i, n=squaresLeft[ANY], j=n-1, k;
4470 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4471 i = seed % k; // pick one
4474 while(i >= j) i -= j--;
4475 j = n - 1 - j; i += j;
4476 put(board, pieceType, rank, j, ANY);
4477 put(board, pieceType, rank, i, ANY);
4480 void SetUpShuffle(Board board, int number)
4484 GetPositionNumber(); nrOfShuffles = 1;
4486 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4487 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4488 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4490 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4492 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4493 p = (int) board[0][i];
4494 if(p < (int) BlackPawn) piecesLeft[p] ++;
4495 board[0][i] = EmptySquare;
4498 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4499 // shuffles restricted to allow normal castling put KRR first
4500 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4501 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4502 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4503 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4504 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4505 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4506 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4507 put(board, WhiteRook, 0, 0, ANY);
4508 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4511 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4512 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4513 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4514 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4515 while(piecesLeft[p] >= 2) {
4516 AddOnePiece(board, p, 0, LITE);
4517 AddOnePiece(board, p, 0, DARK);
4519 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4522 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4523 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4524 // but we leave King and Rooks for last, to possibly obey FRC restriction
4525 if(p == (int)WhiteRook) continue;
4526 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4527 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4530 // now everything is placed, except perhaps King (Unicorn) and Rooks
4532 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4533 // Last King gets castling rights
4534 while(piecesLeft[(int)WhiteUnicorn]) {
4535 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4536 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4539 while(piecesLeft[(int)WhiteKing]) {
4540 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4541 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4546 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4547 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4550 // Only Rooks can be left; simply place them all
4551 while(piecesLeft[(int)WhiteRook]) {
4552 i = put(board, WhiteRook, 0, 0, ANY);
4553 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4556 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4558 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4561 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4562 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4565 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4568 int SetCharTable( char *table, const char * map )
4569 /* [HGM] moved here from winboard.c because of its general usefulness */
4570 /* Basically a safe strcpy that uses the last character as King */
4572 int result = FALSE; int NrPieces;
4574 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4575 && NrPieces >= 12 && !(NrPieces&1)) {
4576 int i; /* [HGM] Accept even length from 12 to 34 */
4578 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4579 for( i=0; i<NrPieces/2-1; i++ ) {
4581 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4583 table[(int) WhiteKing] = map[NrPieces/2-1];
4584 table[(int) BlackKing] = map[NrPieces-1];
4592 void Prelude(Board board)
4593 { // [HGM] superchess: random selection of exo-pieces
4594 int i, j, k; ChessSquare p;
4595 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4597 GetPositionNumber(); // use FRC position number
4599 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4600 SetCharTable(pieceToChar, appData.pieceToCharTable);
4601 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4602 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4605 j = seed%4; seed /= 4;
4606 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4607 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4608 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4609 j = seed%3 + (seed%3 >= j); seed /= 3;
4610 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4611 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4612 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4613 j = seed%3; seed /= 3;
4614 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4615 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4616 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4617 j = seed%2 + (seed%2 >= j); seed /= 2;
4618 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4619 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4620 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4621 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4622 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4623 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4624 put(board, exoPieces[0], 0, 0, ANY);
4625 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4629 InitPosition(redraw)
4632 ChessSquare (* pieces)[BOARD_SIZE];
4633 int i, j, pawnRow, overrule,
4634 oldx = gameInfo.boardWidth,
4635 oldy = gameInfo.boardHeight,
4636 oldh = gameInfo.holdingsWidth,
4637 oldv = gameInfo.variant;
4639 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4641 /* [AS] Initialize pv info list [HGM] and game status */
4643 for( i=0; i<MAX_MOVES; i++ ) {
4644 pvInfoList[i].depth = 0;
4645 epStatus[i]=EP_NONE;
4646 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4649 initialRulePlies = 0; /* 50-move counter start */
4651 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4652 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4656 /* [HGM] logic here is completely changed. In stead of full positions */
4657 /* the initialized data only consist of the two backranks. The switch */
4658 /* selects which one we will use, which is than copied to the Board */
4659 /* initialPosition, which for the rest is initialized by Pawns and */
4660 /* empty squares. This initial position is then copied to boards[0], */
4661 /* possibly after shuffling, so that it remains available. */
4663 gameInfo.holdingsWidth = 0; /* default board sizes */
4664 gameInfo.boardWidth = 8;
4665 gameInfo.boardHeight = 8;
4666 gameInfo.holdingsSize = 0;
4667 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4668 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4669 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4671 switch (gameInfo.variant) {
4672 case VariantFischeRandom:
4673 shuffleOpenings = TRUE;
4677 case VariantShatranj:
4678 pieces = ShatranjArray;
4679 nrCastlingRights = 0;
4680 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4682 case VariantTwoKings:
4683 pieces = twoKingsArray;
4685 case VariantCapaRandom:
4686 shuffleOpenings = TRUE;
4687 case VariantCapablanca:
4688 pieces = CapablancaArray;
4689 gameInfo.boardWidth = 10;
4690 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4693 pieces = GothicArray;
4694 gameInfo.boardWidth = 10;
4695 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4698 pieces = JanusArray;
4699 gameInfo.boardWidth = 10;
4700 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4701 nrCastlingRights = 6;
4702 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4703 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4704 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4705 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4706 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4707 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4710 pieces = FalconArray;
4711 gameInfo.boardWidth = 10;
4712 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4714 case VariantXiangqi:
4715 pieces = XiangqiArray;
4716 gameInfo.boardWidth = 9;
4717 gameInfo.boardHeight = 10;
4718 nrCastlingRights = 0;
4719 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4722 pieces = ShogiArray;
4723 gameInfo.boardWidth = 9;
4724 gameInfo.boardHeight = 9;
4725 gameInfo.holdingsSize = 7;
4726 nrCastlingRights = 0;
4727 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4729 case VariantCourier:
4730 pieces = CourierArray;
4731 gameInfo.boardWidth = 12;
4732 nrCastlingRights = 0;
4733 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4734 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4736 case VariantKnightmate:
4737 pieces = KnightmateArray;
4738 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4741 pieces = fairyArray;
4742 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4745 pieces = GreatArray;
4746 gameInfo.boardWidth = 10;
4747 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4748 gameInfo.holdingsSize = 8;
4752 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4753 gameInfo.holdingsSize = 8;
4754 startedFromSetupPosition = TRUE;
4756 case VariantCrazyhouse:
4757 case VariantBughouse:
4759 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4760 gameInfo.holdingsSize = 5;
4762 case VariantWildCastle:
4764 /* !!?shuffle with kings guaranteed to be on d or e file */
4765 shuffleOpenings = 1;
4767 case VariantNoCastle:
4769 nrCastlingRights = 0;
4770 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4771 /* !!?unconstrained back-rank shuffle */
4772 shuffleOpenings = 1;
4777 if(appData.NrFiles >= 0) {
4778 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4779 gameInfo.boardWidth = appData.NrFiles;
4781 if(appData.NrRanks >= 0) {
4782 gameInfo.boardHeight = appData.NrRanks;
4784 if(appData.holdingsSize >= 0) {
4785 i = appData.holdingsSize;
4786 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4787 gameInfo.holdingsSize = i;
4789 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4790 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4791 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4793 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4794 if(pawnRow < 1) pawnRow = 1;
4796 /* User pieceToChar list overrules defaults */
4797 if(appData.pieceToCharTable != NULL)
4798 SetCharTable(pieceToChar, appData.pieceToCharTable);
4800 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4802 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4803 s = (ChessSquare) 0; /* account holding counts in guard band */
4804 for( i=0; i<BOARD_HEIGHT; i++ )
4805 initialPosition[i][j] = s;
4807 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4808 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4809 initialPosition[pawnRow][j] = WhitePawn;
4810 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4811 if(gameInfo.variant == VariantXiangqi) {
4813 initialPosition[pawnRow][j] =
4814 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4815 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4816 initialPosition[2][j] = WhiteCannon;
4817 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4821 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4823 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4826 initialPosition[1][j] = WhiteBishop;
4827 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4829 initialPosition[1][j] = WhiteRook;
4830 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4833 if( nrCastlingRights == -1) {
4834 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4835 /* This sets default castling rights from none to normal corners */
4836 /* Variants with other castling rights must set them themselves above */
4837 nrCastlingRights = 6;
4839 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4840 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4841 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4842 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4843 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4844 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4847 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4848 if(gameInfo.variant == VariantGreat) { // promotion commoners
4849 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4850 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4851 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4852 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4854 if (appData.debugMode) {
4855 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4857 if(shuffleOpenings) {
4858 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4859 startedFromSetupPosition = TRUE;
4861 if(startedFromPositionFile) {
4862 /* [HGM] loadPos: use PositionFile for every new game */
4863 CopyBoard(initialPosition, filePosition);
4864 for(i=0; i<nrCastlingRights; i++)
4865 castlingRights[0][i] = initialRights[i] = fileRights[i];
4866 startedFromSetupPosition = TRUE;
4869 CopyBoard(boards[0], initialPosition);
4871 if(oldx != gameInfo.boardWidth ||
4872 oldy != gameInfo.boardHeight ||
4873 oldh != gameInfo.holdingsWidth
4875 || oldv == VariantGothic || // For licensing popups
4876 gameInfo.variant == VariantGothic
4879 || oldv == VariantFalcon ||
4880 gameInfo.variant == VariantFalcon
4883 InitDrawingSizes(-2 ,0);
4886 DrawPosition(TRUE, boards[currentMove]);
4890 SendBoard(cps, moveNum)
4891 ChessProgramState *cps;
4894 char message[MSG_SIZ];
4896 if (cps->useSetboard) {
4897 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4898 sprintf(message, "setboard %s\n", fen);
4899 SendToProgram(message, cps);
4905 /* Kludge to set black to move, avoiding the troublesome and now
4906 * deprecated "black" command.
4908 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4910 SendToProgram("edit\n", cps);
4911 SendToProgram("#\n", cps);
4912 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4913 bp = &boards[moveNum][i][BOARD_LEFT];
4914 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4915 if ((int) *bp < (int) BlackPawn) {
4916 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4918 if(message[0] == '+' || message[0] == '~') {
4919 sprintf(message, "%c%c%c+\n",
4920 PieceToChar((ChessSquare)(DEMOTED *bp)),
4923 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4924 message[1] = BOARD_RGHT - 1 - j + '1';
4925 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4927 SendToProgram(message, cps);
4932 SendToProgram("c\n", cps);
4933 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4934 bp = &boards[moveNum][i][BOARD_LEFT];
4935 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4936 if (((int) *bp != (int) EmptySquare)
4937 && ((int) *bp >= (int) BlackPawn)) {
4938 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4940 if(message[0] == '+' || message[0] == '~') {
4941 sprintf(message, "%c%c%c+\n",
4942 PieceToChar((ChessSquare)(DEMOTED *bp)),
4945 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4946 message[1] = BOARD_RGHT - 1 - j + '1';
4947 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4949 SendToProgram(message, cps);
4954 SendToProgram(".\n", cps);
4956 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4960 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4962 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4963 /* [HGM] add Shogi promotions */
4964 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4969 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4970 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4972 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4973 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4976 piece = boards[currentMove][fromY][fromX];
4977 if(gameInfo.variant == VariantShogi) {
4978 promotionZoneSize = 3;
4979 highestPromotingPiece = (int)WhiteFerz;
4982 // next weed out all moves that do not touch the promotion zone at all
4983 if((int)piece >= BlackPawn) {
4984 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4986 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4988 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4989 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4992 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4994 // weed out mandatory Shogi promotions
4995 if(gameInfo.variant == VariantShogi) {
4996 if(piece >= BlackPawn) {
4997 if(toY == 0 && piece == BlackPawn ||
4998 toY == 0 && piece == BlackQueen ||
4999 toY <= 1 && piece == BlackKnight) {
5004 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5005 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5006 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5013 // weed out obviously illegal Pawn moves
5014 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5015 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5016 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5017 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5018 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5019 // note we are not allowed to test for valid (non-)capture, due to premove
5022 // we either have a choice what to promote to, or (in Shogi) whether to promote
5023 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5024 *promoChoice = PieceToChar(BlackFerz); // no choice
5027 if(appData.alwaysPromoteToQueen) { // predetermined
5028 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5029 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5030 else *promoChoice = PieceToChar(BlackQueen);
5034 // suppress promotion popup on illegal moves that are not premoves
5035 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5036 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5037 if(appData.testLegality && !premove) {
5038 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5039 epStatus[currentMove], castlingRights[currentMove],
5040 fromY, fromX, toY, toX, NULLCHAR);
5041 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5042 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5050 InPalace(row, column)
5052 { /* [HGM] for Xiangqi */
5053 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5054 column < (BOARD_WIDTH + 4)/2 &&
5055 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5060 PieceForSquare (x, y)
5064 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5067 return boards[currentMove][y][x];
5071 OKToStartUserMove(x, y)
5074 ChessSquare from_piece;
5077 if (matchMode) return FALSE;
5078 if (gameMode == EditPosition) return TRUE;
5080 if (x >= 0 && y >= 0)
5081 from_piece = boards[currentMove][y][x];
5083 from_piece = EmptySquare;
5085 if (from_piece == EmptySquare) return FALSE;
5087 white_piece = (int)from_piece >= (int)WhitePawn &&
5088 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5091 case PlayFromGameFile:
5093 case TwoMachinesPlay:
5101 case MachinePlaysWhite:
5102 case IcsPlayingBlack:
5103 if (appData.zippyPlay) return FALSE;
5105 DisplayMoveError(_("You are playing Black"));
5110 case MachinePlaysBlack:
5111 case IcsPlayingWhite:
5112 if (appData.zippyPlay) return FALSE;
5114 DisplayMoveError(_("You are playing White"));
5120 if (!white_piece && WhiteOnMove(currentMove)) {
5121 DisplayMoveError(_("It is White's turn"));
5124 if (white_piece && !WhiteOnMove(currentMove)) {
5125 DisplayMoveError(_("It is Black's turn"));
5128 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5129 /* Editing correspondence game history */
5130 /* Could disallow this or prompt for confirmation */
5133 if (currentMove < forwardMostMove) {
5134 /* Discarding moves */
5135 /* Could prompt for confirmation here,
5136 but I don't think that's such a good idea */
5137 forwardMostMove = currentMove;
5141 case BeginningOfGame:
5142 if (appData.icsActive) return FALSE;
5143 if (!appData.noChessProgram) {
5145 DisplayMoveError(_("You are playing White"));
5152 if (!white_piece && WhiteOnMove(currentMove)) {
5153 DisplayMoveError(_("It is White's turn"));
5156 if (white_piece && !WhiteOnMove(currentMove)) {
5157 DisplayMoveError(_("It is Black's turn"));
5166 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5167 && gameMode != AnalyzeFile && gameMode != Training) {
5168 DisplayMoveError(_("Displayed position is not current"));
5174 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5175 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5176 int lastLoadGameUseList = FALSE;
5177 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5178 ChessMove lastLoadGameStart = (ChessMove) 0;
5181 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5182 int fromX, fromY, toX, toY;
5187 ChessSquare pdown, pup;
5189 /* Check if the user is playing in turn. This is complicated because we
5190 let the user "pick up" a piece before it is his turn. So the piece he
5191 tried to pick up may have been captured by the time he puts it down!
5192 Therefore we use the color the user is supposed to be playing in this
5193 test, not the color of the piece that is currently on the starting
5194 square---except in EditGame mode, where the user is playing both
5195 sides; fortunately there the capture race can't happen. (It can
5196 now happen in IcsExamining mode, but that's just too bad. The user
5197 will get a somewhat confusing message in that case.)
5201 case PlayFromGameFile:
5203 case TwoMachinesPlay:
5207 /* We switched into a game mode where moves are not accepted,
5208 perhaps while the mouse button was down. */
5209 return ImpossibleMove;
5211 case MachinePlaysWhite:
5212 /* User is moving for Black */
5213 if (WhiteOnMove(currentMove)) {
5214 DisplayMoveError(_("It is White's turn"));
5215 return ImpossibleMove;
5219 case MachinePlaysBlack:
5220 /* User is moving for White */
5221 if (!WhiteOnMove(currentMove)) {
5222 DisplayMoveError(_("It is Black's turn"));
5223 return ImpossibleMove;
5229 case BeginningOfGame:
5232 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5233 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5234 /* User is moving for Black */
5235 if (WhiteOnMove(currentMove)) {
5236 DisplayMoveError(_("It is White's turn"));
5237 return ImpossibleMove;
5240 /* User is moving for White */
5241 if (!WhiteOnMove(currentMove)) {
5242 DisplayMoveError(_("It is Black's turn"));
5243 return ImpossibleMove;
5248 case IcsPlayingBlack:
5249 /* User is moving for Black */
5250 if (WhiteOnMove(currentMove)) {
5251 if (!appData.premove) {
5252 DisplayMoveError(_("It is White's turn"));
5253 } else if (toX >= 0 && toY >= 0) {
5256 premoveFromX = fromX;
5257 premoveFromY = fromY;
5258 premovePromoChar = promoChar;
5260 if (appData.debugMode)
5261 fprintf(debugFP, "Got premove: fromX %d,"
5262 "fromY %d, toX %d, toY %d\n",
5263 fromX, fromY, toX, toY);
5265 return ImpossibleMove;
5269 case IcsPlayingWhite:
5270 /* User is moving for White */
5271 if (!WhiteOnMove(currentMove)) {
5272 if (!appData.premove) {
5273 DisplayMoveError(_("It is Black's turn"));
5274 } else if (toX >= 0 && toY >= 0) {
5277 premoveFromX = fromX;
5278 premoveFromY = fromY;
5279 premovePromoChar = promoChar;
5281 if (appData.debugMode)
5282 fprintf(debugFP, "Got premove: fromX %d,"
5283 "fromY %d, toX %d, toY %d\n",
5284 fromX, fromY, toX, toY);
5286 return ImpossibleMove;
5294 /* EditPosition, empty square, or different color piece;
5295 click-click move is possible */
5296 if (toX == -2 || toY == -2) {
5297 boards[0][fromY][fromX] = EmptySquare;
5298 return AmbiguousMove;
5299 } else if (toX >= 0 && toY >= 0) {
5300 boards[0][toY][toX] = boards[0][fromY][fromX];
5301 boards[0][fromY][fromX] = EmptySquare;
5302 return AmbiguousMove;
5304 return ImpossibleMove;
5307 if(toX < 0 || toY < 0) return ImpossibleMove;
5308 pdown = boards[currentMove][fromY][fromX];
5309 pup = boards[currentMove][toY][toX];
5311 /* [HGM] If move started in holdings, it means a drop */
5312 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5313 if( pup != EmptySquare ) return ImpossibleMove;
5314 if(appData.testLegality) {
5315 /* it would be more logical if LegalityTest() also figured out
5316 * which drops are legal. For now we forbid pawns on back rank.
5317 * Shogi is on its own here...
5319 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5320 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5321 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5323 return WhiteDrop; /* Not needed to specify white or black yet */
5326 userOfferedDraw = FALSE;
5328 /* [HGM] always test for legality, to get promotion info */
5329 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5330 epStatus[currentMove], castlingRights[currentMove],
5331 fromY, fromX, toY, toX, promoChar);
5332 /* [HGM] but possibly ignore an IllegalMove result */
5333 if (appData.testLegality) {
5334 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5335 DisplayMoveError(_("Illegal move"));
5336 return ImpossibleMove;
5341 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5342 function is made into one that returns an OK move type if FinishMove
5343 should be called. This to give the calling driver routine the
5344 opportunity to finish the userMove input with a promotion popup,
5345 without bothering the user with this for invalid or illegal moves */
5347 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5350 /* Common tail of UserMoveEvent and DropMenuEvent */
5352 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5354 int fromX, fromY, toX, toY;
5355 /*char*/int promoChar;
5359 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5360 // [HGM] superchess: suppress promotions to non-available piece
5361 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5362 if(WhiteOnMove(currentMove)) {
5363 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5365 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5369 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5370 move type in caller when we know the move is a legal promotion */
5371 if(moveType == NormalMove && promoChar)
5372 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5374 /* [HGM] convert drag-and-drop piece drops to standard form */
5375 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5376 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5377 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5378 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5379 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5380 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5381 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5382 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5386 /* [HGM] <popupFix> The following if has been moved here from
5387 UserMoveEvent(). Because it seemed to belong here (why not allow
5388 piece drops in training games?), and because it can only be
5389 performed after it is known to what we promote. */
5390 if (gameMode == Training) {
5391 /* compare the move played on the board to the next move in the
5392 * game. If they match, display the move and the opponent's response.
5393 * If they don't match, display an error message.
5396 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5397 CopyBoard(testBoard, boards[currentMove]);
5398 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5400 if (CompareBoards(testBoard, boards[currentMove+1])) {
5401 ForwardInner(currentMove+1);
5403 /* Autoplay the opponent's response.
5404 * if appData.animate was TRUE when Training mode was entered,
5405 * the response will be animated.
5407 saveAnimate = appData.animate;
5408 appData.animate = animateTraining;
5409 ForwardInner(currentMove+1);
5410 appData.animate = saveAnimate;
5412 /* check for the end of the game */
5413 if (currentMove >= forwardMostMove) {
5414 gameMode = PlayFromGameFile;
5416 SetTrainingModeOff();
5417 DisplayInformation(_("End of game"));
5420 DisplayError(_("Incorrect move"), 0);
5425 /* Ok, now we know that the move is good, so we can kill
5426 the previous line in Analysis Mode */
5427 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5428 forwardMostMove = currentMove;
5431 /* If we need the chess program but it's dead, restart it */
5432 ResurrectChessProgram();
5434 /* A user move restarts a paused game*/
5438 thinkOutput[0] = NULLCHAR;
5440 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5442 if (gameMode == BeginningOfGame) {
5443 if (appData.noChessProgram) {
5444 gameMode = EditGame;
5448 gameMode = MachinePlaysBlack;
5451 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5453 if (first.sendName) {
5454 sprintf(buf, "name %s\n", gameInfo.white);
5455 SendToProgram(buf, &first);
5462 /* Relay move to ICS or chess engine */
5463 if (appData.icsActive) {
5464 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5465 gameMode == IcsExamining) {
5466 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5470 if (first.sendTime && (gameMode == BeginningOfGame ||
5471 gameMode == MachinePlaysWhite ||
5472 gameMode == MachinePlaysBlack)) {
5473 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5475 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5476 // [HGM] book: if program might be playing, let it use book
5477 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5478 first.maybeThinking = TRUE;
5479 } else SendMoveToProgram(forwardMostMove-1, &first);
5480 if (currentMove == cmailOldMove + 1) {
5481 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5485 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5489 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5490 EP_UNKNOWN, castlingRights[currentMove]) ) {
5496 if (WhiteOnMove(currentMove)) {
5497 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5499 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5503 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5508 case MachinePlaysBlack:
5509 case MachinePlaysWhite:
5510 /* disable certain menu options while machine is thinking */
5511 SetMachineThinkingEnables();
5518 if(bookHit) { // [HGM] book: simulate book reply
5519 static char bookMove[MSG_SIZ]; // a bit generous?
5521 programStats.nodes = programStats.depth = programStats.time =
5522 programStats.score = programStats.got_only_move = 0;
5523 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5525 strcpy(bookMove, "move ");
5526 strcat(bookMove, bookHit);
5527 HandleMachineMove(bookMove, &first);
5533 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5534 int fromX, fromY, toX, toY;
5537 /* [HGM] This routine was added to allow calling of its two logical
5538 parts from other modules in the old way. Before, UserMoveEvent()
5539 automatically called FinishMove() if the move was OK, and returned
5540 otherwise. I separated the two, in order to make it possible to
5541 slip a promotion popup in between. But that it always needs two
5542 calls, to the first part, (now called UserMoveTest() ), and to
5543 FinishMove if the first part succeeded. Calls that do not need
5544 to do anything in between, can call this routine the old way.
5546 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5547 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5548 if(moveType == AmbiguousMove)
5549 DrawPosition(FALSE, boards[currentMove]);
5550 else if(moveType != ImpossibleMove && moveType != Comment)
5551 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5554 void LeftClick(ClickType clickType, int xPix, int yPix)
5557 Boolean saveAnimate;
5558 static int second = 0, promotionChoice = 0;
5559 char promoChoice = NULLCHAR;
5561 if (clickType == Press) ErrorPopDown();
5563 x = EventToSquare(xPix, BOARD_WIDTH);
5564 y = EventToSquare(yPix, BOARD_HEIGHT);
5565 if (!flipView && y >= 0) {
5566 y = BOARD_HEIGHT - 1 - y;
5568 if (flipView && x >= 0) {
5569 x = BOARD_WIDTH - 1 - x;
5572 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5573 if(clickType == Release) return; // ignore upclick of click-click destination
5574 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5575 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5576 if(gameInfo.holdingsWidth &&
5577 (WhiteOnMove(currentMove)
5578 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5579 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5580 // click in right holdings, for determining promotion piece
5581 ChessSquare p = boards[currentMove][y][x];
5582 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5583 if(p != EmptySquare) {
5584 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5589 DrawPosition(FALSE, boards[currentMove]);
5593 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5594 if(clickType == Press
5595 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5596 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5597 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5601 if (clickType == Press) {
5603 if (OKToStartUserMove(x, y)) {
5607 DragPieceBegin(xPix, yPix);
5608 if (appData.highlightDragging) {
5609 SetHighlights(x, y, -1, -1);
5617 if (clickType == Press && gameMode != EditPosition) {
5622 // ignore off-board to clicks
5623 if(y < 0 || x < 0) return;
5625 /* Check if clicking again on the same color piece */
5626 fromP = boards[currentMove][fromY][fromX];
5627 toP = boards[currentMove][y][x];
5628 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5629 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5630 WhitePawn <= toP && toP <= WhiteKing &&
5631 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5632 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5633 (BlackPawn <= fromP && fromP <= BlackKing &&
5634 BlackPawn <= toP && toP <= BlackKing &&
5635 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5636 !(fromP == BlackKing && toP == BlackRook && frc))) {
5637 /* Clicked again on same color piece -- changed his mind */
5638 second = (x == fromX && y == fromY);
5639 if (appData.highlightDragging) {
5640 SetHighlights(x, y, -1, -1);
5644 if (OKToStartUserMove(x, y)) {
5647 DragPieceBegin(xPix, yPix);
5651 // ignore clicks on holdings
5652 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5655 if (clickType == Release && x == fromX && y == fromY) {
5656 DragPieceEnd(xPix, yPix);
5657 if (appData.animateDragging) {
5658 /* Undo animation damage if any */
5659 DrawPosition(FALSE, NULL);
5662 /* Second up/down in same square; just abort move */
5667 ClearPremoveHighlights();
5669 /* First upclick in same square; start click-click mode */
5670 SetHighlights(x, y, -1, -1);
5675 /* we now have a different from- and (possibly off-board) to-square */
5676 /* Completed move */
5679 saveAnimate = appData.animate;
5680 if (clickType == Press) {
5681 /* Finish clickclick move */
5682 if (appData.animate || appData.highlightLastMove) {
5683 SetHighlights(fromX, fromY, toX, toY);
5688 /* Finish drag move */
5689 if (appData.highlightLastMove) {
5690 SetHighlights(fromX, fromY, toX, toY);
5694 DragPieceEnd(xPix, yPix);
5695 /* Don't animate move and drag both */
5696 appData.animate = FALSE;
5699 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5700 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5703 DrawPosition(TRUE, NULL);
5707 // off-board moves should not be highlighted
5708 if(x < 0 || x < 0) ClearHighlights();
5710 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5711 SetHighlights(fromX, fromY, toX, toY);
5712 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5713 // [HGM] super: promotion to captured piece selected from holdings
5714 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5715 promotionChoice = TRUE;
5716 // kludge follows to temporarily execute move on display, without promoting yet
5717 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5718 boards[currentMove][toY][toX] = p;
5719 DrawPosition(FALSE, boards[currentMove]);
5720 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5721 boards[currentMove][toY][toX] = q;
5722 DisplayMessage("Click in holdings to choose piece", "");
5727 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5728 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5729 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5732 appData.animate = saveAnimate;
5733 if (appData.animate || appData.animateDragging) {
5734 /* Undo animation damage if needed */
5735 DrawPosition(FALSE, NULL);
5739 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5741 // char * hint = lastHint;
5742 FrontEndProgramStats stats;
5744 stats.which = cps == &first ? 0 : 1;
5745 stats.depth = cpstats->depth;
5746 stats.nodes = cpstats->nodes;
5747 stats.score = cpstats->score;
5748 stats.time = cpstats->time;
5749 stats.pv = cpstats->movelist;
5750 stats.hint = lastHint;
5751 stats.an_move_index = 0;
5752 stats.an_move_count = 0;
5754 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5755 stats.hint = cpstats->move_name;
5756 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5757 stats.an_move_count = cpstats->nr_moves;
5760 SetProgramStats( &stats );
5763 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5764 { // [HGM] book: this routine intercepts moves to simulate book replies
5765 char *bookHit = NULL;
5767 //first determine if the incoming move brings opponent into his book
5768 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5769 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5770 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5771 if(bookHit != NULL && !cps->bookSuspend) {
5772 // make sure opponent is not going to reply after receiving move to book position
5773 SendToProgram("force\n", cps);
5774 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5776 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5777 // now arrange restart after book miss
5779 // after a book hit we never send 'go', and the code after the call to this routine
5780 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5782 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5783 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5784 SendToProgram(buf, cps);
5785 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5786 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5787 SendToProgram("go\n", cps);
5788 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5789 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5790 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5791 SendToProgram("go\n", cps);
5792 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5794 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5798 ChessProgramState *savedState;
5799 void DeferredBookMove(void)
5801 if(savedState->lastPing != savedState->lastPong)
5802 ScheduleDelayedEvent(DeferredBookMove, 10);
5804 HandleMachineMove(savedMessage, savedState);
5808 HandleMachineMove(message, cps)
5810 ChessProgramState *cps;
5812 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5813 char realname[MSG_SIZ];
5814 int fromX, fromY, toX, toY;
5821 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5823 * Kludge to ignore BEL characters
5825 while (*message == '\007') message++;
5828 * [HGM] engine debug message: ignore lines starting with '#' character
5830 if(cps->debug && *message == '#') return;
5833 * Look for book output
5835 if (cps == &first && bookRequested) {
5836 if (message[0] == '\t' || message[0] == ' ') {
5837 /* Part of the book output is here; append it */
5838 strcat(bookOutput, message);
5839 strcat(bookOutput, " \n");
5841 } else if (bookOutput[0] != NULLCHAR) {
5842 /* All of book output has arrived; display it */
5843 char *p = bookOutput;
5844 while (*p != NULLCHAR) {
5845 if (*p == '\t') *p = ' ';
5848 DisplayInformation(bookOutput);
5849 bookRequested = FALSE;
5850 /* Fall through to parse the current output */
5855 * Look for machine move.
5857 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5858 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5860 /* This method is only useful on engines that support ping */
5861 if (cps->lastPing != cps->lastPong) {
5862 if (gameMode == BeginningOfGame) {
5863 /* Extra move from before last new; ignore */
5864 if (appData.debugMode) {
5865 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5868 if (appData.debugMode) {
5869 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5870 cps->which, gameMode);
5873 SendToProgram("undo\n", cps);
5879 case BeginningOfGame:
5880 /* Extra move from before last reset; ignore */
5881 if (appData.debugMode) {
5882 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5889 /* Extra move after we tried to stop. The mode test is
5890 not a reliable way of detecting this problem, but it's
5891 the best we can do on engines that don't support ping.
5893 if (appData.debugMode) {
5894 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5895 cps->which, gameMode);
5897 SendToProgram("undo\n", cps);
5900 case MachinePlaysWhite:
5901 case IcsPlayingWhite:
5902 machineWhite = TRUE;
5905 case MachinePlaysBlack:
5906 case IcsPlayingBlack:
5907 machineWhite = FALSE;
5910 case TwoMachinesPlay:
5911 machineWhite = (cps->twoMachinesColor[0] == 'w');
5914 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5915 if (appData.debugMode) {
5917 "Ignoring move out of turn by %s, gameMode %d"
5918 ", forwardMost %d\n",
5919 cps->which, gameMode, forwardMostMove);
5924 if (appData.debugMode) { int f = forwardMostMove;
5925 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5926 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5928 if(cps->alphaRank) AlphaRank(machineMove, 4);
5929 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5930 &fromX, &fromY, &toX, &toY, &promoChar)) {
5931 /* Machine move could not be parsed; ignore it. */
5932 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5933 machineMove, cps->which);
5934 DisplayError(buf1, 0);
5935 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5936 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5937 if (gameMode == TwoMachinesPlay) {
5938 GameEnds(machineWhite ? BlackWins : WhiteWins,
5944 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5945 /* So we have to redo legality test with true e.p. status here, */
5946 /* to make sure an illegal e.p. capture does not slip through, */
5947 /* to cause a forfeit on a justified illegal-move complaint */
5948 /* of the opponent. */
5949 if( gameMode==TwoMachinesPlay && appData.testLegality
5950 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5953 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5954 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5955 fromY, fromX, toY, toX, promoChar);
5956 if (appData.debugMode) {
5958 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5959 castlingRights[forwardMostMove][i], castlingRank[i]);
5960 fprintf(debugFP, "castling rights\n");
5962 if(moveType == IllegalMove) {
5963 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5964 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5965 GameEnds(machineWhite ? BlackWins : WhiteWins,
5968 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5969 /* [HGM] Kludge to handle engines that send FRC-style castling
5970 when they shouldn't (like TSCP-Gothic) */
5972 case WhiteASideCastleFR:
5973 case BlackASideCastleFR:
5975 currentMoveString[2]++;
5977 case WhiteHSideCastleFR:
5978 case BlackHSideCastleFR:
5980 currentMoveString[2]--;
5982 default: ; // nothing to do, but suppresses warning of pedantic compilers
5985 hintRequested = FALSE;
5986 lastHint[0] = NULLCHAR;
5987 bookRequested = FALSE;
5988 /* Program may be pondering now */
5989 cps->maybeThinking = TRUE;
5990 if (cps->sendTime == 2) cps->sendTime = 1;
5991 if (cps->offeredDraw) cps->offeredDraw--;
5994 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5996 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5998 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5999 char buf[3*MSG_SIZ];
6001 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6002 programStats.score / 100.,
6004 programStats.time / 100.,
6005 (unsigned int)programStats.nodes,
6006 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6007 programStats.movelist);
6009 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6013 /* currentMoveString is set as a side-effect of ParseOneMove */
6014 strcpy(machineMove, currentMoveString);
6015 strcat(machineMove, "\n");
6016 strcpy(moveList[forwardMostMove], machineMove);
6018 /* [AS] Save move info and clear stats for next move */
6019 pvInfoList[ forwardMostMove ].score = programStats.score;
6020 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6021 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6022 ClearProgramStats();
6023 thinkOutput[0] = NULLCHAR;
6024 hiddenThinkOutputState = 0;
6026 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6028 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6029 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6032 while( count < adjudicateLossPlies ) {
6033 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6036 score = -score; /* Flip score for winning side */
6039 if( score > adjudicateLossThreshold ) {
6046 if( count >= adjudicateLossPlies ) {
6047 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6049 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6050 "Xboard adjudication",
6057 if( gameMode == TwoMachinesPlay ) {
6058 // [HGM] some adjudications useful with buggy engines
6059 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6060 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6063 if( appData.testLegality )
6064 { /* [HGM] Some more adjudications for obstinate engines */
6065 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6066 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6067 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6068 static int moveCount = 6;
6070 char *reason = NULL;
6072 /* Count what is on board. */
6073 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6074 { ChessSquare p = boards[forwardMostMove][i][j];
6078 { /* count B,N,R and other of each side */
6081 NrK++; break; // [HGM] atomic: count Kings
6085 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6086 bishopsColor |= 1 << ((i^j)&1);
6091 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6092 bishopsColor |= 1 << ((i^j)&1);
6107 PawnAdvance += m; NrPawns++;
6109 NrPieces += (p != EmptySquare);
6110 NrW += ((int)p < (int)BlackPawn);
6111 if(gameInfo.variant == VariantXiangqi &&
6112 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6113 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6114 NrW -= ((int)p < (int)BlackPawn);
6118 /* Some material-based adjudications that have to be made before stalemate test */
6119 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6120 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6121 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6122 if(appData.checkMates) {
6123 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6124 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6126 "Xboard adjudication: King destroyed", GE_XBOARD );
6131 /* Bare King in Shatranj (loses) or Losers (wins) */
6132 if( NrW == 1 || NrPieces - NrW == 1) {
6133 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6134 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6135 if(appData.checkMates) {
6136 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6137 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6138 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6139 "Xboard adjudication: Bare king", GE_XBOARD );
6143 if( gameInfo.variant == VariantShatranj && --bare < 0)
6145 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6146 if(appData.checkMates) {
6147 /* but only adjudicate if adjudication enabled */
6148 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6149 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6151 "Xboard adjudication: Bare king", GE_XBOARD );
6158 // don't wait for engine to announce game end if we can judge ourselves
6159 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6160 castlingRights[forwardMostMove]) ) {
6162 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6163 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6164 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6165 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6168 reason = "Xboard adjudication: 3rd check";
6169 epStatus[forwardMostMove] = EP_CHECKMATE;
6179 reason = "Xboard adjudication: Stalemate";
6180 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6181 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6182 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6183 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6184 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6185 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6186 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6187 EP_CHECKMATE : EP_WINS);
6188 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6189 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6193 reason = "Xboard adjudication: Checkmate";
6194 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6198 switch(i = epStatus[forwardMostMove]) {
6200 result = GameIsDrawn; break;
6202 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6204 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6206 result = (ChessMove) 0;
6208 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6209 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6210 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6211 GameEnds( result, reason, GE_XBOARD );
6215 /* Next absolutely insufficient mating material. */
6216 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6217 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6218 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6219 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6220 { /* KBK, KNK, KK of KBKB with like Bishops */
6222 /* always flag draws, for judging claims */
6223 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6225 if(appData.materialDraws) {
6226 /* but only adjudicate them if adjudication enabled */
6227 SendToProgram("force\n", cps->other); // suppress reply
6228 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6229 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6230 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6235 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6237 ( NrWR == 1 && NrBR == 1 /* KRKR */
6238 || NrWQ==1 && NrBQ==1 /* KQKQ */
6239 || NrWN==2 || NrBN==2 /* KNNK */
6240 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6242 if(--moveCount < 0 && appData.trivialDraws)
6243 { /* if the first 3 moves do not show a tactical win, declare draw */
6244 SendToProgram("force\n", cps->other); // suppress reply
6245 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6246 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6247 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6250 } else moveCount = 6;
6254 /* Check for rep-draws */
6256 for(k = forwardMostMove-2;
6257 k>=backwardMostMove && k>=forwardMostMove-100 &&
6258 epStatus[k] < EP_UNKNOWN &&
6259 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6262 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6263 /* compare castling rights */
6264 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6265 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6266 rights++; /* King lost rights, while rook still had them */
6267 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6268 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6269 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6270 rights++; /* but at least one rook lost them */
6272 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6273 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6275 if( castlingRights[forwardMostMove][5] >= 0 ) {
6276 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6277 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6280 if( rights == 0 && ++count > appData.drawRepeats-2
6281 && appData.drawRepeats > 1) {
6282 /* adjudicate after user-specified nr of repeats */
6283 SendToProgram("force\n", cps->other); // suppress reply
6284 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6285 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6286 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6287 // [HGM] xiangqi: check for forbidden perpetuals
6288 int m, ourPerpetual = 1, hisPerpetual = 1;
6289 for(m=forwardMostMove; m>k; m-=2) {
6290 if(MateTest(boards[m], PosFlags(m),
6291 EP_NONE, castlingRights[m]) != MT_CHECK)
6292 ourPerpetual = 0; // the current mover did not always check
6293 if(MateTest(boards[m-1], PosFlags(m-1),
6294 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6295 hisPerpetual = 0; // the opponent did not always check
6297 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6298 ourPerpetual, hisPerpetual);
6299 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6300 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6301 "Xboard adjudication: perpetual checking", GE_XBOARD );
6304 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6305 break; // (or we would have caught him before). Abort repetition-checking loop.
6306 // Now check for perpetual chases
6307 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6308 hisPerpetual = PerpetualChase(k, forwardMostMove);
6309 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6310 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6311 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6312 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6315 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6316 break; // Abort repetition-checking loop.
6318 // if neither of us is checking or chasing all the time, or both are, it is draw
6320 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6323 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6324 epStatus[forwardMostMove] = EP_REP_DRAW;
6328 /* Now we test for 50-move draws. Determine ply count */
6329 count = forwardMostMove;
6330 /* look for last irreversble move */
6331 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6333 /* if we hit starting position, add initial plies */
6334 if( count == backwardMostMove )
6335 count -= initialRulePlies;
6336 count = forwardMostMove - count;
6338 epStatus[forwardMostMove] = EP_RULE_DRAW;
6339 /* this is used to judge if draw claims are legal */
6340 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6341 SendToProgram("force\n", cps->other); // suppress reply
6342 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6343 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6344 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6348 /* if draw offer is pending, treat it as a draw claim
6349 * when draw condition present, to allow engines a way to
6350 * claim draws before making their move to avoid a race
6351 * condition occurring after their move
6353 if( cps->other->offeredDraw || cps->offeredDraw ) {
6355 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6356 p = "Draw claim: 50-move rule";
6357 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6358 p = "Draw claim: 3-fold repetition";
6359 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6360 p = "Draw claim: insufficient mating material";
6362 SendToProgram("force\n", cps->other); // suppress reply
6363 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6364 GameEnds( GameIsDrawn, p, GE_XBOARD );
6365 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6371 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6372 SendToProgram("force\n", cps->other); // suppress reply
6373 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6374 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6376 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6383 if (gameMode == TwoMachinesPlay) {
6384 /* [HGM] relaying draw offers moved to after reception of move */
6385 /* and interpreting offer as claim if it brings draw condition */
6386 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6387 SendToProgram("draw\n", cps->other);
6389 if (cps->other->sendTime) {
6390 SendTimeRemaining(cps->other,
6391 cps->other->twoMachinesColor[0] == 'w');
6393 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6394 if (firstMove && !bookHit) {
6396 if (cps->other->useColors) {
6397 SendToProgram(cps->other->twoMachinesColor, cps->other);
6399 SendToProgram("go\n", cps->other);
6401 cps->other->maybeThinking = TRUE;
6404 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6406 if (!pausing && appData.ringBellAfterMoves) {
6411 * Reenable menu items that were disabled while
6412 * machine was thinking
6414 if (gameMode != TwoMachinesPlay)
6415 SetUserThinkingEnables();
6417 // [HGM] book: after book hit opponent has received move and is now in force mode
6418 // force the book reply into it, and then fake that it outputted this move by jumping
6419 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6421 static char bookMove[MSG_SIZ]; // a bit generous?
6423 strcpy(bookMove, "move ");
6424 strcat(bookMove, bookHit);
6427 programStats.nodes = programStats.depth = programStats.time =
6428 programStats.score = programStats.got_only_move = 0;
6429 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6431 if(cps->lastPing != cps->lastPong) {
6432 savedMessage = message; // args for deferred call
6434 ScheduleDelayedEvent(DeferredBookMove, 10);
6443 /* Set special modes for chess engines. Later something general
6444 * could be added here; for now there is just one kludge feature,
6445 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6446 * when "xboard" is given as an interactive command.
6448 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6449 cps->useSigint = FALSE;
6450 cps->useSigterm = FALSE;
6452 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6453 ParseFeatures(message+8, cps);
6454 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6457 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6458 * want this, I was asked to put it in, and obliged.
6460 if (!strncmp(message, "setboard ", 9)) {
6461 Board initial_position; int i;
6463 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6465 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6466 DisplayError(_("Bad FEN received from engine"), 0);
6470 CopyBoard(boards[0], initial_position);
6471 initialRulePlies = FENrulePlies;
6472 epStatus[0] = FENepStatus;
6473 for( i=0; i<nrCastlingRights; i++ )
6474 castlingRights[0][i] = FENcastlingRights[i];
6475 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6476 else gameMode = MachinePlaysBlack;
6477 DrawPosition(FALSE, boards[currentMove]);
6483 * Look for communication commands
6485 if (!strncmp(message, "telluser ", 9)) {
6486 DisplayNote(message + 9);
6489 if (!strncmp(message, "tellusererror ", 14)) {
6490 DisplayError(message + 14, 0);
6493 if (!strncmp(message, "tellopponent ", 13)) {
6494 if (appData.icsActive) {
6496 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6500 DisplayNote(message + 13);
6504 if (!strncmp(message, "tellothers ", 11)) {
6505 if (appData.icsActive) {
6507 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6513 if (!strncmp(message, "tellall ", 8)) {
6514 if (appData.icsActive) {
6516 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6520 DisplayNote(message + 8);
6524 if (strncmp(message, "warning", 7) == 0) {
6525 /* Undocumented feature, use tellusererror in new code */
6526 DisplayError(message, 0);
6529 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6530 strcpy(realname, cps->tidy);
6531 strcat(realname, " query");
6532 AskQuestion(realname, buf2, buf1, cps->pr);
6535 /* Commands from the engine directly to ICS. We don't allow these to be
6536 * sent until we are logged on. Crafty kibitzes have been known to
6537 * interfere with the login process.
6540 if (!strncmp(message, "tellics ", 8)) {
6541 SendToICS(message + 8);
6545 if (!strncmp(message, "tellicsnoalias ", 15)) {
6546 SendToICS(ics_prefix);
6547 SendToICS(message + 15);
6551 /* The following are for backward compatibility only */
6552 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6553 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6554 SendToICS(ics_prefix);
6560 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6564 * If the move is illegal, cancel it and redraw the board.
6565 * Also deal with other error cases. Matching is rather loose
6566 * here to accommodate engines written before the spec.
6568 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6569 strncmp(message, "Error", 5) == 0) {
6570 if (StrStr(message, "name") ||
6571 StrStr(message, "rating") || StrStr(message, "?") ||
6572 StrStr(message, "result") || StrStr(message, "board") ||
6573 StrStr(message, "bk") || StrStr(message, "computer") ||
6574 StrStr(message, "variant") || StrStr(message, "hint") ||
6575 StrStr(message, "random") || StrStr(message, "depth") ||
6576 StrStr(message, "accepted")) {
6579 if (StrStr(message, "protover")) {
6580 /* Program is responding to input, so it's apparently done
6581 initializing, and this error message indicates it is
6582 protocol version 1. So we don't need to wait any longer
6583 for it to initialize and send feature commands. */
6584 FeatureDone(cps, 1);
6585 cps->protocolVersion = 1;
6588 cps->maybeThinking = FALSE;
6590 if (StrStr(message, "draw")) {
6591 /* Program doesn't have "draw" command */
6592 cps->sendDrawOffers = 0;
6595 if (cps->sendTime != 1 &&
6596 (StrStr(message, "time") || StrStr(message, "otim"))) {
6597 /* Program apparently doesn't have "time" or "otim" command */
6601 if (StrStr(message, "analyze")) {
6602 cps->analysisSupport = FALSE;
6603 cps->analyzing = FALSE;
6605 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6606 DisplayError(buf2, 0);
6609 if (StrStr(message, "(no matching move)st")) {
6610 /* Special kludge for GNU Chess 4 only */
6611 cps->stKludge = TRUE;
6612 SendTimeControl(cps, movesPerSession, timeControl,
6613 timeIncrement, appData.searchDepth,
6617 if (StrStr(message, "(no matching move)sd")) {
6618 /* Special kludge for GNU Chess 4 only */
6619 cps->sdKludge = TRUE;
6620 SendTimeControl(cps, movesPerSession, timeControl,
6621 timeIncrement, appData.searchDepth,
6625 if (!StrStr(message, "llegal")) {
6628 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6629 gameMode == IcsIdle) return;
6630 if (forwardMostMove <= backwardMostMove) return;
6631 if (pausing) PauseEvent();
6632 if(appData.forceIllegal) {
6633 // [HGM] illegal: machine refused move; force position after move into it
6634 SendToProgram("force\n", cps);
6635 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6636 // we have a real problem now, as SendBoard will use the a2a3 kludge
6637 // when black is to move, while there might be nothing on a2 or black
6638 // might already have the move. So send the board as if white has the move.
6639 // But first we must change the stm of the engine, as it refused the last move
6640 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6641 if(WhiteOnMove(forwardMostMove)) {
6642 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6643 SendBoard(cps, forwardMostMove); // kludgeless board
6645 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6646 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6647 SendBoard(cps, forwardMostMove+1); // kludgeless board
6649 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6650 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6651 gameMode == TwoMachinesPlay)
6652 SendToProgram("go\n", cps);
6655 if (gameMode == PlayFromGameFile) {
6656 /* Stop reading this game file */
6657 gameMode = EditGame;
6660 currentMove = forwardMostMove-1;
6661 DisplayMove(currentMove-1); /* before DisplayMoveError */
6662 SwitchClocks(forwardMostMove-1); // [HGM] race
6663 DisplayBothClocks();
6664 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6665 parseList[currentMove], cps->which);
6666 DisplayMoveError(buf1);
6667 DrawPosition(FALSE, boards[currentMove]);
6669 /* [HGM] illegal-move claim should forfeit game when Xboard */
6670 /* only passes fully legal moves */
6671 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6672 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6673 "False illegal-move claim", GE_XBOARD );
6677 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6678 /* Program has a broken "time" command that
6679 outputs a string not ending in newline.
6685 * If chess program startup fails, exit with an error message.
6686 * Attempts to recover here are futile.
6688 if ((StrStr(message, "unknown host") != NULL)
6689 || (StrStr(message, "No remote directory") != NULL)
6690 || (StrStr(message, "not found") != NULL)
6691 || (StrStr(message, "No such file") != NULL)
6692 || (StrStr(message, "can't alloc") != NULL)
6693 || (StrStr(message, "Permission denied") != NULL)) {
6695 cps->maybeThinking = FALSE;
6696 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6697 cps->which, cps->program, cps->host, message);
6698 RemoveInputSource(cps->isr);
6699 DisplayFatalError(buf1, 0, 1);
6704 * Look for hint output
6706 if (sscanf(message, "Hint: %s", buf1) == 1) {
6707 if (cps == &first && hintRequested) {
6708 hintRequested = FALSE;
6709 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6710 &fromX, &fromY, &toX, &toY, &promoChar)) {
6711 (void) CoordsToAlgebraic(boards[forwardMostMove],
6712 PosFlags(forwardMostMove), EP_UNKNOWN,
6713 fromY, fromX, toY, toX, promoChar, buf1);
6714 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6715 DisplayInformation(buf2);
6717 /* Hint move could not be parsed!? */
6718 snprintf(buf2, sizeof(buf2),
6719 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6721 DisplayError(buf2, 0);
6724 strcpy(lastHint, buf1);
6730 * Ignore other messages if game is not in progress
6732 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6733 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6736 * look for win, lose, draw, or draw offer
6738 if (strncmp(message, "1-0", 3) == 0) {
6739 char *p, *q, *r = "";
6740 p = strchr(message, '{');
6748 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6750 } else if (strncmp(message, "0-1", 3) == 0) {
6751 char *p, *q, *r = "";
6752 p = strchr(message, '{');
6760 /* Kludge for Arasan 4.1 bug */
6761 if (strcmp(r, "Black resigns") == 0) {
6762 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6765 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6767 } else if (strncmp(message, "1/2", 3) == 0) {
6768 char *p, *q, *r = "";
6769 p = strchr(message, '{');
6778 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6781 } else if (strncmp(message, "White resign", 12) == 0) {
6782 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6784 } else if (strncmp(message, "Black resign", 12) == 0) {
6785 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6787 } else if (strncmp(message, "White matches", 13) == 0 ||
6788 strncmp(message, "Black matches", 13) == 0 ) {
6789 /* [HGM] ignore GNUShogi noises */
6791 } else if (strncmp(message, "White", 5) == 0 &&
6792 message[5] != '(' &&
6793 StrStr(message, "Black") == NULL) {
6794 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6796 } else if (strncmp(message, "Black", 5) == 0 &&
6797 message[5] != '(') {
6798 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6800 } else if (strcmp(message, "resign") == 0 ||
6801 strcmp(message, "computer resigns") == 0) {
6803 case MachinePlaysBlack:
6804 case IcsPlayingBlack:
6805 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6807 case MachinePlaysWhite:
6808 case IcsPlayingWhite:
6809 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6811 case TwoMachinesPlay:
6812 if (cps->twoMachinesColor[0] == 'w')
6813 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6815 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6822 } else if (strncmp(message, "opponent mates", 14) == 0) {
6824 case MachinePlaysBlack:
6825 case IcsPlayingBlack:
6826 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6828 case MachinePlaysWhite:
6829 case IcsPlayingWhite:
6830 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6832 case TwoMachinesPlay:
6833 if (cps->twoMachinesColor[0] == 'w')
6834 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6836 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6843 } else if (strncmp(message, "computer mates", 14) == 0) {
6845 case MachinePlaysBlack:
6846 case IcsPlayingBlack:
6847 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6849 case MachinePlaysWhite:
6850 case IcsPlayingWhite:
6851 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6853 case TwoMachinesPlay:
6854 if (cps->twoMachinesColor[0] == 'w')
6855 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6857 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6864 } else if (strncmp(message, "checkmate", 9) == 0) {
6865 if (WhiteOnMove(forwardMostMove)) {
6866 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6868 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6871 } else if (strstr(message, "Draw") != NULL ||
6872 strstr(message, "game is a draw") != NULL) {
6873 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6875 } else if (strstr(message, "offer") != NULL &&
6876 strstr(message, "draw") != NULL) {
6878 if (appData.zippyPlay && first.initDone) {
6879 /* Relay offer to ICS */
6880 SendToICS(ics_prefix);
6881 SendToICS("draw\n");
6884 cps->offeredDraw = 2; /* valid until this engine moves twice */
6885 if (gameMode == TwoMachinesPlay) {
6886 if (cps->other->offeredDraw) {
6887 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6888 /* [HGM] in two-machine mode we delay relaying draw offer */
6889 /* until after we also have move, to see if it is really claim */
6891 } else if (gameMode == MachinePlaysWhite ||
6892 gameMode == MachinePlaysBlack) {
6893 if (userOfferedDraw) {
6894 DisplayInformation(_("Machine accepts your draw offer"));
6895 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6897 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6904 * Look for thinking output
6906 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6907 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6909 int plylev, mvleft, mvtot, curscore, time;
6910 char mvname[MOVE_LEN];
6914 int prefixHint = FALSE;
6915 mvname[0] = NULLCHAR;
6918 case MachinePlaysBlack:
6919 case IcsPlayingBlack:
6920 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6922 case MachinePlaysWhite:
6923 case IcsPlayingWhite:
6924 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6929 case IcsObserving: /* [DM] icsEngineAnalyze */
6930 if (!appData.icsEngineAnalyze) ignore = TRUE;
6932 case TwoMachinesPlay:
6933 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6944 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6945 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6947 if (plyext != ' ' && plyext != '\t') {
6951 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6952 if( cps->scoreIsAbsolute &&
6953 ( gameMode == MachinePlaysBlack ||
6954 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6955 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6956 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6957 !WhiteOnMove(currentMove)
6960 curscore = -curscore;
6964 programStats.depth = plylev;
6965 programStats.nodes = nodes;
6966 programStats.time = time;
6967 programStats.score = curscore;
6968 programStats.got_only_move = 0;
6970 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6973 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6974 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6975 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6976 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6977 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6978 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6979 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6980 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6983 /* Buffer overflow protection */
6984 if (buf1[0] != NULLCHAR) {
6985 if (strlen(buf1) >= sizeof(programStats.movelist)
6986 && appData.debugMode) {
6988 "PV is too long; using the first %u bytes.\n",
6989 (unsigned) sizeof(programStats.movelist) - 1);
6992 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6994 sprintf(programStats.movelist, " no PV\n");
6997 if (programStats.seen_stat) {
6998 programStats.ok_to_send = 1;
7001 if (strchr(programStats.movelist, '(') != NULL) {
7002 programStats.line_is_book = 1;
7003 programStats.nr_moves = 0;
7004 programStats.moves_left = 0;
7006 programStats.line_is_book = 0;
7009 SendProgramStatsToFrontend( cps, &programStats );
7012 [AS] Protect the thinkOutput buffer from overflow... this
7013 is only useful if buf1 hasn't overflowed first!
7015 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7017 (gameMode == TwoMachinesPlay ?
7018 ToUpper(cps->twoMachinesColor[0]) : ' '),
7019 ((double) curscore) / 100.0,
7020 prefixHint ? lastHint : "",
7021 prefixHint ? " " : "" );
7023 if( buf1[0] != NULLCHAR ) {
7024 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7026 if( strlen(buf1) > max_len ) {
7027 if( appData.debugMode) {
7028 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7030 buf1[max_len+1] = '\0';
7033 strcat( thinkOutput, buf1 );
7036 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7037 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7038 DisplayMove(currentMove - 1);
7042 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7043 /* crafty (9.25+) says "(only move) <move>"
7044 * if there is only 1 legal move
7046 sscanf(p, "(only move) %s", buf1);
7047 sprintf(thinkOutput, "%s (only move)", buf1);
7048 sprintf(programStats.movelist, "%s (only move)", buf1);
7049 programStats.depth = 1;
7050 programStats.nr_moves = 1;
7051 programStats.moves_left = 1;
7052 programStats.nodes = 1;
7053 programStats.time = 1;
7054 programStats.got_only_move = 1;
7056 /* Not really, but we also use this member to
7057 mean "line isn't going to change" (Crafty
7058 isn't searching, so stats won't change) */
7059 programStats.line_is_book = 1;
7061 SendProgramStatsToFrontend( cps, &programStats );
7063 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7064 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7065 DisplayMove(currentMove - 1);
7068 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7069 &time, &nodes, &plylev, &mvleft,
7070 &mvtot, mvname) >= 5) {
7071 /* The stat01: line is from Crafty (9.29+) in response
7072 to the "." command */
7073 programStats.seen_stat = 1;
7074 cps->maybeThinking = TRUE;
7076 if (programStats.got_only_move || !appData.periodicUpdates)
7079 programStats.depth = plylev;
7080 programStats.time = time;
7081 programStats.nodes = nodes;
7082 programStats.moves_left = mvleft;
7083 programStats.nr_moves = mvtot;
7084 strcpy(programStats.move_name, mvname);
7085 programStats.ok_to_send = 1;
7086 programStats.movelist[0] = '\0';
7088 SendProgramStatsToFrontend( cps, &programStats );
7092 } else if (strncmp(message,"++",2) == 0) {
7093 /* Crafty 9.29+ outputs this */
7094 programStats.got_fail = 2;
7097 } else if (strncmp(message,"--",2) == 0) {
7098 /* Crafty 9.29+ outputs this */
7099 programStats.got_fail = 1;
7102 } else if (thinkOutput[0] != NULLCHAR &&
7103 strncmp(message, " ", 4) == 0) {
7104 unsigned message_len;
7107 while (*p && *p == ' ') p++;
7109 message_len = strlen( p );
7111 /* [AS] Avoid buffer overflow */
7112 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7113 strcat(thinkOutput, " ");
7114 strcat(thinkOutput, p);
7117 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7118 strcat(programStats.movelist, " ");
7119 strcat(programStats.movelist, p);
7122 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7123 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7124 DisplayMove(currentMove - 1);
7132 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7133 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7135 ChessProgramStats cpstats;
7137 if (plyext != ' ' && plyext != '\t') {
7141 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7142 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7143 curscore = -curscore;
7146 cpstats.depth = plylev;
7147 cpstats.nodes = nodes;
7148 cpstats.time = time;
7149 cpstats.score = curscore;
7150 cpstats.got_only_move = 0;
7151 cpstats.movelist[0] = '\0';
7153 if (buf1[0] != NULLCHAR) {
7154 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7157 cpstats.ok_to_send = 0;
7158 cpstats.line_is_book = 0;
7159 cpstats.nr_moves = 0;
7160 cpstats.moves_left = 0;
7162 SendProgramStatsToFrontend( cps, &cpstats );
7169 /* Parse a game score from the character string "game", and
7170 record it as the history of the current game. The game
7171 score is NOT assumed to start from the standard position.
7172 The display is not updated in any way.
7175 ParseGameHistory(game)
7179 int fromX, fromY, toX, toY, boardIndex;
7184 if (appData.debugMode)
7185 fprintf(debugFP, "Parsing game history: %s\n", game);
7187 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7188 gameInfo.site = StrSave(appData.icsHost);
7189 gameInfo.date = PGNDate();
7190 gameInfo.round = StrSave("-");
7192 /* Parse out names of players */
7193 while (*game == ' ') game++;
7195 while (*game != ' ') *p++ = *game++;
7197 gameInfo.white = StrSave(buf);
7198 while (*game == ' ') game++;
7200 while (*game != ' ' && *game != '\n') *p++ = *game++;
7202 gameInfo.black = StrSave(buf);
7205 boardIndex = blackPlaysFirst ? 1 : 0;
7208 yyboardindex = boardIndex;
7209 moveType = (ChessMove) yylex();
7211 case IllegalMove: /* maybe suicide chess, etc. */
7212 if (appData.debugMode) {
7213 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7214 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7215 setbuf(debugFP, NULL);
7217 case WhitePromotionChancellor:
7218 case BlackPromotionChancellor:
7219 case WhitePromotionArchbishop:
7220 case BlackPromotionArchbishop:
7221 case WhitePromotionQueen:
7222 case BlackPromotionQueen:
7223 case WhitePromotionRook:
7224 case BlackPromotionRook:
7225 case WhitePromotionBishop:
7226 case BlackPromotionBishop:
7227 case WhitePromotionKnight:
7228 case BlackPromotionKnight:
7229 case WhitePromotionKing:
7230 case BlackPromotionKing:
7232 case WhiteCapturesEnPassant:
7233 case BlackCapturesEnPassant:
7234 case WhiteKingSideCastle:
7235 case WhiteQueenSideCastle:
7236 case BlackKingSideCastle:
7237 case BlackQueenSideCastle:
7238 case WhiteKingSideCastleWild:
7239 case WhiteQueenSideCastleWild:
7240 case BlackKingSideCastleWild:
7241 case BlackQueenSideCastleWild:
7243 case WhiteHSideCastleFR:
7244 case WhiteASideCastleFR:
7245 case BlackHSideCastleFR:
7246 case BlackASideCastleFR:
7248 fromX = currentMoveString[0] - AAA;
7249 fromY = currentMoveString[1] - ONE;
7250 toX = currentMoveString[2] - AAA;
7251 toY = currentMoveString[3] - ONE;
7252 promoChar = currentMoveString[4];
7256 fromX = moveType == WhiteDrop ?
7257 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7258 (int) CharToPiece(ToLower(currentMoveString[0]));
7260 toX = currentMoveString[2] - AAA;
7261 toY = currentMoveString[3] - ONE;
7262 promoChar = NULLCHAR;
7266 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7267 if (appData.debugMode) {
7268 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7269 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7270 setbuf(debugFP, NULL);
7272 DisplayError(buf, 0);
7274 case ImpossibleMove:
7276 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7277 if (appData.debugMode) {
7278 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7279 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7280 setbuf(debugFP, NULL);
7282 DisplayError(buf, 0);
7284 case (ChessMove) 0: /* end of file */
7285 if (boardIndex < backwardMostMove) {
7286 /* Oops, gap. How did that happen? */
7287 DisplayError(_("Gap in move list"), 0);
7290 backwardMostMove = blackPlaysFirst ? 1 : 0;
7291 if (boardIndex > forwardMostMove) {
7292 forwardMostMove = boardIndex;
7296 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7297 strcat(parseList[boardIndex-1], " ");
7298 strcat(parseList[boardIndex-1], yy_text);
7310 case GameUnfinished:
7311 if (gameMode == IcsExamining) {
7312 if (boardIndex < backwardMostMove) {
7313 /* Oops, gap. How did that happen? */
7316 backwardMostMove = blackPlaysFirst ? 1 : 0;
7319 gameInfo.result = moveType;
7320 p = strchr(yy_text, '{');
7321 if (p == NULL) p = strchr(yy_text, '(');
7324 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7326 q = strchr(p, *p == '{' ? '}' : ')');
7327 if (q != NULL) *q = NULLCHAR;
7330 gameInfo.resultDetails = StrSave(p);
7333 if (boardIndex >= forwardMostMove &&
7334 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7335 backwardMostMove = blackPlaysFirst ? 1 : 0;
7338 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7339 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7340 parseList[boardIndex]);
7341 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7342 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7343 /* currentMoveString is set as a side-effect of yylex */
7344 strcpy(moveList[boardIndex], currentMoveString);
7345 strcat(moveList[boardIndex], "\n");
7347 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7348 castlingRights[boardIndex], &epStatus[boardIndex]);
7349 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7350 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7356 if(gameInfo.variant != VariantShogi)
7357 strcat(parseList[boardIndex - 1], "+");
7361 strcat(parseList[boardIndex - 1], "#");
7368 /* Apply a move to the given board */
7370 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7371 int fromX, fromY, toX, toY;
7377 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7379 /* [HGM] compute & store e.p. status and castling rights for new position */
7380 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7383 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7387 if( board[toY][toX] != EmptySquare )
7390 if( board[fromY][fromX] == WhitePawn ) {
7391 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7394 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7395 gameInfo.variant != VariantBerolina || toX < fromX)
7396 *ep = toX | berolina;
7397 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7398 gameInfo.variant != VariantBerolina || toX > fromX)
7402 if( board[fromY][fromX] == BlackPawn ) {
7403 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7405 if( toY-fromY== -2) {
7406 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7407 gameInfo.variant != VariantBerolina || toX < fromX)
7408 *ep = toX | berolina;
7409 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7410 gameInfo.variant != VariantBerolina || toX > fromX)
7415 for(i=0; i<nrCastlingRights; i++) {
7416 if(castling[i] == fromX && castlingRank[i] == fromY ||
7417 castling[i] == toX && castlingRank[i] == toY
7418 ) castling[i] = -1; // revoke for moved or captured piece
7423 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7424 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7425 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7427 if (fromX == toX && fromY == toY) return;
7429 if (fromY == DROP_RANK) {
7431 piece = board[toY][toX] = (ChessSquare) fromX;
7433 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7434 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7435 if(gameInfo.variant == VariantKnightmate)
7436 king += (int) WhiteUnicorn - (int) WhiteKing;
7438 /* Code added by Tord: */
7439 /* FRC castling assumed when king captures friendly rook. */
7440 if (board[fromY][fromX] == WhiteKing &&
7441 board[toY][toX] == WhiteRook) {
7442 board[fromY][fromX] = EmptySquare;
7443 board[toY][toX] = EmptySquare;
7445 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7447 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7449 } else if (board[fromY][fromX] == BlackKing &&
7450 board[toY][toX] == BlackRook) {
7451 board[fromY][fromX] = EmptySquare;
7452 board[toY][toX] = EmptySquare;
7454 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7456 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7458 /* End of code added by Tord */
7460 } else if (board[fromY][fromX] == king
7461 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7462 && toY == fromY && toX > fromX+1) {
7463 board[fromY][fromX] = EmptySquare;
7464 board[toY][toX] = king;
7465 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7466 board[fromY][BOARD_RGHT-1] = EmptySquare;
7467 } else if (board[fromY][fromX] == king
7468 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7469 && toY == fromY && toX < fromX-1) {
7470 board[fromY][fromX] = EmptySquare;
7471 board[toY][toX] = king;
7472 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7473 board[fromY][BOARD_LEFT] = EmptySquare;
7474 } else if (board[fromY][fromX] == WhitePawn
7475 && toY == BOARD_HEIGHT-1
7476 && gameInfo.variant != VariantXiangqi
7478 /* white pawn promotion */
7479 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7480 if (board[toY][toX] == EmptySquare) {
7481 board[toY][toX] = WhiteQueen;
7483 if(gameInfo.variant==VariantBughouse ||
7484 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7485 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7486 board[fromY][fromX] = EmptySquare;
7487 } else if ((fromY == BOARD_HEIGHT-4)
7489 && gameInfo.variant != VariantXiangqi
7490 && gameInfo.variant != VariantBerolina
7491 && (board[fromY][fromX] == WhitePawn)
7492 && (board[toY][toX] == EmptySquare)) {
7493 board[fromY][fromX] = EmptySquare;
7494 board[toY][toX] = WhitePawn;
7495 captured = board[toY - 1][toX];
7496 board[toY - 1][toX] = EmptySquare;
7497 } else if ((fromY == BOARD_HEIGHT-4)
7499 && gameInfo.variant == VariantBerolina
7500 && (board[fromY][fromX] == WhitePawn)
7501 && (board[toY][toX] == EmptySquare)) {
7502 board[fromY][fromX] = EmptySquare;
7503 board[toY][toX] = WhitePawn;
7504 if(oldEP & EP_BEROLIN_A) {
7505 captured = board[fromY][fromX-1];
7506 board[fromY][fromX-1] = EmptySquare;
7507 }else{ captured = board[fromY][fromX+1];
7508 board[fromY][fromX+1] = EmptySquare;
7510 } else if (board[fromY][fromX] == king
7511 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7512 && toY == fromY && toX > fromX+1) {
7513 board[fromY][fromX] = EmptySquare;
7514 board[toY][toX] = king;
7515 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7516 board[fromY][BOARD_RGHT-1] = EmptySquare;
7517 } else if (board[fromY][fromX] == king
7518 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7519 && toY == fromY && toX < fromX-1) {
7520 board[fromY][fromX] = EmptySquare;
7521 board[toY][toX] = king;
7522 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7523 board[fromY][BOARD_LEFT] = EmptySquare;
7524 } else if (fromY == 7 && fromX == 3
7525 && board[fromY][fromX] == BlackKing
7526 && toY == 7 && toX == 5) {
7527 board[fromY][fromX] = EmptySquare;
7528 board[toY][toX] = BlackKing;
7529 board[fromY][7] = EmptySquare;
7530 board[toY][4] = BlackRook;
7531 } else if (fromY == 7 && fromX == 3
7532 && board[fromY][fromX] == BlackKing
7533 && toY == 7 && toX == 1) {
7534 board[fromY][fromX] = EmptySquare;
7535 board[toY][toX] = BlackKing;
7536 board[fromY][0] = EmptySquare;
7537 board[toY][2] = BlackRook;
7538 } else if (board[fromY][fromX] == BlackPawn
7540 && gameInfo.variant != VariantXiangqi
7542 /* black pawn promotion */
7543 board[0][toX] = CharToPiece(ToLower(promoChar));
7544 if (board[0][toX] == EmptySquare) {
7545 board[0][toX] = BlackQueen;
7547 if(gameInfo.variant==VariantBughouse ||
7548 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7549 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7550 board[fromY][fromX] = EmptySquare;
7551 } else if ((fromY == 3)
7553 && gameInfo.variant != VariantXiangqi
7554 && gameInfo.variant != VariantBerolina
7555 && (board[fromY][fromX] == BlackPawn)
7556 && (board[toY][toX] == EmptySquare)) {
7557 board[fromY][fromX] = EmptySquare;
7558 board[toY][toX] = BlackPawn;
7559 captured = board[toY + 1][toX];
7560 board[toY + 1][toX] = EmptySquare;
7561 } else if ((fromY == 3)
7563 && gameInfo.variant == VariantBerolina
7564 && (board[fromY][fromX] == BlackPawn)
7565 && (board[toY][toX] == EmptySquare)) {
7566 board[fromY][fromX] = EmptySquare;
7567 board[toY][toX] = BlackPawn;
7568 if(oldEP & EP_BEROLIN_A) {
7569 captured = board[fromY][fromX-1];
7570 board[fromY][fromX-1] = EmptySquare;
7571 }else{ captured = board[fromY][fromX+1];
7572 board[fromY][fromX+1] = EmptySquare;
7575 board[toY][toX] = board[fromY][fromX];
7576 board[fromY][fromX] = EmptySquare;
7579 /* [HGM] now we promote for Shogi, if needed */
7580 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7581 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7584 if (gameInfo.holdingsWidth != 0) {
7586 /* !!A lot more code needs to be written to support holdings */
7587 /* [HGM] OK, so I have written it. Holdings are stored in the */
7588 /* penultimate board files, so they are automaticlly stored */
7589 /* in the game history. */
7590 if (fromY == DROP_RANK) {
7591 /* Delete from holdings, by decreasing count */
7592 /* and erasing image if necessary */
7594 if(p < (int) BlackPawn) { /* white drop */
7595 p -= (int)WhitePawn;
7596 p = PieceToNumber((ChessSquare)p);
7597 if(p >= gameInfo.holdingsSize) p = 0;
7598 if(--board[p][BOARD_WIDTH-2] <= 0)
7599 board[p][BOARD_WIDTH-1] = EmptySquare;
7600 if((int)board[p][BOARD_WIDTH-2] < 0)
7601 board[p][BOARD_WIDTH-2] = 0;
7602 } else { /* black drop */
7603 p -= (int)BlackPawn;
7604 p = PieceToNumber((ChessSquare)p);
7605 if(p >= gameInfo.holdingsSize) p = 0;
7606 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7607 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7608 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7609 board[BOARD_HEIGHT-1-p][1] = 0;
7612 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7613 && gameInfo.variant != VariantBughouse ) {
7614 /* [HGM] holdings: Add to holdings, if holdings exist */
7615 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7616 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7617 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7620 if (p >= (int) BlackPawn) {
7621 p -= (int)BlackPawn;
7622 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7623 /* in Shogi restore piece to its original first */
7624 captured = (ChessSquare) (DEMOTED captured);
7627 p = PieceToNumber((ChessSquare)p);
7628 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7629 board[p][BOARD_WIDTH-2]++;
7630 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7632 p -= (int)WhitePawn;
7633 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7634 captured = (ChessSquare) (DEMOTED captured);
7637 p = PieceToNumber((ChessSquare)p);
7638 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7639 board[BOARD_HEIGHT-1-p][1]++;
7640 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7643 } else if (gameInfo.variant == VariantAtomic) {
7644 if (captured != EmptySquare) {
7646 for (y = toY-1; y <= toY+1; y++) {
7647 for (x = toX-1; x <= toX+1; x++) {
7648 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7649 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7650 board[y][x] = EmptySquare;
7654 board[toY][toX] = EmptySquare;
7657 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7658 /* [HGM] Shogi promotions */
7659 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7662 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7663 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7664 // [HGM] superchess: take promotion piece out of holdings
7665 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7666 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7667 if(!--board[k][BOARD_WIDTH-2])
7668 board[k][BOARD_WIDTH-1] = EmptySquare;
7670 if(!--board[BOARD_HEIGHT-1-k][1])
7671 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7677 /* Updates forwardMostMove */
7679 MakeMove(fromX, fromY, toX, toY, promoChar)
7680 int fromX, fromY, toX, toY;
7683 // forwardMostMove++; // [HGM] bare: moved downstream
7685 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7686 int timeLeft; static int lastLoadFlag=0; int king, piece;
7687 piece = boards[forwardMostMove][fromY][fromX];
7688 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7689 if(gameInfo.variant == VariantKnightmate)
7690 king += (int) WhiteUnicorn - (int) WhiteKing;
7691 if(forwardMostMove == 0) {
7693 fprintf(serverMoves, "%s;", second.tidy);
7694 fprintf(serverMoves, "%s;", first.tidy);
7695 if(!blackPlaysFirst)
7696 fprintf(serverMoves, "%s;", second.tidy);
7697 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7698 lastLoadFlag = loadFlag;
7700 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7701 // print castling suffix
7702 if( toY == fromY && piece == king ) {
7704 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7706 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7709 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7710 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7711 boards[forwardMostMove][toY][toX] == EmptySquare
7713 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7715 if(promoChar != NULLCHAR)
7716 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7718 fprintf(serverMoves, "/%d/%d",
7719 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7720 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7721 else timeLeft = blackTimeRemaining/1000;
7722 fprintf(serverMoves, "/%d", timeLeft);
7724 fflush(serverMoves);
7727 if (forwardMostMove+1 >= MAX_MOVES) {
7728 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7732 if (commentList[forwardMostMove+1] != NULL) {
7733 free(commentList[forwardMostMove+1]);
7734 commentList[forwardMostMove+1] = NULL;
7736 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7737 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7738 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7739 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7740 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7741 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
7742 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7743 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7744 gameInfo.result = GameUnfinished;
7745 if (gameInfo.resultDetails != NULL) {
7746 free(gameInfo.resultDetails);
7747 gameInfo.resultDetails = NULL;
7749 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7750 moveList[forwardMostMove - 1]);
7751 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7752 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7753 fromY, fromX, toY, toX, promoChar,
7754 parseList[forwardMostMove - 1]);
7755 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7756 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7757 castlingRights[forwardMostMove]) ) {
7763 if(gameInfo.variant != VariantShogi)
7764 strcat(parseList[forwardMostMove - 1], "+");
7768 strcat(parseList[forwardMostMove - 1], "#");
7771 if (appData.debugMode) {
7772 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7777 /* Updates currentMove if not pausing */
7779 ShowMove(fromX, fromY, toX, toY)
7781 int instant = (gameMode == PlayFromGameFile) ?
7782 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7783 if(appData.noGUI) return;
7784 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7786 if (forwardMostMove == currentMove + 1) {
7787 AnimateMove(boards[forwardMostMove - 1],
7788 fromX, fromY, toX, toY);
7790 if (appData.highlightLastMove) {
7791 SetHighlights(fromX, fromY, toX, toY);
7794 currentMove = forwardMostMove;
7797 if (instant) return;
7799 DisplayMove(currentMove - 1);
7800 DrawPosition(FALSE, boards[currentMove]);
7801 DisplayBothClocks();
7802 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7805 void SendEgtPath(ChessProgramState *cps)
7806 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7807 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7809 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7812 char c, *q = name+1, *r, *s;
7814 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7815 while(*p && *p != ',') *q++ = *p++;
7817 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7818 strcmp(name, ",nalimov:") == 0 ) {
7819 // take nalimov path from the menu-changeable option first, if it is defined
7820 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7821 SendToProgram(buf,cps); // send egtbpath command for nalimov
7823 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7824 (s = StrStr(appData.egtFormats, name)) != NULL) {
7825 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7826 s = r = StrStr(s, ":") + 1; // beginning of path info
7827 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7828 c = *r; *r = 0; // temporarily null-terminate path info
7829 *--q = 0; // strip of trailig ':' from name
7830 sprintf(buf, "egtpath %s %s\n", name+1, s);
7832 SendToProgram(buf,cps); // send egtbpath command for this format
7834 if(*p == ',') p++; // read away comma to position for next format name
7839 InitChessProgram(cps, setup)
7840 ChessProgramState *cps;
7841 int setup; /* [HGM] needed to setup FRC opening position */
7843 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7844 if (appData.noChessProgram) return;
7845 hintRequested = FALSE;
7846 bookRequested = FALSE;
7848 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7849 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7850 if(cps->memSize) { /* [HGM] memory */
7851 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7852 SendToProgram(buf, cps);
7854 SendEgtPath(cps); /* [HGM] EGT */
7855 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7856 sprintf(buf, "cores %d\n", appData.smpCores);
7857 SendToProgram(buf, cps);
7860 SendToProgram(cps->initString, cps);
7861 if (gameInfo.variant != VariantNormal &&
7862 gameInfo.variant != VariantLoadable
7863 /* [HGM] also send variant if board size non-standard */
7864 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7866 char *v = VariantName(gameInfo.variant);
7867 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7868 /* [HGM] in protocol 1 we have to assume all variants valid */
7869 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7870 DisplayFatalError(buf, 0, 1);
7874 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7875 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7876 if( gameInfo.variant == VariantXiangqi )
7877 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7878 if( gameInfo.variant == VariantShogi )
7879 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7880 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7881 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7882 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7883 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7884 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7885 if( gameInfo.variant == VariantCourier )
7886 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7887 if( gameInfo.variant == VariantSuper )
7888 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7889 if( gameInfo.variant == VariantGreat )
7890 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7893 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7894 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7895 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7896 if(StrStr(cps->variants, b) == NULL) {
7897 // specific sized variant not known, check if general sizing allowed
7898 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7899 if(StrStr(cps->variants, "boardsize") == NULL) {
7900 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7901 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7902 DisplayFatalError(buf, 0, 1);
7905 /* [HGM] here we really should compare with the maximum supported board size */
7908 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7909 sprintf(buf, "variant %s\n", b);
7910 SendToProgram(buf, cps);
7912 currentlyInitializedVariant = gameInfo.variant;
7914 /* [HGM] send opening position in FRC to first engine */
7916 SendToProgram("force\n", cps);
7918 /* engine is now in force mode! Set flag to wake it up after first move. */
7919 setboardSpoiledMachineBlack = 1;
7923 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7924 SendToProgram(buf, cps);
7926 cps->maybeThinking = FALSE;
7927 cps->offeredDraw = 0;
7928 if (!appData.icsActive) {
7929 SendTimeControl(cps, movesPerSession, timeControl,
7930 timeIncrement, appData.searchDepth,
7933 if (appData.showThinking
7934 // [HGM] thinking: four options require thinking output to be sent
7935 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7937 SendToProgram("post\n", cps);
7939 SendToProgram("hard\n", cps);
7940 if (!appData.ponderNextMove) {
7941 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7942 it without being sure what state we are in first. "hard"
7943 is not a toggle, so that one is OK.
7945 SendToProgram("easy\n", cps);
7948 sprintf(buf, "ping %d\n", ++cps->lastPing);
7949 SendToProgram(buf, cps);
7951 cps->initDone = TRUE;
7956 StartChessProgram(cps)
7957 ChessProgramState *cps;
7962 if (appData.noChessProgram) return;
7963 cps->initDone = FALSE;
7965 if (strcmp(cps->host, "localhost") == 0) {
7966 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7967 } else if (*appData.remoteShell == NULLCHAR) {
7968 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7970 if (*appData.remoteUser == NULLCHAR) {
7971 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7974 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7975 cps->host, appData.remoteUser, cps->program);
7977 err = StartChildProcess(buf, "", &cps->pr);
7981 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7982 DisplayFatalError(buf, err, 1);
7988 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7989 if (cps->protocolVersion > 1) {
7990 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7991 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7992 cps->comboCnt = 0; // and values of combo boxes
7993 SendToProgram(buf, cps);
7995 SendToProgram("xboard\n", cps);
8001 TwoMachinesEventIfReady P((void))
8003 if (first.lastPing != first.lastPong) {
8004 DisplayMessage("", _("Waiting for first chess program"));
8005 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8008 if (second.lastPing != second.lastPong) {
8009 DisplayMessage("", _("Waiting for second chess program"));
8010 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8018 NextMatchGame P((void))
8020 int index; /* [HGM] autoinc: step load index during match */
8022 if (*appData.loadGameFile != NULLCHAR) {
8023 index = appData.loadGameIndex;
8024 if(index < 0) { // [HGM] autoinc
8025 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8026 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8028 LoadGameFromFile(appData.loadGameFile,
8030 appData.loadGameFile, FALSE);
8031 } else if (*appData.loadPositionFile != NULLCHAR) {
8032 index = appData.loadPositionIndex;
8033 if(index < 0) { // [HGM] autoinc
8034 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8035 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8037 LoadPositionFromFile(appData.loadPositionFile,
8039 appData.loadPositionFile);
8041 TwoMachinesEventIfReady();
8044 void UserAdjudicationEvent( int result )
8046 ChessMove gameResult = GameIsDrawn;
8049 gameResult = WhiteWins;
8051 else if( result < 0 ) {
8052 gameResult = BlackWins;
8055 if( gameMode == TwoMachinesPlay ) {
8056 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8061 // [HGM] save: calculate checksum of game to make games easily identifiable
8062 int StringCheckSum(char *s)
8065 if(s==NULL) return 0;
8066 while(*s) i = i*259 + *s++;
8073 for(i=backwardMostMove; i<forwardMostMove; i++) {
8074 sum += pvInfoList[i].depth;
8075 sum += StringCheckSum(parseList[i]);
8076 sum += StringCheckSum(commentList[i]);
8079 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8080 return sum + StringCheckSum(commentList[i]);
8081 } // end of save patch
8084 GameEnds(result, resultDetails, whosays)
8086 char *resultDetails;
8089 GameMode nextGameMode;
8093 if(endingGame) return; /* [HGM] crash: forbid recursion */
8096 if (appData.debugMode) {
8097 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8098 result, resultDetails ? resultDetails : "(null)", whosays);
8101 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8102 /* If we are playing on ICS, the server decides when the
8103 game is over, but the engine can offer to draw, claim
8107 if (appData.zippyPlay && first.initDone) {
8108 if (result == GameIsDrawn) {
8109 /* In case draw still needs to be claimed */
8110 SendToICS(ics_prefix);
8111 SendToICS("draw\n");
8112 } else if (StrCaseStr(resultDetails, "resign")) {
8113 SendToICS(ics_prefix);
8114 SendToICS("resign\n");
8118 endingGame = 0; /* [HGM] crash */
8122 /* If we're loading the game from a file, stop */
8123 if (whosays == GE_FILE) {
8124 (void) StopLoadGameTimer();
8128 /* Cancel draw offers */
8129 first.offeredDraw = second.offeredDraw = 0;
8131 /* If this is an ICS game, only ICS can really say it's done;
8132 if not, anyone can. */
8133 isIcsGame = (gameMode == IcsPlayingWhite ||
8134 gameMode == IcsPlayingBlack ||
8135 gameMode == IcsObserving ||
8136 gameMode == IcsExamining);
8138 if (!isIcsGame || whosays == GE_ICS) {
8139 /* OK -- not an ICS game, or ICS said it was done */
8141 if (!isIcsGame && !appData.noChessProgram)
8142 SetUserThinkingEnables();
8144 /* [HGM] if a machine claims the game end we verify this claim */
8145 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8146 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8148 ChessMove trueResult = (ChessMove) -1;
8150 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8151 first.twoMachinesColor[0] :
8152 second.twoMachinesColor[0] ;
8154 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8155 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8156 /* [HGM] verify: engine mate claims accepted if they were flagged */
8157 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8159 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8160 /* [HGM] verify: engine mate claims accepted if they were flagged */
8161 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8163 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8164 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8167 // now verify win claims, but not in drop games, as we don't understand those yet
8168 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8169 || gameInfo.variant == VariantGreat) &&
8170 (result == WhiteWins && claimer == 'w' ||
8171 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8172 if (appData.debugMode) {
8173 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8174 result, epStatus[forwardMostMove], forwardMostMove);
8176 if(result != trueResult) {
8177 sprintf(buf, "False win claim: '%s'", resultDetails);
8178 result = claimer == 'w' ? BlackWins : WhiteWins;
8179 resultDetails = buf;
8182 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8183 && (forwardMostMove <= backwardMostMove ||
8184 epStatus[forwardMostMove-1] > EP_DRAWS ||
8185 (claimer=='b')==(forwardMostMove&1))
8187 /* [HGM] verify: draws that were not flagged are false claims */
8188 sprintf(buf, "False draw claim: '%s'", resultDetails);
8189 result = claimer == 'w' ? BlackWins : WhiteWins;
8190 resultDetails = buf;
8192 /* (Claiming a loss is accepted no questions asked!) */
8194 /* [HGM] bare: don't allow bare King to win */
8195 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8196 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8197 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8198 && result != GameIsDrawn)
8199 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8200 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8201 int p = (int)boards[forwardMostMove][i][j] - color;
8202 if(p >= 0 && p <= (int)WhiteKing) k++;
8204 if (appData.debugMode) {
8205 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8206 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8209 result = GameIsDrawn;
8210 sprintf(buf, "%s but bare king", resultDetails);
8211 resultDetails = buf;
8217 if(serverMoves != NULL && !loadFlag) { char c = '=';
8218 if(result==WhiteWins) c = '+';
8219 if(result==BlackWins) c = '-';
8220 if(resultDetails != NULL)
8221 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8223 if (resultDetails != NULL) {
8224 gameInfo.result = result;
8225 gameInfo.resultDetails = StrSave(resultDetails);
8227 /* display last move only if game was not loaded from file */
8228 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8229 DisplayMove(currentMove - 1);
8231 if (forwardMostMove != 0) {
8232 if (gameMode != PlayFromGameFile && gameMode != EditGame
8233 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8235 if (*appData.saveGameFile != NULLCHAR) {
8236 SaveGameToFile(appData.saveGameFile, TRUE);
8237 } else if (appData.autoSaveGames) {
8240 if (*appData.savePositionFile != NULLCHAR) {
8241 SavePositionToFile(appData.savePositionFile);
8246 /* Tell program how game ended in case it is learning */
8247 /* [HGM] Moved this to after saving the PGN, just in case */
8248 /* engine died and we got here through time loss. In that */
8249 /* case we will get a fatal error writing the pipe, which */
8250 /* would otherwise lose us the PGN. */
8251 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8252 /* output during GameEnds should never be fatal anymore */
8253 if (gameMode == MachinePlaysWhite ||
8254 gameMode == MachinePlaysBlack ||
8255 gameMode == TwoMachinesPlay ||
8256 gameMode == IcsPlayingWhite ||
8257 gameMode == IcsPlayingBlack ||
8258 gameMode == BeginningOfGame) {
8260 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8262 if (first.pr != NoProc) {
8263 SendToProgram(buf, &first);
8265 if (second.pr != NoProc &&
8266 gameMode == TwoMachinesPlay) {
8267 SendToProgram(buf, &second);
8272 if (appData.icsActive) {
8273 if (appData.quietPlay &&
8274 (gameMode == IcsPlayingWhite ||
8275 gameMode == IcsPlayingBlack)) {
8276 SendToICS(ics_prefix);
8277 SendToICS("set shout 1\n");
8279 nextGameMode = IcsIdle;
8280 ics_user_moved = FALSE;
8281 /* clean up premove. It's ugly when the game has ended and the
8282 * premove highlights are still on the board.
8286 ClearPremoveHighlights();
8287 DrawPosition(FALSE, boards[currentMove]);
8289 if (whosays == GE_ICS) {
8292 if (gameMode == IcsPlayingWhite)
8294 else if(gameMode == IcsPlayingBlack)
8298 if (gameMode == IcsPlayingBlack)
8300 else if(gameMode == IcsPlayingWhite)
8307 PlayIcsUnfinishedSound();
8310 } else if (gameMode == EditGame ||
8311 gameMode == PlayFromGameFile ||
8312 gameMode == AnalyzeMode ||
8313 gameMode == AnalyzeFile) {
8314 nextGameMode = gameMode;
8316 nextGameMode = EndOfGame;
8321 nextGameMode = gameMode;
8324 if (appData.noChessProgram) {
8325 gameMode = nextGameMode;
8327 endingGame = 0; /* [HGM] crash */
8332 /* Put first chess program into idle state */
8333 if (first.pr != NoProc &&
8334 (gameMode == MachinePlaysWhite ||
8335 gameMode == MachinePlaysBlack ||
8336 gameMode == TwoMachinesPlay ||
8337 gameMode == IcsPlayingWhite ||
8338 gameMode == IcsPlayingBlack ||
8339 gameMode == BeginningOfGame)) {
8340 SendToProgram("force\n", &first);
8341 if (first.usePing) {
8343 sprintf(buf, "ping %d\n", ++first.lastPing);
8344 SendToProgram(buf, &first);
8347 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8348 /* Kill off first chess program */
8349 if (first.isr != NULL)
8350 RemoveInputSource(first.isr);
8353 if (first.pr != NoProc) {
8355 DoSleep( appData.delayBeforeQuit );
8356 SendToProgram("quit\n", &first);
8357 DoSleep( appData.delayAfterQuit );
8358 DestroyChildProcess(first.pr, first.useSigterm);
8363 /* Put second chess program into idle state */
8364 if (second.pr != NoProc &&
8365 gameMode == TwoMachinesPlay) {
8366 SendToProgram("force\n", &second);
8367 if (second.usePing) {
8369 sprintf(buf, "ping %d\n", ++second.lastPing);
8370 SendToProgram(buf, &second);
8373 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8374 /* Kill off second chess program */
8375 if (second.isr != NULL)
8376 RemoveInputSource(second.isr);
8379 if (second.pr != NoProc) {
8380 DoSleep( appData.delayBeforeQuit );
8381 SendToProgram("quit\n", &second);
8382 DoSleep( appData.delayAfterQuit );
8383 DestroyChildProcess(second.pr, second.useSigterm);
8388 if (matchMode && gameMode == TwoMachinesPlay) {
8391 if (first.twoMachinesColor[0] == 'w') {
8398 if (first.twoMachinesColor[0] == 'b') {
8407 if (matchGame < appData.matchGames) {
8409 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8410 tmp = first.twoMachinesColor;
8411 first.twoMachinesColor = second.twoMachinesColor;
8412 second.twoMachinesColor = tmp;
8414 gameMode = nextGameMode;
8416 if(appData.matchPause>10000 || appData.matchPause<10)
8417 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8418 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8419 endingGame = 0; /* [HGM] crash */
8423 gameMode = nextGameMode;
8424 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8425 first.tidy, second.tidy,
8426 first.matchWins, second.matchWins,
8427 appData.matchGames - (first.matchWins + second.matchWins));
8428 DisplayFatalError(buf, 0, 0);
8431 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8432 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8434 gameMode = nextGameMode;
8436 endingGame = 0; /* [HGM] crash */
8439 /* Assumes program was just initialized (initString sent).
8440 Leaves program in force mode. */
8442 FeedMovesToProgram(cps, upto)
8443 ChessProgramState *cps;
8448 if (appData.debugMode)
8449 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8450 startedFromSetupPosition ? "position and " : "",
8451 backwardMostMove, upto, cps->which);
8452 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8453 // [HGM] variantswitch: make engine aware of new variant
8454 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8455 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8456 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8457 SendToProgram(buf, cps);
8458 currentlyInitializedVariant = gameInfo.variant;
8460 SendToProgram("force\n", cps);
8461 if (startedFromSetupPosition) {
8462 SendBoard(cps, backwardMostMove);
8463 if (appData.debugMode) {
8464 fprintf(debugFP, "feedMoves\n");
8467 for (i = backwardMostMove; i < upto; i++) {
8468 SendMoveToProgram(i, cps);
8474 ResurrectChessProgram()
8476 /* The chess program may have exited.
8477 If so, restart it and feed it all the moves made so far. */
8479 if (appData.noChessProgram || first.pr != NoProc) return;
8481 StartChessProgram(&first);
8482 InitChessProgram(&first, FALSE);
8483 FeedMovesToProgram(&first, currentMove);
8485 if (!first.sendTime) {
8486 /* can't tell gnuchess what its clock should read,
8487 so we bow to its notion. */
8489 timeRemaining[0][currentMove] = whiteTimeRemaining;
8490 timeRemaining[1][currentMove] = blackTimeRemaining;
8493 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8494 appData.icsEngineAnalyze) && first.analysisSupport) {
8495 SendToProgram("analyze\n", &first);
8496 first.analyzing = TRUE;
8509 if (appData.debugMode) {
8510 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8511 redraw, init, gameMode);
8513 pausing = pauseExamInvalid = FALSE;
8514 startedFromSetupPosition = blackPlaysFirst = FALSE;
8516 whiteFlag = blackFlag = FALSE;
8517 userOfferedDraw = FALSE;
8518 hintRequested = bookRequested = FALSE;
8519 first.maybeThinking = FALSE;
8520 second.maybeThinking = FALSE;
8521 first.bookSuspend = FALSE; // [HGM] book
8522 second.bookSuspend = FALSE;
8523 thinkOutput[0] = NULLCHAR;
8524 lastHint[0] = NULLCHAR;
8525 ClearGameInfo(&gameInfo);
8526 gameInfo.variant = StringToVariant(appData.variant);
8527 ics_user_moved = ics_clock_paused = FALSE;
8528 ics_getting_history = H_FALSE;
8530 white_holding[0] = black_holding[0] = NULLCHAR;
8531 ClearProgramStats();
8532 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8536 flipView = appData.flipView;
8537 ClearPremoveHighlights();
8539 alarmSounded = FALSE;
8541 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8542 if(appData.serverMovesName != NULL) {
8543 /* [HGM] prepare to make moves file for broadcasting */
8544 clock_t t = clock();
8545 if(serverMoves != NULL) fclose(serverMoves);
8546 serverMoves = fopen(appData.serverMovesName, "r");
8547 if(serverMoves != NULL) {
8548 fclose(serverMoves);
8549 /* delay 15 sec before overwriting, so all clients can see end */
8550 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8552 serverMoves = fopen(appData.serverMovesName, "w");
8556 gameMode = BeginningOfGame;
8558 if(appData.icsActive) gameInfo.variant = VariantNormal;
8559 currentMove = forwardMostMove = backwardMostMove = 0;
8560 InitPosition(redraw);
8561 for (i = 0; i < MAX_MOVES; i++) {
8562 if (commentList[i] != NULL) {
8563 free(commentList[i]);
8564 commentList[i] = NULL;
8568 timeRemaining[0][0] = whiteTimeRemaining;
8569 timeRemaining[1][0] = blackTimeRemaining;
8570 if (first.pr == NULL) {
8571 StartChessProgram(&first);
8574 InitChessProgram(&first, startedFromSetupPosition);
8577 DisplayMessage("", "");
8578 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8579 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8586 if (!AutoPlayOneMove())
8588 if (matchMode || appData.timeDelay == 0)
8590 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8592 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8601 int fromX, fromY, toX, toY;
8603 if (appData.debugMode) {
8604 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8607 if (gameMode != PlayFromGameFile)
8610 if (currentMove >= forwardMostMove) {
8611 gameMode = EditGame;
8614 /* [AS] Clear current move marker at the end of a game */
8615 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8620 toX = moveList[currentMove][2] - AAA;
8621 toY = moveList[currentMove][3] - ONE;
8623 if (moveList[currentMove][1] == '@') {
8624 if (appData.highlightLastMove) {
8625 SetHighlights(-1, -1, toX, toY);
8628 fromX = moveList[currentMove][0] - AAA;
8629 fromY = moveList[currentMove][1] - ONE;
8631 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8633 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8635 if (appData.highlightLastMove) {
8636 SetHighlights(fromX, fromY, toX, toY);
8639 DisplayMove(currentMove);
8640 SendMoveToProgram(currentMove++, &first);
8641 DisplayBothClocks();
8642 DrawPosition(FALSE, boards[currentMove]);
8643 // [HGM] PV info: always display, routine tests if empty
8644 DisplayComment(currentMove - 1, commentList[currentMove]);
8650 LoadGameOneMove(readAhead)
8651 ChessMove readAhead;
8653 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8654 char promoChar = NULLCHAR;
8659 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8660 gameMode != AnalyzeMode && gameMode != Training) {
8665 yyboardindex = forwardMostMove;
8666 if (readAhead != (ChessMove)0) {
8667 moveType = readAhead;
8669 if (gameFileFP == NULL)
8671 moveType = (ChessMove) yylex();
8677 if (appData.debugMode)
8678 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8680 if (*p == '{' || *p == '[' || *p == '(') {
8681 p[strlen(p) - 1] = NULLCHAR;
8685 /* append the comment but don't display it */
8686 while (*p == '\n') p++;
8687 AppendComment(currentMove, p);
8690 case WhiteCapturesEnPassant:
8691 case BlackCapturesEnPassant:
8692 case WhitePromotionChancellor:
8693 case BlackPromotionChancellor:
8694 case WhitePromotionArchbishop:
8695 case BlackPromotionArchbishop:
8696 case WhitePromotionCentaur:
8697 case BlackPromotionCentaur:
8698 case WhitePromotionQueen:
8699 case BlackPromotionQueen:
8700 case WhitePromotionRook:
8701 case BlackPromotionRook:
8702 case WhitePromotionBishop:
8703 case BlackPromotionBishop:
8704 case WhitePromotionKnight:
8705 case BlackPromotionKnight:
8706 case WhitePromotionKing:
8707 case BlackPromotionKing:
8709 case WhiteKingSideCastle:
8710 case WhiteQueenSideCastle:
8711 case BlackKingSideCastle:
8712 case BlackQueenSideCastle:
8713 case WhiteKingSideCastleWild:
8714 case WhiteQueenSideCastleWild:
8715 case BlackKingSideCastleWild:
8716 case BlackQueenSideCastleWild:
8718 case WhiteHSideCastleFR:
8719 case WhiteASideCastleFR:
8720 case BlackHSideCastleFR:
8721 case BlackASideCastleFR:
8723 if (appData.debugMode)
8724 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8725 fromX = currentMoveString[0] - AAA;
8726 fromY = currentMoveString[1] - ONE;
8727 toX = currentMoveString[2] - AAA;
8728 toY = currentMoveString[3] - ONE;
8729 promoChar = currentMoveString[4];
8734 if (appData.debugMode)
8735 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8736 fromX = moveType == WhiteDrop ?
8737 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8738 (int) CharToPiece(ToLower(currentMoveString[0]));
8740 toX = currentMoveString[2] - AAA;
8741 toY = currentMoveString[3] - ONE;
8747 case GameUnfinished:
8748 if (appData.debugMode)
8749 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8750 p = strchr(yy_text, '{');
8751 if (p == NULL) p = strchr(yy_text, '(');
8754 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8756 q = strchr(p, *p == '{' ? '}' : ')');
8757 if (q != NULL) *q = NULLCHAR;
8760 GameEnds(moveType, p, GE_FILE);
8762 if (cmailMsgLoaded) {
8764 flipView = WhiteOnMove(currentMove);
8765 if (moveType == GameUnfinished) flipView = !flipView;
8766 if (appData.debugMode)
8767 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8771 case (ChessMove) 0: /* end of file */
8772 if (appData.debugMode)
8773 fprintf(debugFP, "Parser hit end of file\n");
8774 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8775 EP_UNKNOWN, castlingRights[currentMove]) ) {
8781 if (WhiteOnMove(currentMove)) {
8782 GameEnds(BlackWins, "Black mates", GE_FILE);
8784 GameEnds(WhiteWins, "White mates", GE_FILE);
8788 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8795 if (lastLoadGameStart == GNUChessGame) {
8796 /* GNUChessGames have numbers, but they aren't move numbers */
8797 if (appData.debugMode)
8798 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8799 yy_text, (int) moveType);
8800 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8802 /* else fall thru */
8807 /* Reached start of next game in file */
8808 if (appData.debugMode)
8809 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8810 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8811 EP_UNKNOWN, castlingRights[currentMove]) ) {
8817 if (WhiteOnMove(currentMove)) {
8818 GameEnds(BlackWins, "Black mates", GE_FILE);
8820 GameEnds(WhiteWins, "White mates", GE_FILE);
8824 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8830 case PositionDiagram: /* should not happen; ignore */
8831 case ElapsedTime: /* ignore */
8832 case NAG: /* ignore */
8833 if (appData.debugMode)
8834 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8835 yy_text, (int) moveType);
8836 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8839 if (appData.testLegality) {
8840 if (appData.debugMode)
8841 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8842 sprintf(move, _("Illegal move: %d.%s%s"),
8843 (forwardMostMove / 2) + 1,
8844 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8845 DisplayError(move, 0);
8848 if (appData.debugMode)
8849 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8850 yy_text, currentMoveString);
8851 fromX = currentMoveString[0] - AAA;
8852 fromY = currentMoveString[1] - ONE;
8853 toX = currentMoveString[2] - AAA;
8854 toY = currentMoveString[3] - ONE;
8855 promoChar = currentMoveString[4];
8860 if (appData.debugMode)
8861 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8862 sprintf(move, _("Ambiguous move: %d.%s%s"),
8863 (forwardMostMove / 2) + 1,
8864 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8865 DisplayError(move, 0);
8870 case ImpossibleMove:
8871 if (appData.debugMode)
8872 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8873 sprintf(move, _("Illegal move: %d.%s%s"),
8874 (forwardMostMove / 2) + 1,
8875 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8876 DisplayError(move, 0);
8882 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8883 DrawPosition(FALSE, boards[currentMove]);
8884 DisplayBothClocks();
8885 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8886 DisplayComment(currentMove - 1, commentList[currentMove]);
8888 (void) StopLoadGameTimer();
8890 cmailOldMove = forwardMostMove;
8893 /* currentMoveString is set as a side-effect of yylex */
8894 strcat(currentMoveString, "\n");
8895 strcpy(moveList[forwardMostMove], currentMoveString);
8897 thinkOutput[0] = NULLCHAR;
8898 MakeMove(fromX, fromY, toX, toY, promoChar);
8899 currentMove = forwardMostMove;
8904 /* Load the nth game from the given file */
8906 LoadGameFromFile(filename, n, title, useList)
8910 /*Boolean*/ int useList;
8915 if (strcmp(filename, "-") == 0) {
8919 f = fopen(filename, "rb");
8921 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8922 DisplayError(buf, errno);
8926 if (fseek(f, 0, 0) == -1) {
8927 /* f is not seekable; probably a pipe */
8930 if (useList && n == 0) {
8931 int error = GameListBuild(f);
8933 DisplayError(_("Cannot build game list"), error);
8934 } else if (!ListEmpty(&gameList) &&
8935 ((ListGame *) gameList.tailPred)->number > 1) {
8936 GameListPopUp(f, title);
8943 return LoadGame(f, n, title, FALSE);
8948 MakeRegisteredMove()
8950 int fromX, fromY, toX, toY;
8952 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8953 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8956 if (appData.debugMode)
8957 fprintf(debugFP, "Restoring %s for game %d\n",
8958 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8960 thinkOutput[0] = NULLCHAR;
8961 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8962 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8963 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8964 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8965 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8966 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8967 MakeMove(fromX, fromY, toX, toY, promoChar);
8968 ShowMove(fromX, fromY, toX, toY);
8970 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8971 EP_UNKNOWN, castlingRights[currentMove]) ) {
8978 if (WhiteOnMove(currentMove)) {
8979 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8981 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8986 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8993 if (WhiteOnMove(currentMove)) {
8994 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8996 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9001 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9012 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9014 CmailLoadGame(f, gameNumber, title, useList)
9022 if (gameNumber > nCmailGames) {
9023 DisplayError(_("No more games in this message"), 0);
9026 if (f == lastLoadGameFP) {
9027 int offset = gameNumber - lastLoadGameNumber;
9029 cmailMsg[0] = NULLCHAR;
9030 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9031 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9032 nCmailMovesRegistered--;
9034 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9035 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9036 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9039 if (! RegisterMove()) return FALSE;
9043 retVal = LoadGame(f, gameNumber, title, useList);
9045 /* Make move registered during previous look at this game, if any */
9046 MakeRegisteredMove();
9048 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9049 commentList[currentMove]
9050 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9051 DisplayComment(currentMove - 1, commentList[currentMove]);
9057 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9062 int gameNumber = lastLoadGameNumber + offset;
9063 if (lastLoadGameFP == NULL) {
9064 DisplayError(_("No game has been loaded yet"), 0);
9067 if (gameNumber <= 0) {
9068 DisplayError(_("Can't back up any further"), 0);
9071 if (cmailMsgLoaded) {
9072 return CmailLoadGame(lastLoadGameFP, gameNumber,
9073 lastLoadGameTitle, lastLoadGameUseList);
9075 return LoadGame(lastLoadGameFP, gameNumber,
9076 lastLoadGameTitle, lastLoadGameUseList);
9082 /* Load the nth game from open file f */
9084 LoadGame(f, gameNumber, title, useList)
9092 int gn = gameNumber;
9093 ListGame *lg = NULL;
9096 GameMode oldGameMode;
9097 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9099 if (appData.debugMode)
9100 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9102 if (gameMode == Training )
9103 SetTrainingModeOff();
9105 oldGameMode = gameMode;
9106 if (gameMode != BeginningOfGame) {
9111 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9112 fclose(lastLoadGameFP);
9116 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9119 fseek(f, lg->offset, 0);
9120 GameListHighlight(gameNumber);
9124 DisplayError(_("Game number out of range"), 0);
9129 if (fseek(f, 0, 0) == -1) {
9130 if (f == lastLoadGameFP ?
9131 gameNumber == lastLoadGameNumber + 1 :
9135 DisplayError(_("Can't seek on game file"), 0);
9141 lastLoadGameNumber = gameNumber;
9142 strcpy(lastLoadGameTitle, title);
9143 lastLoadGameUseList = useList;
9147 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9148 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9149 lg->gameInfo.black);
9151 } else if (*title != NULLCHAR) {
9152 if (gameNumber > 1) {
9153 sprintf(buf, "%s %d", title, gameNumber);
9156 DisplayTitle(title);
9160 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9161 gameMode = PlayFromGameFile;
9165 currentMove = forwardMostMove = backwardMostMove = 0;
9166 CopyBoard(boards[0], initialPosition);
9170 * Skip the first gn-1 games in the file.
9171 * Also skip over anything that precedes an identifiable
9172 * start of game marker, to avoid being confused by
9173 * garbage at the start of the file. Currently
9174 * recognized start of game markers are the move number "1",
9175 * the pattern "gnuchess .* game", the pattern
9176 * "^[#;%] [^ ]* game file", and a PGN tag block.
9177 * A game that starts with one of the latter two patterns
9178 * will also have a move number 1, possibly
9179 * following a position diagram.
9180 * 5-4-02: Let's try being more lenient and allowing a game to
9181 * start with an unnumbered move. Does that break anything?
9183 cm = lastLoadGameStart = (ChessMove) 0;
9185 yyboardindex = forwardMostMove;
9186 cm = (ChessMove) yylex();
9189 if (cmailMsgLoaded) {
9190 nCmailGames = CMAIL_MAX_GAMES - gn;
9193 DisplayError(_("Game not found in file"), 0);
9200 lastLoadGameStart = cm;
9204 switch (lastLoadGameStart) {
9211 gn--; /* count this game */
9212 lastLoadGameStart = cm;
9221 switch (lastLoadGameStart) {
9226 gn--; /* count this game */
9227 lastLoadGameStart = cm;
9230 lastLoadGameStart = cm; /* game counted already */
9238 yyboardindex = forwardMostMove;
9239 cm = (ChessMove) yylex();
9240 } while (cm == PGNTag || cm == Comment);
9247 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9248 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9249 != CMAIL_OLD_RESULT) {
9251 cmailResult[ CMAIL_MAX_GAMES
9252 - gn - 1] = CMAIL_OLD_RESULT;
9258 /* Only a NormalMove can be at the start of a game
9259 * without a position diagram. */
9260 if (lastLoadGameStart == (ChessMove) 0) {
9262 lastLoadGameStart = MoveNumberOne;
9271 if (appData.debugMode)
9272 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9274 if (cm == XBoardGame) {
9275 /* Skip any header junk before position diagram and/or move 1 */
9277 yyboardindex = forwardMostMove;
9278 cm = (ChessMove) yylex();
9280 if (cm == (ChessMove) 0 ||
9281 cm == GNUChessGame || cm == XBoardGame) {
9282 /* Empty game; pretend end-of-file and handle later */
9287 if (cm == MoveNumberOne || cm == PositionDiagram ||
9288 cm == PGNTag || cm == Comment)
9291 } else if (cm == GNUChessGame) {
9292 if (gameInfo.event != NULL) {
9293 free(gameInfo.event);
9295 gameInfo.event = StrSave(yy_text);
9298 startedFromSetupPosition = FALSE;
9299 while (cm == PGNTag) {
9300 if (appData.debugMode)
9301 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9302 err = ParsePGNTag(yy_text, &gameInfo);
9303 if (!err) numPGNTags++;
9305 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9306 if(gameInfo.variant != oldVariant) {
9307 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9309 oldVariant = gameInfo.variant;
9310 if (appData.debugMode)
9311 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9315 if (gameInfo.fen != NULL) {
9316 Board initial_position;
9317 startedFromSetupPosition = TRUE;
9318 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9320 DisplayError(_("Bad FEN position in file"), 0);
9323 CopyBoard(boards[0], initial_position);
9324 if (blackPlaysFirst) {
9325 currentMove = forwardMostMove = backwardMostMove = 1;
9326 CopyBoard(boards[1], initial_position);
9327 strcpy(moveList[0], "");
9328 strcpy(parseList[0], "");
9329 timeRemaining[0][1] = whiteTimeRemaining;
9330 timeRemaining[1][1] = blackTimeRemaining;
9331 if (commentList[0] != NULL) {
9332 commentList[1] = commentList[0];
9333 commentList[0] = NULL;
9336 currentMove = forwardMostMove = backwardMostMove = 0;
9338 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9340 initialRulePlies = FENrulePlies;
9341 epStatus[forwardMostMove] = FENepStatus;
9342 for( i=0; i< nrCastlingRights; i++ )
9343 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9345 yyboardindex = forwardMostMove;
9347 gameInfo.fen = NULL;
9350 yyboardindex = forwardMostMove;
9351 cm = (ChessMove) yylex();
9353 /* Handle comments interspersed among the tags */
9354 while (cm == Comment) {
9356 if (appData.debugMode)
9357 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9359 if (*p == '{' || *p == '[' || *p == '(') {
9360 p[strlen(p) - 1] = NULLCHAR;
9363 while (*p == '\n') p++;
9364 AppendComment(currentMove, p);
9365 yyboardindex = forwardMostMove;
9366 cm = (ChessMove) yylex();
9370 /* don't rely on existence of Event tag since if game was
9371 * pasted from clipboard the Event tag may not exist
9373 if (numPGNTags > 0){
9375 if (gameInfo.variant == VariantNormal) {
9376 gameInfo.variant = StringToVariant(gameInfo.event);
9379 if( appData.autoDisplayTags ) {
9380 tags = PGNTags(&gameInfo);
9381 TagsPopUp(tags, CmailMsg());
9386 /* Make something up, but don't display it now */
9391 if (cm == PositionDiagram) {
9394 Board initial_position;
9396 if (appData.debugMode)
9397 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9399 if (!startedFromSetupPosition) {
9401 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9402 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9412 initial_position[i][j++] = CharToPiece(*p);
9415 while (*p == ' ' || *p == '\t' ||
9416 *p == '\n' || *p == '\r') p++;
9418 if (strncmp(p, "black", strlen("black"))==0)
9419 blackPlaysFirst = TRUE;
9421 blackPlaysFirst = FALSE;
9422 startedFromSetupPosition = TRUE;
9424 CopyBoard(boards[0], initial_position);
9425 if (blackPlaysFirst) {
9426 currentMove = forwardMostMove = backwardMostMove = 1;
9427 CopyBoard(boards[1], initial_position);
9428 strcpy(moveList[0], "");
9429 strcpy(parseList[0], "");
9430 timeRemaining[0][1] = whiteTimeRemaining;
9431 timeRemaining[1][1] = blackTimeRemaining;
9432 if (commentList[0] != NULL) {
9433 commentList[1] = commentList[0];
9434 commentList[0] = NULL;
9437 currentMove = forwardMostMove = backwardMostMove = 0;
9440 yyboardindex = forwardMostMove;
9441 cm = (ChessMove) yylex();
9444 if (first.pr == NoProc) {
9445 StartChessProgram(&first);
9447 InitChessProgram(&first, FALSE);
9448 SendToProgram("force\n", &first);
9449 if (startedFromSetupPosition) {
9450 SendBoard(&first, forwardMostMove);
9451 if (appData.debugMode) {
9452 fprintf(debugFP, "Load Game\n");
9454 DisplayBothClocks();
9457 /* [HGM] server: flag to write setup moves in broadcast file as one */
9458 loadFlag = appData.suppressLoadMoves;
9460 while (cm == Comment) {
9462 if (appData.debugMode)
9463 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9465 if (*p == '{' || *p == '[' || *p == '(') {
9466 p[strlen(p) - 1] = NULLCHAR;
9469 while (*p == '\n') p++;
9470 AppendComment(currentMove, p);
9471 yyboardindex = forwardMostMove;
9472 cm = (ChessMove) yylex();
9475 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9476 cm == WhiteWins || cm == BlackWins ||
9477 cm == GameIsDrawn || cm == GameUnfinished) {
9478 DisplayMessage("", _("No moves in game"));
9479 if (cmailMsgLoaded) {
9480 if (appData.debugMode)
9481 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9485 DrawPosition(FALSE, boards[currentMove]);
9486 DisplayBothClocks();
9487 gameMode = EditGame;
9494 // [HGM] PV info: routine tests if comment empty
9495 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9496 DisplayComment(currentMove - 1, commentList[currentMove]);
9498 if (!matchMode && appData.timeDelay != 0)
9499 DrawPosition(FALSE, boards[currentMove]);
9501 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9502 programStats.ok_to_send = 1;
9505 /* if the first token after the PGN tags is a move
9506 * and not move number 1, retrieve it from the parser
9508 if (cm != MoveNumberOne)
9509 LoadGameOneMove(cm);
9511 /* load the remaining moves from the file */
9512 while (LoadGameOneMove((ChessMove)0)) {
9513 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9514 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9517 /* rewind to the start of the game */
9518 currentMove = backwardMostMove;
9520 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9522 if (oldGameMode == AnalyzeFile ||
9523 oldGameMode == AnalyzeMode) {
9527 if (matchMode || appData.timeDelay == 0) {
9529 gameMode = EditGame;
9531 } else if (appData.timeDelay > 0) {
9535 if (appData.debugMode)
9536 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9538 loadFlag = 0; /* [HGM] true game starts */
9542 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9544 ReloadPosition(offset)
9547 int positionNumber = lastLoadPositionNumber + offset;
9548 if (lastLoadPositionFP == NULL) {
9549 DisplayError(_("No position has been loaded yet"), 0);
9552 if (positionNumber <= 0) {
9553 DisplayError(_("Can't back up any further"), 0);
9556 return LoadPosition(lastLoadPositionFP, positionNumber,
9557 lastLoadPositionTitle);
9560 /* Load the nth position from the given file */
9562 LoadPositionFromFile(filename, n, title)
9570 if (strcmp(filename, "-") == 0) {
9571 return LoadPosition(stdin, n, "stdin");
9573 f = fopen(filename, "rb");
9575 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9576 DisplayError(buf, errno);
9579 return LoadPosition(f, n, title);
9584 /* Load the nth position from the given open file, and close it */
9586 LoadPosition(f, positionNumber, title)
9591 char *p, line[MSG_SIZ];
9592 Board initial_position;
9593 int i, j, fenMode, pn;
9595 if (gameMode == Training )
9596 SetTrainingModeOff();
9598 if (gameMode != BeginningOfGame) {
9601 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9602 fclose(lastLoadPositionFP);
9604 if (positionNumber == 0) positionNumber = 1;
9605 lastLoadPositionFP = f;
9606 lastLoadPositionNumber = positionNumber;
9607 strcpy(lastLoadPositionTitle, title);
9608 if (first.pr == NoProc) {
9609 StartChessProgram(&first);
9610 InitChessProgram(&first, FALSE);
9612 pn = positionNumber;
9613 if (positionNumber < 0) {
9614 /* Negative position number means to seek to that byte offset */
9615 if (fseek(f, -positionNumber, 0) == -1) {
9616 DisplayError(_("Can't seek on position file"), 0);
9621 if (fseek(f, 0, 0) == -1) {
9622 if (f == lastLoadPositionFP ?
9623 positionNumber == lastLoadPositionNumber + 1 :
9624 positionNumber == 1) {
9627 DisplayError(_("Can't seek on position file"), 0);
9632 /* See if this file is FEN or old-style xboard */
9633 if (fgets(line, MSG_SIZ, f) == NULL) {
9634 DisplayError(_("Position not found in file"), 0);
9637 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9638 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9641 if (fenMode || line[0] == '#') pn--;
9643 /* skip positions before number pn */
9644 if (fgets(line, MSG_SIZ, f) == NULL) {
9646 DisplayError(_("Position not found in file"), 0);
9649 if (fenMode || line[0] == '#') pn--;
9654 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9655 DisplayError(_("Bad FEN position in file"), 0);
9659 (void) fgets(line, MSG_SIZ, f);
9660 (void) fgets(line, MSG_SIZ, f);
9662 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9663 (void) fgets(line, MSG_SIZ, f);
9664 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9667 initial_position[i][j++] = CharToPiece(*p);
9671 blackPlaysFirst = FALSE;
9673 (void) fgets(line, MSG_SIZ, f);
9674 if (strncmp(line, "black", strlen("black"))==0)
9675 blackPlaysFirst = TRUE;
9678 startedFromSetupPosition = TRUE;
9680 SendToProgram("force\n", &first);
9681 CopyBoard(boards[0], initial_position);
9682 if (blackPlaysFirst) {
9683 currentMove = forwardMostMove = backwardMostMove = 1;
9684 strcpy(moveList[0], "");
9685 strcpy(parseList[0], "");
9686 CopyBoard(boards[1], initial_position);
9687 DisplayMessage("", _("Black to play"));
9689 currentMove = forwardMostMove = backwardMostMove = 0;
9690 DisplayMessage("", _("White to play"));
9692 /* [HGM] copy FEN attributes as well */
9694 initialRulePlies = FENrulePlies;
9695 epStatus[forwardMostMove] = FENepStatus;
9696 for( i=0; i< nrCastlingRights; i++ )
9697 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9699 SendBoard(&first, forwardMostMove);
9700 if (appData.debugMode) {
9702 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9703 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9704 fprintf(debugFP, "Load Position\n");
9707 if (positionNumber > 1) {
9708 sprintf(line, "%s %d", title, positionNumber);
9711 DisplayTitle(title);
9713 gameMode = EditGame;
9716 timeRemaining[0][1] = whiteTimeRemaining;
9717 timeRemaining[1][1] = blackTimeRemaining;
9718 DrawPosition(FALSE, boards[currentMove]);
9725 CopyPlayerNameIntoFileName(dest, src)
9728 while (*src != NULLCHAR && *src != ',') {
9733 *(*dest)++ = *src++;
9738 char *DefaultFileName(ext)
9741 static char def[MSG_SIZ];
9744 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9746 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9748 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9757 /* Save the current game to the given file */
9759 SaveGameToFile(filename, append)
9766 if (strcmp(filename, "-") == 0) {
9767 return SaveGame(stdout, 0, NULL);
9769 f = fopen(filename, append ? "a" : "w");
9771 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9772 DisplayError(buf, errno);
9775 return SaveGame(f, 0, NULL);
9784 static char buf[MSG_SIZ];
9787 p = strchr(str, ' ');
9788 if (p == NULL) return str;
9789 strncpy(buf, str, p - str);
9790 buf[p - str] = NULLCHAR;
9794 #define PGN_MAX_LINE 75
9796 #define PGN_SIDE_WHITE 0
9797 #define PGN_SIDE_BLACK 1
9800 static int FindFirstMoveOutOfBook( int side )
9804 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9805 int index = backwardMostMove;
9806 int has_book_hit = 0;
9808 if( (index % 2) != side ) {
9812 while( index < forwardMostMove ) {
9813 /* Check to see if engine is in book */
9814 int depth = pvInfoList[index].depth;
9815 int score = pvInfoList[index].score;
9821 else if( score == 0 && depth == 63 ) {
9822 in_book = 1; /* Zappa */
9824 else if( score == 2 && depth == 99 ) {
9825 in_book = 1; /* Abrok */
9828 has_book_hit += in_book;
9844 void GetOutOfBookInfo( char * buf )
9848 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9850 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9851 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9855 if( oob[0] >= 0 || oob[1] >= 0 ) {
9856 for( i=0; i<2; i++ ) {
9860 if( i > 0 && oob[0] >= 0 ) {
9864 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9865 sprintf( buf+strlen(buf), "%s%.2f",
9866 pvInfoList[idx].score >= 0 ? "+" : "",
9867 pvInfoList[idx].score / 100.0 );
9873 /* Save game in PGN style and close the file */
9878 int i, offset, linelen, newblock;
9882 int movelen, numlen, blank;
9883 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9885 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9887 tm = time((time_t *) NULL);
9889 PrintPGNTags(f, &gameInfo);
9891 if (backwardMostMove > 0 || startedFromSetupPosition) {
9892 char *fen = PositionToFEN(backwardMostMove, NULL);
9893 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9894 fprintf(f, "\n{--------------\n");
9895 PrintPosition(f, backwardMostMove);
9896 fprintf(f, "--------------}\n");
9900 /* [AS] Out of book annotation */
9901 if( appData.saveOutOfBookInfo ) {
9904 GetOutOfBookInfo( buf );
9906 if( buf[0] != '\0' ) {
9907 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9914 i = backwardMostMove;
9918 while (i < forwardMostMove) {
9919 /* Print comments preceding this move */
9920 if (commentList[i] != NULL) {
9921 if (linelen > 0) fprintf(f, "\n");
9922 fprintf(f, "{\n%s}\n", commentList[i]);
9927 /* Format move number */
9929 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9932 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9934 numtext[0] = NULLCHAR;
9937 numlen = strlen(numtext);
9940 /* Print move number */
9941 blank = linelen > 0 && numlen > 0;
9942 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9951 fprintf(f, "%s", numtext);
9955 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9956 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9959 blank = linelen > 0 && movelen > 0;
9960 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9969 fprintf(f, "%s", move_buffer);
9972 /* [AS] Add PV info if present */
9973 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9974 /* [HGM] add time */
9975 char buf[MSG_SIZ]; int seconds;
9977 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9979 if( seconds <= 0) buf[0] = 0; else
9980 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9981 seconds = (seconds + 4)/10; // round to full seconds
9982 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9983 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9986 sprintf( move_buffer, "{%s%.2f/%d%s}",
9987 pvInfoList[i].score >= 0 ? "+" : "",
9988 pvInfoList[i].score / 100.0,
9989 pvInfoList[i].depth,
9992 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9994 /* Print score/depth */
9995 blank = linelen > 0 && movelen > 0;
9996 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10005 fprintf(f, "%s", move_buffer);
10006 linelen += movelen;
10012 /* Start a new line */
10013 if (linelen > 0) fprintf(f, "\n");
10015 /* Print comments after last move */
10016 if (commentList[i] != NULL) {
10017 fprintf(f, "{\n%s}\n", commentList[i]);
10021 if (gameInfo.resultDetails != NULL &&
10022 gameInfo.resultDetails[0] != NULLCHAR) {
10023 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10024 PGNResult(gameInfo.result));
10026 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10030 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10034 /* Save game in old style and close the file */
10036 SaveGameOldStyle(f)
10042 tm = time((time_t *) NULL);
10044 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10047 if (backwardMostMove > 0 || startedFromSetupPosition) {
10048 fprintf(f, "\n[--------------\n");
10049 PrintPosition(f, backwardMostMove);
10050 fprintf(f, "--------------]\n");
10055 i = backwardMostMove;
10056 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10058 while (i < forwardMostMove) {
10059 if (commentList[i] != NULL) {
10060 fprintf(f, "[%s]\n", commentList[i]);
10063 if ((i % 2) == 1) {
10064 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10067 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10069 if (commentList[i] != NULL) {
10073 if (i >= forwardMostMove) {
10077 fprintf(f, "%s\n", parseList[i]);
10082 if (commentList[i] != NULL) {
10083 fprintf(f, "[%s]\n", commentList[i]);
10086 /* This isn't really the old style, but it's close enough */
10087 if (gameInfo.resultDetails != NULL &&
10088 gameInfo.resultDetails[0] != NULLCHAR) {
10089 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10090 gameInfo.resultDetails);
10092 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10099 /* Save the current game to open file f and close the file */
10101 SaveGame(f, dummy, dummy2)
10106 if (gameMode == EditPosition) EditPositionDone(TRUE);
10107 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10108 if (appData.oldSaveStyle)
10109 return SaveGameOldStyle(f);
10111 return SaveGamePGN(f);
10114 /* Save the current position to the given file */
10116 SavePositionToFile(filename)
10122 if (strcmp(filename, "-") == 0) {
10123 return SavePosition(stdout, 0, NULL);
10125 f = fopen(filename, "a");
10127 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10128 DisplayError(buf, errno);
10131 SavePosition(f, 0, NULL);
10137 /* Save the current position to the given open file and close the file */
10139 SavePosition(f, dummy, dummy2)
10147 if (gameMode == EditPosition) EditPositionDone(TRUE);
10148 if (appData.oldSaveStyle) {
10149 tm = time((time_t *) NULL);
10151 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10153 fprintf(f, "[--------------\n");
10154 PrintPosition(f, currentMove);
10155 fprintf(f, "--------------]\n");
10157 fen = PositionToFEN(currentMove, NULL);
10158 fprintf(f, "%s\n", fen);
10166 ReloadCmailMsgEvent(unregister)
10170 static char *inFilename = NULL;
10171 static char *outFilename;
10173 struct stat inbuf, outbuf;
10176 /* Any registered moves are unregistered if unregister is set, */
10177 /* i.e. invoked by the signal handler */
10179 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10180 cmailMoveRegistered[i] = FALSE;
10181 if (cmailCommentList[i] != NULL) {
10182 free(cmailCommentList[i]);
10183 cmailCommentList[i] = NULL;
10186 nCmailMovesRegistered = 0;
10189 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10190 cmailResult[i] = CMAIL_NOT_RESULT;
10194 if (inFilename == NULL) {
10195 /* Because the filenames are static they only get malloced once */
10196 /* and they never get freed */
10197 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10198 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10200 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10201 sprintf(outFilename, "%s.out", appData.cmailGameName);
10204 status = stat(outFilename, &outbuf);
10206 cmailMailedMove = FALSE;
10208 status = stat(inFilename, &inbuf);
10209 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10212 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10213 counts the games, notes how each one terminated, etc.
10215 It would be nice to remove this kludge and instead gather all
10216 the information while building the game list. (And to keep it
10217 in the game list nodes instead of having a bunch of fixed-size
10218 parallel arrays.) Note this will require getting each game's
10219 termination from the PGN tags, as the game list builder does
10220 not process the game moves. --mann
10222 cmailMsgLoaded = TRUE;
10223 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10225 /* Load first game in the file or popup game menu */
10226 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10228 #endif /* !WIN32 */
10236 char string[MSG_SIZ];
10238 if ( cmailMailedMove
10239 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10240 return TRUE; /* Allow free viewing */
10243 /* Unregister move to ensure that we don't leave RegisterMove */
10244 /* with the move registered when the conditions for registering no */
10246 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10247 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10248 nCmailMovesRegistered --;
10250 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10252 free(cmailCommentList[lastLoadGameNumber - 1]);
10253 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10257 if (cmailOldMove == -1) {
10258 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10262 if (currentMove > cmailOldMove + 1) {
10263 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10267 if (currentMove < cmailOldMove) {
10268 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10272 if (forwardMostMove > currentMove) {
10273 /* Silently truncate extra moves */
10277 if ( (currentMove == cmailOldMove + 1)
10278 || ( (currentMove == cmailOldMove)
10279 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10280 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10281 if (gameInfo.result != GameUnfinished) {
10282 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10285 if (commentList[currentMove] != NULL) {
10286 cmailCommentList[lastLoadGameNumber - 1]
10287 = StrSave(commentList[currentMove]);
10289 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10291 if (appData.debugMode)
10292 fprintf(debugFP, "Saving %s for game %d\n",
10293 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10296 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10298 f = fopen(string, "w");
10299 if (appData.oldSaveStyle) {
10300 SaveGameOldStyle(f); /* also closes the file */
10302 sprintf(string, "%s.pos.out", appData.cmailGameName);
10303 f = fopen(string, "w");
10304 SavePosition(f, 0, NULL); /* also closes the file */
10306 fprintf(f, "{--------------\n");
10307 PrintPosition(f, currentMove);
10308 fprintf(f, "--------------}\n\n");
10310 SaveGame(f, 0, NULL); /* also closes the file*/
10313 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10314 nCmailMovesRegistered ++;
10315 } else if (nCmailGames == 1) {
10316 DisplayError(_("You have not made a move yet"), 0);
10327 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10328 FILE *commandOutput;
10329 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10330 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10336 if (! cmailMsgLoaded) {
10337 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10341 if (nCmailGames == nCmailResults) {
10342 DisplayError(_("No unfinished games"), 0);
10346 #if CMAIL_PROHIBIT_REMAIL
10347 if (cmailMailedMove) {
10348 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);
10349 DisplayError(msg, 0);
10354 if (! (cmailMailedMove || RegisterMove())) return;
10356 if ( cmailMailedMove
10357 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10358 sprintf(string, partCommandString,
10359 appData.debugMode ? " -v" : "", appData.cmailGameName);
10360 commandOutput = popen(string, "r");
10362 if (commandOutput == NULL) {
10363 DisplayError(_("Failed to invoke cmail"), 0);
10365 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10366 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10368 if (nBuffers > 1) {
10369 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10370 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10371 nBytes = MSG_SIZ - 1;
10373 (void) memcpy(msg, buffer, nBytes);
10375 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10377 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10378 cmailMailedMove = TRUE; /* Prevent >1 moves */
10381 for (i = 0; i < nCmailGames; i ++) {
10382 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10387 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10389 sprintf(buffer, "%s/%s.%s.archive",
10391 appData.cmailGameName,
10393 LoadGameFromFile(buffer, 1, buffer, FALSE);
10394 cmailMsgLoaded = FALSE;
10398 DisplayInformation(msg);
10399 pclose(commandOutput);
10402 if ((*cmailMsg) != '\0') {
10403 DisplayInformation(cmailMsg);
10408 #endif /* !WIN32 */
10417 int prependComma = 0;
10419 char string[MSG_SIZ]; /* Space for game-list */
10422 if (!cmailMsgLoaded) return "";
10424 if (cmailMailedMove) {
10425 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10427 /* Create a list of games left */
10428 sprintf(string, "[");
10429 for (i = 0; i < nCmailGames; i ++) {
10430 if (! ( cmailMoveRegistered[i]
10431 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10432 if (prependComma) {
10433 sprintf(number, ",%d", i + 1);
10435 sprintf(number, "%d", i + 1);
10439 strcat(string, number);
10442 strcat(string, "]");
10444 if (nCmailMovesRegistered + nCmailResults == 0) {
10445 switch (nCmailGames) {
10448 _("Still need to make move for game\n"));
10453 _("Still need to make moves for both games\n"));
10458 _("Still need to make moves for all %d games\n"),
10463 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10466 _("Still need to make a move for game %s\n"),
10471 if (nCmailResults == nCmailGames) {
10472 sprintf(cmailMsg, _("No unfinished games\n"));
10474 sprintf(cmailMsg, _("Ready to send mail\n"));
10480 _("Still need to make moves for games %s\n"),
10492 if (gameMode == Training)
10493 SetTrainingModeOff();
10496 cmailMsgLoaded = FALSE;
10497 if (appData.icsActive) {
10498 SendToICS(ics_prefix);
10499 SendToICS("refresh\n");
10509 /* Give up on clean exit */
10513 /* Keep trying for clean exit */
10517 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10519 if (telnetISR != NULL) {
10520 RemoveInputSource(telnetISR);
10522 if (icsPR != NoProc) {
10523 DestroyChildProcess(icsPR, TRUE);
10526 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10527 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10529 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10530 /* make sure this other one finishes before killing it! */
10531 if(endingGame) { int count = 0;
10532 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10533 while(endingGame && count++ < 10) DoSleep(1);
10534 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10537 /* Kill off chess programs */
10538 if (first.pr != NoProc) {
10541 DoSleep( appData.delayBeforeQuit );
10542 SendToProgram("quit\n", &first);
10543 DoSleep( appData.delayAfterQuit );
10544 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10546 if (second.pr != NoProc) {
10547 DoSleep( appData.delayBeforeQuit );
10548 SendToProgram("quit\n", &second);
10549 DoSleep( appData.delayAfterQuit );
10550 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10552 if (first.isr != NULL) {
10553 RemoveInputSource(first.isr);
10555 if (second.isr != NULL) {
10556 RemoveInputSource(second.isr);
10559 ShutDownFrontEnd();
10566 if (appData.debugMode)
10567 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10571 if (gameMode == MachinePlaysWhite ||
10572 gameMode == MachinePlaysBlack) {
10575 DisplayBothClocks();
10577 if (gameMode == PlayFromGameFile) {
10578 if (appData.timeDelay >= 0)
10579 AutoPlayGameLoop();
10580 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10581 Reset(FALSE, TRUE);
10582 SendToICS(ics_prefix);
10583 SendToICS("refresh\n");
10584 } else if (currentMove < forwardMostMove) {
10585 ForwardInner(forwardMostMove);
10587 pauseExamInvalid = FALSE;
10589 switch (gameMode) {
10593 pauseExamForwardMostMove = forwardMostMove;
10594 pauseExamInvalid = FALSE;
10597 case IcsPlayingWhite:
10598 case IcsPlayingBlack:
10602 case PlayFromGameFile:
10603 (void) StopLoadGameTimer();
10607 case BeginningOfGame:
10608 if (appData.icsActive) return;
10609 /* else fall through */
10610 case MachinePlaysWhite:
10611 case MachinePlaysBlack:
10612 case TwoMachinesPlay:
10613 if (forwardMostMove == 0)
10614 return; /* don't pause if no one has moved */
10615 if ((gameMode == MachinePlaysWhite &&
10616 !WhiteOnMove(forwardMostMove)) ||
10617 (gameMode == MachinePlaysBlack &&
10618 WhiteOnMove(forwardMostMove))) {
10631 char title[MSG_SIZ];
10633 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10634 strcpy(title, _("Edit comment"));
10636 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10637 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10638 parseList[currentMove - 1]);
10641 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10648 char *tags = PGNTags(&gameInfo);
10649 EditTagsPopUp(tags);
10656 if (appData.noChessProgram || gameMode == AnalyzeMode)
10659 if (gameMode != AnalyzeFile) {
10660 if (!appData.icsEngineAnalyze) {
10662 if (gameMode != EditGame) return;
10664 ResurrectChessProgram();
10665 SendToProgram("analyze\n", &first);
10666 first.analyzing = TRUE;
10667 /*first.maybeThinking = TRUE;*/
10668 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10669 EngineOutputPopUp();
10671 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10676 StartAnalysisClock();
10677 GetTimeMark(&lastNodeCountTime);
10684 if (appData.noChessProgram || gameMode == AnalyzeFile)
10687 if (gameMode != AnalyzeMode) {
10689 if (gameMode != EditGame) return;
10690 ResurrectChessProgram();
10691 SendToProgram("analyze\n", &first);
10692 first.analyzing = TRUE;
10693 /*first.maybeThinking = TRUE;*/
10694 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10695 EngineOutputPopUp();
10697 gameMode = AnalyzeFile;
10702 StartAnalysisClock();
10703 GetTimeMark(&lastNodeCountTime);
10708 MachineWhiteEvent()
10711 char *bookHit = NULL;
10713 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10717 if (gameMode == PlayFromGameFile ||
10718 gameMode == TwoMachinesPlay ||
10719 gameMode == Training ||
10720 gameMode == AnalyzeMode ||
10721 gameMode == EndOfGame)
10724 if (gameMode == EditPosition)
10725 EditPositionDone(TRUE);
10727 if (!WhiteOnMove(currentMove)) {
10728 DisplayError(_("It is not White's turn"), 0);
10732 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10735 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10736 gameMode == AnalyzeFile)
10739 ResurrectChessProgram(); /* in case it isn't running */
10740 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10741 gameMode = MachinePlaysWhite;
10744 gameMode = MachinePlaysWhite;
10748 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10750 if (first.sendName) {
10751 sprintf(buf, "name %s\n", gameInfo.black);
10752 SendToProgram(buf, &first);
10754 if (first.sendTime) {
10755 if (first.useColors) {
10756 SendToProgram("black\n", &first); /*gnu kludge*/
10758 SendTimeRemaining(&first, TRUE);
10760 if (first.useColors) {
10761 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10763 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10764 SetMachineThinkingEnables();
10765 first.maybeThinking = TRUE;
10769 if (appData.autoFlipView && !flipView) {
10770 flipView = !flipView;
10771 DrawPosition(FALSE, NULL);
10772 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10775 if(bookHit) { // [HGM] book: simulate book reply
10776 static char bookMove[MSG_SIZ]; // a bit generous?
10778 programStats.nodes = programStats.depth = programStats.time =
10779 programStats.score = programStats.got_only_move = 0;
10780 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10782 strcpy(bookMove, "move ");
10783 strcat(bookMove, bookHit);
10784 HandleMachineMove(bookMove, &first);
10789 MachineBlackEvent()
10792 char *bookHit = NULL;
10794 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10798 if (gameMode == PlayFromGameFile ||
10799 gameMode == TwoMachinesPlay ||
10800 gameMode == Training ||
10801 gameMode == AnalyzeMode ||
10802 gameMode == EndOfGame)
10805 if (gameMode == EditPosition)
10806 EditPositionDone(TRUE);
10808 if (WhiteOnMove(currentMove)) {
10809 DisplayError(_("It is not Black's turn"), 0);
10813 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10816 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10817 gameMode == AnalyzeFile)
10820 ResurrectChessProgram(); /* in case it isn't running */
10821 gameMode = MachinePlaysBlack;
10825 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10827 if (first.sendName) {
10828 sprintf(buf, "name %s\n", gameInfo.white);
10829 SendToProgram(buf, &first);
10831 if (first.sendTime) {
10832 if (first.useColors) {
10833 SendToProgram("white\n", &first); /*gnu kludge*/
10835 SendTimeRemaining(&first, FALSE);
10837 if (first.useColors) {
10838 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10840 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10841 SetMachineThinkingEnables();
10842 first.maybeThinking = TRUE;
10845 if (appData.autoFlipView && flipView) {
10846 flipView = !flipView;
10847 DrawPosition(FALSE, NULL);
10848 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10850 if(bookHit) { // [HGM] book: simulate book reply
10851 static char bookMove[MSG_SIZ]; // a bit generous?
10853 programStats.nodes = programStats.depth = programStats.time =
10854 programStats.score = programStats.got_only_move = 0;
10855 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10857 strcpy(bookMove, "move ");
10858 strcat(bookMove, bookHit);
10859 HandleMachineMove(bookMove, &first);
10865 DisplayTwoMachinesTitle()
10868 if (appData.matchGames > 0) {
10869 if (first.twoMachinesColor[0] == 'w') {
10870 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10871 gameInfo.white, gameInfo.black,
10872 first.matchWins, second.matchWins,
10873 matchGame - 1 - (first.matchWins + second.matchWins));
10875 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10876 gameInfo.white, gameInfo.black,
10877 second.matchWins, first.matchWins,
10878 matchGame - 1 - (first.matchWins + second.matchWins));
10881 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10887 TwoMachinesEvent P((void))
10891 ChessProgramState *onmove;
10892 char *bookHit = NULL;
10894 if (appData.noChessProgram) return;
10896 switch (gameMode) {
10897 case TwoMachinesPlay:
10899 case MachinePlaysWhite:
10900 case MachinePlaysBlack:
10901 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10902 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10906 case BeginningOfGame:
10907 case PlayFromGameFile:
10910 if (gameMode != EditGame) return;
10913 EditPositionDone(TRUE);
10924 forwardMostMove = currentMove;
10925 ResurrectChessProgram(); /* in case first program isn't running */
10927 if (second.pr == NULL) {
10928 StartChessProgram(&second);
10929 if (second.protocolVersion == 1) {
10930 TwoMachinesEventIfReady();
10932 /* kludge: allow timeout for initial "feature" command */
10934 DisplayMessage("", _("Starting second chess program"));
10935 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10939 DisplayMessage("", "");
10940 InitChessProgram(&second, FALSE);
10941 SendToProgram("force\n", &second);
10942 if (startedFromSetupPosition) {
10943 SendBoard(&second, backwardMostMove);
10944 if (appData.debugMode) {
10945 fprintf(debugFP, "Two Machines\n");
10948 for (i = backwardMostMove; i < forwardMostMove; i++) {
10949 SendMoveToProgram(i, &second);
10952 gameMode = TwoMachinesPlay;
10956 DisplayTwoMachinesTitle();
10958 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10964 SendToProgram(first.computerString, &first);
10965 if (first.sendName) {
10966 sprintf(buf, "name %s\n", second.tidy);
10967 SendToProgram(buf, &first);
10969 SendToProgram(second.computerString, &second);
10970 if (second.sendName) {
10971 sprintf(buf, "name %s\n", first.tidy);
10972 SendToProgram(buf, &second);
10976 if (!first.sendTime || !second.sendTime) {
10977 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10978 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10980 if (onmove->sendTime) {
10981 if (onmove->useColors) {
10982 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10984 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10986 if (onmove->useColors) {
10987 SendToProgram(onmove->twoMachinesColor, onmove);
10989 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10990 // SendToProgram("go\n", onmove);
10991 onmove->maybeThinking = TRUE;
10992 SetMachineThinkingEnables();
10996 if(bookHit) { // [HGM] book: simulate book reply
10997 static char bookMove[MSG_SIZ]; // a bit generous?
10999 programStats.nodes = programStats.depth = programStats.time =
11000 programStats.score = programStats.got_only_move = 0;
11001 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11003 strcpy(bookMove, "move ");
11004 strcat(bookMove, bookHit);
11005 savedMessage = bookMove; // args for deferred call
11006 savedState = onmove;
11007 ScheduleDelayedEvent(DeferredBookMove, 1);
11014 if (gameMode == Training) {
11015 SetTrainingModeOff();
11016 gameMode = PlayFromGameFile;
11017 DisplayMessage("", _("Training mode off"));
11019 gameMode = Training;
11020 animateTraining = appData.animate;
11022 /* make sure we are not already at the end of the game */
11023 if (currentMove < forwardMostMove) {
11024 SetTrainingModeOn();
11025 DisplayMessage("", _("Training mode on"));
11027 gameMode = PlayFromGameFile;
11028 DisplayError(_("Already at end of game"), 0);
11037 if (!appData.icsActive) return;
11038 switch (gameMode) {
11039 case IcsPlayingWhite:
11040 case IcsPlayingBlack:
11043 case BeginningOfGame:
11051 EditPositionDone(TRUE);
11064 gameMode = IcsIdle;
11075 switch (gameMode) {
11077 SetTrainingModeOff();
11079 case MachinePlaysWhite:
11080 case MachinePlaysBlack:
11081 case BeginningOfGame:
11082 SendToProgram("force\n", &first);
11083 SetUserThinkingEnables();
11085 case PlayFromGameFile:
11086 (void) StopLoadGameTimer();
11087 if (gameFileFP != NULL) {
11092 EditPositionDone(TRUE);
11097 SendToProgram("force\n", &first);
11099 case TwoMachinesPlay:
11100 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11101 ResurrectChessProgram();
11102 SetUserThinkingEnables();
11105 ResurrectChessProgram();
11107 case IcsPlayingBlack:
11108 case IcsPlayingWhite:
11109 DisplayError(_("Warning: You are still playing a game"), 0);
11112 DisplayError(_("Warning: You are still observing a game"), 0);
11115 DisplayError(_("Warning: You are still examining a game"), 0);
11126 first.offeredDraw = second.offeredDraw = 0;
11128 if (gameMode == PlayFromGameFile) {
11129 whiteTimeRemaining = timeRemaining[0][currentMove];
11130 blackTimeRemaining = timeRemaining[1][currentMove];
11134 if (gameMode == MachinePlaysWhite ||
11135 gameMode == MachinePlaysBlack ||
11136 gameMode == TwoMachinesPlay ||
11137 gameMode == EndOfGame) {
11138 i = forwardMostMove;
11139 while (i > currentMove) {
11140 SendToProgram("undo\n", &first);
11143 whiteTimeRemaining = timeRemaining[0][currentMove];
11144 blackTimeRemaining = timeRemaining[1][currentMove];
11145 DisplayBothClocks();
11146 if (whiteFlag || blackFlag) {
11147 whiteFlag = blackFlag = 0;
11152 gameMode = EditGame;
11159 EditPositionEvent()
11161 if (gameMode == EditPosition) {
11167 if (gameMode != EditGame) return;
11169 gameMode = EditPosition;
11172 if (currentMove > 0)
11173 CopyBoard(boards[0], boards[currentMove]);
11175 blackPlaysFirst = !WhiteOnMove(currentMove);
11177 currentMove = forwardMostMove = backwardMostMove = 0;
11178 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11185 /* [DM] icsEngineAnalyze - possible call from other functions */
11186 if (appData.icsEngineAnalyze) {
11187 appData.icsEngineAnalyze = FALSE;
11189 DisplayMessage("",_("Close ICS engine analyze..."));
11191 if (first.analysisSupport && first.analyzing) {
11192 SendToProgram("exit\n", &first);
11193 first.analyzing = FALSE;
11195 thinkOutput[0] = NULLCHAR;
11199 EditPositionDone(Boolean fakeRights)
11201 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11203 startedFromSetupPosition = TRUE;
11204 InitChessProgram(&first, FALSE);
11206 { /* don't do this if we just pasted FEN */
11207 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11208 if(boards[0][0][BOARD_WIDTH>>1] == king)
11210 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11211 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11214 castlingRights[0][2] = -1;
11215 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11217 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11218 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11221 castlingRights[0][5] = -1;
11223 SendToProgram("force\n", &first);
11224 if (blackPlaysFirst) {
11225 strcpy(moveList[0], "");
11226 strcpy(parseList[0], "");
11227 currentMove = forwardMostMove = backwardMostMove = 1;
11228 CopyBoard(boards[1], boards[0]);
11229 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11231 epStatus[1] = epStatus[0];
11232 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11235 currentMove = forwardMostMove = backwardMostMove = 0;
11237 SendBoard(&first, forwardMostMove);
11238 if (appData.debugMode) {
11239 fprintf(debugFP, "EditPosDone\n");
11242 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11243 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11244 gameMode = EditGame;
11246 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11247 ClearHighlights(); /* [AS] */
11250 /* Pause for `ms' milliseconds */
11251 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11261 } while (SubtractTimeMarks(&m2, &m1) < ms);
11264 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11266 SendMultiLineToICS(buf)
11269 char temp[MSG_SIZ+1], *p;
11276 strncpy(temp, buf, len);
11281 if (*p == '\n' || *p == '\r')
11286 strcat(temp, "\n");
11288 SendToPlayer(temp, strlen(temp));
11292 SetWhiteToPlayEvent()
11294 if (gameMode == EditPosition) {
11295 blackPlaysFirst = FALSE;
11296 DisplayBothClocks(); /* works because currentMove is 0 */
11297 } else if (gameMode == IcsExamining) {
11298 SendToICS(ics_prefix);
11299 SendToICS("tomove white\n");
11304 SetBlackToPlayEvent()
11306 if (gameMode == EditPosition) {
11307 blackPlaysFirst = TRUE;
11308 currentMove = 1; /* kludge */
11309 DisplayBothClocks();
11311 } else if (gameMode == IcsExamining) {
11312 SendToICS(ics_prefix);
11313 SendToICS("tomove black\n");
11318 EditPositionMenuEvent(selection, x, y)
11319 ChessSquare selection;
11323 ChessSquare piece = boards[0][y][x];
11325 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11327 switch (selection) {
11329 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11330 SendToICS(ics_prefix);
11331 SendToICS("bsetup clear\n");
11332 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11333 SendToICS(ics_prefix);
11334 SendToICS("clearboard\n");
11336 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11337 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11338 for (y = 0; y < BOARD_HEIGHT; y++) {
11339 if (gameMode == IcsExamining) {
11340 if (boards[currentMove][y][x] != EmptySquare) {
11341 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11346 boards[0][y][x] = p;
11351 if (gameMode == EditPosition) {
11352 DrawPosition(FALSE, boards[0]);
11357 SetWhiteToPlayEvent();
11361 SetBlackToPlayEvent();
11365 if (gameMode == IcsExamining) {
11366 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11369 boards[0][y][x] = EmptySquare;
11370 DrawPosition(FALSE, boards[0]);
11375 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11376 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11377 selection = (ChessSquare) (PROMOTED piece);
11378 } else if(piece == EmptySquare) selection = WhiteSilver;
11379 else selection = (ChessSquare)((int)piece - 1);
11383 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11384 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11385 selection = (ChessSquare) (DEMOTED piece);
11386 } else if(piece == EmptySquare) selection = BlackSilver;
11387 else selection = (ChessSquare)((int)piece + 1);
11392 if(gameInfo.variant == VariantShatranj ||
11393 gameInfo.variant == VariantXiangqi ||
11394 gameInfo.variant == VariantCourier )
11395 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11400 if(gameInfo.variant == VariantXiangqi)
11401 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11402 if(gameInfo.variant == VariantKnightmate)
11403 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11406 if (gameMode == IcsExamining) {
11407 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11408 PieceToChar(selection), AAA + x, ONE + y);
11411 boards[0][y][x] = selection;
11412 DrawPosition(FALSE, boards[0]);
11420 DropMenuEvent(selection, x, y)
11421 ChessSquare selection;
11424 ChessMove moveType;
11426 switch (gameMode) {
11427 case IcsPlayingWhite:
11428 case MachinePlaysBlack:
11429 if (!WhiteOnMove(currentMove)) {
11430 DisplayMoveError(_("It is Black's turn"));
11433 moveType = WhiteDrop;
11435 case IcsPlayingBlack:
11436 case MachinePlaysWhite:
11437 if (WhiteOnMove(currentMove)) {
11438 DisplayMoveError(_("It is White's turn"));
11441 moveType = BlackDrop;
11444 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11450 if (moveType == BlackDrop && selection < BlackPawn) {
11451 selection = (ChessSquare) ((int) selection
11452 + (int) BlackPawn - (int) WhitePawn);
11454 if (boards[currentMove][y][x] != EmptySquare) {
11455 DisplayMoveError(_("That square is occupied"));
11459 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11465 /* Accept a pending offer of any kind from opponent */
11467 if (appData.icsActive) {
11468 SendToICS(ics_prefix);
11469 SendToICS("accept\n");
11470 } else if (cmailMsgLoaded) {
11471 if (currentMove == cmailOldMove &&
11472 commentList[cmailOldMove] != NULL &&
11473 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11474 "Black offers a draw" : "White offers a draw")) {
11476 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11477 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11479 DisplayError(_("There is no pending offer on this move"), 0);
11480 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11483 /* Not used for offers from chess program */
11490 /* Decline a pending offer of any kind from opponent */
11492 if (appData.icsActive) {
11493 SendToICS(ics_prefix);
11494 SendToICS("decline\n");
11495 } else if (cmailMsgLoaded) {
11496 if (currentMove == cmailOldMove &&
11497 commentList[cmailOldMove] != NULL &&
11498 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11499 "Black offers a draw" : "White offers a draw")) {
11501 AppendComment(cmailOldMove, "Draw declined");
11502 DisplayComment(cmailOldMove - 1, "Draw declined");
11505 DisplayError(_("There is no pending offer on this move"), 0);
11508 /* Not used for offers from chess program */
11515 /* Issue ICS rematch command */
11516 if (appData.icsActive) {
11517 SendToICS(ics_prefix);
11518 SendToICS("rematch\n");
11525 /* Call your opponent's flag (claim a win on time) */
11526 if (appData.icsActive) {
11527 SendToICS(ics_prefix);
11528 SendToICS("flag\n");
11530 switch (gameMode) {
11533 case MachinePlaysWhite:
11536 GameEnds(GameIsDrawn, "Both players ran out of time",
11539 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11541 DisplayError(_("Your opponent is not out of time"), 0);
11544 case MachinePlaysBlack:
11547 GameEnds(GameIsDrawn, "Both players ran out of time",
11550 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11552 DisplayError(_("Your opponent is not out of time"), 0);
11562 /* Offer draw or accept pending draw offer from opponent */
11564 if (appData.icsActive) {
11565 /* Note: tournament rules require draw offers to be
11566 made after you make your move but before you punch
11567 your clock. Currently ICS doesn't let you do that;
11568 instead, you immediately punch your clock after making
11569 a move, but you can offer a draw at any time. */
11571 SendToICS(ics_prefix);
11572 SendToICS("draw\n");
11573 } else if (cmailMsgLoaded) {
11574 if (currentMove == cmailOldMove &&
11575 commentList[cmailOldMove] != NULL &&
11576 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11577 "Black offers a draw" : "White offers a draw")) {
11578 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11579 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11580 } else if (currentMove == cmailOldMove + 1) {
11581 char *offer = WhiteOnMove(cmailOldMove) ?
11582 "White offers a draw" : "Black offers a draw";
11583 AppendComment(currentMove, offer);
11584 DisplayComment(currentMove - 1, offer);
11585 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11587 DisplayError(_("You must make your move before offering a draw"), 0);
11588 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11590 } else if (first.offeredDraw) {
11591 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11593 if (first.sendDrawOffers) {
11594 SendToProgram("draw\n", &first);
11595 userOfferedDraw = TRUE;
11603 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11605 if (appData.icsActive) {
11606 SendToICS(ics_prefix);
11607 SendToICS("adjourn\n");
11609 /* Currently GNU Chess doesn't offer or accept Adjourns */
11617 /* Offer Abort or accept pending Abort offer from opponent */
11619 if (appData.icsActive) {
11620 SendToICS(ics_prefix);
11621 SendToICS("abort\n");
11623 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11630 /* Resign. You can do this even if it's not your turn. */
11632 if (appData.icsActive) {
11633 SendToICS(ics_prefix);
11634 SendToICS("resign\n");
11636 switch (gameMode) {
11637 case MachinePlaysWhite:
11638 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11640 case MachinePlaysBlack:
11641 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11644 if (cmailMsgLoaded) {
11646 if (WhiteOnMove(cmailOldMove)) {
11647 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11649 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11651 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11662 StopObservingEvent()
11664 /* Stop observing current games */
11665 SendToICS(ics_prefix);
11666 SendToICS("unobserve\n");
11670 StopExaminingEvent()
11672 /* Stop observing current game */
11673 SendToICS(ics_prefix);
11674 SendToICS("unexamine\n");
11678 ForwardInner(target)
11683 if (appData.debugMode)
11684 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11685 target, currentMove, forwardMostMove);
11687 if (gameMode == EditPosition)
11690 if (gameMode == PlayFromGameFile && !pausing)
11693 if (gameMode == IcsExamining && pausing)
11694 limit = pauseExamForwardMostMove;
11696 limit = forwardMostMove;
11698 if (target > limit) target = limit;
11700 if (target > 0 && moveList[target - 1][0]) {
11701 int fromX, fromY, toX, toY;
11702 toX = moveList[target - 1][2] - AAA;
11703 toY = moveList[target - 1][3] - ONE;
11704 if (moveList[target - 1][1] == '@') {
11705 if (appData.highlightLastMove) {
11706 SetHighlights(-1, -1, toX, toY);
11709 fromX = moveList[target - 1][0] - AAA;
11710 fromY = moveList[target - 1][1] - ONE;
11711 if (target == currentMove + 1) {
11712 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11714 if (appData.highlightLastMove) {
11715 SetHighlights(fromX, fromY, toX, toY);
11719 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11720 gameMode == Training || gameMode == PlayFromGameFile ||
11721 gameMode == AnalyzeFile) {
11722 while (currentMove < target) {
11723 SendMoveToProgram(currentMove++, &first);
11726 currentMove = target;
11729 if (gameMode == EditGame || gameMode == EndOfGame) {
11730 whiteTimeRemaining = timeRemaining[0][currentMove];
11731 blackTimeRemaining = timeRemaining[1][currentMove];
11733 DisplayBothClocks();
11734 DisplayMove(currentMove - 1);
11735 DrawPosition(FALSE, boards[currentMove]);
11736 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11737 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11738 DisplayComment(currentMove - 1, commentList[currentMove]);
11746 if (gameMode == IcsExamining && !pausing) {
11747 SendToICS(ics_prefix);
11748 SendToICS("forward\n");
11750 ForwardInner(currentMove + 1);
11757 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11758 /* to optimze, we temporarily turn off analysis mode while we feed
11759 * the remaining moves to the engine. Otherwise we get analysis output
11762 if (first.analysisSupport) {
11763 SendToProgram("exit\nforce\n", &first);
11764 first.analyzing = FALSE;
11768 if (gameMode == IcsExamining && !pausing) {
11769 SendToICS(ics_prefix);
11770 SendToICS("forward 999999\n");
11772 ForwardInner(forwardMostMove);
11775 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11776 /* we have fed all the moves, so reactivate analysis mode */
11777 SendToProgram("analyze\n", &first);
11778 first.analyzing = TRUE;
11779 /*first.maybeThinking = TRUE;*/
11780 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11785 BackwardInner(target)
11788 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11790 if (appData.debugMode)
11791 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11792 target, currentMove, forwardMostMove);
11794 if (gameMode == EditPosition) return;
11795 if (currentMove <= backwardMostMove) {
11797 DrawPosition(full_redraw, boards[currentMove]);
11800 if (gameMode == PlayFromGameFile && !pausing)
11803 if (moveList[target][0]) {
11804 int fromX, fromY, toX, toY;
11805 toX = moveList[target][2] - AAA;
11806 toY = moveList[target][3] - ONE;
11807 if (moveList[target][1] == '@') {
11808 if (appData.highlightLastMove) {
11809 SetHighlights(-1, -1, toX, toY);
11812 fromX = moveList[target][0] - AAA;
11813 fromY = moveList[target][1] - ONE;
11814 if (target == currentMove - 1) {
11815 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11817 if (appData.highlightLastMove) {
11818 SetHighlights(fromX, fromY, toX, toY);
11822 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11823 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11824 while (currentMove > target) {
11825 SendToProgram("undo\n", &first);
11829 currentMove = target;
11832 if (gameMode == EditGame || gameMode == EndOfGame) {
11833 whiteTimeRemaining = timeRemaining[0][currentMove];
11834 blackTimeRemaining = timeRemaining[1][currentMove];
11836 DisplayBothClocks();
11837 DisplayMove(currentMove - 1);
11838 DrawPosition(full_redraw, boards[currentMove]);
11839 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11840 // [HGM] PV info: routine tests if comment empty
11841 DisplayComment(currentMove - 1, commentList[currentMove]);
11847 if (gameMode == IcsExamining && !pausing) {
11848 SendToICS(ics_prefix);
11849 SendToICS("backward\n");
11851 BackwardInner(currentMove - 1);
11858 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11859 /* to optimize, we temporarily turn off analysis mode while we undo
11860 * all the moves. Otherwise we get analysis output after each undo.
11862 if (first.analysisSupport) {
11863 SendToProgram("exit\nforce\n", &first);
11864 first.analyzing = FALSE;
11868 if (gameMode == IcsExamining && !pausing) {
11869 SendToICS(ics_prefix);
11870 SendToICS("backward 999999\n");
11872 BackwardInner(backwardMostMove);
11875 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11876 /* we have fed all the moves, so reactivate analysis mode */
11877 SendToProgram("analyze\n", &first);
11878 first.analyzing = TRUE;
11879 /*first.maybeThinking = TRUE;*/
11880 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11887 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11888 if (to >= forwardMostMove) to = forwardMostMove;
11889 if (to <= backwardMostMove) to = backwardMostMove;
11890 if (to < currentMove) {
11900 if (gameMode != IcsExamining) {
11901 DisplayError(_("You are not examining a game"), 0);
11905 DisplayError(_("You can't revert while pausing"), 0);
11908 SendToICS(ics_prefix);
11909 SendToICS("revert\n");
11915 switch (gameMode) {
11916 case MachinePlaysWhite:
11917 case MachinePlaysBlack:
11918 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11919 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11922 if (forwardMostMove < 2) return;
11923 currentMove = forwardMostMove = forwardMostMove - 2;
11924 whiteTimeRemaining = timeRemaining[0][currentMove];
11925 blackTimeRemaining = timeRemaining[1][currentMove];
11926 DisplayBothClocks();
11927 DisplayMove(currentMove - 1);
11928 ClearHighlights();/*!! could figure this out*/
11929 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11930 SendToProgram("remove\n", &first);
11931 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11934 case BeginningOfGame:
11938 case IcsPlayingWhite:
11939 case IcsPlayingBlack:
11940 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11941 SendToICS(ics_prefix);
11942 SendToICS("takeback 2\n");
11944 SendToICS(ics_prefix);
11945 SendToICS("takeback 1\n");
11954 ChessProgramState *cps;
11956 switch (gameMode) {
11957 case MachinePlaysWhite:
11958 if (!WhiteOnMove(forwardMostMove)) {
11959 DisplayError(_("It is your turn"), 0);
11964 case MachinePlaysBlack:
11965 if (WhiteOnMove(forwardMostMove)) {
11966 DisplayError(_("It is your turn"), 0);
11971 case TwoMachinesPlay:
11972 if (WhiteOnMove(forwardMostMove) ==
11973 (first.twoMachinesColor[0] == 'w')) {
11979 case BeginningOfGame:
11983 SendToProgram("?\n", cps);
11987 TruncateGameEvent()
11990 if (gameMode != EditGame) return;
11997 if (forwardMostMove > currentMove) {
11998 if (gameInfo.resultDetails != NULL) {
11999 free(gameInfo.resultDetails);
12000 gameInfo.resultDetails = NULL;
12001 gameInfo.result = GameUnfinished;
12003 forwardMostMove = currentMove;
12004 HistorySet(parseList, backwardMostMove, forwardMostMove,
12012 if (appData.noChessProgram) return;
12013 switch (gameMode) {
12014 case MachinePlaysWhite:
12015 if (WhiteOnMove(forwardMostMove)) {
12016 DisplayError(_("Wait until your turn"), 0);
12020 case BeginningOfGame:
12021 case MachinePlaysBlack:
12022 if (!WhiteOnMove(forwardMostMove)) {
12023 DisplayError(_("Wait until your turn"), 0);
12028 DisplayError(_("No hint available"), 0);
12031 SendToProgram("hint\n", &first);
12032 hintRequested = TRUE;
12038 if (appData.noChessProgram) return;
12039 switch (gameMode) {
12040 case MachinePlaysWhite:
12041 if (WhiteOnMove(forwardMostMove)) {
12042 DisplayError(_("Wait until your turn"), 0);
12046 case BeginningOfGame:
12047 case MachinePlaysBlack:
12048 if (!WhiteOnMove(forwardMostMove)) {
12049 DisplayError(_("Wait until your turn"), 0);
12054 EditPositionDone(TRUE);
12056 case TwoMachinesPlay:
12061 SendToProgram("bk\n", &first);
12062 bookOutput[0] = NULLCHAR;
12063 bookRequested = TRUE;
12069 char *tags = PGNTags(&gameInfo);
12070 TagsPopUp(tags, CmailMsg());
12074 /* end button procedures */
12077 PrintPosition(fp, move)
12083 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12084 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12085 char c = PieceToChar(boards[move][i][j]);
12086 fputc(c == 'x' ? '.' : c, fp);
12087 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12090 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12091 fprintf(fp, "white to play\n");
12093 fprintf(fp, "black to play\n");
12100 if (gameInfo.white != NULL) {
12101 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12107 /* Find last component of program's own name, using some heuristics */
12109 TidyProgramName(prog, host, buf)
12110 char *prog, *host, buf[MSG_SIZ];
12113 int local = (strcmp(host, "localhost") == 0);
12114 while (!local && (p = strchr(prog, ';')) != NULL) {
12116 while (*p == ' ') p++;
12119 if (*prog == '"' || *prog == '\'') {
12120 q = strchr(prog + 1, *prog);
12122 q = strchr(prog, ' ');
12124 if (q == NULL) q = prog + strlen(prog);
12126 while (p >= prog && *p != '/' && *p != '\\') p--;
12128 if(p == prog && *p == '"') p++;
12129 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12130 memcpy(buf, p, q - p);
12131 buf[q - p] = NULLCHAR;
12139 TimeControlTagValue()
12142 if (!appData.clockMode) {
12144 } else if (movesPerSession > 0) {
12145 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12146 } else if (timeIncrement == 0) {
12147 sprintf(buf, "%ld", timeControl/1000);
12149 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12151 return StrSave(buf);
12157 /* This routine is used only for certain modes */
12158 VariantClass v = gameInfo.variant;
12159 ClearGameInfo(&gameInfo);
12160 gameInfo.variant = v;
12162 switch (gameMode) {
12163 case MachinePlaysWhite:
12164 gameInfo.event = StrSave( appData.pgnEventHeader );
12165 gameInfo.site = StrSave(HostName());
12166 gameInfo.date = PGNDate();
12167 gameInfo.round = StrSave("-");
12168 gameInfo.white = StrSave(first.tidy);
12169 gameInfo.black = StrSave(UserName());
12170 gameInfo.timeControl = TimeControlTagValue();
12173 case MachinePlaysBlack:
12174 gameInfo.event = StrSave( appData.pgnEventHeader );
12175 gameInfo.site = StrSave(HostName());
12176 gameInfo.date = PGNDate();
12177 gameInfo.round = StrSave("-");
12178 gameInfo.white = StrSave(UserName());
12179 gameInfo.black = StrSave(first.tidy);
12180 gameInfo.timeControl = TimeControlTagValue();
12183 case TwoMachinesPlay:
12184 gameInfo.event = StrSave( appData.pgnEventHeader );
12185 gameInfo.site = StrSave(HostName());
12186 gameInfo.date = PGNDate();
12187 if (matchGame > 0) {
12189 sprintf(buf, "%d", matchGame);
12190 gameInfo.round = StrSave(buf);
12192 gameInfo.round = StrSave("-");
12194 if (first.twoMachinesColor[0] == 'w') {
12195 gameInfo.white = StrSave(first.tidy);
12196 gameInfo.black = StrSave(second.tidy);
12198 gameInfo.white = StrSave(second.tidy);
12199 gameInfo.black = StrSave(first.tidy);
12201 gameInfo.timeControl = TimeControlTagValue();
12205 gameInfo.event = StrSave("Edited game");
12206 gameInfo.site = StrSave(HostName());
12207 gameInfo.date = PGNDate();
12208 gameInfo.round = StrSave("-");
12209 gameInfo.white = StrSave("-");
12210 gameInfo.black = StrSave("-");
12214 gameInfo.event = StrSave("Edited position");
12215 gameInfo.site = StrSave(HostName());
12216 gameInfo.date = PGNDate();
12217 gameInfo.round = StrSave("-");
12218 gameInfo.white = StrSave("-");
12219 gameInfo.black = StrSave("-");
12222 case IcsPlayingWhite:
12223 case IcsPlayingBlack:
12228 case PlayFromGameFile:
12229 gameInfo.event = StrSave("Game from non-PGN file");
12230 gameInfo.site = StrSave(HostName());
12231 gameInfo.date = PGNDate();
12232 gameInfo.round = StrSave("-");
12233 gameInfo.white = StrSave("?");
12234 gameInfo.black = StrSave("?");
12243 ReplaceComment(index, text)
12249 while (*text == '\n') text++;
12250 len = strlen(text);
12251 while (len > 0 && text[len - 1] == '\n') len--;
12253 if (commentList[index] != NULL)
12254 free(commentList[index]);
12257 commentList[index] = NULL;
12260 commentList[index] = (char *) malloc(len + 2);
12261 strncpy(commentList[index], text, len);
12262 commentList[index][len] = '\n';
12263 commentList[index][len + 1] = NULLCHAR;
12276 if (ch == '\r') continue;
12278 } while (ch != '\0');
12282 AppendComment(index, text)
12289 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12292 while (*text == '\n') text++;
12293 len = strlen(text);
12294 while (len > 0 && text[len - 1] == '\n') len--;
12296 if (len == 0) return;
12298 if (commentList[index] != NULL) {
12299 old = commentList[index];
12300 oldlen = strlen(old);
12301 commentList[index] = (char *) malloc(oldlen + len + 2);
12302 strcpy(commentList[index], old);
12304 strncpy(&commentList[index][oldlen], text, len);
12305 commentList[index][oldlen + len] = '\n';
12306 commentList[index][oldlen + len + 1] = NULLCHAR;
12308 commentList[index] = (char *) malloc(len + 2);
12309 strncpy(commentList[index], text, len);
12310 commentList[index][len] = '\n';
12311 commentList[index][len + 1] = NULLCHAR;
12315 static char * FindStr( char * text, char * sub_text )
12317 char * result = strstr( text, sub_text );
12319 if( result != NULL ) {
12320 result += strlen( sub_text );
12326 /* [AS] Try to extract PV info from PGN comment */
12327 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12328 char *GetInfoFromComment( int index, char * text )
12332 if( text != NULL && index > 0 ) {
12335 int time = -1, sec = 0, deci;
12336 char * s_eval = FindStr( text, "[%eval " );
12337 char * s_emt = FindStr( text, "[%emt " );
12339 if( s_eval != NULL || s_emt != NULL ) {
12343 if( s_eval != NULL ) {
12344 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12348 if( delim != ']' ) {
12353 if( s_emt != NULL ) {
12357 /* We expect something like: [+|-]nnn.nn/dd */
12360 sep = strchr( text, '/' );
12361 if( sep == NULL || sep < (text+4) ) {
12365 time = -1; sec = -1; deci = -1;
12366 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12367 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12368 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12369 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12373 if( score_lo < 0 || score_lo >= 100 ) {
12377 if(sec >= 0) time = 600*time + 10*sec; else
12378 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12380 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12382 /* [HGM] PV time: now locate end of PV info */
12383 while( *++sep >= '0' && *sep <= '9'); // strip depth
12385 while( *++sep >= '0' && *sep <= '9'); // strip time
12387 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12389 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12390 while(*sep == ' ') sep++;
12401 pvInfoList[index-1].depth = depth;
12402 pvInfoList[index-1].score = score;
12403 pvInfoList[index-1].time = 10*time; // centi-sec
12409 SendToProgram(message, cps)
12411 ChessProgramState *cps;
12413 int count, outCount, error;
12416 if (cps->pr == NULL) return;
12419 if (appData.debugMode) {
12422 fprintf(debugFP, "%ld >%-6s: %s",
12423 SubtractTimeMarks(&now, &programStartTime),
12424 cps->which, message);
12427 count = strlen(message);
12428 outCount = OutputToProcess(cps->pr, message, count, &error);
12429 if (outCount < count && !exiting
12430 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12431 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12432 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12433 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12434 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12435 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12437 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12439 gameInfo.resultDetails = StrSave(buf);
12441 DisplayFatalError(buf, error, 1);
12446 ReceiveFromProgram(isr, closure, message, count, error)
12447 InputSourceRef isr;
12455 ChessProgramState *cps = (ChessProgramState *)closure;
12457 if (isr != cps->isr) return; /* Killed intentionally */
12461 _("Error: %s chess program (%s) exited unexpectedly"),
12462 cps->which, cps->program);
12463 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12464 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12465 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12466 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12468 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12470 gameInfo.resultDetails = StrSave(buf);
12472 RemoveInputSource(cps->isr);
12473 DisplayFatalError(buf, 0, 1);
12476 _("Error reading from %s chess program (%s)"),
12477 cps->which, cps->program);
12478 RemoveInputSource(cps->isr);
12480 /* [AS] Program is misbehaving badly... kill it */
12481 if( count == -2 ) {
12482 DestroyChildProcess( cps->pr, 9 );
12486 DisplayFatalError(buf, error, 1);
12491 if ((end_str = strchr(message, '\r')) != NULL)
12492 *end_str = NULLCHAR;
12493 if ((end_str = strchr(message, '\n')) != NULL)
12494 *end_str = NULLCHAR;
12496 if (appData.debugMode) {
12497 TimeMark now; int print = 1;
12498 char *quote = ""; char c; int i;
12500 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12501 char start = message[0];
12502 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12503 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12504 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12505 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12506 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12507 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12508 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12509 sscanf(message, "pong %c", &c)!=1 && start != '#')
12510 { quote = "# "; print = (appData.engineComments == 2); }
12511 message[0] = start; // restore original message
12515 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12516 SubtractTimeMarks(&now, &programStartTime), cps->which,
12522 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12523 if (appData.icsEngineAnalyze) {
12524 if (strstr(message, "whisper") != NULL ||
12525 strstr(message, "kibitz") != NULL ||
12526 strstr(message, "tellics") != NULL) return;
12529 HandleMachineMove(message, cps);
12534 SendTimeControl(cps, mps, tc, inc, sd, st)
12535 ChessProgramState *cps;
12536 int mps, inc, sd, st;
12542 if( timeControl_2 > 0 ) {
12543 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12544 tc = timeControl_2;
12547 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12548 inc /= cps->timeOdds;
12549 st /= cps->timeOdds;
12551 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12554 /* Set exact time per move, normally using st command */
12555 if (cps->stKludge) {
12556 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12558 if (seconds == 0) {
12559 sprintf(buf, "level 1 %d\n", st/60);
12561 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12564 sprintf(buf, "st %d\n", st);
12567 /* Set conventional or incremental time control, using level command */
12568 if (seconds == 0) {
12569 /* Note old gnuchess bug -- minutes:seconds used to not work.
12570 Fixed in later versions, but still avoid :seconds
12571 when seconds is 0. */
12572 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12574 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12575 seconds, inc/1000);
12578 SendToProgram(buf, cps);
12580 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12581 /* Orthogonally, limit search to given depth */
12583 if (cps->sdKludge) {
12584 sprintf(buf, "depth\n%d\n", sd);
12586 sprintf(buf, "sd %d\n", sd);
12588 SendToProgram(buf, cps);
12591 if(cps->nps > 0) { /* [HGM] nps */
12592 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12594 sprintf(buf, "nps %d\n", cps->nps);
12595 SendToProgram(buf, cps);
12600 ChessProgramState *WhitePlayer()
12601 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12603 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12604 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12610 SendTimeRemaining(cps, machineWhite)
12611 ChessProgramState *cps;
12612 int /*boolean*/ machineWhite;
12614 char message[MSG_SIZ];
12617 /* Note: this routine must be called when the clocks are stopped
12618 or when they have *just* been set or switched; otherwise
12619 it will be off by the time since the current tick started.
12621 if (machineWhite) {
12622 time = whiteTimeRemaining / 10;
12623 otime = blackTimeRemaining / 10;
12625 time = blackTimeRemaining / 10;
12626 otime = whiteTimeRemaining / 10;
12628 /* [HGM] translate opponent's time by time-odds factor */
12629 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12630 if (appData.debugMode) {
12631 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12634 if (time <= 0) time = 1;
12635 if (otime <= 0) otime = 1;
12637 sprintf(message, "time %ld\n", time);
12638 SendToProgram(message, cps);
12640 sprintf(message, "otim %ld\n", otime);
12641 SendToProgram(message, cps);
12645 BoolFeature(p, name, loc, cps)
12649 ChessProgramState *cps;
12652 int len = strlen(name);
12654 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12656 sscanf(*p, "%d", &val);
12658 while (**p && **p != ' ') (*p)++;
12659 sprintf(buf, "accepted %s\n", name);
12660 SendToProgram(buf, cps);
12667 IntFeature(p, name, loc, cps)
12671 ChessProgramState *cps;
12674 int len = strlen(name);
12675 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12677 sscanf(*p, "%d", loc);
12678 while (**p && **p != ' ') (*p)++;
12679 sprintf(buf, "accepted %s\n", name);
12680 SendToProgram(buf, cps);
12687 StringFeature(p, name, loc, cps)
12691 ChessProgramState *cps;
12694 int len = strlen(name);
12695 if (strncmp((*p), name, len) == 0
12696 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12698 sscanf(*p, "%[^\"]", loc);
12699 while (**p && **p != '\"') (*p)++;
12700 if (**p == '\"') (*p)++;
12701 sprintf(buf, "accepted %s\n", name);
12702 SendToProgram(buf, cps);
12709 ParseOption(Option *opt, ChessProgramState *cps)
12710 // [HGM] options: process the string that defines an engine option, and determine
12711 // name, type, default value, and allowed value range
12713 char *p, *q, buf[MSG_SIZ];
12714 int n, min = (-1)<<31, max = 1<<31, def;
12716 if(p = strstr(opt->name, " -spin ")) {
12717 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12718 if(max < min) max = min; // enforce consistency
12719 if(def < min) def = min;
12720 if(def > max) def = max;
12725 } else if((p = strstr(opt->name, " -slider "))) {
12726 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12727 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12728 if(max < min) max = min; // enforce consistency
12729 if(def < min) def = min;
12730 if(def > max) def = max;
12734 opt->type = Spin; // Slider;
12735 } else if((p = strstr(opt->name, " -string "))) {
12736 opt->textValue = p+9;
12737 opt->type = TextBox;
12738 } else if((p = strstr(opt->name, " -file "))) {
12739 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12740 opt->textValue = p+7;
12741 opt->type = TextBox; // FileName;
12742 } else if((p = strstr(opt->name, " -path "))) {
12743 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12744 opt->textValue = p+7;
12745 opt->type = TextBox; // PathName;
12746 } else if(p = strstr(opt->name, " -check ")) {
12747 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12748 opt->value = (def != 0);
12749 opt->type = CheckBox;
12750 } else if(p = strstr(opt->name, " -combo ")) {
12751 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12752 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12753 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12754 opt->value = n = 0;
12755 while(q = StrStr(q, " /// ")) {
12756 n++; *q = 0; // count choices, and null-terminate each of them
12758 if(*q == '*') { // remember default, which is marked with * prefix
12762 cps->comboList[cps->comboCnt++] = q;
12764 cps->comboList[cps->comboCnt++] = NULL;
12766 opt->type = ComboBox;
12767 } else if(p = strstr(opt->name, " -button")) {
12768 opt->type = Button;
12769 } else if(p = strstr(opt->name, " -save")) {
12770 opt->type = SaveButton;
12771 } else return FALSE;
12772 *p = 0; // terminate option name
12773 // now look if the command-line options define a setting for this engine option.
12774 if(cps->optionSettings && cps->optionSettings[0])
12775 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12776 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12777 sprintf(buf, "option %s", p);
12778 if(p = strstr(buf, ",")) *p = 0;
12780 SendToProgram(buf, cps);
12786 FeatureDone(cps, val)
12787 ChessProgramState* cps;
12790 DelayedEventCallback cb = GetDelayedEvent();
12791 if ((cb == InitBackEnd3 && cps == &first) ||
12792 (cb == TwoMachinesEventIfReady && cps == &second)) {
12793 CancelDelayedEvent();
12794 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12796 cps->initDone = val;
12799 /* Parse feature command from engine */
12801 ParseFeatures(args, cps)
12803 ChessProgramState *cps;
12811 while (*p == ' ') p++;
12812 if (*p == NULLCHAR) return;
12814 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12815 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12816 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12817 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12818 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12819 if (BoolFeature(&p, "reuse", &val, cps)) {
12820 /* Engine can disable reuse, but can't enable it if user said no */
12821 if (!val) cps->reuse = FALSE;
12824 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12825 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12826 if (gameMode == TwoMachinesPlay) {
12827 DisplayTwoMachinesTitle();
12833 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12834 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12835 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12836 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12837 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12838 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12839 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12840 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12841 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12842 if (IntFeature(&p, "done", &val, cps)) {
12843 FeatureDone(cps, val);
12846 /* Added by Tord: */
12847 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12848 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12849 /* End of additions by Tord */
12851 /* [HGM] added features: */
12852 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12853 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12854 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12855 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12856 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12857 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12858 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12859 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12860 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12861 SendToProgram(buf, cps);
12864 if(cps->nrOptions >= MAX_OPTIONS) {
12866 sprintf(buf, "%s engine has too many options\n", cps->which);
12867 DisplayError(buf, 0);
12871 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12872 /* End of additions by HGM */
12874 /* unknown feature: complain and skip */
12876 while (*q && *q != '=') q++;
12877 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12878 SendToProgram(buf, cps);
12884 while (*p && *p != '\"') p++;
12885 if (*p == '\"') p++;
12887 while (*p && *p != ' ') p++;
12895 PeriodicUpdatesEvent(newState)
12898 if (newState == appData.periodicUpdates)
12901 appData.periodicUpdates=newState;
12903 /* Display type changes, so update it now */
12904 // DisplayAnalysis();
12906 /* Get the ball rolling again... */
12908 AnalysisPeriodicEvent(1);
12909 StartAnalysisClock();
12914 PonderNextMoveEvent(newState)
12917 if (newState == appData.ponderNextMove) return;
12918 if (gameMode == EditPosition) EditPositionDone(TRUE);
12920 SendToProgram("hard\n", &first);
12921 if (gameMode == TwoMachinesPlay) {
12922 SendToProgram("hard\n", &second);
12925 SendToProgram("easy\n", &first);
12926 thinkOutput[0] = NULLCHAR;
12927 if (gameMode == TwoMachinesPlay) {
12928 SendToProgram("easy\n", &second);
12931 appData.ponderNextMove = newState;
12935 NewSettingEvent(option, command, value)
12941 if (gameMode == EditPosition) EditPositionDone(TRUE);
12942 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12943 SendToProgram(buf, &first);
12944 if (gameMode == TwoMachinesPlay) {
12945 SendToProgram(buf, &second);
12950 ShowThinkingEvent()
12951 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12953 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12954 int newState = appData.showThinking
12955 // [HGM] thinking: other features now need thinking output as well
12956 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12958 if (oldState == newState) return;
12959 oldState = newState;
12960 if (gameMode == EditPosition) EditPositionDone(TRUE);
12962 SendToProgram("post\n", &first);
12963 if (gameMode == TwoMachinesPlay) {
12964 SendToProgram("post\n", &second);
12967 SendToProgram("nopost\n", &first);
12968 thinkOutput[0] = NULLCHAR;
12969 if (gameMode == TwoMachinesPlay) {
12970 SendToProgram("nopost\n", &second);
12973 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12977 AskQuestionEvent(title, question, replyPrefix, which)
12978 char *title; char *question; char *replyPrefix; char *which;
12980 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12981 if (pr == NoProc) return;
12982 AskQuestion(title, question, replyPrefix, pr);
12986 DisplayMove(moveNumber)
12989 char message[MSG_SIZ];
12991 char cpThinkOutput[MSG_SIZ];
12993 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12995 if (moveNumber == forwardMostMove - 1 ||
12996 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12998 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13000 if (strchr(cpThinkOutput, '\n')) {
13001 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13004 *cpThinkOutput = NULLCHAR;
13007 /* [AS] Hide thinking from human user */
13008 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13009 *cpThinkOutput = NULLCHAR;
13010 if( thinkOutput[0] != NULLCHAR ) {
13013 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13014 cpThinkOutput[i] = '.';
13016 cpThinkOutput[i] = NULLCHAR;
13017 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13021 if (moveNumber == forwardMostMove - 1 &&
13022 gameInfo.resultDetails != NULL) {
13023 if (gameInfo.resultDetails[0] == NULLCHAR) {
13024 sprintf(res, " %s", PGNResult(gameInfo.result));
13026 sprintf(res, " {%s} %s",
13027 gameInfo.resultDetails, PGNResult(gameInfo.result));
13033 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13034 DisplayMessage(res, cpThinkOutput);
13036 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13037 WhiteOnMove(moveNumber) ? " " : ".. ",
13038 parseList[moveNumber], res);
13039 DisplayMessage(message, cpThinkOutput);
13044 DisplayComment(moveNumber, text)
13048 char title[MSG_SIZ];
13049 char buf[8000]; // comment can be long!
13052 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13053 strcpy(title, "Comment");
13055 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13056 WhiteOnMove(moveNumber) ? " " : ".. ",
13057 parseList[moveNumber]);
13059 // [HGM] PV info: display PV info together with (or as) comment
13060 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13061 if(text == NULL) text = "";
13062 score = pvInfoList[moveNumber].score;
13063 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13064 depth, (pvInfoList[moveNumber].time+50)/100, text);
13067 if (text != NULL && (appData.autoDisplayComment || commentUp))
13068 CommentPopUp(title, text);
13071 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13072 * might be busy thinking or pondering. It can be omitted if your
13073 * gnuchess is configured to stop thinking immediately on any user
13074 * input. However, that gnuchess feature depends on the FIONREAD
13075 * ioctl, which does not work properly on some flavors of Unix.
13079 ChessProgramState *cps;
13082 if (!cps->useSigint) return;
13083 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13084 switch (gameMode) {
13085 case MachinePlaysWhite:
13086 case MachinePlaysBlack:
13087 case TwoMachinesPlay:
13088 case IcsPlayingWhite:
13089 case IcsPlayingBlack:
13092 /* Skip if we know it isn't thinking */
13093 if (!cps->maybeThinking) return;
13094 if (appData.debugMode)
13095 fprintf(debugFP, "Interrupting %s\n", cps->which);
13096 InterruptChildProcess(cps->pr);
13097 cps->maybeThinking = FALSE;
13102 #endif /*ATTENTION*/
13108 if (whiteTimeRemaining <= 0) {
13111 if (appData.icsActive) {
13112 if (appData.autoCallFlag &&
13113 gameMode == IcsPlayingBlack && !blackFlag) {
13114 SendToICS(ics_prefix);
13115 SendToICS("flag\n");
13119 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13121 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13122 if (appData.autoCallFlag) {
13123 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13130 if (blackTimeRemaining <= 0) {
13133 if (appData.icsActive) {
13134 if (appData.autoCallFlag &&
13135 gameMode == IcsPlayingWhite && !whiteFlag) {
13136 SendToICS(ics_prefix);
13137 SendToICS("flag\n");
13141 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13143 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13144 if (appData.autoCallFlag) {
13145 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13158 if (!appData.clockMode || appData.icsActive ||
13159 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13162 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13164 if ( !WhiteOnMove(forwardMostMove) )
13165 /* White made time control */
13166 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13167 /* [HGM] time odds: correct new time quota for time odds! */
13168 / WhitePlayer()->timeOdds;
13170 /* Black made time control */
13171 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13172 / WhitePlayer()->other->timeOdds;
13176 DisplayBothClocks()
13178 int wom = gameMode == EditPosition ?
13179 !blackPlaysFirst : WhiteOnMove(currentMove);
13180 DisplayWhiteClock(whiteTimeRemaining, wom);
13181 DisplayBlackClock(blackTimeRemaining, !wom);
13185 /* Timekeeping seems to be a portability nightmare. I think everyone
13186 has ftime(), but I'm really not sure, so I'm including some ifdefs
13187 to use other calls if you don't. Clocks will be less accurate if
13188 you have neither ftime nor gettimeofday.
13191 /* VS 2008 requires the #include outside of the function */
13192 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13193 #include <sys/timeb.h>
13196 /* Get the current time as a TimeMark */
13201 #if HAVE_GETTIMEOFDAY
13203 struct timeval timeVal;
13204 struct timezone timeZone;
13206 gettimeofday(&timeVal, &timeZone);
13207 tm->sec = (long) timeVal.tv_sec;
13208 tm->ms = (int) (timeVal.tv_usec / 1000L);
13210 #else /*!HAVE_GETTIMEOFDAY*/
13213 // include <sys/timeb.h> / moved to just above start of function
13214 struct timeb timeB;
13217 tm->sec = (long) timeB.time;
13218 tm->ms = (int) timeB.millitm;
13220 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13221 tm->sec = (long) time(NULL);
13227 /* Return the difference in milliseconds between two
13228 time marks. We assume the difference will fit in a long!
13231 SubtractTimeMarks(tm2, tm1)
13232 TimeMark *tm2, *tm1;
13234 return 1000L*(tm2->sec - tm1->sec) +
13235 (long) (tm2->ms - tm1->ms);
13240 * Code to manage the game clocks.
13242 * In tournament play, black starts the clock and then white makes a move.
13243 * We give the human user a slight advantage if he is playing white---the
13244 * clocks don't run until he makes his first move, so it takes zero time.
13245 * Also, we don't account for network lag, so we could get out of sync
13246 * with GNU Chess's clock -- but then, referees are always right.
13249 static TimeMark tickStartTM;
13250 static long intendedTickLength;
13253 NextTickLength(timeRemaining)
13254 long timeRemaining;
13256 long nominalTickLength, nextTickLength;
13258 if (timeRemaining > 0L && timeRemaining <= 10000L)
13259 nominalTickLength = 100L;
13261 nominalTickLength = 1000L;
13262 nextTickLength = timeRemaining % nominalTickLength;
13263 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13265 return nextTickLength;
13268 /* Adjust clock one minute up or down */
13270 AdjustClock(Boolean which, int dir)
13272 if(which) blackTimeRemaining += 60000*dir;
13273 else whiteTimeRemaining += 60000*dir;
13274 DisplayBothClocks();
13277 /* Stop clocks and reset to a fresh time control */
13281 (void) StopClockTimer();
13282 if (appData.icsActive) {
13283 whiteTimeRemaining = blackTimeRemaining = 0;
13284 } else { /* [HGM] correct new time quote for time odds */
13285 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13286 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13288 if (whiteFlag || blackFlag) {
13290 whiteFlag = blackFlag = FALSE;
13292 DisplayBothClocks();
13295 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13297 /* Decrement running clock by amount of time that has passed */
13301 long timeRemaining;
13302 long lastTickLength, fudge;
13305 if (!appData.clockMode) return;
13306 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13310 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13312 /* Fudge if we woke up a little too soon */
13313 fudge = intendedTickLength - lastTickLength;
13314 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13316 if (WhiteOnMove(forwardMostMove)) {
13317 if(whiteNPS >= 0) lastTickLength = 0;
13318 timeRemaining = whiteTimeRemaining -= lastTickLength;
13319 DisplayWhiteClock(whiteTimeRemaining - fudge,
13320 WhiteOnMove(currentMove));
13322 if(blackNPS >= 0) lastTickLength = 0;
13323 timeRemaining = blackTimeRemaining -= lastTickLength;
13324 DisplayBlackClock(blackTimeRemaining - fudge,
13325 !WhiteOnMove(currentMove));
13328 if (CheckFlags()) return;
13331 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13332 StartClockTimer(intendedTickLength);
13334 /* if the time remaining has fallen below the alarm threshold, sound the
13335 * alarm. if the alarm has sounded and (due to a takeback or time control
13336 * with increment) the time remaining has increased to a level above the
13337 * threshold, reset the alarm so it can sound again.
13340 if (appData.icsActive && appData.icsAlarm) {
13342 /* make sure we are dealing with the user's clock */
13343 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13344 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13347 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13348 alarmSounded = FALSE;
13349 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13351 alarmSounded = TRUE;
13357 /* A player has just moved, so stop the previously running
13358 clock and (if in clock mode) start the other one.
13359 We redisplay both clocks in case we're in ICS mode, because
13360 ICS gives us an update to both clocks after every move.
13361 Note that this routine is called *after* forwardMostMove
13362 is updated, so the last fractional tick must be subtracted
13363 from the color that is *not* on move now.
13366 SwitchClocks(int newMoveNr)
13368 long lastTickLength;
13370 int flagged = FALSE;
13374 if (StopClockTimer() && appData.clockMode) {
13375 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13376 if (!WhiteOnMove(forwardMostMove)) {
13377 if(blackNPS >= 0) lastTickLength = 0;
13378 blackTimeRemaining -= lastTickLength;
13379 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13380 // if(pvInfoList[forwardMostMove-1].time == -1)
13381 pvInfoList[forwardMostMove-1].time = // use GUI time
13382 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13384 if(whiteNPS >= 0) lastTickLength = 0;
13385 whiteTimeRemaining -= lastTickLength;
13386 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13387 // if(pvInfoList[forwardMostMove-1].time == -1)
13388 pvInfoList[forwardMostMove-1].time =
13389 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13391 flagged = CheckFlags();
13393 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13394 CheckTimeControl();
13396 if (flagged || !appData.clockMode) return;
13398 switch (gameMode) {
13399 case MachinePlaysBlack:
13400 case MachinePlaysWhite:
13401 case BeginningOfGame:
13402 if (pausing) return;
13406 case PlayFromGameFile:
13415 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13416 whiteTimeRemaining : blackTimeRemaining);
13417 StartClockTimer(intendedTickLength);
13421 /* Stop both clocks */
13425 long lastTickLength;
13428 if (!StopClockTimer()) return;
13429 if (!appData.clockMode) return;
13433 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13434 if (WhiteOnMove(forwardMostMove)) {
13435 if(whiteNPS >= 0) lastTickLength = 0;
13436 whiteTimeRemaining -= lastTickLength;
13437 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13439 if(blackNPS >= 0) lastTickLength = 0;
13440 blackTimeRemaining -= lastTickLength;
13441 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13446 /* Start clock of player on move. Time may have been reset, so
13447 if clock is already running, stop and restart it. */
13451 (void) StopClockTimer(); /* in case it was running already */
13452 DisplayBothClocks();
13453 if (CheckFlags()) return;
13455 if (!appData.clockMode) return;
13456 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13458 GetTimeMark(&tickStartTM);
13459 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13460 whiteTimeRemaining : blackTimeRemaining);
13462 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13463 whiteNPS = blackNPS = -1;
13464 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13465 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13466 whiteNPS = first.nps;
13467 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13468 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13469 blackNPS = first.nps;
13470 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13471 whiteNPS = second.nps;
13472 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13473 blackNPS = second.nps;
13474 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13476 StartClockTimer(intendedTickLength);
13483 long second, minute, hour, day;
13485 static char buf[32];
13487 if (ms > 0 && ms <= 9900) {
13488 /* convert milliseconds to tenths, rounding up */
13489 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13491 sprintf(buf, " %03.1f ", tenths/10.0);
13495 /* convert milliseconds to seconds, rounding up */
13496 /* use floating point to avoid strangeness of integer division
13497 with negative dividends on many machines */
13498 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13505 day = second / (60 * 60 * 24);
13506 second = second % (60 * 60 * 24);
13507 hour = second / (60 * 60);
13508 second = second % (60 * 60);
13509 minute = second / 60;
13510 second = second % 60;
13513 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13514 sign, day, hour, minute, second);
13516 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13518 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13525 * This is necessary because some C libraries aren't ANSI C compliant yet.
13528 StrStr(string, match)
13529 char *string, *match;
13533 length = strlen(match);
13535 for (i = strlen(string) - length; i >= 0; i--, string++)
13536 if (!strncmp(match, string, length))
13543 StrCaseStr(string, match)
13544 char *string, *match;
13548 length = strlen(match);
13550 for (i = strlen(string) - length; i >= 0; i--, string++) {
13551 for (j = 0; j < length; j++) {
13552 if (ToLower(match[j]) != ToLower(string[j]))
13555 if (j == length) return string;
13569 c1 = ToLower(*s1++);
13570 c2 = ToLower(*s2++);
13571 if (c1 > c2) return 1;
13572 if (c1 < c2) return -1;
13573 if (c1 == NULLCHAR) return 0;
13582 return isupper(c) ? tolower(c) : c;
13590 return islower(c) ? toupper(c) : c;
13592 #endif /* !_amigados */
13600 if ((ret = (char *) malloc(strlen(s) + 1))) {
13607 StrSavePtr(s, savePtr)
13608 char *s, **savePtr;
13613 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13614 strcpy(*savePtr, s);
13626 clock = time((time_t *)NULL);
13627 tm = localtime(&clock);
13628 sprintf(buf, "%04d.%02d.%02d",
13629 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13630 return StrSave(buf);
13635 PositionToFEN(move, overrideCastling)
13637 char *overrideCastling;
13639 int i, j, fromX, fromY, toX, toY;
13646 whiteToPlay = (gameMode == EditPosition) ?
13647 !blackPlaysFirst : (move % 2 == 0);
13650 /* Piece placement data */
13651 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13653 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13654 if (boards[move][i][j] == EmptySquare) {
13656 } else { ChessSquare piece = boards[move][i][j];
13657 if (emptycount > 0) {
13658 if(emptycount<10) /* [HGM] can be >= 10 */
13659 *p++ = '0' + emptycount;
13660 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13663 if(PieceToChar(piece) == '+') {
13664 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13666 piece = (ChessSquare)(DEMOTED piece);
13668 *p++ = PieceToChar(piece);
13670 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13671 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13676 if (emptycount > 0) {
13677 if(emptycount<10) /* [HGM] can be >= 10 */
13678 *p++ = '0' + emptycount;
13679 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13686 /* [HGM] print Crazyhouse or Shogi holdings */
13687 if( gameInfo.holdingsWidth ) {
13688 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13690 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13691 piece = boards[move][i][BOARD_WIDTH-1];
13692 if( piece != EmptySquare )
13693 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13694 *p++ = PieceToChar(piece);
13696 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13697 piece = boards[move][BOARD_HEIGHT-i-1][0];
13698 if( piece != EmptySquare )
13699 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13700 *p++ = PieceToChar(piece);
13703 if( q == p ) *p++ = '-';
13709 *p++ = whiteToPlay ? 'w' : 'b';
13712 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13713 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13715 if(nrCastlingRights) {
13717 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13718 /* [HGM] write directly from rights */
13719 if(castlingRights[move][2] >= 0 &&
13720 castlingRights[move][0] >= 0 )
13721 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13722 if(castlingRights[move][2] >= 0 &&
13723 castlingRights[move][1] >= 0 )
13724 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13725 if(castlingRights[move][5] >= 0 &&
13726 castlingRights[move][3] >= 0 )
13727 *p++ = castlingRights[move][3] + AAA;
13728 if(castlingRights[move][5] >= 0 &&
13729 castlingRights[move][4] >= 0 )
13730 *p++ = castlingRights[move][4] + AAA;
13733 /* [HGM] write true castling rights */
13734 if( nrCastlingRights == 6 ) {
13735 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13736 castlingRights[move][2] >= 0 ) *p++ = 'K';
13737 if(castlingRights[move][1] == BOARD_LEFT &&
13738 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13739 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13740 castlingRights[move][5] >= 0 ) *p++ = 'k';
13741 if(castlingRights[move][4] == BOARD_LEFT &&
13742 castlingRights[move][5] >= 0 ) *p++ = 'q';
13745 if (q == p) *p++ = '-'; /* No castling rights */
13749 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13750 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13751 /* En passant target square */
13752 if (move > backwardMostMove) {
13753 fromX = moveList[move - 1][0] - AAA;
13754 fromY = moveList[move - 1][1] - ONE;
13755 toX = moveList[move - 1][2] - AAA;
13756 toY = moveList[move - 1][3] - ONE;
13757 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13758 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13759 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13761 /* 2-square pawn move just happened */
13763 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13767 } else if(move == backwardMostMove) {
13768 // [HGM] perhaps we should always do it like this, and forget the above?
13769 if(epStatus[move] >= 0) {
13770 *p++ = epStatus[move] + AAA;
13771 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13782 /* [HGM] find reversible plies */
13783 { int i = 0, j=move;
13785 if (appData.debugMode) { int k;
13786 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13787 for(k=backwardMostMove; k<=forwardMostMove; k++)
13788 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13792 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13793 if( j == backwardMostMove ) i += initialRulePlies;
13794 sprintf(p, "%d ", i);
13795 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13797 /* Fullmove number */
13798 sprintf(p, "%d", (move / 2) + 1);
13800 return StrSave(buf);
13804 ParseFEN(board, blackPlaysFirst, fen)
13806 int *blackPlaysFirst;
13816 /* [HGM] by default clear Crazyhouse holdings, if present */
13817 if(gameInfo.holdingsWidth) {
13818 for(i=0; i<BOARD_HEIGHT; i++) {
13819 board[i][0] = EmptySquare; /* black holdings */
13820 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13821 board[i][1] = (ChessSquare) 0; /* black counts */
13822 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13826 /* Piece placement data */
13827 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13830 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13831 if (*p == '/') p++;
13832 emptycount = gameInfo.boardWidth - j;
13833 while (emptycount--)
13834 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13836 #if(BOARD_SIZE >= 10)
13837 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13838 p++; emptycount=10;
13839 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13840 while (emptycount--)
13841 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13843 } else if (isdigit(*p)) {
13844 emptycount = *p++ - '0';
13845 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13846 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13847 while (emptycount--)
13848 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13849 } else if (*p == '+' || isalpha(*p)) {
13850 if (j >= gameInfo.boardWidth) return FALSE;
13852 piece = CharToPiece(*++p);
13853 if(piece == EmptySquare) return FALSE; /* unknown piece */
13854 piece = (ChessSquare) (PROMOTED piece ); p++;
13855 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13856 } else piece = CharToPiece(*p++);
13858 if(piece==EmptySquare) return FALSE; /* unknown piece */
13859 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13860 piece = (ChessSquare) (PROMOTED piece);
13861 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13864 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13870 while (*p == '/' || *p == ' ') p++;
13872 /* [HGM] look for Crazyhouse holdings here */
13873 while(*p==' ') p++;
13874 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13876 if(*p == '-' ) *p++; /* empty holdings */ else {
13877 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13878 /* if we would allow FEN reading to set board size, we would */
13879 /* have to add holdings and shift the board read so far here */
13880 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13882 if((int) piece >= (int) BlackPawn ) {
13883 i = (int)piece - (int)BlackPawn;
13884 i = PieceToNumber((ChessSquare)i);
13885 if( i >= gameInfo.holdingsSize ) return FALSE;
13886 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13887 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13889 i = (int)piece - (int)WhitePawn;
13890 i = PieceToNumber((ChessSquare)i);
13891 if( i >= gameInfo.holdingsSize ) return FALSE;
13892 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13893 board[i][BOARD_WIDTH-2]++; /* black holdings */
13897 if(*p == ']') *p++;
13900 while(*p == ' ') p++;
13905 *blackPlaysFirst = FALSE;
13908 *blackPlaysFirst = TRUE;
13914 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13915 /* return the extra info in global variiables */
13917 /* set defaults in case FEN is incomplete */
13918 FENepStatus = EP_UNKNOWN;
13919 for(i=0; i<nrCastlingRights; i++ ) {
13920 FENcastlingRights[i] =
13921 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13922 } /* assume possible unless obviously impossible */
13923 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13924 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13925 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13926 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13927 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13928 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13929 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13930 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13933 while(*p==' ') p++;
13934 if(nrCastlingRights) {
13935 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13936 /* castling indicator present, so default becomes no castlings */
13937 for(i=0; i<nrCastlingRights; i++ ) {
13938 FENcastlingRights[i] = -1;
13941 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13942 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13943 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13944 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13945 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13947 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13948 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13949 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13951 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13952 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13953 if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13954 && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13955 if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13956 && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13959 for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13960 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13961 FENcastlingRights[2] = whiteKingFile;
13964 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13965 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13966 FENcastlingRights[2] = whiteKingFile;
13969 for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13970 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13971 FENcastlingRights[5] = blackKingFile;
13974 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13975 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13976 FENcastlingRights[5] = blackKingFile;
13979 default: /* FRC castlings */
13980 if(c >= 'a') { /* black rights */
13981 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13982 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13983 if(i == BOARD_RGHT) break;
13984 FENcastlingRights[5] = i;
13986 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13987 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13989 FENcastlingRights[3] = c;
13991 FENcastlingRights[4] = c;
13992 } else { /* white rights */
13993 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13994 if(board[0][i] == WhiteKing) break;
13995 if(i == BOARD_RGHT) break;
13996 FENcastlingRights[2] = i;
13997 c -= AAA - 'a' + 'A';
13998 if(board[0][c] >= WhiteKing) break;
14000 FENcastlingRights[0] = c;
14002 FENcastlingRights[1] = c;
14006 for(i=0; i<nrCastlingRights; i++)
14007 if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14008 if (appData.debugMode) {
14009 fprintf(debugFP, "FEN castling rights:");
14010 for(i=0; i<nrCastlingRights; i++)
14011 fprintf(debugFP, " %d", FENcastlingRights[i]);
14012 fprintf(debugFP, "\n");
14015 while(*p==' ') p++;
14018 /* read e.p. field in games that know e.p. capture */
14019 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14020 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14022 p++; FENepStatus = EP_NONE;
14024 char c = *p++ - AAA;
14026 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14027 if(*p >= '0' && *p <='9') *p++;
14033 if(sscanf(p, "%d", &i) == 1) {
14034 FENrulePlies = i; /* 50-move ply counter */
14035 /* (The move number is still ignored) */
14042 EditPositionPasteFEN(char *fen)
14045 Board initial_position;
14047 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14048 DisplayError(_("Bad FEN position in clipboard"), 0);
14051 int savedBlackPlaysFirst = blackPlaysFirst;
14052 EditPositionEvent();
14053 blackPlaysFirst = savedBlackPlaysFirst;
14054 CopyBoard(boards[0], initial_position);
14055 /* [HGM] copy FEN attributes as well */
14057 initialRulePlies = FENrulePlies;
14058 epStatus[0] = FENepStatus;
14059 for( i=0; i<nrCastlingRights; i++ )
14060 castlingRights[0][i] = FENcastlingRights[i];
14062 EditPositionDone(FALSE);
14063 DisplayBothClocks();
14064 DrawPosition(FALSE, boards[currentMove]);
14069 static char cseq[12] = "\\ ";
14071 Boolean set_cont_sequence(char *new_seq)
14076 // handle bad attempts to set the sequence
14078 return 0; // acceptable error - no debug
14080 len = strlen(new_seq);
14081 ret = (len > 0) && (len < sizeof(cseq));
14083 strcpy(cseq, new_seq);
14084 else if (appData.debugMode)
14085 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14090 reformat a source message so words don't cross the width boundary. internal
14091 newlines are not removed. returns the wrapped size (no null character unless
14092 included in source message). If dest is NULL, only calculate the size required
14093 for the dest buffer. lp argument indicats line position upon entry, and it's
14094 passed back upon exit.
14096 int wrap(char *dest, char *src, int count, int width, int *lp)
14098 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14100 cseq_len = strlen(cseq);
14101 old_line = line = *lp;
14102 ansi = len = clen = 0;
14104 for (i=0; i < count; i++)
14106 if (src[i] == '\033')
14109 // if we hit the width, back up
14110 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14112 // store i & len in case the word is too long
14113 old_i = i, old_len = len;
14115 // find the end of the last word
14116 while (i && src[i] != ' ' && src[i] != '\n')
14122 // word too long? restore i & len before splitting it
14123 if ((old_i-i+clen) >= width)
14130 if (i && src[i-1] == ' ')
14133 if (src[i] != ' ' && src[i] != '\n')
14140 // now append the newline and continuation sequence
14145 strncpy(dest+len, cseq, cseq_len);
14153 dest[len] = src[i];
14157 if (src[i] == '\n')
14162 if (dest && appData.debugMode)
14164 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14165 count, width, line, len, *lp);
14166 show_bytes(debugFP, src, count);
14167 fprintf(debugFP, "\ndest: ");
14168 show_bytes(debugFP, dest, len);
14169 fprintf(debugFP, "\n");
14171 *lp = dest ? line : old_line;