2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
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((void));
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;
2168 while (i < buf_len) {
2169 /* Deal with part of the TELNET option negotiation
2170 protocol. We refuse to do anything beyond the
2171 defaults, except that we allow the WILL ECHO option,
2172 which ICS uses to turn off password echoing when we are
2173 directly connected to it. We reject this option
2174 if localLineEditing mode is on (always on in xboard)
2175 and we are talking to port 23, which might be a real
2176 telnet server that will try to keep WILL ECHO on permanently.
2178 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2179 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2180 unsigned char option;
2182 switch ((unsigned char) buf[++i]) {
2184 if (appData.debugMode)
2185 fprintf(debugFP, "\n<WILL ");
2186 switch (option = (unsigned char) buf[++i]) {
2188 if (appData.debugMode)
2189 fprintf(debugFP, "ECHO ");
2190 /* Reply only if this is a change, according
2191 to the protocol rules. */
2192 if (remoteEchoOption) break;
2193 if (appData.localLineEditing &&
2194 atoi(appData.icsPort) == TN_PORT) {
2195 TelnetRequest(TN_DONT, TN_ECHO);
2198 TelnetRequest(TN_DO, TN_ECHO);
2199 remoteEchoOption = TRUE;
2203 if (appData.debugMode)
2204 fprintf(debugFP, "%d ", option);
2205 /* Whatever this is, we don't want it. */
2206 TelnetRequest(TN_DONT, option);
2211 if (appData.debugMode)
2212 fprintf(debugFP, "\n<WONT ");
2213 switch (option = (unsigned char) buf[++i]) {
2215 if (appData.debugMode)
2216 fprintf(debugFP, "ECHO ");
2217 /* Reply only if this is a change, according
2218 to the protocol rules. */
2219 if (!remoteEchoOption) break;
2221 TelnetRequest(TN_DONT, TN_ECHO);
2222 remoteEchoOption = FALSE;
2225 if (appData.debugMode)
2226 fprintf(debugFP, "%d ", (unsigned char) option);
2227 /* Whatever this is, it must already be turned
2228 off, because we never agree to turn on
2229 anything non-default, so according to the
2230 protocol rules, we don't reply. */
2235 if (appData.debugMode)
2236 fprintf(debugFP, "\n<DO ");
2237 switch (option = (unsigned char) buf[++i]) {
2239 /* Whatever this is, we refuse to do it. */
2240 if (appData.debugMode)
2241 fprintf(debugFP, "%d ", option);
2242 TelnetRequest(TN_WONT, option);
2247 if (appData.debugMode)
2248 fprintf(debugFP, "\n<DONT ");
2249 switch (option = (unsigned char) buf[++i]) {
2251 if (appData.debugMode)
2252 fprintf(debugFP, "%d ", option);
2253 /* Whatever this is, we are already not doing
2254 it, because we never agree to do anything
2255 non-default, so according to the protocol
2256 rules, we don't reply. */
2261 if (appData.debugMode)
2262 fprintf(debugFP, "\n<IAC ");
2263 /* Doubled IAC; pass it through */
2267 if (appData.debugMode)
2268 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2269 /* Drop all other telnet commands on the floor */
2272 if (oldi > next_out)
2273 SendToPlayer(&buf[next_out], oldi - next_out);
2279 /* OK, this at least will *usually* work */
2280 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2284 if (loggedOn && !intfSet) {
2285 if (ics_type == ICS_ICC) {
2287 "/set-quietly interface %s\n/set-quietly style 12\n",
2289 } else if (ics_type == ICS_CHESSNET) {
2290 sprintf(str, "/style 12\n");
2292 strcpy(str, "alias $ @\n$set interface ");
2293 strcat(str, programVersion);
2294 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2296 strcat(str, "$iset nohighlight 1\n");
2298 strcat(str, "$iset lock 1\n$style 12\n");
2301 NotifyFrontendLogin();
2305 if (started == STARTED_COMMENT) {
2306 /* Accumulate characters in comment */
2307 parse[parse_pos++] = buf[i];
2308 if (buf[i] == '\n') {
2309 parse[parse_pos] = NULLCHAR;
2310 if(chattingPartner>=0) {
2312 sprintf(mess, "%s%s", talker, parse);
2313 OutputChatMessage(chattingPartner, mess);
2314 chattingPartner = -1;
2316 if(!suppressKibitz) // [HGM] kibitz
2317 AppendComment(forwardMostMove, StripHighlight(parse));
2318 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2319 int nrDigit = 0, nrAlph = 0, i;
2320 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2321 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2322 parse[parse_pos] = NULLCHAR;
2323 // try to be smart: if it does not look like search info, it should go to
2324 // ICS interaction window after all, not to engine-output window.
2325 for(i=0; i<parse_pos; i++) { // count letters and digits
2326 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2327 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2328 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2330 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2331 int depth=0; float score;
2332 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2333 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2334 pvInfoList[forwardMostMove-1].depth = depth;
2335 pvInfoList[forwardMostMove-1].score = 100*score;
2337 OutputKibitz(suppressKibitz, parse);
2340 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2341 SendToPlayer(tmp, strlen(tmp));
2344 started = STARTED_NONE;
2346 /* Don't match patterns against characters in chatter */
2351 if (started == STARTED_CHATTER) {
2352 if (buf[i] != '\n') {
2353 /* Don't match patterns against characters in chatter */
2357 started = STARTED_NONE;
2360 /* Kludge to deal with rcmd protocol */
2361 if (firstTime && looking_at(buf, &i, "\001*")) {
2362 DisplayFatalError(&buf[1], 0, 1);
2368 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2371 if (appData.debugMode)
2372 fprintf(debugFP, "ics_type %d\n", ics_type);
2375 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2376 ics_type = ICS_FICS;
2378 if (appData.debugMode)
2379 fprintf(debugFP, "ics_type %d\n", ics_type);
2382 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2383 ics_type = ICS_CHESSNET;
2385 if (appData.debugMode)
2386 fprintf(debugFP, "ics_type %d\n", ics_type);
2391 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2392 looking_at(buf, &i, "Logging you in as \"*\"") ||
2393 looking_at(buf, &i, "will be \"*\""))) {
2394 strcpy(ics_handle, star_match[0]);
2398 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2400 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2401 DisplayIcsInteractionTitle(buf);
2402 have_set_title = TRUE;
2405 /* skip finger notes */
2406 if (started == STARTED_NONE &&
2407 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2408 (buf[i] == '1' && buf[i+1] == '0')) &&
2409 buf[i+2] == ':' && buf[i+3] == ' ') {
2410 started = STARTED_CHATTER;
2415 /* skip formula vars */
2416 if (started == STARTED_NONE &&
2417 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2418 started = STARTED_CHATTER;
2424 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2425 if (appData.autoKibitz && started == STARTED_NONE &&
2426 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2427 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2428 if(looking_at(buf, &i, "* kibitzes: ") &&
2429 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2430 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2431 suppressKibitz = TRUE;
2432 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2433 && (gameMode == IcsPlayingWhite)) ||
2434 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2435 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2436 started = STARTED_CHATTER; // own kibitz we simply discard
2438 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2439 parse_pos = 0; parse[0] = NULLCHAR;
2440 savingComment = TRUE;
2441 suppressKibitz = gameMode != IcsObserving ? 2 :
2442 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2446 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2447 started = STARTED_CHATTER;
2448 suppressKibitz = TRUE;
2450 } // [HGM] kibitz: end of patch
2452 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2454 // [HGM] chat: intercept tells by users for which we have an open chat window
2456 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2457 looking_at(buf, &i, "* whispers:") ||
2458 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2459 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2461 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2462 chattingPartner = -1;
2464 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2465 for(p=0; p<MAX_CHAT; p++) {
2466 if(channel == atoi(chatPartner[p])) {
2467 talker[0] = '['; strcat(talker, "]");
2468 chattingPartner = p; break;
2471 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2472 for(p=0; p<MAX_CHAT; p++) {
2473 if(!strcmp("WHISPER", chatPartner[p])) {
2474 talker[0] = '['; strcat(talker, "]");
2475 chattingPartner = p; break;
2478 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2479 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2481 chattingPartner = p; break;
2483 if(chattingPartner<0) i = oldi; else {
2484 started = STARTED_COMMENT;
2485 parse_pos = 0; parse[0] = NULLCHAR;
2486 savingComment = TRUE;
2487 suppressKibitz = TRUE;
2489 } // [HGM] chat: end of patch
2491 if (appData.zippyTalk || appData.zippyPlay) {
2492 /* [DM] Backup address for color zippy lines */
2496 if (loggedOn == TRUE)
2497 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2498 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2500 if (ZippyControl(buf, &i) ||
2501 ZippyConverse(buf, &i) ||
2502 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2504 if (!appData.colorize) continue;
2508 } // [DM] 'else { ' deleted
2510 /* Regular tells and says */
2511 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2512 looking_at(buf, &i, "* (your partner) tells you: ") ||
2513 looking_at(buf, &i, "* says: ") ||
2514 /* Don't color "message" or "messages" output */
2515 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2516 looking_at(buf, &i, "*. * at *:*: ") ||
2517 looking_at(buf, &i, "--* (*:*): ") ||
2518 /* Message notifications (same color as tells) */
2519 looking_at(buf, &i, "* has left a message ") ||
2520 looking_at(buf, &i, "* just sent you a message:\n") ||
2521 /* Whispers and kibitzes */
2522 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2523 looking_at(buf, &i, "* kibitzes: ") ||
2525 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2527 if (tkind == 1 && strchr(star_match[0], ':')) {
2528 /* Avoid "tells you:" spoofs in channels */
2531 if (star_match[0][0] == NULLCHAR ||
2532 strchr(star_match[0], ' ') ||
2533 (tkind == 3 && strchr(star_match[1], ' '))) {
2534 /* Reject bogus matches */
2537 if (appData.colorize) {
2538 if (oldi > next_out) {
2539 SendToPlayer(&buf[next_out], oldi - next_out);
2544 Colorize(ColorTell, FALSE);
2545 curColor = ColorTell;
2548 Colorize(ColorKibitz, FALSE);
2549 curColor = ColorKibitz;
2552 p = strrchr(star_match[1], '(');
2559 Colorize(ColorChannel1, FALSE);
2560 curColor = ColorChannel1;
2562 Colorize(ColorChannel, FALSE);
2563 curColor = ColorChannel;
2567 curColor = ColorNormal;
2571 if (started == STARTED_NONE && appData.autoComment &&
2572 (gameMode == IcsObserving ||
2573 gameMode == IcsPlayingWhite ||
2574 gameMode == IcsPlayingBlack)) {
2575 parse_pos = i - oldi;
2576 memcpy(parse, &buf[oldi], parse_pos);
2577 parse[parse_pos] = NULLCHAR;
2578 started = STARTED_COMMENT;
2579 savingComment = TRUE;
2581 started = STARTED_CHATTER;
2582 savingComment = FALSE;
2589 if (looking_at(buf, &i, "* s-shouts: ") ||
2590 looking_at(buf, &i, "* c-shouts: ")) {
2591 if (appData.colorize) {
2592 if (oldi > next_out) {
2593 SendToPlayer(&buf[next_out], oldi - next_out);
2596 Colorize(ColorSShout, FALSE);
2597 curColor = ColorSShout;
2600 started = STARTED_CHATTER;
2604 if (looking_at(buf, &i, "--->")) {
2609 if (looking_at(buf, &i, "* shouts: ") ||
2610 looking_at(buf, &i, "--> ")) {
2611 if (appData.colorize) {
2612 if (oldi > next_out) {
2613 SendToPlayer(&buf[next_out], oldi - next_out);
2616 Colorize(ColorShout, FALSE);
2617 curColor = ColorShout;
2620 started = STARTED_CHATTER;
2624 if (looking_at( buf, &i, "Challenge:")) {
2625 if (appData.colorize) {
2626 if (oldi > next_out) {
2627 SendToPlayer(&buf[next_out], oldi - next_out);
2630 Colorize(ColorChallenge, FALSE);
2631 curColor = ColorChallenge;
2637 if (looking_at(buf, &i, "* offers you") ||
2638 looking_at(buf, &i, "* offers to be") ||
2639 looking_at(buf, &i, "* would like to") ||
2640 looking_at(buf, &i, "* requests to") ||
2641 looking_at(buf, &i, "Your opponent offers") ||
2642 looking_at(buf, &i, "Your opponent requests")) {
2644 if (appData.colorize) {
2645 if (oldi > next_out) {
2646 SendToPlayer(&buf[next_out], oldi - next_out);
2649 Colorize(ColorRequest, FALSE);
2650 curColor = ColorRequest;
2655 if (looking_at(buf, &i, "* (*) seeking")) {
2656 if (appData.colorize) {
2657 if (oldi > next_out) {
2658 SendToPlayer(&buf[next_out], oldi - next_out);
2661 Colorize(ColorSeek, FALSE);
2662 curColor = ColorSeek;
2667 if (looking_at(buf, &i, "\\ ")) {
2668 if (prevColor != ColorNormal) {
2669 if (oldi > next_out) {
2670 SendToPlayer(&buf[next_out], oldi - next_out);
2673 Colorize(prevColor, TRUE);
2674 curColor = prevColor;
2676 if (savingComment) {
2677 parse_pos = i - oldi;
2678 memcpy(parse, &buf[oldi], parse_pos);
2679 parse[parse_pos] = NULLCHAR;
2680 started = STARTED_COMMENT;
2682 started = STARTED_CHATTER;
2687 if (looking_at(buf, &i, "Black Strength :") ||
2688 looking_at(buf, &i, "<<< style 10 board >>>") ||
2689 looking_at(buf, &i, "<10>") ||
2690 looking_at(buf, &i, "#@#")) {
2691 /* Wrong board style */
2693 SendToICS(ics_prefix);
2694 SendToICS("set style 12\n");
2695 SendToICS(ics_prefix);
2696 SendToICS("refresh\n");
2700 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2702 have_sent_ICS_logon = 1;
2706 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2707 (looking_at(buf, &i, "\n<12> ") ||
2708 looking_at(buf, &i, "<12> "))) {
2710 if (oldi > next_out) {
2711 SendToPlayer(&buf[next_out], oldi - next_out);
2714 started = STARTED_BOARD;
2719 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2720 looking_at(buf, &i, "<b1> ")) {
2721 if (oldi > next_out) {
2722 SendToPlayer(&buf[next_out], oldi - next_out);
2725 started = STARTED_HOLDINGS;
2730 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2732 /* Header for a move list -- first line */
2734 switch (ics_getting_history) {
2738 case BeginningOfGame:
2739 /* User typed "moves" or "oldmoves" while we
2740 were idle. Pretend we asked for these
2741 moves and soak them up so user can step
2742 through them and/or save them.
2745 gameMode = IcsObserving;
2748 ics_getting_history = H_GOT_UNREQ_HEADER;
2750 case EditGame: /*?*/
2751 case EditPosition: /*?*/
2752 /* Should above feature work in these modes too? */
2753 /* For now it doesn't */
2754 ics_getting_history = H_GOT_UNWANTED_HEADER;
2757 ics_getting_history = H_GOT_UNWANTED_HEADER;
2762 /* Is this the right one? */
2763 if (gameInfo.white && gameInfo.black &&
2764 strcmp(gameInfo.white, star_match[0]) == 0 &&
2765 strcmp(gameInfo.black, star_match[2]) == 0) {
2767 ics_getting_history = H_GOT_REQ_HEADER;
2770 case H_GOT_REQ_HEADER:
2771 case H_GOT_UNREQ_HEADER:
2772 case H_GOT_UNWANTED_HEADER:
2773 case H_GETTING_MOVES:
2774 /* Should not happen */
2775 DisplayError(_("Error gathering move list: two headers"), 0);
2776 ics_getting_history = H_FALSE;
2780 /* Save player ratings into gameInfo if needed */
2781 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2782 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2783 (gameInfo.whiteRating == -1 ||
2784 gameInfo.blackRating == -1)) {
2786 gameInfo.whiteRating = string_to_rating(star_match[1]);
2787 gameInfo.blackRating = string_to_rating(star_match[3]);
2788 if (appData.debugMode)
2789 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2790 gameInfo.whiteRating, gameInfo.blackRating);
2795 if (looking_at(buf, &i,
2796 "* * match, initial time: * minute*, increment: * second")) {
2797 /* Header for a move list -- second line */
2798 /* Initial board will follow if this is a wild game */
2799 if (gameInfo.event != NULL) free(gameInfo.event);
2800 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2801 gameInfo.event = StrSave(str);
2802 /* [HGM] we switched variant. Translate boards if needed. */
2803 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2807 if (looking_at(buf, &i, "Move ")) {
2808 /* Beginning of a move list */
2809 switch (ics_getting_history) {
2811 /* Normally should not happen */
2812 /* Maybe user hit reset while we were parsing */
2815 /* Happens if we are ignoring a move list that is not
2816 * the one we just requested. Common if the user
2817 * tries to observe two games without turning off
2820 case H_GETTING_MOVES:
2821 /* Should not happen */
2822 DisplayError(_("Error gathering move list: nested"), 0);
2823 ics_getting_history = H_FALSE;
2825 case H_GOT_REQ_HEADER:
2826 ics_getting_history = H_GETTING_MOVES;
2827 started = STARTED_MOVES;
2829 if (oldi > next_out) {
2830 SendToPlayer(&buf[next_out], oldi - next_out);
2833 case H_GOT_UNREQ_HEADER:
2834 ics_getting_history = H_GETTING_MOVES;
2835 started = STARTED_MOVES_NOHIDE;
2838 case H_GOT_UNWANTED_HEADER:
2839 ics_getting_history = H_FALSE;
2845 if (looking_at(buf, &i, "% ") ||
2846 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2847 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2848 savingComment = FALSE;
2851 case STARTED_MOVES_NOHIDE:
2852 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2853 parse[parse_pos + i - oldi] = NULLCHAR;
2854 ParseGameHistory(parse);
2856 if (appData.zippyPlay && first.initDone) {
2857 FeedMovesToProgram(&first, forwardMostMove);
2858 if (gameMode == IcsPlayingWhite) {
2859 if (WhiteOnMove(forwardMostMove)) {
2860 if (first.sendTime) {
2861 if (first.useColors) {
2862 SendToProgram("black\n", &first);
2864 SendTimeRemaining(&first, TRUE);
2866 if (first.useColors) {
2867 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2869 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2870 first.maybeThinking = TRUE;
2872 if (first.usePlayother) {
2873 if (first.sendTime) {
2874 SendTimeRemaining(&first, TRUE);
2876 SendToProgram("playother\n", &first);
2882 } else if (gameMode == IcsPlayingBlack) {
2883 if (!WhiteOnMove(forwardMostMove)) {
2884 if (first.sendTime) {
2885 if (first.useColors) {
2886 SendToProgram("white\n", &first);
2888 SendTimeRemaining(&first, FALSE);
2890 if (first.useColors) {
2891 SendToProgram("black\n", &first);
2893 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2894 first.maybeThinking = TRUE;
2896 if (first.usePlayother) {
2897 if (first.sendTime) {
2898 SendTimeRemaining(&first, FALSE);
2900 SendToProgram("playother\n", &first);
2909 if (gameMode == IcsObserving && ics_gamenum == -1) {
2910 /* Moves came from oldmoves or moves command
2911 while we weren't doing anything else.
2913 currentMove = forwardMostMove;
2914 ClearHighlights();/*!!could figure this out*/
2915 flipView = appData.flipView;
2916 DrawPosition(TRUE, boards[currentMove]);
2917 DisplayBothClocks();
2918 sprintf(str, "%s vs. %s",
2919 gameInfo.white, gameInfo.black);
2923 /* Moves were history of an active game */
2924 if (gameInfo.resultDetails != NULL) {
2925 free(gameInfo.resultDetails);
2926 gameInfo.resultDetails = NULL;
2929 HistorySet(parseList, backwardMostMove,
2930 forwardMostMove, currentMove-1);
2931 DisplayMove(currentMove - 1);
2932 if (started == STARTED_MOVES) next_out = i;
2933 started = STARTED_NONE;
2934 ics_getting_history = H_FALSE;
2937 case STARTED_OBSERVE:
2938 started = STARTED_NONE;
2939 SendToICS(ics_prefix);
2940 SendToICS("refresh\n");
2946 if(bookHit) { // [HGM] book: simulate book reply
2947 static char bookMove[MSG_SIZ]; // a bit generous?
2949 programStats.nodes = programStats.depth = programStats.time =
2950 programStats.score = programStats.got_only_move = 0;
2951 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2953 strcpy(bookMove, "move ");
2954 strcat(bookMove, bookHit);
2955 HandleMachineMove(bookMove, &first);
2960 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2961 started == STARTED_HOLDINGS ||
2962 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2963 /* Accumulate characters in move list or board */
2964 parse[parse_pos++] = buf[i];
2967 /* Start of game messages. Mostly we detect start of game
2968 when the first board image arrives. On some versions
2969 of the ICS, though, we need to do a "refresh" after starting
2970 to observe in order to get the current board right away. */
2971 if (looking_at(buf, &i, "Adding game * to observation list")) {
2972 started = STARTED_OBSERVE;
2976 /* Handle auto-observe */
2977 if (appData.autoObserve &&
2978 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2979 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2981 /* Choose the player that was highlighted, if any. */
2982 if (star_match[0][0] == '\033' ||
2983 star_match[1][0] != '\033') {
2984 player = star_match[0];
2986 player = star_match[2];
2988 sprintf(str, "%sobserve %s\n",
2989 ics_prefix, StripHighlightAndTitle(player));
2992 /* Save ratings from notify string */
2993 strcpy(player1Name, star_match[0]);
2994 player1Rating = string_to_rating(star_match[1]);
2995 strcpy(player2Name, star_match[2]);
2996 player2Rating = string_to_rating(star_match[3]);
2998 if (appData.debugMode)
3000 "Ratings from 'Game notification:' %s %d, %s %d\n",
3001 player1Name, player1Rating,
3002 player2Name, player2Rating);
3007 /* Deal with automatic examine mode after a game,
3008 and with IcsObserving -> IcsExamining transition */
3009 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3010 looking_at(buf, &i, "has made you an examiner of game *")) {
3012 int gamenum = atoi(star_match[0]);
3013 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3014 gamenum == ics_gamenum) {
3015 /* We were already playing or observing this game;
3016 no need to refetch history */
3017 gameMode = IcsExamining;
3019 pauseExamForwardMostMove = forwardMostMove;
3020 } else if (currentMove < forwardMostMove) {
3021 ForwardInner(forwardMostMove);
3024 /* I don't think this case really can happen */
3025 SendToICS(ics_prefix);
3026 SendToICS("refresh\n");
3031 /* Error messages */
3032 // if (ics_user_moved) {
3033 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3034 if (looking_at(buf, &i, "Illegal move") ||
3035 looking_at(buf, &i, "Not a legal move") ||
3036 looking_at(buf, &i, "Your king is in check") ||
3037 looking_at(buf, &i, "It isn't your turn") ||
3038 looking_at(buf, &i, "It is not your move")) {
3040 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3041 currentMove = --forwardMostMove;
3042 DisplayMove(currentMove - 1); /* before DMError */
3043 DrawPosition(FALSE, boards[currentMove]);
3045 DisplayBothClocks();
3047 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3053 if (looking_at(buf, &i, "still have time") ||
3054 looking_at(buf, &i, "not out of time") ||
3055 looking_at(buf, &i, "either player is out of time") ||
3056 looking_at(buf, &i, "has timeseal; checking")) {
3057 /* We must have called his flag a little too soon */
3058 whiteFlag = blackFlag = FALSE;
3062 if (looking_at(buf, &i, "added * seconds to") ||
3063 looking_at(buf, &i, "seconds were added to")) {
3064 /* Update the clocks */
3065 SendToICS(ics_prefix);
3066 SendToICS("refresh\n");
3070 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3071 ics_clock_paused = TRUE;
3076 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3077 ics_clock_paused = FALSE;
3082 /* Grab player ratings from the Creating: message.
3083 Note we have to check for the special case when
3084 the ICS inserts things like [white] or [black]. */
3085 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3086 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3088 0 player 1 name (not necessarily white)
3090 2 empty, white, or black (IGNORED)
3091 3 player 2 name (not necessarily black)
3094 The names/ratings are sorted out when the game
3095 actually starts (below).
3097 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3098 player1Rating = string_to_rating(star_match[1]);
3099 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3100 player2Rating = string_to_rating(star_match[4]);
3102 if (appData.debugMode)
3104 "Ratings from 'Creating:' %s %d, %s %d\n",
3105 player1Name, player1Rating,
3106 player2Name, player2Rating);
3111 /* Improved generic start/end-of-game messages */
3112 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3113 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3114 /* If tkind == 0: */
3115 /* star_match[0] is the game number */
3116 /* [1] is the white player's name */
3117 /* [2] is the black player's name */
3118 /* For end-of-game: */
3119 /* [3] is the reason for the game end */
3120 /* [4] is a PGN end game-token, preceded by " " */
3121 /* For start-of-game: */
3122 /* [3] begins with "Creating" or "Continuing" */
3123 /* [4] is " *" or empty (don't care). */
3124 int gamenum = atoi(star_match[0]);
3125 char *whitename, *blackname, *why, *endtoken;
3126 ChessMove endtype = (ChessMove) 0;
3129 whitename = star_match[1];
3130 blackname = star_match[2];
3131 why = star_match[3];
3132 endtoken = star_match[4];
3134 whitename = star_match[1];
3135 blackname = star_match[3];
3136 why = star_match[5];
3137 endtoken = star_match[6];
3140 /* Game start messages */
3141 if (strncmp(why, "Creating ", 9) == 0 ||
3142 strncmp(why, "Continuing ", 11) == 0) {
3143 gs_gamenum = gamenum;
3144 strcpy(gs_kind, strchr(why, ' ') + 1);
3145 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3147 if (appData.zippyPlay) {
3148 ZippyGameStart(whitename, blackname);
3154 /* Game end messages */
3155 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3156 ics_gamenum != gamenum) {
3159 while (endtoken[0] == ' ') endtoken++;
3160 switch (endtoken[0]) {
3163 endtype = GameUnfinished;
3166 endtype = BlackWins;
3169 if (endtoken[1] == '/')
3170 endtype = GameIsDrawn;
3172 endtype = WhiteWins;
3175 GameEnds(endtype, why, GE_ICS);
3177 if (appData.zippyPlay && first.initDone) {
3178 ZippyGameEnd(endtype, why);
3179 if (first.pr == NULL) {
3180 /* Start the next process early so that we'll
3181 be ready for the next challenge */
3182 StartChessProgram(&first);
3184 /* Send "new" early, in case this command takes
3185 a long time to finish, so that we'll be ready
3186 for the next challenge. */
3187 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3194 if (looking_at(buf, &i, "Removing game * from observation") ||
3195 looking_at(buf, &i, "no longer observing game *") ||
3196 looking_at(buf, &i, "Game * (*) has no examiners")) {
3197 if (gameMode == IcsObserving &&
3198 atoi(star_match[0]) == ics_gamenum)
3200 /* icsEngineAnalyze */
3201 if (appData.icsEngineAnalyze) {
3208 ics_user_moved = FALSE;
3213 if (looking_at(buf, &i, "no longer examining game *")) {
3214 if (gameMode == IcsExamining &&
3215 atoi(star_match[0]) == ics_gamenum)
3219 ics_user_moved = FALSE;
3224 /* Advance leftover_start past any newlines we find,
3225 so only partial lines can get reparsed */
3226 if (looking_at(buf, &i, "\n")) {
3227 prevColor = curColor;
3228 if (curColor != ColorNormal) {
3229 if (oldi > next_out) {
3230 SendToPlayer(&buf[next_out], oldi - next_out);
3233 Colorize(ColorNormal, FALSE);
3234 curColor = ColorNormal;
3236 if (started == STARTED_BOARD) {
3237 started = STARTED_NONE;
3238 parse[parse_pos] = NULLCHAR;
3239 ParseBoard12(parse);
3242 /* Send premove here */
3243 if (appData.premove) {
3245 if (currentMove == 0 &&
3246 gameMode == IcsPlayingWhite &&
3247 appData.premoveWhite) {
3248 sprintf(str, "%s\n", appData.premoveWhiteText);
3249 if (appData.debugMode)
3250 fprintf(debugFP, "Sending premove:\n");
3252 } else if (currentMove == 1 &&
3253 gameMode == IcsPlayingBlack &&
3254 appData.premoveBlack) {
3255 sprintf(str, "%s\n", appData.premoveBlackText);
3256 if (appData.debugMode)
3257 fprintf(debugFP, "Sending premove:\n");
3259 } else if (gotPremove) {
3261 ClearPremoveHighlights();
3262 if (appData.debugMode)
3263 fprintf(debugFP, "Sending premove:\n");
3264 UserMoveEvent(premoveFromX, premoveFromY,
3265 premoveToX, premoveToY,
3270 /* Usually suppress following prompt */
3271 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3272 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3273 if (looking_at(buf, &i, "*% ")) {
3274 savingComment = FALSE;
3278 } else if (started == STARTED_HOLDINGS) {
3280 char new_piece[MSG_SIZ];
3281 started = STARTED_NONE;
3282 parse[parse_pos] = NULLCHAR;
3283 if (appData.debugMode)
3284 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3285 parse, currentMove);
3286 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3287 gamenum == ics_gamenum) {
3288 if (gameInfo.variant == VariantNormal) {
3289 /* [HGM] We seem to switch variant during a game!
3290 * Presumably no holdings were displayed, so we have
3291 * to move the position two files to the right to
3292 * create room for them!
3294 VariantClass newVariant;
3295 switch(gameInfo.boardWidth) { // base guess on board width
3296 case 9: newVariant = VariantShogi; break;
3297 case 10: newVariant = VariantGreat; break;
3298 default: newVariant = VariantCrazyhouse; break;
3300 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3301 /* Get a move list just to see the header, which
3302 will tell us whether this is really bug or zh */
3303 if (ics_getting_history == H_FALSE) {
3304 ics_getting_history = H_REQUESTED;
3305 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3309 new_piece[0] = NULLCHAR;
3310 sscanf(parse, "game %d white [%s black [%s <- %s",
3311 &gamenum, white_holding, black_holding,
3313 white_holding[strlen(white_holding)-1] = NULLCHAR;
3314 black_holding[strlen(black_holding)-1] = NULLCHAR;
3315 /* [HGM] copy holdings to board holdings area */
3316 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3317 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3318 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3320 if (appData.zippyPlay && first.initDone) {
3321 ZippyHoldings(white_holding, black_holding,
3325 if (tinyLayout || smallLayout) {
3326 char wh[16], bh[16];
3327 PackHolding(wh, white_holding);
3328 PackHolding(bh, black_holding);
3329 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3330 gameInfo.white, gameInfo.black);
3332 sprintf(str, "%s [%s] vs. %s [%s]",
3333 gameInfo.white, white_holding,
3334 gameInfo.black, black_holding);
3337 DrawPosition(FALSE, boards[currentMove]);
3340 /* Suppress following prompt */
3341 if (looking_at(buf, &i, "*% ")) {
3342 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3343 savingComment = FALSE;
3350 i++; /* skip unparsed character and loop back */
3353 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3354 started != STARTED_HOLDINGS && i > next_out) {
3355 SendToPlayer(&buf[next_out], i - next_out);
3358 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3360 leftover_len = buf_len - leftover_start;
3361 /* if buffer ends with something we couldn't parse,
3362 reparse it after appending the next read */
3364 } else if (count == 0) {
3365 RemoveInputSource(isr);
3366 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3368 DisplayFatalError(_("Error reading from ICS"), error, 1);
3373 /* Board style 12 looks like this:
3375 <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
3377 * The "<12> " is stripped before it gets to this routine. The two
3378 * trailing 0's (flip state and clock ticking) are later addition, and
3379 * some chess servers may not have them, or may have only the first.
3380 * Additional trailing fields may be added in the future.
3383 #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"
3385 #define RELATION_OBSERVING_PLAYED 0
3386 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3387 #define RELATION_PLAYING_MYMOVE 1
3388 #define RELATION_PLAYING_NOTMYMOVE -1
3389 #define RELATION_EXAMINING 2
3390 #define RELATION_ISOLATED_BOARD -3
3391 #define RELATION_STARTING_POSITION -4 /* FICS only */
3394 ParseBoard12(string)
3397 GameMode newGameMode;
3398 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3399 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3400 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3401 char to_play, board_chars[200];
3402 char move_str[500], str[500], elapsed_time[500];
3403 char black[32], white[32];
3405 int prevMove = currentMove;
3408 int fromX, fromY, toX, toY;
3410 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3411 char *bookHit = NULL; // [HGM] book
3412 Boolean weird = FALSE, reqFlag = FALSE;
3414 fromX = fromY = toX = toY = -1;
3418 if (appData.debugMode)
3419 fprintf(debugFP, _("Parsing board: %s\n"), string);
3421 move_str[0] = NULLCHAR;
3422 elapsed_time[0] = NULLCHAR;
3423 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3425 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3426 if(string[i] == ' ') { ranks++; files = 0; }
3428 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3431 for(j = 0; j <i; j++) board_chars[j] = string[j];
3432 board_chars[i] = '\0';
3435 n = sscanf(string, PATTERN, &to_play, &double_push,
3436 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3437 &gamenum, white, black, &relation, &basetime, &increment,
3438 &white_stren, &black_stren, &white_time, &black_time,
3439 &moveNum, str, elapsed_time, move_str, &ics_flip,
3443 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3444 DisplayError(str, 0);
3448 /* Convert the move number to internal form */
3449 moveNum = (moveNum - 1) * 2;
3450 if (to_play == 'B') moveNum++;
3451 if (moveNum >= MAX_MOVES) {
3452 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3458 case RELATION_OBSERVING_PLAYED:
3459 case RELATION_OBSERVING_STATIC:
3460 if (gamenum == -1) {
3461 /* Old ICC buglet */
3462 relation = RELATION_OBSERVING_STATIC;
3464 newGameMode = IcsObserving;
3466 case RELATION_PLAYING_MYMOVE:
3467 case RELATION_PLAYING_NOTMYMOVE:
3469 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3470 IcsPlayingWhite : IcsPlayingBlack;
3472 case RELATION_EXAMINING:
3473 newGameMode = IcsExamining;
3475 case RELATION_ISOLATED_BOARD:
3477 /* Just display this board. If user was doing something else,
3478 we will forget about it until the next board comes. */
3479 newGameMode = IcsIdle;
3481 case RELATION_STARTING_POSITION:
3482 newGameMode = gameMode;
3486 /* Modify behavior for initial board display on move listing
3489 switch (ics_getting_history) {
3493 case H_GOT_REQ_HEADER:
3494 case H_GOT_UNREQ_HEADER:
3495 /* This is the initial position of the current game */
3496 gamenum = ics_gamenum;
3497 moveNum = 0; /* old ICS bug workaround */
3498 if (to_play == 'B') {
3499 startedFromSetupPosition = TRUE;
3500 blackPlaysFirst = TRUE;
3502 if (forwardMostMove == 0) forwardMostMove = 1;
3503 if (backwardMostMove == 0) backwardMostMove = 1;
3504 if (currentMove == 0) currentMove = 1;
3506 newGameMode = gameMode;
3507 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3509 case H_GOT_UNWANTED_HEADER:
3510 /* This is an initial board that we don't want */
3512 case H_GETTING_MOVES:
3513 /* Should not happen */
3514 DisplayError(_("Error gathering move list: extra board"), 0);
3515 ics_getting_history = H_FALSE;
3519 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3520 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3521 /* [HGM] We seem to have switched variant unexpectedly
3522 * Try to guess new variant from board size
3524 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3525 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3526 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3527 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3528 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3529 if(!weird) newVariant = VariantNormal;
3530 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3531 /* Get a move list just to see the header, which
3532 will tell us whether this is really bug or zh */
3533 if (ics_getting_history == H_FALSE) {
3534 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3535 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3540 /* Take action if this is the first board of a new game, or of a
3541 different game than is currently being displayed. */
3542 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3543 relation == RELATION_ISOLATED_BOARD) {
3545 /* Forget the old game and get the history (if any) of the new one */
3546 if (gameMode != BeginningOfGame) {
3550 if (appData.autoRaiseBoard) BoardToTop();
3552 if (gamenum == -1) {
3553 newGameMode = IcsIdle;
3554 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3555 appData.getMoveList && !reqFlag) {
3556 /* Need to get game history */
3557 ics_getting_history = H_REQUESTED;
3558 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3562 /* Initially flip the board to have black on the bottom if playing
3563 black or if the ICS flip flag is set, but let the user change
3564 it with the Flip View button. */
3565 flipView = appData.autoFlipView ?
3566 (newGameMode == IcsPlayingBlack) || ics_flip :
3569 /* Done with values from previous mode; copy in new ones */
3570 gameMode = newGameMode;
3572 ics_gamenum = gamenum;
3573 if (gamenum == gs_gamenum) {
3574 int klen = strlen(gs_kind);
3575 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3576 sprintf(str, "ICS %s", gs_kind);
3577 gameInfo.event = StrSave(str);
3579 gameInfo.event = StrSave("ICS game");
3581 gameInfo.site = StrSave(appData.icsHost);
3582 gameInfo.date = PGNDate();
3583 gameInfo.round = StrSave("-");
3584 gameInfo.white = StrSave(white);
3585 gameInfo.black = StrSave(black);
3586 timeControl = basetime * 60 * 1000;
3588 timeIncrement = increment * 1000;
3589 movesPerSession = 0;
3590 gameInfo.timeControl = TimeControlTagValue();
3591 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3592 if (appData.debugMode) {
3593 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3594 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3595 setbuf(debugFP, NULL);
3598 gameInfo.outOfBook = NULL;
3600 /* Do we have the ratings? */
3601 if (strcmp(player1Name, white) == 0 &&
3602 strcmp(player2Name, black) == 0) {
3603 if (appData.debugMode)
3604 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3605 player1Rating, player2Rating);
3606 gameInfo.whiteRating = player1Rating;
3607 gameInfo.blackRating = player2Rating;
3608 } else if (strcmp(player2Name, white) == 0 &&
3609 strcmp(player1Name, black) == 0) {
3610 if (appData.debugMode)
3611 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3612 player2Rating, player1Rating);
3613 gameInfo.whiteRating = player2Rating;
3614 gameInfo.blackRating = player1Rating;
3616 player1Name[0] = player2Name[0] = NULLCHAR;
3618 /* Silence shouts if requested */
3619 if (appData.quietPlay &&
3620 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3621 SendToICS(ics_prefix);
3622 SendToICS("set shout 0\n");
3626 /* Deal with midgame name changes */
3628 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3629 if (gameInfo.white) free(gameInfo.white);
3630 gameInfo.white = StrSave(white);
3632 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3633 if (gameInfo.black) free(gameInfo.black);
3634 gameInfo.black = StrSave(black);
3638 /* Throw away game result if anything actually changes in examine mode */
3639 if (gameMode == IcsExamining && !newGame) {
3640 gameInfo.result = GameUnfinished;
3641 if (gameInfo.resultDetails != NULL) {
3642 free(gameInfo.resultDetails);
3643 gameInfo.resultDetails = NULL;
3647 /* In pausing && IcsExamining mode, we ignore boards coming
3648 in if they are in a different variation than we are. */
3649 if (pauseExamInvalid) return;
3650 if (pausing && gameMode == IcsExamining) {
3651 if (moveNum <= pauseExamForwardMostMove) {
3652 pauseExamInvalid = TRUE;
3653 forwardMostMove = pauseExamForwardMostMove;
3658 if (appData.debugMode) {
3659 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3661 /* Parse the board */
3662 for (k = 0; k < ranks; k++) {
3663 for (j = 0; j < files; j++)
3664 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3665 if(gameInfo.holdingsWidth > 1) {
3666 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3667 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3670 CopyBoard(boards[moveNum], board);
3671 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3673 startedFromSetupPosition =
3674 !CompareBoards(board, initialPosition);
3675 if(startedFromSetupPosition)
3676 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3679 /* [HGM] Set castling rights. Take the outermost Rooks,
3680 to make it also work for FRC opening positions. Note that board12
3681 is really defective for later FRC positions, as it has no way to
3682 indicate which Rook can castle if they are on the same side of King.
3683 For the initial position we grant rights to the outermost Rooks,
3684 and remember thos rights, and we then copy them on positions
3685 later in an FRC game. This means WB might not recognize castlings with
3686 Rooks that have moved back to their original position as illegal,
3687 but in ICS mode that is not its job anyway.
3689 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3690 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3692 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3693 if(board[0][i] == WhiteRook) j = i;
3694 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3695 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3696 if(board[0][i] == WhiteRook) j = i;
3697 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3698 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3699 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3700 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3701 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3702 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3705 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3706 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3707 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3708 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3709 if(board[BOARD_HEIGHT-1][k] == bKing)
3710 initialRights[5] = castlingRights[moveNum][5] = k;
3711 if(gameInfo.variant == VariantTwoKings) {
3712 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3713 if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3714 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3717 r = castlingRights[moveNum][0] = initialRights[0];
3718 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3719 r = castlingRights[moveNum][1] = initialRights[1];
3720 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3721 r = castlingRights[moveNum][3] = initialRights[3];
3722 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3723 r = castlingRights[moveNum][4] = initialRights[4];
3724 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3725 /* wildcastle kludge: always assume King has rights */
3726 r = castlingRights[moveNum][2] = initialRights[2];
3727 r = castlingRights[moveNum][5] = initialRights[5];
3729 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3730 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3733 if (ics_getting_history == H_GOT_REQ_HEADER ||
3734 ics_getting_history == H_GOT_UNREQ_HEADER) {
3735 /* This was an initial position from a move list, not
3736 the current position */
3740 /* Update currentMove and known move number limits */
3741 newMove = newGame || moveNum > forwardMostMove;
3744 forwardMostMove = backwardMostMove = currentMove = moveNum;
3745 if (gameMode == IcsExamining && moveNum == 0) {
3746 /* Workaround for ICS limitation: we are not told the wild
3747 type when starting to examine a game. But if we ask for
3748 the move list, the move list header will tell us */
3749 ics_getting_history = H_REQUESTED;
3750 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3753 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3754 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3756 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3757 /* [HGM] applied this also to an engine that is silently watching */
3758 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3759 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3760 gameInfo.variant == currentlyInitializedVariant) {
3761 takeback = forwardMostMove - moveNum;
3762 for (i = 0; i < takeback; i++) {
3763 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3764 SendToProgram("undo\n", &first);
3769 forwardMostMove = moveNum;
3770 if (!pausing || currentMove > forwardMostMove)
3771 currentMove = forwardMostMove;
3773 /* New part of history that is not contiguous with old part */
3774 if (pausing && gameMode == IcsExamining) {
3775 pauseExamInvalid = TRUE;
3776 forwardMostMove = pauseExamForwardMostMove;
3779 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3781 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3782 // [HGM] when we will receive the move list we now request, it will be
3783 // fed to the engine from the first move on. So if the engine is not
3784 // in the initial position now, bring it there.
3785 InitChessProgram(&first, 0);
3788 ics_getting_history = H_REQUESTED;
3789 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3792 forwardMostMove = backwardMostMove = currentMove = moveNum;
3795 /* Update the clocks */
3796 if (strchr(elapsed_time, '.')) {
3798 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3799 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3801 /* Time is in seconds */
3802 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3803 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3808 if (appData.zippyPlay && newGame &&
3809 gameMode != IcsObserving && gameMode != IcsIdle &&
3810 gameMode != IcsExamining)
3811 ZippyFirstBoard(moveNum, basetime, increment);
3814 /* Put the move on the move list, first converting
3815 to canonical algebraic form. */
3817 if (appData.debugMode) {
3818 if (appData.debugMode) { int f = forwardMostMove;
3819 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3820 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3822 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3823 fprintf(debugFP, "moveNum = %d\n", moveNum);
3824 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3825 setbuf(debugFP, NULL);
3827 if (moveNum <= backwardMostMove) {
3828 /* We don't know what the board looked like before
3830 strcpy(parseList[moveNum - 1], move_str);
3831 strcat(parseList[moveNum - 1], " ");
3832 strcat(parseList[moveNum - 1], elapsed_time);
3833 moveList[moveNum - 1][0] = NULLCHAR;
3834 } else if (strcmp(move_str, "none") == 0) {
3835 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3836 /* Again, we don't know what the board looked like;
3837 this is really the start of the game. */
3838 parseList[moveNum - 1][0] = NULLCHAR;
3839 moveList[moveNum - 1][0] = NULLCHAR;
3840 backwardMostMove = moveNum;
3841 startedFromSetupPosition = TRUE;
3842 fromX = fromY = toX = toY = -1;
3844 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3845 // So we parse the long-algebraic move string in stead of the SAN move
3846 int valid; char buf[MSG_SIZ], *prom;
3848 // str looks something like "Q/a1-a2"; kill the slash
3850 sprintf(buf, "%c%s", str[0], str+2);
3851 else strcpy(buf, str); // might be castling
3852 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3853 strcat(buf, prom); // long move lacks promo specification!
3854 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3855 if(appData.debugMode)
3856 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3857 strcpy(move_str, buf);
3859 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3860 &fromX, &fromY, &toX, &toY, &promoChar)
3861 || ParseOneMove(buf, moveNum - 1, &moveType,
3862 &fromX, &fromY, &toX, &toY, &promoChar);
3863 // end of long SAN patch
3865 (void) CoordsToAlgebraic(boards[moveNum - 1],
3866 PosFlags(moveNum - 1), EP_UNKNOWN,
3867 fromY, fromX, toY, toX, promoChar,
3868 parseList[moveNum-1]);
3869 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3870 castlingRights[moveNum]) ) {
3876 if(gameInfo.variant != VariantShogi)
3877 strcat(parseList[moveNum - 1], "+");
3880 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3881 strcat(parseList[moveNum - 1], "#");
3884 strcat(parseList[moveNum - 1], " ");
3885 strcat(parseList[moveNum - 1], elapsed_time);
3886 /* currentMoveString is set as a side-effect of ParseOneMove */
3887 strcpy(moveList[moveNum - 1], currentMoveString);
3888 strcat(moveList[moveNum - 1], "\n");
3890 /* Move from ICS was illegal!? Punt. */
3891 if (appData.debugMode) {
3892 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3893 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3895 strcpy(parseList[moveNum - 1], move_str);
3896 strcat(parseList[moveNum - 1], " ");
3897 strcat(parseList[moveNum - 1], elapsed_time);
3898 moveList[moveNum - 1][0] = NULLCHAR;
3899 fromX = fromY = toX = toY = -1;
3902 if (appData.debugMode) {
3903 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3904 setbuf(debugFP, NULL);
3908 /* Send move to chess program (BEFORE animating it). */
3909 if (appData.zippyPlay && !newGame && newMove &&
3910 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3912 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3913 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3914 if (moveList[moveNum - 1][0] == NULLCHAR) {
3915 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3917 DisplayError(str, 0);
3919 if (first.sendTime) {
3920 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3922 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3923 if (firstMove && !bookHit) {
3925 if (first.useColors) {
3926 SendToProgram(gameMode == IcsPlayingWhite ?
3928 "black\ngo\n", &first);
3930 SendToProgram("go\n", &first);
3932 first.maybeThinking = TRUE;
3935 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3936 if (moveList[moveNum - 1][0] == NULLCHAR) {
3937 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3938 DisplayError(str, 0);
3940 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3941 SendMoveToProgram(moveNum - 1, &first);
3948 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3949 /* If move comes from a remote source, animate it. If it
3950 isn't remote, it will have already been animated. */
3951 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3952 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3954 if (!pausing && appData.highlightLastMove) {
3955 SetHighlights(fromX, fromY, toX, toY);
3959 /* Start the clocks */
3960 whiteFlag = blackFlag = FALSE;
3961 appData.clockMode = !(basetime == 0 && increment == 0);
3963 ics_clock_paused = TRUE;
3965 } else if (ticking == 1) {
3966 ics_clock_paused = FALSE;
3968 if (gameMode == IcsIdle ||
3969 relation == RELATION_OBSERVING_STATIC ||
3970 relation == RELATION_EXAMINING ||
3972 DisplayBothClocks();
3976 /* Display opponents and material strengths */
3977 if (gameInfo.variant != VariantBughouse &&
3978 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3979 if (tinyLayout || smallLayout) {
3980 if(gameInfo.variant == VariantNormal)
3981 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3982 gameInfo.white, white_stren, gameInfo.black, black_stren,
3983 basetime, increment);
3985 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3986 gameInfo.white, white_stren, gameInfo.black, black_stren,
3987 basetime, increment, (int) gameInfo.variant);
3989 if(gameInfo.variant == VariantNormal)
3990 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3991 gameInfo.white, white_stren, gameInfo.black, black_stren,
3992 basetime, increment);
3994 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3995 gameInfo.white, white_stren, gameInfo.black, black_stren,
3996 basetime, increment, VariantName(gameInfo.variant));
3999 if (appData.debugMode) {
4000 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4005 /* Display the board */
4006 if (!pausing && !appData.noGUI) {
4008 if (appData.premove)
4010 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4011 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4012 ClearPremoveHighlights();
4014 DrawPosition(FALSE, boards[currentMove]);
4015 DisplayMove(moveNum - 1);
4016 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4017 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4018 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4019 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4023 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4025 if(bookHit) { // [HGM] book: simulate book reply
4026 static char bookMove[MSG_SIZ]; // a bit generous?
4028 programStats.nodes = programStats.depth = programStats.time =
4029 programStats.score = programStats.got_only_move = 0;
4030 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4032 strcpy(bookMove, "move ");
4033 strcat(bookMove, bookHit);
4034 HandleMachineMove(bookMove, &first);
4043 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4044 ics_getting_history = H_REQUESTED;
4045 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4051 AnalysisPeriodicEvent(force)
4054 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4055 && !force) || !appData.periodicUpdates)
4058 /* Send . command to Crafty to collect stats */
4059 SendToProgram(".\n", &first);
4061 /* Don't send another until we get a response (this makes
4062 us stop sending to old Crafty's which don't understand
4063 the "." command (sending illegal cmds resets node count & time,
4064 which looks bad)) */
4065 programStats.ok_to_send = 0;
4068 void ics_update_width(new_width)
4071 ics_printf("set width %d\n", new_width);
4075 SendMoveToProgram(moveNum, cps)
4077 ChessProgramState *cps;
4081 if (cps->useUsermove) {
4082 SendToProgram("usermove ", cps);
4086 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4087 int len = space - parseList[moveNum];
4088 memcpy(buf, parseList[moveNum], len);
4090 buf[len] = NULLCHAR;
4092 sprintf(buf, "%s\n", parseList[moveNum]);
4094 SendToProgram(buf, cps);
4096 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4097 AlphaRank(moveList[moveNum], 4);
4098 SendToProgram(moveList[moveNum], cps);
4099 AlphaRank(moveList[moveNum], 4); // and back
4101 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4102 * the engine. It would be nice to have a better way to identify castle
4104 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4105 && cps->useOOCastle) {
4106 int fromX = moveList[moveNum][0] - AAA;
4107 int fromY = moveList[moveNum][1] - ONE;
4108 int toX = moveList[moveNum][2] - AAA;
4109 int toY = moveList[moveNum][3] - ONE;
4110 if((boards[moveNum][fromY][fromX] == WhiteKing
4111 && boards[moveNum][toY][toX] == WhiteRook)
4112 || (boards[moveNum][fromY][fromX] == BlackKing
4113 && boards[moveNum][toY][toX] == BlackRook)) {
4114 if(toX > fromX) SendToProgram("O-O\n", cps);
4115 else SendToProgram("O-O-O\n", cps);
4117 else SendToProgram(moveList[moveNum], cps);
4119 else SendToProgram(moveList[moveNum], cps);
4120 /* End of additions by Tord */
4123 /* [HGM] setting up the opening has brought engine in force mode! */
4124 /* Send 'go' if we are in a mode where machine should play. */
4125 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4126 (gameMode == TwoMachinesPlay ||
4128 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4130 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4131 SendToProgram("go\n", cps);
4132 if (appData.debugMode) {
4133 fprintf(debugFP, "(extra)\n");
4136 setboardSpoiledMachineBlack = 0;
4140 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4142 int fromX, fromY, toX, toY;
4144 char user_move[MSG_SIZ];
4148 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4149 (int)moveType, fromX, fromY, toX, toY);
4150 DisplayError(user_move + strlen("say "), 0);
4152 case WhiteKingSideCastle:
4153 case BlackKingSideCastle:
4154 case WhiteQueenSideCastleWild:
4155 case BlackQueenSideCastleWild:
4157 case WhiteHSideCastleFR:
4158 case BlackHSideCastleFR:
4160 sprintf(user_move, "o-o\n");
4162 case WhiteQueenSideCastle:
4163 case BlackQueenSideCastle:
4164 case WhiteKingSideCastleWild:
4165 case BlackKingSideCastleWild:
4167 case WhiteASideCastleFR:
4168 case BlackASideCastleFR:
4170 sprintf(user_move, "o-o-o\n");
4172 case WhitePromotionQueen:
4173 case BlackPromotionQueen:
4174 case WhitePromotionRook:
4175 case BlackPromotionRook:
4176 case WhitePromotionBishop:
4177 case BlackPromotionBishop:
4178 case WhitePromotionKnight:
4179 case BlackPromotionKnight:
4180 case WhitePromotionKing:
4181 case BlackPromotionKing:
4182 case WhitePromotionChancellor:
4183 case BlackPromotionChancellor:
4184 case WhitePromotionArchbishop:
4185 case BlackPromotionArchbishop:
4186 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4187 sprintf(user_move, "%c%c%c%c=%c\n",
4188 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189 PieceToChar(WhiteFerz));
4190 else if(gameInfo.variant == VariantGreat)
4191 sprintf(user_move, "%c%c%c%c=%c\n",
4192 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193 PieceToChar(WhiteMan));
4195 sprintf(user_move, "%c%c%c%c=%c\n",
4196 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4197 PieceToChar(PromoPiece(moveType)));
4201 sprintf(user_move, "%c@%c%c\n",
4202 ToUpper(PieceToChar((ChessSquare) fromX)),
4203 AAA + toX, ONE + toY);
4206 case WhiteCapturesEnPassant:
4207 case BlackCapturesEnPassant:
4208 case IllegalMove: /* could be a variant we don't quite understand */
4209 sprintf(user_move, "%c%c%c%c\n",
4210 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4213 SendToICS(user_move);
4214 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4215 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4219 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4224 if (rf == DROP_RANK) {
4225 sprintf(move, "%c@%c%c\n",
4226 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4228 if (promoChar == 'x' || promoChar == NULLCHAR) {
4229 sprintf(move, "%c%c%c%c\n",
4230 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4232 sprintf(move, "%c%c%c%c%c\n",
4233 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4239 ProcessICSInitScript(f)
4244 while (fgets(buf, MSG_SIZ, f)) {
4245 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4252 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4254 AlphaRank(char *move, int n)
4256 // char *p = move, c; int x, y;
4258 if (appData.debugMode) {
4259 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4263 move[2]>='0' && move[2]<='9' &&
4264 move[3]>='a' && move[3]<='x' ) {
4266 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4267 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4269 if(move[0]>='0' && move[0]<='9' &&
4270 move[1]>='a' && move[1]<='x' &&
4271 move[2]>='0' && move[2]<='9' &&
4272 move[3]>='a' && move[3]<='x' ) {
4273 /* input move, Shogi -> normal */
4274 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4275 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4276 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4277 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4280 move[3]>='0' && move[3]<='9' &&
4281 move[2]>='a' && move[2]<='x' ) {
4283 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4284 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4287 move[0]>='a' && move[0]<='x' &&
4288 move[3]>='0' && move[3]<='9' &&
4289 move[2]>='a' && move[2]<='x' ) {
4290 /* output move, normal -> Shogi */
4291 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4292 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4293 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4294 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4295 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4297 if (appData.debugMode) {
4298 fprintf(debugFP, " out = '%s'\n", move);
4302 /* Parser for moves from gnuchess, ICS, or user typein box */
4304 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4307 ChessMove *moveType;
4308 int *fromX, *fromY, *toX, *toY;
4311 if (appData.debugMode) {
4312 fprintf(debugFP, "move to parse: %s\n", move);
4314 *moveType = yylexstr(moveNum, move);
4316 switch (*moveType) {
4317 case WhitePromotionChancellor:
4318 case BlackPromotionChancellor:
4319 case WhitePromotionArchbishop:
4320 case BlackPromotionArchbishop:
4321 case WhitePromotionQueen:
4322 case BlackPromotionQueen:
4323 case WhitePromotionRook:
4324 case BlackPromotionRook:
4325 case WhitePromotionBishop:
4326 case BlackPromotionBishop:
4327 case WhitePromotionKnight:
4328 case BlackPromotionKnight:
4329 case WhitePromotionKing:
4330 case BlackPromotionKing:
4332 case WhiteCapturesEnPassant:
4333 case BlackCapturesEnPassant:
4334 case WhiteKingSideCastle:
4335 case WhiteQueenSideCastle:
4336 case BlackKingSideCastle:
4337 case BlackQueenSideCastle:
4338 case WhiteKingSideCastleWild:
4339 case WhiteQueenSideCastleWild:
4340 case BlackKingSideCastleWild:
4341 case BlackQueenSideCastleWild:
4342 /* Code added by Tord: */
4343 case WhiteHSideCastleFR:
4344 case WhiteASideCastleFR:
4345 case BlackHSideCastleFR:
4346 case BlackASideCastleFR:
4347 /* End of code added by Tord */
4348 case IllegalMove: /* bug or odd chess variant */
4349 *fromX = currentMoveString[0] - AAA;
4350 *fromY = currentMoveString[1] - ONE;
4351 *toX = currentMoveString[2] - AAA;
4352 *toY = currentMoveString[3] - ONE;
4353 *promoChar = currentMoveString[4];
4354 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4355 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4356 if (appData.debugMode) {
4357 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4359 *fromX = *fromY = *toX = *toY = 0;
4362 if (appData.testLegality) {
4363 return (*moveType != IllegalMove);
4365 return !(*fromX == *toX && *fromY == *toY);
4370 *fromX = *moveType == WhiteDrop ?
4371 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4372 (int) CharToPiece(ToLower(currentMoveString[0]));
4374 *toX = currentMoveString[2] - AAA;
4375 *toY = currentMoveString[3] - ONE;
4376 *promoChar = NULLCHAR;
4380 case ImpossibleMove:
4381 case (ChessMove) 0: /* end of file */
4390 if (appData.debugMode) {
4391 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4394 *fromX = *fromY = *toX = *toY = 0;
4395 *promoChar = NULLCHAR;
4400 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4401 // All positions will have equal probability, but the current method will not provide a unique
4402 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4408 int piecesLeft[(int)BlackPawn];
4409 int seed, nrOfShuffles;
4411 void GetPositionNumber()
4412 { // sets global variable seed
4415 seed = appData.defaultFrcPosition;
4416 if(seed < 0) { // randomize based on time for negative FRC position numbers
4417 for(i=0; i<50; i++) seed += random();
4418 seed = random() ^ random() >> 8 ^ random() << 8;
4419 if(seed<0) seed = -seed;
4423 int put(Board board, int pieceType, int rank, int n, int shade)
4424 // put the piece on the (n-1)-th empty squares of the given shade
4428 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4429 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4430 board[rank][i] = (ChessSquare) pieceType;
4431 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4433 piecesLeft[pieceType]--;
4441 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4442 // calculate where the next piece goes, (any empty square), and put it there
4446 i = seed % squaresLeft[shade];
4447 nrOfShuffles *= squaresLeft[shade];
4448 seed /= squaresLeft[shade];
4449 put(board, pieceType, rank, i, shade);
4452 void AddTwoPieces(Board board, int pieceType, int rank)
4453 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4455 int i, n=squaresLeft[ANY], j=n-1, k;
4457 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4458 i = seed % k; // pick one
4461 while(i >= j) i -= j--;
4462 j = n - 1 - j; i += j;
4463 put(board, pieceType, rank, j, ANY);
4464 put(board, pieceType, rank, i, ANY);
4467 void SetUpShuffle(Board board, int number)
4471 GetPositionNumber(); nrOfShuffles = 1;
4473 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4474 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4475 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4477 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4479 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4480 p = (int) board[0][i];
4481 if(p < (int) BlackPawn) piecesLeft[p] ++;
4482 board[0][i] = EmptySquare;
4485 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4486 // shuffles restricted to allow normal castling put KRR first
4487 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4488 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4489 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4490 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4491 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4492 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4493 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4494 put(board, WhiteRook, 0, 0, ANY);
4495 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4498 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4499 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4500 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4501 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4502 while(piecesLeft[p] >= 2) {
4503 AddOnePiece(board, p, 0, LITE);
4504 AddOnePiece(board, p, 0, DARK);
4506 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4509 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4510 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4511 // but we leave King and Rooks for last, to possibly obey FRC restriction
4512 if(p == (int)WhiteRook) continue;
4513 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4514 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4517 // now everything is placed, except perhaps King (Unicorn) and Rooks
4519 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4520 // Last King gets castling rights
4521 while(piecesLeft[(int)WhiteUnicorn]) {
4522 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4523 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4526 while(piecesLeft[(int)WhiteKing]) {
4527 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4528 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4533 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4534 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4537 // Only Rooks can be left; simply place them all
4538 while(piecesLeft[(int)WhiteRook]) {
4539 i = put(board, WhiteRook, 0, 0, ANY);
4540 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4543 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4545 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4548 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4549 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4552 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4555 int SetCharTable( char *table, const char * map )
4556 /* [HGM] moved here from winboard.c because of its general usefulness */
4557 /* Basically a safe strcpy that uses the last character as King */
4559 int result = FALSE; int NrPieces;
4561 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4562 && NrPieces >= 12 && !(NrPieces&1)) {
4563 int i; /* [HGM] Accept even length from 12 to 34 */
4565 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4566 for( i=0; i<NrPieces/2-1; i++ ) {
4568 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4570 table[(int) WhiteKing] = map[NrPieces/2-1];
4571 table[(int) BlackKing] = map[NrPieces-1];
4579 void Prelude(Board board)
4580 { // [HGM] superchess: random selection of exo-pieces
4581 int i, j, k; ChessSquare p;
4582 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4584 GetPositionNumber(); // use FRC position number
4586 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4587 SetCharTable(pieceToChar, appData.pieceToCharTable);
4588 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4589 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4592 j = seed%4; seed /= 4;
4593 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4594 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4595 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4596 j = seed%3 + (seed%3 >= j); seed /= 3;
4597 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4598 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4599 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4600 j = seed%3; seed /= 3;
4601 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4602 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4603 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4604 j = seed%2 + (seed%2 >= j); seed /= 2;
4605 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4606 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4607 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4608 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4609 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4610 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4611 put(board, exoPieces[0], 0, 0, ANY);
4612 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4616 InitPosition(redraw)
4619 ChessSquare (* pieces)[BOARD_SIZE];
4620 int i, j, pawnRow, overrule,
4621 oldx = gameInfo.boardWidth,
4622 oldy = gameInfo.boardHeight,
4623 oldh = gameInfo.holdingsWidth,
4624 oldv = gameInfo.variant;
4626 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4628 /* [AS] Initialize pv info list [HGM] and game status */
4630 for( i=0; i<MAX_MOVES; i++ ) {
4631 pvInfoList[i].depth = 0;
4632 epStatus[i]=EP_NONE;
4633 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4636 initialRulePlies = 0; /* 50-move counter start */
4638 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4639 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4643 /* [HGM] logic here is completely changed. In stead of full positions */
4644 /* the initialized data only consist of the two backranks. The switch */
4645 /* selects which one we will use, which is than copied to the Board */
4646 /* initialPosition, which for the rest is initialized by Pawns and */
4647 /* empty squares. This initial position is then copied to boards[0], */
4648 /* possibly after shuffling, so that it remains available. */
4650 gameInfo.holdingsWidth = 0; /* default board sizes */
4651 gameInfo.boardWidth = 8;
4652 gameInfo.boardHeight = 8;
4653 gameInfo.holdingsSize = 0;
4654 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4655 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4656 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4658 switch (gameInfo.variant) {
4659 case VariantFischeRandom:
4660 shuffleOpenings = TRUE;
4664 case VariantShatranj:
4665 pieces = ShatranjArray;
4666 nrCastlingRights = 0;
4667 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4669 case VariantTwoKings:
4670 pieces = twoKingsArray;
4672 case VariantCapaRandom:
4673 shuffleOpenings = TRUE;
4674 case VariantCapablanca:
4675 pieces = CapablancaArray;
4676 gameInfo.boardWidth = 10;
4677 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4680 pieces = GothicArray;
4681 gameInfo.boardWidth = 10;
4682 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4685 pieces = JanusArray;
4686 gameInfo.boardWidth = 10;
4687 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4688 nrCastlingRights = 6;
4689 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4690 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4691 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4692 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4693 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4694 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4697 pieces = FalconArray;
4698 gameInfo.boardWidth = 10;
4699 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4701 case VariantXiangqi:
4702 pieces = XiangqiArray;
4703 gameInfo.boardWidth = 9;
4704 gameInfo.boardHeight = 10;
4705 nrCastlingRights = 0;
4706 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4709 pieces = ShogiArray;
4710 gameInfo.boardWidth = 9;
4711 gameInfo.boardHeight = 9;
4712 gameInfo.holdingsSize = 7;
4713 nrCastlingRights = 0;
4714 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4716 case VariantCourier:
4717 pieces = CourierArray;
4718 gameInfo.boardWidth = 12;
4719 nrCastlingRights = 0;
4720 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4721 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4723 case VariantKnightmate:
4724 pieces = KnightmateArray;
4725 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4728 pieces = fairyArray;
4729 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4732 pieces = GreatArray;
4733 gameInfo.boardWidth = 10;
4734 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4735 gameInfo.holdingsSize = 8;
4739 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4740 gameInfo.holdingsSize = 8;
4741 startedFromSetupPosition = TRUE;
4743 case VariantCrazyhouse:
4744 case VariantBughouse:
4746 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4747 gameInfo.holdingsSize = 5;
4749 case VariantWildCastle:
4751 /* !!?shuffle with kings guaranteed to be on d or e file */
4752 shuffleOpenings = 1;
4754 case VariantNoCastle:
4756 nrCastlingRights = 0;
4757 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4758 /* !!?unconstrained back-rank shuffle */
4759 shuffleOpenings = 1;
4764 if(appData.NrFiles >= 0) {
4765 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4766 gameInfo.boardWidth = appData.NrFiles;
4768 if(appData.NrRanks >= 0) {
4769 gameInfo.boardHeight = appData.NrRanks;
4771 if(appData.holdingsSize >= 0) {
4772 i = appData.holdingsSize;
4773 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4774 gameInfo.holdingsSize = i;
4776 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4777 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4778 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4780 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4781 if(pawnRow < 1) pawnRow = 1;
4783 /* User pieceToChar list overrules defaults */
4784 if(appData.pieceToCharTable != NULL)
4785 SetCharTable(pieceToChar, appData.pieceToCharTable);
4787 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4789 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4790 s = (ChessSquare) 0; /* account holding counts in guard band */
4791 for( i=0; i<BOARD_HEIGHT; i++ )
4792 initialPosition[i][j] = s;
4794 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4795 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4796 initialPosition[pawnRow][j] = WhitePawn;
4797 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4798 if(gameInfo.variant == VariantXiangqi) {
4800 initialPosition[pawnRow][j] =
4801 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4802 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4803 initialPosition[2][j] = WhiteCannon;
4804 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4808 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4810 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4813 initialPosition[1][j] = WhiteBishop;
4814 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4816 initialPosition[1][j] = WhiteRook;
4817 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4820 if( nrCastlingRights == -1) {
4821 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4822 /* This sets default castling rights from none to normal corners */
4823 /* Variants with other castling rights must set them themselves above */
4824 nrCastlingRights = 6;
4826 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4827 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4828 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4829 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4830 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4831 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4834 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4835 if(gameInfo.variant == VariantGreat) { // promotion commoners
4836 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4837 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4838 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4839 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4841 if (appData.debugMode) {
4842 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4844 if(shuffleOpenings) {
4845 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4846 startedFromSetupPosition = TRUE;
4848 if(startedFromPositionFile) {
4849 /* [HGM] loadPos: use PositionFile for every new game */
4850 CopyBoard(initialPosition, filePosition);
4851 for(i=0; i<nrCastlingRights; i++)
4852 castlingRights[0][i] = initialRights[i] = fileRights[i];
4853 startedFromSetupPosition = TRUE;
4856 CopyBoard(boards[0], initialPosition);
4858 if(oldx != gameInfo.boardWidth ||
4859 oldy != gameInfo.boardHeight ||
4860 oldh != gameInfo.holdingsWidth
4862 || oldv == VariantGothic || // For licensing popups
4863 gameInfo.variant == VariantGothic
4866 || oldv == VariantFalcon ||
4867 gameInfo.variant == VariantFalcon
4870 InitDrawingSizes(-2 ,0);
4873 DrawPosition(TRUE, boards[currentMove]);
4877 SendBoard(cps, moveNum)
4878 ChessProgramState *cps;
4881 char message[MSG_SIZ];
4883 if (cps->useSetboard) {
4884 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4885 sprintf(message, "setboard %s\n", fen);
4886 SendToProgram(message, cps);
4892 /* Kludge to set black to move, avoiding the troublesome and now
4893 * deprecated "black" command.
4895 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4897 SendToProgram("edit\n", cps);
4898 SendToProgram("#\n", cps);
4899 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4900 bp = &boards[moveNum][i][BOARD_LEFT];
4901 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4902 if ((int) *bp < (int) BlackPawn) {
4903 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4905 if(message[0] == '+' || message[0] == '~') {
4906 sprintf(message, "%c%c%c+\n",
4907 PieceToChar((ChessSquare)(DEMOTED *bp)),
4910 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4911 message[1] = BOARD_RGHT - 1 - j + '1';
4912 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4914 SendToProgram(message, cps);
4919 SendToProgram("c\n", cps);
4920 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4921 bp = &boards[moveNum][i][BOARD_LEFT];
4922 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4923 if (((int) *bp != (int) EmptySquare)
4924 && ((int) *bp >= (int) BlackPawn)) {
4925 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4927 if(message[0] == '+' || message[0] == '~') {
4928 sprintf(message, "%c%c%c+\n",
4929 PieceToChar((ChessSquare)(DEMOTED *bp)),
4932 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4933 message[1] = BOARD_RGHT - 1 - j + '1';
4934 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4936 SendToProgram(message, cps);
4941 SendToProgram(".\n", cps);
4943 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4947 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4949 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4950 /* [HGM] add Shogi promotions */
4951 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4956 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4957 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4959 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4960 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4963 piece = boards[currentMove][fromY][fromX];
4964 if(gameInfo.variant == VariantShogi) {
4965 promotionZoneSize = 3;
4966 highestPromotingPiece = (int)WhiteFerz;
4969 // next weed out all moves that do not touch the promotion zone at all
4970 if((int)piece >= BlackPawn) {
4971 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4973 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4975 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4976 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4979 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4981 // weed out mandatory Shogi promotions
4982 if(gameInfo.variant == VariantShogi) {
4983 if(piece >= BlackPawn) {
4984 if(toY == 0 && piece == BlackPawn ||
4985 toY == 0 && piece == BlackQueen ||
4986 toY <= 1 && piece == BlackKnight) {
4991 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4992 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4993 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5000 // weed out obviously illegal Pawn moves
5001 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5002 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5003 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5004 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5005 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5006 // note we are not allowed to test for valid (non-)capture, due to premove
5009 // we either have a choice what to promote to, or (in Shogi) whether to promote
5010 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5011 *promoChoice = PieceToChar(BlackFerz); // no choice
5014 if(appData.alwaysPromoteToQueen) { // predetermined
5015 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5016 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5017 else *promoChoice = PieceToChar(BlackQueen);
5021 // suppress promotion popup on illegal moves that are not premoves
5022 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5023 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5024 if(appData.testLegality && !premove) {
5025 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5026 epStatus[currentMove], castlingRights[currentMove],
5027 fromY, fromX, toY, toX, NULLCHAR);
5028 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5029 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5037 InPalace(row, column)
5039 { /* [HGM] for Xiangqi */
5040 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5041 column < (BOARD_WIDTH + 4)/2 &&
5042 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5047 PieceForSquare (x, y)
5051 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5054 return boards[currentMove][y][x];
5058 OKToStartUserMove(x, y)
5061 ChessSquare from_piece;
5064 if (matchMode) return FALSE;
5065 if (gameMode == EditPosition) return TRUE;
5067 if (x >= 0 && y >= 0)
5068 from_piece = boards[currentMove][y][x];
5070 from_piece = EmptySquare;
5072 if (from_piece == EmptySquare) return FALSE;
5074 white_piece = (int)from_piece >= (int)WhitePawn &&
5075 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5078 case PlayFromGameFile:
5080 case TwoMachinesPlay:
5088 case MachinePlaysWhite:
5089 case IcsPlayingBlack:
5090 if (appData.zippyPlay) return FALSE;
5092 DisplayMoveError(_("You are playing Black"));
5097 case MachinePlaysBlack:
5098 case IcsPlayingWhite:
5099 if (appData.zippyPlay) return FALSE;
5101 DisplayMoveError(_("You are playing White"));
5107 if (!white_piece && WhiteOnMove(currentMove)) {
5108 DisplayMoveError(_("It is White's turn"));
5111 if (white_piece && !WhiteOnMove(currentMove)) {
5112 DisplayMoveError(_("It is Black's turn"));
5115 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5116 /* Editing correspondence game history */
5117 /* Could disallow this or prompt for confirmation */
5120 if (currentMove < forwardMostMove) {
5121 /* Discarding moves */
5122 /* Could prompt for confirmation here,
5123 but I don't think that's such a good idea */
5124 forwardMostMove = currentMove;
5128 case BeginningOfGame:
5129 if (appData.icsActive) return FALSE;
5130 if (!appData.noChessProgram) {
5132 DisplayMoveError(_("You are playing White"));
5139 if (!white_piece && WhiteOnMove(currentMove)) {
5140 DisplayMoveError(_("It is White's turn"));
5143 if (white_piece && !WhiteOnMove(currentMove)) {
5144 DisplayMoveError(_("It is Black's turn"));
5153 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5154 && gameMode != AnalyzeFile && gameMode != Training) {
5155 DisplayMoveError(_("Displayed position is not current"));
5161 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5162 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5163 int lastLoadGameUseList = FALSE;
5164 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5165 ChessMove lastLoadGameStart = (ChessMove) 0;
5168 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5169 int fromX, fromY, toX, toY;
5174 ChessSquare pdown, pup;
5176 /* Check if the user is playing in turn. This is complicated because we
5177 let the user "pick up" a piece before it is his turn. So the piece he
5178 tried to pick up may have been captured by the time he puts it down!
5179 Therefore we use the color the user is supposed to be playing in this
5180 test, not the color of the piece that is currently on the starting
5181 square---except in EditGame mode, where the user is playing both
5182 sides; fortunately there the capture race can't happen. (It can
5183 now happen in IcsExamining mode, but that's just too bad. The user
5184 will get a somewhat confusing message in that case.)
5188 case PlayFromGameFile:
5190 case TwoMachinesPlay:
5194 /* We switched into a game mode where moves are not accepted,
5195 perhaps while the mouse button was down. */
5196 return ImpossibleMove;
5198 case MachinePlaysWhite:
5199 /* User is moving for Black */
5200 if (WhiteOnMove(currentMove)) {
5201 DisplayMoveError(_("It is White's turn"));
5202 return ImpossibleMove;
5206 case MachinePlaysBlack:
5207 /* User is moving for White */
5208 if (!WhiteOnMove(currentMove)) {
5209 DisplayMoveError(_("It is Black's turn"));
5210 return ImpossibleMove;
5216 case BeginningOfGame:
5219 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5220 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5221 /* User is moving for Black */
5222 if (WhiteOnMove(currentMove)) {
5223 DisplayMoveError(_("It is White's turn"));
5224 return ImpossibleMove;
5227 /* User is moving for White */
5228 if (!WhiteOnMove(currentMove)) {
5229 DisplayMoveError(_("It is Black's turn"));
5230 return ImpossibleMove;
5235 case IcsPlayingBlack:
5236 /* User is moving for Black */
5237 if (WhiteOnMove(currentMove)) {
5238 if (!appData.premove) {
5239 DisplayMoveError(_("It is White's turn"));
5240 } else if (toX >= 0 && toY >= 0) {
5243 premoveFromX = fromX;
5244 premoveFromY = fromY;
5245 premovePromoChar = promoChar;
5247 if (appData.debugMode)
5248 fprintf(debugFP, "Got premove: fromX %d,"
5249 "fromY %d, toX %d, toY %d\n",
5250 fromX, fromY, toX, toY);
5252 return ImpossibleMove;
5256 case IcsPlayingWhite:
5257 /* User is moving for White */
5258 if (!WhiteOnMove(currentMove)) {
5259 if (!appData.premove) {
5260 DisplayMoveError(_("It is Black's turn"));
5261 } else if (toX >= 0 && toY >= 0) {
5264 premoveFromX = fromX;
5265 premoveFromY = fromY;
5266 premovePromoChar = promoChar;
5268 if (appData.debugMode)
5269 fprintf(debugFP, "Got premove: fromX %d,"
5270 "fromY %d, toX %d, toY %d\n",
5271 fromX, fromY, toX, toY);
5273 return ImpossibleMove;
5281 /* EditPosition, empty square, or different color piece;
5282 click-click move is possible */
5283 if (toX == -2 || toY == -2) {
5284 boards[0][fromY][fromX] = EmptySquare;
5285 return AmbiguousMove;
5286 } else if (toX >= 0 && toY >= 0) {
5287 boards[0][toY][toX] = boards[0][fromY][fromX];
5288 boards[0][fromY][fromX] = EmptySquare;
5289 return AmbiguousMove;
5291 return ImpossibleMove;
5294 if(toX < 0 || toY < 0) return ImpossibleMove;
5295 pdown = boards[currentMove][fromY][fromX];
5296 pup = boards[currentMove][toY][toX];
5298 /* [HGM] If move started in holdings, it means a drop */
5299 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5300 if( pup != EmptySquare ) return ImpossibleMove;
5301 if(appData.testLegality) {
5302 /* it would be more logical if LegalityTest() also figured out
5303 * which drops are legal. For now we forbid pawns on back rank.
5304 * Shogi is on its own here...
5306 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5307 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5308 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5310 return WhiteDrop; /* Not needed to specify white or black yet */
5313 userOfferedDraw = FALSE;
5315 /* [HGM] always test for legality, to get promotion info */
5316 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5317 epStatus[currentMove], castlingRights[currentMove],
5318 fromY, fromX, toY, toX, promoChar);
5319 /* [HGM] but possibly ignore an IllegalMove result */
5320 if (appData.testLegality) {
5321 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5322 DisplayMoveError(_("Illegal move"));
5323 return ImpossibleMove;
5328 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5329 function is made into one that returns an OK move type if FinishMove
5330 should be called. This to give the calling driver routine the
5331 opportunity to finish the userMove input with a promotion popup,
5332 without bothering the user with this for invalid or illegal moves */
5334 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5337 /* Common tail of UserMoveEvent and DropMenuEvent */
5339 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5341 int fromX, fromY, toX, toY;
5342 /*char*/int promoChar;
5346 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5347 // [HGM] superchess: suppress promotions to non-available piece
5348 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5349 if(WhiteOnMove(currentMove)) {
5350 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5352 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5356 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5357 move type in caller when we know the move is a legal promotion */
5358 if(moveType == NormalMove && promoChar)
5359 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5361 /* [HGM] convert drag-and-drop piece drops to standard form */
5362 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5363 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5364 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5365 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5366 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5367 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5368 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5369 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5373 /* [HGM] <popupFix> The following if has been moved here from
5374 UserMoveEvent(). Because it seemed to belong here (why not allow
5375 piece drops in training games?), and because it can only be
5376 performed after it is known to what we promote. */
5377 if (gameMode == Training) {
5378 /* compare the move played on the board to the next move in the
5379 * game. If they match, display the move and the opponent's response.
5380 * If they don't match, display an error message.
5383 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5384 CopyBoard(testBoard, boards[currentMove]);
5385 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5387 if (CompareBoards(testBoard, boards[currentMove+1])) {
5388 ForwardInner(currentMove+1);
5390 /* Autoplay the opponent's response.
5391 * if appData.animate was TRUE when Training mode was entered,
5392 * the response will be animated.
5394 saveAnimate = appData.animate;
5395 appData.animate = animateTraining;
5396 ForwardInner(currentMove+1);
5397 appData.animate = saveAnimate;
5399 /* check for the end of the game */
5400 if (currentMove >= forwardMostMove) {
5401 gameMode = PlayFromGameFile;
5403 SetTrainingModeOff();
5404 DisplayInformation(_("End of game"));
5407 DisplayError(_("Incorrect move"), 0);
5412 /* Ok, now we know that the move is good, so we can kill
5413 the previous line in Analysis Mode */
5414 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5415 forwardMostMove = currentMove;
5418 /* If we need the chess program but it's dead, restart it */
5419 ResurrectChessProgram();
5421 /* A user move restarts a paused game*/
5425 thinkOutput[0] = NULLCHAR;
5427 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5429 if (gameMode == BeginningOfGame) {
5430 if (appData.noChessProgram) {
5431 gameMode = EditGame;
5435 gameMode = MachinePlaysBlack;
5438 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5440 if (first.sendName) {
5441 sprintf(buf, "name %s\n", gameInfo.white);
5442 SendToProgram(buf, &first);
5449 /* Relay move to ICS or chess engine */
5450 if (appData.icsActive) {
5451 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5452 gameMode == IcsExamining) {
5453 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5457 if (first.sendTime && (gameMode == BeginningOfGame ||
5458 gameMode == MachinePlaysWhite ||
5459 gameMode == MachinePlaysBlack)) {
5460 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5462 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5463 // [HGM] book: if program might be playing, let it use book
5464 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5465 first.maybeThinking = TRUE;
5466 } else SendMoveToProgram(forwardMostMove-1, &first);
5467 if (currentMove == cmailOldMove + 1) {
5468 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5472 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5476 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5477 EP_UNKNOWN, castlingRights[currentMove]) ) {
5483 if (WhiteOnMove(currentMove)) {
5484 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5486 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5490 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5495 case MachinePlaysBlack:
5496 case MachinePlaysWhite:
5497 /* disable certain menu options while machine is thinking */
5498 SetMachineThinkingEnables();
5505 if(bookHit) { // [HGM] book: simulate book reply
5506 static char bookMove[MSG_SIZ]; // a bit generous?
5508 programStats.nodes = programStats.depth = programStats.time =
5509 programStats.score = programStats.got_only_move = 0;
5510 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5512 strcpy(bookMove, "move ");
5513 strcat(bookMove, bookHit);
5514 HandleMachineMove(bookMove, &first);
5520 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5521 int fromX, fromY, toX, toY;
5524 /* [HGM] This routine was added to allow calling of its two logical
5525 parts from other modules in the old way. Before, UserMoveEvent()
5526 automatically called FinishMove() if the move was OK, and returned
5527 otherwise. I separated the two, in order to make it possible to
5528 slip a promotion popup in between. But that it always needs two
5529 calls, to the first part, (now called UserMoveTest() ), and to
5530 FinishMove if the first part succeeded. Calls that do not need
5531 to do anything in between, can call this routine the old way.
5533 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5534 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5535 if(moveType == AmbiguousMove)
5536 DrawPosition(FALSE, boards[currentMove]);
5537 else if(moveType != ImpossibleMove && moveType != Comment)
5538 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5541 void LeftClick(ClickType clickType, int xPix, int yPix)
5544 Boolean saveAnimate;
5545 static int second = 0, promotionChoice = 0;
5546 char promoChoice = NULLCHAR;
5548 if (clickType == Press) ErrorPopDown();
5550 x = EventToSquare(xPix, BOARD_WIDTH);
5551 y = EventToSquare(yPix, BOARD_HEIGHT);
5552 if (!flipView && y >= 0) {
5553 y = BOARD_HEIGHT - 1 - y;
5555 if (flipView && x >= 0) {
5556 x = BOARD_WIDTH - 1 - x;
5559 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5560 if(clickType == Release) return; // ignore upclick of click-click destination
5561 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5562 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5563 if(gameInfo.holdingsWidth &&
5564 (WhiteOnMove(currentMove)
5565 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5566 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5567 // click in right holdings, for determining promotion piece
5568 ChessSquare p = boards[currentMove][y][x];
5569 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5570 if(p != EmptySquare) {
5571 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5576 DrawPosition(FALSE, boards[currentMove]);
5580 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5581 if(clickType == Press
5582 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5583 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5584 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5588 if (clickType == Press) {
5590 if (OKToStartUserMove(x, y)) {
5594 DragPieceBegin(xPix, yPix);
5595 if (appData.highlightDragging) {
5596 SetHighlights(x, y, -1, -1);
5604 if (clickType == Press && gameMode != EditPosition) {
5609 // ignore off-board to clicks
5610 if(y < 0 || x < 0) return;
5612 /* Check if clicking again on the same color piece */
5613 fromP = boards[currentMove][fromY][fromX];
5614 toP = boards[currentMove][y][x];
5615 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5616 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5617 WhitePawn <= toP && toP <= WhiteKing &&
5618 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5619 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5620 (BlackPawn <= fromP && fromP <= BlackKing &&
5621 BlackPawn <= toP && toP <= BlackKing &&
5622 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5623 !(fromP == BlackKing && toP == BlackRook && frc))) {
5624 /* Clicked again on same color piece -- changed his mind */
5625 second = (x == fromX && y == fromY);
5626 if (appData.highlightDragging) {
5627 SetHighlights(x, y, -1, -1);
5631 if (OKToStartUserMove(x, y)) {
5634 DragPieceBegin(xPix, yPix);
5638 // ignore clicks on holdings
5639 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5642 if (clickType == Release && x == fromX && y == fromY) {
5643 DragPieceEnd(xPix, yPix);
5644 if (appData.animateDragging) {
5645 /* Undo animation damage if any */
5646 DrawPosition(FALSE, NULL);
5649 /* Second up/down in same square; just abort move */
5654 ClearPremoveHighlights();
5656 /* First upclick in same square; start click-click mode */
5657 SetHighlights(x, y, -1, -1);
5662 /* we now have a different from- and (possibly off-board) to-square */
5663 /* Completed move */
5666 saveAnimate = appData.animate;
5667 if (clickType == Press) {
5668 /* Finish clickclick move */
5669 if (appData.animate || appData.highlightLastMove) {
5670 SetHighlights(fromX, fromY, toX, toY);
5675 /* Finish drag move */
5676 if (appData.highlightLastMove) {
5677 SetHighlights(fromX, fromY, toX, toY);
5681 DragPieceEnd(xPix, yPix);
5682 /* Don't animate move and drag both */
5683 appData.animate = FALSE;
5686 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5687 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5690 DrawPosition(TRUE, NULL);
5694 // off-board moves should not be highlighted
5695 if(x < 0 || x < 0) ClearHighlights();
5697 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5698 SetHighlights(fromX, fromY, toX, toY);
5699 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5700 // [HGM] super: promotion to captured piece selected from holdings
5701 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5702 promotionChoice = TRUE;
5703 // kludge follows to temporarily execute move on display, without promoting yet
5704 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5705 boards[currentMove][toY][toX] = p;
5706 DrawPosition(FALSE, boards[currentMove]);
5707 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5708 boards[currentMove][toY][toX] = q;
5709 DisplayMessage("Click in holdings to choose piece", "");
5714 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5715 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5716 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5719 appData.animate = saveAnimate;
5720 if (appData.animate || appData.animateDragging) {
5721 /* Undo animation damage if needed */
5722 DrawPosition(FALSE, NULL);
5726 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5728 // char * hint = lastHint;
5729 FrontEndProgramStats stats;
5731 stats.which = cps == &first ? 0 : 1;
5732 stats.depth = cpstats->depth;
5733 stats.nodes = cpstats->nodes;
5734 stats.score = cpstats->score;
5735 stats.time = cpstats->time;
5736 stats.pv = cpstats->movelist;
5737 stats.hint = lastHint;
5738 stats.an_move_index = 0;
5739 stats.an_move_count = 0;
5741 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5742 stats.hint = cpstats->move_name;
5743 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5744 stats.an_move_count = cpstats->nr_moves;
5747 SetProgramStats( &stats );
5750 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5751 { // [HGM] book: this routine intercepts moves to simulate book replies
5752 char *bookHit = NULL;
5754 //first determine if the incoming move brings opponent into his book
5755 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5756 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5757 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5758 if(bookHit != NULL && !cps->bookSuspend) {
5759 // make sure opponent is not going to reply after receiving move to book position
5760 SendToProgram("force\n", cps);
5761 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5763 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5764 // now arrange restart after book miss
5766 // after a book hit we never send 'go', and the code after the call to this routine
5767 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5769 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5770 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5771 SendToProgram(buf, cps);
5772 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5773 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5774 SendToProgram("go\n", cps);
5775 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5776 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5777 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5778 SendToProgram("go\n", cps);
5779 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5781 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5785 ChessProgramState *savedState;
5786 void DeferredBookMove(void)
5788 if(savedState->lastPing != savedState->lastPong)
5789 ScheduleDelayedEvent(DeferredBookMove, 10);
5791 HandleMachineMove(savedMessage, savedState);
5795 HandleMachineMove(message, cps)
5797 ChessProgramState *cps;
5799 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5800 char realname[MSG_SIZ];
5801 int fromX, fromY, toX, toY;
5808 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5810 * Kludge to ignore BEL characters
5812 while (*message == '\007') message++;
5815 * [HGM] engine debug message: ignore lines starting with '#' character
5817 if(cps->debug && *message == '#') return;
5820 * Look for book output
5822 if (cps == &first && bookRequested) {
5823 if (message[0] == '\t' || message[0] == ' ') {
5824 /* Part of the book output is here; append it */
5825 strcat(bookOutput, message);
5826 strcat(bookOutput, " \n");
5828 } else if (bookOutput[0] != NULLCHAR) {
5829 /* All of book output has arrived; display it */
5830 char *p = bookOutput;
5831 while (*p != NULLCHAR) {
5832 if (*p == '\t') *p = ' ';
5835 DisplayInformation(bookOutput);
5836 bookRequested = FALSE;
5837 /* Fall through to parse the current output */
5842 * Look for machine move.
5844 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5845 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5847 /* This method is only useful on engines that support ping */
5848 if (cps->lastPing != cps->lastPong) {
5849 if (gameMode == BeginningOfGame) {
5850 /* Extra move from before last new; ignore */
5851 if (appData.debugMode) {
5852 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5855 if (appData.debugMode) {
5856 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5857 cps->which, gameMode);
5860 SendToProgram("undo\n", cps);
5866 case BeginningOfGame:
5867 /* Extra move from before last reset; ignore */
5868 if (appData.debugMode) {
5869 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5876 /* Extra move after we tried to stop. The mode test is
5877 not a reliable way of detecting this problem, but it's
5878 the best we can do on engines that don't support ping.
5880 if (appData.debugMode) {
5881 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5882 cps->which, gameMode);
5884 SendToProgram("undo\n", cps);
5887 case MachinePlaysWhite:
5888 case IcsPlayingWhite:
5889 machineWhite = TRUE;
5892 case MachinePlaysBlack:
5893 case IcsPlayingBlack:
5894 machineWhite = FALSE;
5897 case TwoMachinesPlay:
5898 machineWhite = (cps->twoMachinesColor[0] == 'w');
5901 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5902 if (appData.debugMode) {
5904 "Ignoring move out of turn by %s, gameMode %d"
5905 ", forwardMost %d\n",
5906 cps->which, gameMode, forwardMostMove);
5911 if (appData.debugMode) { int f = forwardMostMove;
5912 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5913 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5915 if(cps->alphaRank) AlphaRank(machineMove, 4);
5916 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5917 &fromX, &fromY, &toX, &toY, &promoChar)) {
5918 /* Machine move could not be parsed; ignore it. */
5919 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5920 machineMove, cps->which);
5921 DisplayError(buf1, 0);
5922 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5923 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5924 if (gameMode == TwoMachinesPlay) {
5925 GameEnds(machineWhite ? BlackWins : WhiteWins,
5931 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5932 /* So we have to redo legality test with true e.p. status here, */
5933 /* to make sure an illegal e.p. capture does not slip through, */
5934 /* to cause a forfeit on a justified illegal-move complaint */
5935 /* of the opponent. */
5936 if( gameMode==TwoMachinesPlay && appData.testLegality
5937 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5940 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5941 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5942 fromY, fromX, toY, toX, promoChar);
5943 if (appData.debugMode) {
5945 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5946 castlingRights[forwardMostMove][i], castlingRank[i]);
5947 fprintf(debugFP, "castling rights\n");
5949 if(moveType == IllegalMove) {
5950 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5951 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5952 GameEnds(machineWhite ? BlackWins : WhiteWins,
5955 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5956 /* [HGM] Kludge to handle engines that send FRC-style castling
5957 when they shouldn't (like TSCP-Gothic) */
5959 case WhiteASideCastleFR:
5960 case BlackASideCastleFR:
5962 currentMoveString[2]++;
5964 case WhiteHSideCastleFR:
5965 case BlackHSideCastleFR:
5967 currentMoveString[2]--;
5969 default: ; // nothing to do, but suppresses warning of pedantic compilers
5972 hintRequested = FALSE;
5973 lastHint[0] = NULLCHAR;
5974 bookRequested = FALSE;
5975 /* Program may be pondering now */
5976 cps->maybeThinking = TRUE;
5977 if (cps->sendTime == 2) cps->sendTime = 1;
5978 if (cps->offeredDraw) cps->offeredDraw--;
5981 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5983 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5985 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5986 char buf[3*MSG_SIZ];
5988 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5989 programStats.score / 100.,
5991 programStats.time / 100.,
5992 (unsigned int)programStats.nodes,
5993 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5994 programStats.movelist);
5996 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6000 /* currentMoveString is set as a side-effect of ParseOneMove */
6001 strcpy(machineMove, currentMoveString);
6002 strcat(machineMove, "\n");
6003 strcpy(moveList[forwardMostMove], machineMove);
6005 /* [AS] Save move info and clear stats for next move */
6006 pvInfoList[ forwardMostMove ].score = programStats.score;
6007 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6008 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6009 ClearProgramStats();
6010 thinkOutput[0] = NULLCHAR;
6011 hiddenThinkOutputState = 0;
6013 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6015 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6016 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6019 while( count < adjudicateLossPlies ) {
6020 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6023 score = -score; /* Flip score for winning side */
6026 if( score > adjudicateLossThreshold ) {
6033 if( count >= adjudicateLossPlies ) {
6034 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6036 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6037 "Xboard adjudication",
6044 if( gameMode == TwoMachinesPlay ) {
6045 // [HGM] some adjudications useful with buggy engines
6046 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6047 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6050 if( appData.testLegality )
6051 { /* [HGM] Some more adjudications for obstinate engines */
6052 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6053 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6054 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6055 static int moveCount = 6;
6057 char *reason = NULL;
6059 /* Count what is on board. */
6060 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6061 { ChessSquare p = boards[forwardMostMove][i][j];
6065 { /* count B,N,R and other of each side */
6068 NrK++; break; // [HGM] atomic: count Kings
6072 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6073 bishopsColor |= 1 << ((i^j)&1);
6078 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6079 bishopsColor |= 1 << ((i^j)&1);
6094 PawnAdvance += m; NrPawns++;
6096 NrPieces += (p != EmptySquare);
6097 NrW += ((int)p < (int)BlackPawn);
6098 if(gameInfo.variant == VariantXiangqi &&
6099 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6100 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6101 NrW -= ((int)p < (int)BlackPawn);
6105 /* Some material-based adjudications that have to be made before stalemate test */
6106 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6107 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6108 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6109 if(appData.checkMates) {
6110 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6111 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6112 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6113 "Xboard adjudication: King destroyed", GE_XBOARD );
6118 /* Bare King in Shatranj (loses) or Losers (wins) */
6119 if( NrW == 1 || NrPieces - NrW == 1) {
6120 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6121 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6122 if(appData.checkMates) {
6123 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6124 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6126 "Xboard adjudication: Bare king", GE_XBOARD );
6130 if( gameInfo.variant == VariantShatranj && --bare < 0)
6132 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6133 if(appData.checkMates) {
6134 /* but only adjudicate if adjudication enabled */
6135 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6136 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6137 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6138 "Xboard adjudication: Bare king", GE_XBOARD );
6145 // don't wait for engine to announce game end if we can judge ourselves
6146 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6147 castlingRights[forwardMostMove]) ) {
6149 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6150 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6151 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6152 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6155 reason = "Xboard adjudication: 3rd check";
6156 epStatus[forwardMostMove] = EP_CHECKMATE;
6166 reason = "Xboard adjudication: Stalemate";
6167 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6168 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6169 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6170 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6171 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6172 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6173 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6174 EP_CHECKMATE : EP_WINS);
6175 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6176 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6180 reason = "Xboard adjudication: Checkmate";
6181 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6185 switch(i = epStatus[forwardMostMove]) {
6187 result = GameIsDrawn; break;
6189 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6191 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6193 result = (ChessMove) 0;
6195 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6196 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6197 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6198 GameEnds( result, reason, GE_XBOARD );
6202 /* Next absolutely insufficient mating material. */
6203 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6204 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6205 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6206 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6207 { /* KBK, KNK, KK of KBKB with like Bishops */
6209 /* always flag draws, for judging claims */
6210 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6212 if(appData.materialDraws) {
6213 /* but only adjudicate them if adjudication enabled */
6214 SendToProgram("force\n", cps->other); // suppress reply
6215 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6216 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6217 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6222 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6224 ( NrWR == 1 && NrBR == 1 /* KRKR */
6225 || NrWQ==1 && NrBQ==1 /* KQKQ */
6226 || NrWN==2 || NrBN==2 /* KNNK */
6227 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6229 if(--moveCount < 0 && appData.trivialDraws)
6230 { /* if the first 3 moves do not show a tactical win, declare draw */
6231 SendToProgram("force\n", cps->other); // suppress reply
6232 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6233 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6234 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6237 } else moveCount = 6;
6241 /* Check for rep-draws */
6243 for(k = forwardMostMove-2;
6244 k>=backwardMostMove && k>=forwardMostMove-100 &&
6245 epStatus[k] < EP_UNKNOWN &&
6246 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6249 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6250 /* compare castling rights */
6251 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6252 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6253 rights++; /* King lost rights, while rook still had them */
6254 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6255 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6256 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6257 rights++; /* but at least one rook lost them */
6259 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6260 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6262 if( castlingRights[forwardMostMove][5] >= 0 ) {
6263 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6264 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6267 if( rights == 0 && ++count > appData.drawRepeats-2
6268 && appData.drawRepeats > 1) {
6269 /* adjudicate after user-specified nr of repeats */
6270 SendToProgram("force\n", cps->other); // suppress reply
6271 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6272 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6273 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6274 // [HGM] xiangqi: check for forbidden perpetuals
6275 int m, ourPerpetual = 1, hisPerpetual = 1;
6276 for(m=forwardMostMove; m>k; m-=2) {
6277 if(MateTest(boards[m], PosFlags(m),
6278 EP_NONE, castlingRights[m]) != MT_CHECK)
6279 ourPerpetual = 0; // the current mover did not always check
6280 if(MateTest(boards[m-1], PosFlags(m-1),
6281 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6282 hisPerpetual = 0; // the opponent did not always check
6284 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6285 ourPerpetual, hisPerpetual);
6286 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6287 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6288 "Xboard adjudication: perpetual checking", GE_XBOARD );
6291 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6292 break; // (or we would have caught him before). Abort repetition-checking loop.
6293 // Now check for perpetual chases
6294 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6295 hisPerpetual = PerpetualChase(k, forwardMostMove);
6296 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6297 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6298 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6299 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6302 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6303 break; // Abort repetition-checking loop.
6305 // if neither of us is checking or chasing all the time, or both are, it is draw
6307 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6310 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6311 epStatus[forwardMostMove] = EP_REP_DRAW;
6315 /* Now we test for 50-move draws. Determine ply count */
6316 count = forwardMostMove;
6317 /* look for last irreversble move */
6318 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6320 /* if we hit starting position, add initial plies */
6321 if( count == backwardMostMove )
6322 count -= initialRulePlies;
6323 count = forwardMostMove - count;
6325 epStatus[forwardMostMove] = EP_RULE_DRAW;
6326 /* this is used to judge if draw claims are legal */
6327 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6328 SendToProgram("force\n", cps->other); // suppress reply
6329 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6330 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6331 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6335 /* if draw offer is pending, treat it as a draw claim
6336 * when draw condition present, to allow engines a way to
6337 * claim draws before making their move to avoid a race
6338 * condition occurring after their move
6340 if( cps->other->offeredDraw || cps->offeredDraw ) {
6342 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6343 p = "Draw claim: 50-move rule";
6344 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6345 p = "Draw claim: 3-fold repetition";
6346 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6347 p = "Draw claim: insufficient mating material";
6349 SendToProgram("force\n", cps->other); // suppress reply
6350 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6351 GameEnds( GameIsDrawn, p, GE_XBOARD );
6352 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6358 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6359 SendToProgram("force\n", cps->other); // suppress reply
6360 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6361 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6363 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6370 if (gameMode == TwoMachinesPlay) {
6371 /* [HGM] relaying draw offers moved to after reception of move */
6372 /* and interpreting offer as claim if it brings draw condition */
6373 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6374 SendToProgram("draw\n", cps->other);
6376 if (cps->other->sendTime) {
6377 SendTimeRemaining(cps->other,
6378 cps->other->twoMachinesColor[0] == 'w');
6380 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6381 if (firstMove && !bookHit) {
6383 if (cps->other->useColors) {
6384 SendToProgram(cps->other->twoMachinesColor, cps->other);
6386 SendToProgram("go\n", cps->other);
6388 cps->other->maybeThinking = TRUE;
6391 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6393 if (!pausing && appData.ringBellAfterMoves) {
6398 * Reenable menu items that were disabled while
6399 * machine was thinking
6401 if (gameMode != TwoMachinesPlay)
6402 SetUserThinkingEnables();
6404 // [HGM] book: after book hit opponent has received move and is now in force mode
6405 // force the book reply into it, and then fake that it outputted this move by jumping
6406 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6408 static char bookMove[MSG_SIZ]; // a bit generous?
6410 strcpy(bookMove, "move ");
6411 strcat(bookMove, bookHit);
6414 programStats.nodes = programStats.depth = programStats.time =
6415 programStats.score = programStats.got_only_move = 0;
6416 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6418 if(cps->lastPing != cps->lastPong) {
6419 savedMessage = message; // args for deferred call
6421 ScheduleDelayedEvent(DeferredBookMove, 10);
6430 /* Set special modes for chess engines. Later something general
6431 * could be added here; for now there is just one kludge feature,
6432 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6433 * when "xboard" is given as an interactive command.
6435 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6436 cps->useSigint = FALSE;
6437 cps->useSigterm = FALSE;
6439 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6440 ParseFeatures(message+8, cps);
6441 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6444 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6445 * want this, I was asked to put it in, and obliged.
6447 if (!strncmp(message, "setboard ", 9)) {
6448 Board initial_position; int i;
6450 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6452 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6453 DisplayError(_("Bad FEN received from engine"), 0);
6457 CopyBoard(boards[0], initial_position);
6458 initialRulePlies = FENrulePlies;
6459 epStatus[0] = FENepStatus;
6460 for( i=0; i<nrCastlingRights; i++ )
6461 castlingRights[0][i] = FENcastlingRights[i];
6462 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6463 else gameMode = MachinePlaysBlack;
6464 DrawPosition(FALSE, boards[currentMove]);
6470 * Look for communication commands
6472 if (!strncmp(message, "telluser ", 9)) {
6473 DisplayNote(message + 9);
6476 if (!strncmp(message, "tellusererror ", 14)) {
6477 DisplayError(message + 14, 0);
6480 if (!strncmp(message, "tellopponent ", 13)) {
6481 if (appData.icsActive) {
6483 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6487 DisplayNote(message + 13);
6491 if (!strncmp(message, "tellothers ", 11)) {
6492 if (appData.icsActive) {
6494 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6500 if (!strncmp(message, "tellall ", 8)) {
6501 if (appData.icsActive) {
6503 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6507 DisplayNote(message + 8);
6511 if (strncmp(message, "warning", 7) == 0) {
6512 /* Undocumented feature, use tellusererror in new code */
6513 DisplayError(message, 0);
6516 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6517 strcpy(realname, cps->tidy);
6518 strcat(realname, " query");
6519 AskQuestion(realname, buf2, buf1, cps->pr);
6522 /* Commands from the engine directly to ICS. We don't allow these to be
6523 * sent until we are logged on. Crafty kibitzes have been known to
6524 * interfere with the login process.
6527 if (!strncmp(message, "tellics ", 8)) {
6528 SendToICS(message + 8);
6532 if (!strncmp(message, "tellicsnoalias ", 15)) {
6533 SendToICS(ics_prefix);
6534 SendToICS(message + 15);
6538 /* The following are for backward compatibility only */
6539 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6540 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6541 SendToICS(ics_prefix);
6547 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6551 * If the move is illegal, cancel it and redraw the board.
6552 * Also deal with other error cases. Matching is rather loose
6553 * here to accommodate engines written before the spec.
6555 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6556 strncmp(message, "Error", 5) == 0) {
6557 if (StrStr(message, "name") ||
6558 StrStr(message, "rating") || StrStr(message, "?") ||
6559 StrStr(message, "result") || StrStr(message, "board") ||
6560 StrStr(message, "bk") || StrStr(message, "computer") ||
6561 StrStr(message, "variant") || StrStr(message, "hint") ||
6562 StrStr(message, "random") || StrStr(message, "depth") ||
6563 StrStr(message, "accepted")) {
6566 if (StrStr(message, "protover")) {
6567 /* Program is responding to input, so it's apparently done
6568 initializing, and this error message indicates it is
6569 protocol version 1. So we don't need to wait any longer
6570 for it to initialize and send feature commands. */
6571 FeatureDone(cps, 1);
6572 cps->protocolVersion = 1;
6575 cps->maybeThinking = FALSE;
6577 if (StrStr(message, "draw")) {
6578 /* Program doesn't have "draw" command */
6579 cps->sendDrawOffers = 0;
6582 if (cps->sendTime != 1 &&
6583 (StrStr(message, "time") || StrStr(message, "otim"))) {
6584 /* Program apparently doesn't have "time" or "otim" command */
6588 if (StrStr(message, "analyze")) {
6589 cps->analysisSupport = FALSE;
6590 cps->analyzing = FALSE;
6592 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6593 DisplayError(buf2, 0);
6596 if (StrStr(message, "(no matching move)st")) {
6597 /* Special kludge for GNU Chess 4 only */
6598 cps->stKludge = TRUE;
6599 SendTimeControl(cps, movesPerSession, timeControl,
6600 timeIncrement, appData.searchDepth,
6604 if (StrStr(message, "(no matching move)sd")) {
6605 /* Special kludge for GNU Chess 4 only */
6606 cps->sdKludge = TRUE;
6607 SendTimeControl(cps, movesPerSession, timeControl,
6608 timeIncrement, appData.searchDepth,
6612 if (!StrStr(message, "llegal")) {
6615 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6616 gameMode == IcsIdle) return;
6617 if (forwardMostMove <= backwardMostMove) return;
6618 if (pausing) PauseEvent();
6619 if(appData.forceIllegal) {
6620 // [HGM] illegal: machine refused move; force position after move into it
6621 SendToProgram("force\n", cps);
6622 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6623 // we have a real problem now, as SendBoard will use the a2a3 kludge
6624 // when black is to move, while there might be nothing on a2 or black
6625 // might already have the move. So send the board as if white has the move.
6626 // But first we must change the stm of the engine, as it refused the last move
6627 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6628 if(WhiteOnMove(forwardMostMove)) {
6629 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6630 SendBoard(cps, forwardMostMove); // kludgeless board
6632 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6633 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6634 SendBoard(cps, forwardMostMove+1); // kludgeless board
6636 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6637 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6638 gameMode == TwoMachinesPlay)
6639 SendToProgram("go\n", cps);
6642 if (gameMode == PlayFromGameFile) {
6643 /* Stop reading this game file */
6644 gameMode = EditGame;
6647 currentMove = --forwardMostMove;
6648 DisplayMove(currentMove-1); /* before DisplayMoveError */
6650 DisplayBothClocks();
6651 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6652 parseList[currentMove], cps->which);
6653 DisplayMoveError(buf1);
6654 DrawPosition(FALSE, boards[currentMove]);
6656 /* [HGM] illegal-move claim should forfeit game when Xboard */
6657 /* only passes fully legal moves */
6658 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6659 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6660 "False illegal-move claim", GE_XBOARD );
6664 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6665 /* Program has a broken "time" command that
6666 outputs a string not ending in newline.
6672 * If chess program startup fails, exit with an error message.
6673 * Attempts to recover here are futile.
6675 if ((StrStr(message, "unknown host") != NULL)
6676 || (StrStr(message, "No remote directory") != NULL)
6677 || (StrStr(message, "not found") != NULL)
6678 || (StrStr(message, "No such file") != NULL)
6679 || (StrStr(message, "can't alloc") != NULL)
6680 || (StrStr(message, "Permission denied") != NULL)) {
6682 cps->maybeThinking = FALSE;
6683 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6684 cps->which, cps->program, cps->host, message);
6685 RemoveInputSource(cps->isr);
6686 DisplayFatalError(buf1, 0, 1);
6691 * Look for hint output
6693 if (sscanf(message, "Hint: %s", buf1) == 1) {
6694 if (cps == &first && hintRequested) {
6695 hintRequested = FALSE;
6696 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6697 &fromX, &fromY, &toX, &toY, &promoChar)) {
6698 (void) CoordsToAlgebraic(boards[forwardMostMove],
6699 PosFlags(forwardMostMove), EP_UNKNOWN,
6700 fromY, fromX, toY, toX, promoChar, buf1);
6701 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6702 DisplayInformation(buf2);
6704 /* Hint move could not be parsed!? */
6705 snprintf(buf2, sizeof(buf2),
6706 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6708 DisplayError(buf2, 0);
6711 strcpy(lastHint, buf1);
6717 * Ignore other messages if game is not in progress
6719 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6720 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6723 * look for win, lose, draw, or draw offer
6725 if (strncmp(message, "1-0", 3) == 0) {
6726 char *p, *q, *r = "";
6727 p = strchr(message, '{');
6735 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6737 } else if (strncmp(message, "0-1", 3) == 0) {
6738 char *p, *q, *r = "";
6739 p = strchr(message, '{');
6747 /* Kludge for Arasan 4.1 bug */
6748 if (strcmp(r, "Black resigns") == 0) {
6749 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6752 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6754 } else if (strncmp(message, "1/2", 3) == 0) {
6755 char *p, *q, *r = "";
6756 p = strchr(message, '{');
6765 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6768 } else if (strncmp(message, "White resign", 12) == 0) {
6769 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6771 } else if (strncmp(message, "Black resign", 12) == 0) {
6772 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6774 } else if (strncmp(message, "White matches", 13) == 0 ||
6775 strncmp(message, "Black matches", 13) == 0 ) {
6776 /* [HGM] ignore GNUShogi noises */
6778 } else if (strncmp(message, "White", 5) == 0 &&
6779 message[5] != '(' &&
6780 StrStr(message, "Black") == NULL) {
6781 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6783 } else if (strncmp(message, "Black", 5) == 0 &&
6784 message[5] != '(') {
6785 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6787 } else if (strcmp(message, "resign") == 0 ||
6788 strcmp(message, "computer resigns") == 0) {
6790 case MachinePlaysBlack:
6791 case IcsPlayingBlack:
6792 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6794 case MachinePlaysWhite:
6795 case IcsPlayingWhite:
6796 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6798 case TwoMachinesPlay:
6799 if (cps->twoMachinesColor[0] == 'w')
6800 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6802 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6809 } else if (strncmp(message, "opponent mates", 14) == 0) {
6811 case MachinePlaysBlack:
6812 case IcsPlayingBlack:
6813 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6815 case MachinePlaysWhite:
6816 case IcsPlayingWhite:
6817 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6819 case TwoMachinesPlay:
6820 if (cps->twoMachinesColor[0] == 'w')
6821 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6823 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6830 } else if (strncmp(message, "computer mates", 14) == 0) {
6832 case MachinePlaysBlack:
6833 case IcsPlayingBlack:
6834 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6836 case MachinePlaysWhite:
6837 case IcsPlayingWhite:
6838 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6840 case TwoMachinesPlay:
6841 if (cps->twoMachinesColor[0] == 'w')
6842 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6844 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6851 } else if (strncmp(message, "checkmate", 9) == 0) {
6852 if (WhiteOnMove(forwardMostMove)) {
6853 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6855 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6858 } else if (strstr(message, "Draw") != NULL ||
6859 strstr(message, "game is a draw") != NULL) {
6860 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6862 } else if (strstr(message, "offer") != NULL &&
6863 strstr(message, "draw") != NULL) {
6865 if (appData.zippyPlay && first.initDone) {
6866 /* Relay offer to ICS */
6867 SendToICS(ics_prefix);
6868 SendToICS("draw\n");
6871 cps->offeredDraw = 2; /* valid until this engine moves twice */
6872 if (gameMode == TwoMachinesPlay) {
6873 if (cps->other->offeredDraw) {
6874 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6875 /* [HGM] in two-machine mode we delay relaying draw offer */
6876 /* until after we also have move, to see if it is really claim */
6878 } else if (gameMode == MachinePlaysWhite ||
6879 gameMode == MachinePlaysBlack) {
6880 if (userOfferedDraw) {
6881 DisplayInformation(_("Machine accepts your draw offer"));
6882 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6884 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6891 * Look for thinking output
6893 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6894 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6896 int plylev, mvleft, mvtot, curscore, time;
6897 char mvname[MOVE_LEN];
6901 int prefixHint = FALSE;
6902 mvname[0] = NULLCHAR;
6905 case MachinePlaysBlack:
6906 case IcsPlayingBlack:
6907 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6909 case MachinePlaysWhite:
6910 case IcsPlayingWhite:
6911 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6916 case IcsObserving: /* [DM] icsEngineAnalyze */
6917 if (!appData.icsEngineAnalyze) ignore = TRUE;
6919 case TwoMachinesPlay:
6920 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6931 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6932 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6934 if (plyext != ' ' && plyext != '\t') {
6938 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6939 if( cps->scoreIsAbsolute &&
6940 ( gameMode == MachinePlaysBlack ||
6941 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6942 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6943 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6944 !WhiteOnMove(currentMove)
6947 curscore = -curscore;
6951 programStats.depth = plylev;
6952 programStats.nodes = nodes;
6953 programStats.time = time;
6954 programStats.score = curscore;
6955 programStats.got_only_move = 0;
6957 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6960 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6961 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6962 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6963 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6964 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6965 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6966 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6967 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6970 /* Buffer overflow protection */
6971 if (buf1[0] != NULLCHAR) {
6972 if (strlen(buf1) >= sizeof(programStats.movelist)
6973 && appData.debugMode) {
6975 "PV is too long; using the first %u bytes.\n",
6976 (unsigned) sizeof(programStats.movelist) - 1);
6979 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6981 sprintf(programStats.movelist, " no PV\n");
6984 if (programStats.seen_stat) {
6985 programStats.ok_to_send = 1;
6988 if (strchr(programStats.movelist, '(') != NULL) {
6989 programStats.line_is_book = 1;
6990 programStats.nr_moves = 0;
6991 programStats.moves_left = 0;
6993 programStats.line_is_book = 0;
6996 SendProgramStatsToFrontend( cps, &programStats );
6999 [AS] Protect the thinkOutput buffer from overflow... this
7000 is only useful if buf1 hasn't overflowed first!
7002 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7004 (gameMode == TwoMachinesPlay ?
7005 ToUpper(cps->twoMachinesColor[0]) : ' '),
7006 ((double) curscore) / 100.0,
7007 prefixHint ? lastHint : "",
7008 prefixHint ? " " : "" );
7010 if( buf1[0] != NULLCHAR ) {
7011 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7013 if( strlen(buf1) > max_len ) {
7014 if( appData.debugMode) {
7015 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7017 buf1[max_len+1] = '\0';
7020 strcat( thinkOutput, buf1 );
7023 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7024 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7025 DisplayMove(currentMove - 1);
7029 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7030 /* crafty (9.25+) says "(only move) <move>"
7031 * if there is only 1 legal move
7033 sscanf(p, "(only move) %s", buf1);
7034 sprintf(thinkOutput, "%s (only move)", buf1);
7035 sprintf(programStats.movelist, "%s (only move)", buf1);
7036 programStats.depth = 1;
7037 programStats.nr_moves = 1;
7038 programStats.moves_left = 1;
7039 programStats.nodes = 1;
7040 programStats.time = 1;
7041 programStats.got_only_move = 1;
7043 /* Not really, but we also use this member to
7044 mean "line isn't going to change" (Crafty
7045 isn't searching, so stats won't change) */
7046 programStats.line_is_book = 1;
7048 SendProgramStatsToFrontend( cps, &programStats );
7050 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7051 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7052 DisplayMove(currentMove - 1);
7055 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7056 &time, &nodes, &plylev, &mvleft,
7057 &mvtot, mvname) >= 5) {
7058 /* The stat01: line is from Crafty (9.29+) in response
7059 to the "." command */
7060 programStats.seen_stat = 1;
7061 cps->maybeThinking = TRUE;
7063 if (programStats.got_only_move || !appData.periodicUpdates)
7066 programStats.depth = plylev;
7067 programStats.time = time;
7068 programStats.nodes = nodes;
7069 programStats.moves_left = mvleft;
7070 programStats.nr_moves = mvtot;
7071 strcpy(programStats.move_name, mvname);
7072 programStats.ok_to_send = 1;
7073 programStats.movelist[0] = '\0';
7075 SendProgramStatsToFrontend( cps, &programStats );
7079 } else if (strncmp(message,"++",2) == 0) {
7080 /* Crafty 9.29+ outputs this */
7081 programStats.got_fail = 2;
7084 } else if (strncmp(message,"--",2) == 0) {
7085 /* Crafty 9.29+ outputs this */
7086 programStats.got_fail = 1;
7089 } else if (thinkOutput[0] != NULLCHAR &&
7090 strncmp(message, " ", 4) == 0) {
7091 unsigned message_len;
7094 while (*p && *p == ' ') p++;
7096 message_len = strlen( p );
7098 /* [AS] Avoid buffer overflow */
7099 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7100 strcat(thinkOutput, " ");
7101 strcat(thinkOutput, p);
7104 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7105 strcat(programStats.movelist, " ");
7106 strcat(programStats.movelist, p);
7109 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7110 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7111 DisplayMove(currentMove - 1);
7119 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7120 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7122 ChessProgramStats cpstats;
7124 if (plyext != ' ' && plyext != '\t') {
7128 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7129 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7130 curscore = -curscore;
7133 cpstats.depth = plylev;
7134 cpstats.nodes = nodes;
7135 cpstats.time = time;
7136 cpstats.score = curscore;
7137 cpstats.got_only_move = 0;
7138 cpstats.movelist[0] = '\0';
7140 if (buf1[0] != NULLCHAR) {
7141 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7144 cpstats.ok_to_send = 0;
7145 cpstats.line_is_book = 0;
7146 cpstats.nr_moves = 0;
7147 cpstats.moves_left = 0;
7149 SendProgramStatsToFrontend( cps, &cpstats );
7156 /* Parse a game score from the character string "game", and
7157 record it as the history of the current game. The game
7158 score is NOT assumed to start from the standard position.
7159 The display is not updated in any way.
7162 ParseGameHistory(game)
7166 int fromX, fromY, toX, toY, boardIndex;
7171 if (appData.debugMode)
7172 fprintf(debugFP, "Parsing game history: %s\n", game);
7174 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7175 gameInfo.site = StrSave(appData.icsHost);
7176 gameInfo.date = PGNDate();
7177 gameInfo.round = StrSave("-");
7179 /* Parse out names of players */
7180 while (*game == ' ') game++;
7182 while (*game != ' ') *p++ = *game++;
7184 gameInfo.white = StrSave(buf);
7185 while (*game == ' ') game++;
7187 while (*game != ' ' && *game != '\n') *p++ = *game++;
7189 gameInfo.black = StrSave(buf);
7192 boardIndex = blackPlaysFirst ? 1 : 0;
7195 yyboardindex = boardIndex;
7196 moveType = (ChessMove) yylex();
7198 case IllegalMove: /* maybe suicide chess, etc. */
7199 if (appData.debugMode) {
7200 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7201 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7202 setbuf(debugFP, NULL);
7204 case WhitePromotionChancellor:
7205 case BlackPromotionChancellor:
7206 case WhitePromotionArchbishop:
7207 case BlackPromotionArchbishop:
7208 case WhitePromotionQueen:
7209 case BlackPromotionQueen:
7210 case WhitePromotionRook:
7211 case BlackPromotionRook:
7212 case WhitePromotionBishop:
7213 case BlackPromotionBishop:
7214 case WhitePromotionKnight:
7215 case BlackPromotionKnight:
7216 case WhitePromotionKing:
7217 case BlackPromotionKing:
7219 case WhiteCapturesEnPassant:
7220 case BlackCapturesEnPassant:
7221 case WhiteKingSideCastle:
7222 case WhiteQueenSideCastle:
7223 case BlackKingSideCastle:
7224 case BlackQueenSideCastle:
7225 case WhiteKingSideCastleWild:
7226 case WhiteQueenSideCastleWild:
7227 case BlackKingSideCastleWild:
7228 case BlackQueenSideCastleWild:
7230 case WhiteHSideCastleFR:
7231 case WhiteASideCastleFR:
7232 case BlackHSideCastleFR:
7233 case BlackASideCastleFR:
7235 fromX = currentMoveString[0] - AAA;
7236 fromY = currentMoveString[1] - ONE;
7237 toX = currentMoveString[2] - AAA;
7238 toY = currentMoveString[3] - ONE;
7239 promoChar = currentMoveString[4];
7243 fromX = moveType == WhiteDrop ?
7244 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7245 (int) CharToPiece(ToLower(currentMoveString[0]));
7247 toX = currentMoveString[2] - AAA;
7248 toY = currentMoveString[3] - ONE;
7249 promoChar = NULLCHAR;
7253 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7254 if (appData.debugMode) {
7255 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7256 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7257 setbuf(debugFP, NULL);
7259 DisplayError(buf, 0);
7261 case ImpossibleMove:
7263 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7264 if (appData.debugMode) {
7265 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7266 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7267 setbuf(debugFP, NULL);
7269 DisplayError(buf, 0);
7271 case (ChessMove) 0: /* end of file */
7272 if (boardIndex < backwardMostMove) {
7273 /* Oops, gap. How did that happen? */
7274 DisplayError(_("Gap in move list"), 0);
7277 backwardMostMove = blackPlaysFirst ? 1 : 0;
7278 if (boardIndex > forwardMostMove) {
7279 forwardMostMove = boardIndex;
7283 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7284 strcat(parseList[boardIndex-1], " ");
7285 strcat(parseList[boardIndex-1], yy_text);
7297 case GameUnfinished:
7298 if (gameMode == IcsExamining) {
7299 if (boardIndex < backwardMostMove) {
7300 /* Oops, gap. How did that happen? */
7303 backwardMostMove = blackPlaysFirst ? 1 : 0;
7306 gameInfo.result = moveType;
7307 p = strchr(yy_text, '{');
7308 if (p == NULL) p = strchr(yy_text, '(');
7311 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7313 q = strchr(p, *p == '{' ? '}' : ')');
7314 if (q != NULL) *q = NULLCHAR;
7317 gameInfo.resultDetails = StrSave(p);
7320 if (boardIndex >= forwardMostMove &&
7321 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7322 backwardMostMove = blackPlaysFirst ? 1 : 0;
7325 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7326 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7327 parseList[boardIndex]);
7328 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7329 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7330 /* currentMoveString is set as a side-effect of yylex */
7331 strcpy(moveList[boardIndex], currentMoveString);
7332 strcat(moveList[boardIndex], "\n");
7334 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7335 castlingRights[boardIndex], &epStatus[boardIndex]);
7336 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7337 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7343 if(gameInfo.variant != VariantShogi)
7344 strcat(parseList[boardIndex - 1], "+");
7348 strcat(parseList[boardIndex - 1], "#");
7355 /* Apply a move to the given board */
7357 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7358 int fromX, fromY, toX, toY;
7364 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7366 /* [HGM] compute & store e.p. status and castling rights for new position */
7367 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7370 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7374 if( board[toY][toX] != EmptySquare )
7377 if( board[fromY][fromX] == WhitePawn ) {
7378 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7381 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7382 gameInfo.variant != VariantBerolina || toX < fromX)
7383 *ep = toX | berolina;
7384 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7385 gameInfo.variant != VariantBerolina || toX > fromX)
7389 if( board[fromY][fromX] == BlackPawn ) {
7390 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7392 if( toY-fromY== -2) {
7393 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7394 gameInfo.variant != VariantBerolina || toX < fromX)
7395 *ep = toX | berolina;
7396 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7397 gameInfo.variant != VariantBerolina || toX > fromX)
7402 for(i=0; i<nrCastlingRights; i++) {
7403 if(castling[i] == fromX && castlingRank[i] == fromY ||
7404 castling[i] == toX && castlingRank[i] == toY
7405 ) castling[i] = -1; // revoke for moved or captured piece
7410 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7411 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7412 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7414 if (fromX == toX && fromY == toY) return;
7416 if (fromY == DROP_RANK) {
7418 piece = board[toY][toX] = (ChessSquare) fromX;
7420 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7421 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7422 if(gameInfo.variant == VariantKnightmate)
7423 king += (int) WhiteUnicorn - (int) WhiteKing;
7425 /* Code added by Tord: */
7426 /* FRC castling assumed when king captures friendly rook. */
7427 if (board[fromY][fromX] == WhiteKing &&
7428 board[toY][toX] == WhiteRook) {
7429 board[fromY][fromX] = EmptySquare;
7430 board[toY][toX] = EmptySquare;
7432 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7434 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7436 } else if (board[fromY][fromX] == BlackKing &&
7437 board[toY][toX] == BlackRook) {
7438 board[fromY][fromX] = EmptySquare;
7439 board[toY][toX] = EmptySquare;
7441 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7443 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7445 /* End of code added by Tord */
7447 } else if (board[fromY][fromX] == king
7448 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7449 && toY == fromY && toX > fromX+1) {
7450 board[fromY][fromX] = EmptySquare;
7451 board[toY][toX] = king;
7452 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7453 board[fromY][BOARD_RGHT-1] = EmptySquare;
7454 } else if (board[fromY][fromX] == king
7455 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7456 && toY == fromY && toX < fromX-1) {
7457 board[fromY][fromX] = EmptySquare;
7458 board[toY][toX] = king;
7459 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7460 board[fromY][BOARD_LEFT] = EmptySquare;
7461 } else if (board[fromY][fromX] == WhitePawn
7462 && toY == BOARD_HEIGHT-1
7463 && gameInfo.variant != VariantXiangqi
7465 /* white pawn promotion */
7466 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7467 if (board[toY][toX] == EmptySquare) {
7468 board[toY][toX] = WhiteQueen;
7470 if(gameInfo.variant==VariantBughouse ||
7471 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7472 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7473 board[fromY][fromX] = EmptySquare;
7474 } else if ((fromY == BOARD_HEIGHT-4)
7476 && gameInfo.variant != VariantXiangqi
7477 && gameInfo.variant != VariantBerolina
7478 && (board[fromY][fromX] == WhitePawn)
7479 && (board[toY][toX] == EmptySquare)) {
7480 board[fromY][fromX] = EmptySquare;
7481 board[toY][toX] = WhitePawn;
7482 captured = board[toY - 1][toX];
7483 board[toY - 1][toX] = EmptySquare;
7484 } else if ((fromY == BOARD_HEIGHT-4)
7486 && gameInfo.variant == VariantBerolina
7487 && (board[fromY][fromX] == WhitePawn)
7488 && (board[toY][toX] == EmptySquare)) {
7489 board[fromY][fromX] = EmptySquare;
7490 board[toY][toX] = WhitePawn;
7491 if(oldEP & EP_BEROLIN_A) {
7492 captured = board[fromY][fromX-1];
7493 board[fromY][fromX-1] = EmptySquare;
7494 }else{ captured = board[fromY][fromX+1];
7495 board[fromY][fromX+1] = EmptySquare;
7497 } else if (board[fromY][fromX] == king
7498 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7499 && toY == fromY && toX > fromX+1) {
7500 board[fromY][fromX] = EmptySquare;
7501 board[toY][toX] = king;
7502 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7503 board[fromY][BOARD_RGHT-1] = EmptySquare;
7504 } else if (board[fromY][fromX] == king
7505 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7506 && toY == fromY && toX < fromX-1) {
7507 board[fromY][fromX] = EmptySquare;
7508 board[toY][toX] = king;
7509 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7510 board[fromY][BOARD_LEFT] = EmptySquare;
7511 } else if (fromY == 7 && fromX == 3
7512 && board[fromY][fromX] == BlackKing
7513 && toY == 7 && toX == 5) {
7514 board[fromY][fromX] = EmptySquare;
7515 board[toY][toX] = BlackKing;
7516 board[fromY][7] = EmptySquare;
7517 board[toY][4] = BlackRook;
7518 } else if (fromY == 7 && fromX == 3
7519 && board[fromY][fromX] == BlackKing
7520 && toY == 7 && toX == 1) {
7521 board[fromY][fromX] = EmptySquare;
7522 board[toY][toX] = BlackKing;
7523 board[fromY][0] = EmptySquare;
7524 board[toY][2] = BlackRook;
7525 } else if (board[fromY][fromX] == BlackPawn
7527 && gameInfo.variant != VariantXiangqi
7529 /* black pawn promotion */
7530 board[0][toX] = CharToPiece(ToLower(promoChar));
7531 if (board[0][toX] == EmptySquare) {
7532 board[0][toX] = BlackQueen;
7534 if(gameInfo.variant==VariantBughouse ||
7535 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7536 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7537 board[fromY][fromX] = EmptySquare;
7538 } else if ((fromY == 3)
7540 && gameInfo.variant != VariantXiangqi
7541 && gameInfo.variant != VariantBerolina
7542 && (board[fromY][fromX] == BlackPawn)
7543 && (board[toY][toX] == EmptySquare)) {
7544 board[fromY][fromX] = EmptySquare;
7545 board[toY][toX] = BlackPawn;
7546 captured = board[toY + 1][toX];
7547 board[toY + 1][toX] = EmptySquare;
7548 } else if ((fromY == 3)
7550 && gameInfo.variant == VariantBerolina
7551 && (board[fromY][fromX] == BlackPawn)
7552 && (board[toY][toX] == EmptySquare)) {
7553 board[fromY][fromX] = EmptySquare;
7554 board[toY][toX] = BlackPawn;
7555 if(oldEP & EP_BEROLIN_A) {
7556 captured = board[fromY][fromX-1];
7557 board[fromY][fromX-1] = EmptySquare;
7558 }else{ captured = board[fromY][fromX+1];
7559 board[fromY][fromX+1] = EmptySquare;
7562 board[toY][toX] = board[fromY][fromX];
7563 board[fromY][fromX] = EmptySquare;
7566 /* [HGM] now we promote for Shogi, if needed */
7567 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7568 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7571 if (gameInfo.holdingsWidth != 0) {
7573 /* !!A lot more code needs to be written to support holdings */
7574 /* [HGM] OK, so I have written it. Holdings are stored in the */
7575 /* penultimate board files, so they are automaticlly stored */
7576 /* in the game history. */
7577 if (fromY == DROP_RANK) {
7578 /* Delete from holdings, by decreasing count */
7579 /* and erasing image if necessary */
7581 if(p < (int) BlackPawn) { /* white drop */
7582 p -= (int)WhitePawn;
7583 p = PieceToNumber((ChessSquare)p);
7584 if(p >= gameInfo.holdingsSize) p = 0;
7585 if(--board[p][BOARD_WIDTH-2] <= 0)
7586 board[p][BOARD_WIDTH-1] = EmptySquare;
7587 if((int)board[p][BOARD_WIDTH-2] < 0)
7588 board[p][BOARD_WIDTH-2] = 0;
7589 } else { /* black drop */
7590 p -= (int)BlackPawn;
7591 p = PieceToNumber((ChessSquare)p);
7592 if(p >= gameInfo.holdingsSize) p = 0;
7593 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7594 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7595 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7596 board[BOARD_HEIGHT-1-p][1] = 0;
7599 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7600 && gameInfo.variant != VariantBughouse ) {
7601 /* [HGM] holdings: Add to holdings, if holdings exist */
7602 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7603 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7604 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7607 if (p >= (int) BlackPawn) {
7608 p -= (int)BlackPawn;
7609 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7610 /* in Shogi restore piece to its original first */
7611 captured = (ChessSquare) (DEMOTED captured);
7614 p = PieceToNumber((ChessSquare)p);
7615 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7616 board[p][BOARD_WIDTH-2]++;
7617 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7619 p -= (int)WhitePawn;
7620 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7621 captured = (ChessSquare) (DEMOTED captured);
7624 p = PieceToNumber((ChessSquare)p);
7625 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7626 board[BOARD_HEIGHT-1-p][1]++;
7627 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7630 } else if (gameInfo.variant == VariantAtomic) {
7631 if (captured != EmptySquare) {
7633 for (y = toY-1; y <= toY+1; y++) {
7634 for (x = toX-1; x <= toX+1; x++) {
7635 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7636 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7637 board[y][x] = EmptySquare;
7641 board[toY][toX] = EmptySquare;
7644 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7645 /* [HGM] Shogi promotions */
7646 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7649 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7650 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7651 // [HGM] superchess: take promotion piece out of holdings
7652 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7653 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7654 if(!--board[k][BOARD_WIDTH-2])
7655 board[k][BOARD_WIDTH-1] = EmptySquare;
7657 if(!--board[BOARD_HEIGHT-1-k][1])
7658 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7664 /* Updates forwardMostMove */
7666 MakeMove(fromX, fromY, toX, toY, promoChar)
7667 int fromX, fromY, toX, toY;
7670 // forwardMostMove++; // [HGM] bare: moved downstream
7672 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7673 int timeLeft; static int lastLoadFlag=0; int king, piece;
7674 piece = boards[forwardMostMove][fromY][fromX];
7675 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7676 if(gameInfo.variant == VariantKnightmate)
7677 king += (int) WhiteUnicorn - (int) WhiteKing;
7678 if(forwardMostMove == 0) {
7680 fprintf(serverMoves, "%s;", second.tidy);
7681 fprintf(serverMoves, "%s;", first.tidy);
7682 if(!blackPlaysFirst)
7683 fprintf(serverMoves, "%s;", second.tidy);
7684 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7685 lastLoadFlag = loadFlag;
7687 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7688 // print castling suffix
7689 if( toY == fromY && piece == king ) {
7691 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7693 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7696 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7697 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7698 boards[forwardMostMove][toY][toX] == EmptySquare
7700 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7702 if(promoChar != NULLCHAR)
7703 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7705 fprintf(serverMoves, "/%d/%d",
7706 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7707 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7708 else timeLeft = blackTimeRemaining/1000;
7709 fprintf(serverMoves, "/%d", timeLeft);
7711 fflush(serverMoves);
7714 if (forwardMostMove+1 >= MAX_MOVES) {
7715 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7719 if (commentList[forwardMostMove+1] != NULL) {
7720 free(commentList[forwardMostMove+1]);
7721 commentList[forwardMostMove+1] = NULL;
7723 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7724 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7725 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7726 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7727 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7728 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7729 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7730 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7731 gameInfo.result = GameUnfinished;
7732 if (gameInfo.resultDetails != NULL) {
7733 free(gameInfo.resultDetails);
7734 gameInfo.resultDetails = NULL;
7736 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7737 moveList[forwardMostMove - 1]);
7738 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7739 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7740 fromY, fromX, toY, toX, promoChar,
7741 parseList[forwardMostMove - 1]);
7742 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7743 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7744 castlingRights[forwardMostMove]) ) {
7750 if(gameInfo.variant != VariantShogi)
7751 strcat(parseList[forwardMostMove - 1], "+");
7755 strcat(parseList[forwardMostMove - 1], "#");
7758 if (appData.debugMode) {
7759 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7764 /* Updates currentMove if not pausing */
7766 ShowMove(fromX, fromY, toX, toY)
7768 int instant = (gameMode == PlayFromGameFile) ?
7769 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7770 if(appData.noGUI) return;
7771 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7773 if (forwardMostMove == currentMove + 1) {
7774 AnimateMove(boards[forwardMostMove - 1],
7775 fromX, fromY, toX, toY);
7777 if (appData.highlightLastMove) {
7778 SetHighlights(fromX, fromY, toX, toY);
7781 currentMove = forwardMostMove;
7784 if (instant) return;
7786 DisplayMove(currentMove - 1);
7787 DrawPosition(FALSE, boards[currentMove]);
7788 DisplayBothClocks();
7789 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7792 void SendEgtPath(ChessProgramState *cps)
7793 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7794 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7796 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7799 char c, *q = name+1, *r, *s;
7801 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7802 while(*p && *p != ',') *q++ = *p++;
7804 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7805 strcmp(name, ",nalimov:") == 0 ) {
7806 // take nalimov path from the menu-changeable option first, if it is defined
7807 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7808 SendToProgram(buf,cps); // send egtbpath command for nalimov
7810 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7811 (s = StrStr(appData.egtFormats, name)) != NULL) {
7812 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7813 s = r = StrStr(s, ":") + 1; // beginning of path info
7814 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7815 c = *r; *r = 0; // temporarily null-terminate path info
7816 *--q = 0; // strip of trailig ':' from name
7817 sprintf(buf, "egtpath %s %s\n", name+1, s);
7819 SendToProgram(buf,cps); // send egtbpath command for this format
7821 if(*p == ',') p++; // read away comma to position for next format name
7826 InitChessProgram(cps, setup)
7827 ChessProgramState *cps;
7828 int setup; /* [HGM] needed to setup FRC opening position */
7830 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7831 if (appData.noChessProgram) return;
7832 hintRequested = FALSE;
7833 bookRequested = FALSE;
7835 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7836 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7837 if(cps->memSize) { /* [HGM] memory */
7838 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7839 SendToProgram(buf, cps);
7841 SendEgtPath(cps); /* [HGM] EGT */
7842 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7843 sprintf(buf, "cores %d\n", appData.smpCores);
7844 SendToProgram(buf, cps);
7847 SendToProgram(cps->initString, cps);
7848 if (gameInfo.variant != VariantNormal &&
7849 gameInfo.variant != VariantLoadable
7850 /* [HGM] also send variant if board size non-standard */
7851 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7853 char *v = VariantName(gameInfo.variant);
7854 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7855 /* [HGM] in protocol 1 we have to assume all variants valid */
7856 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7857 DisplayFatalError(buf, 0, 1);
7861 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7862 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7863 if( gameInfo.variant == VariantXiangqi )
7864 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7865 if( gameInfo.variant == VariantShogi )
7866 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7867 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7868 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7869 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7870 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7871 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7872 if( gameInfo.variant == VariantCourier )
7873 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7874 if( gameInfo.variant == VariantSuper )
7875 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7876 if( gameInfo.variant == VariantGreat )
7877 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7880 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7881 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7882 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7883 if(StrStr(cps->variants, b) == NULL) {
7884 // specific sized variant not known, check if general sizing allowed
7885 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7886 if(StrStr(cps->variants, "boardsize") == NULL) {
7887 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7888 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7889 DisplayFatalError(buf, 0, 1);
7892 /* [HGM] here we really should compare with the maximum supported board size */
7895 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7896 sprintf(buf, "variant %s\n", b);
7897 SendToProgram(buf, cps);
7899 currentlyInitializedVariant = gameInfo.variant;
7901 /* [HGM] send opening position in FRC to first engine */
7903 SendToProgram("force\n", cps);
7905 /* engine is now in force mode! Set flag to wake it up after first move. */
7906 setboardSpoiledMachineBlack = 1;
7910 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7911 SendToProgram(buf, cps);
7913 cps->maybeThinking = FALSE;
7914 cps->offeredDraw = 0;
7915 if (!appData.icsActive) {
7916 SendTimeControl(cps, movesPerSession, timeControl,
7917 timeIncrement, appData.searchDepth,
7920 if (appData.showThinking
7921 // [HGM] thinking: four options require thinking output to be sent
7922 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7924 SendToProgram("post\n", cps);
7926 SendToProgram("hard\n", cps);
7927 if (!appData.ponderNextMove) {
7928 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7929 it without being sure what state we are in first. "hard"
7930 is not a toggle, so that one is OK.
7932 SendToProgram("easy\n", cps);
7935 sprintf(buf, "ping %d\n", ++cps->lastPing);
7936 SendToProgram(buf, cps);
7938 cps->initDone = TRUE;
7943 StartChessProgram(cps)
7944 ChessProgramState *cps;
7949 if (appData.noChessProgram) return;
7950 cps->initDone = FALSE;
7952 if (strcmp(cps->host, "localhost") == 0) {
7953 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7954 } else if (*appData.remoteShell == NULLCHAR) {
7955 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7957 if (*appData.remoteUser == NULLCHAR) {
7958 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7961 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7962 cps->host, appData.remoteUser, cps->program);
7964 err = StartChildProcess(buf, "", &cps->pr);
7968 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7969 DisplayFatalError(buf, err, 1);
7975 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7976 if (cps->protocolVersion > 1) {
7977 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7978 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7979 cps->comboCnt = 0; // and values of combo boxes
7980 SendToProgram(buf, cps);
7982 SendToProgram("xboard\n", cps);
7988 TwoMachinesEventIfReady P((void))
7990 if (first.lastPing != first.lastPong) {
7991 DisplayMessage("", _("Waiting for first chess program"));
7992 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7995 if (second.lastPing != second.lastPong) {
7996 DisplayMessage("", _("Waiting for second chess program"));
7997 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8005 NextMatchGame P((void))
8007 int index; /* [HGM] autoinc: step load index during match */
8009 if (*appData.loadGameFile != NULLCHAR) {
8010 index = appData.loadGameIndex;
8011 if(index < 0) { // [HGM] autoinc
8012 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8013 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8015 LoadGameFromFile(appData.loadGameFile,
8017 appData.loadGameFile, FALSE);
8018 } else if (*appData.loadPositionFile != NULLCHAR) {
8019 index = appData.loadPositionIndex;
8020 if(index < 0) { // [HGM] autoinc
8021 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8022 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8024 LoadPositionFromFile(appData.loadPositionFile,
8026 appData.loadPositionFile);
8028 TwoMachinesEventIfReady();
8031 void UserAdjudicationEvent( int result )
8033 ChessMove gameResult = GameIsDrawn;
8036 gameResult = WhiteWins;
8038 else if( result < 0 ) {
8039 gameResult = BlackWins;
8042 if( gameMode == TwoMachinesPlay ) {
8043 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8048 // [HGM] save: calculate checksum of game to make games easily identifiable
8049 int StringCheckSum(char *s)
8052 if(s==NULL) return 0;
8053 while(*s) i = i*259 + *s++;
8060 for(i=backwardMostMove; i<forwardMostMove; i++) {
8061 sum += pvInfoList[i].depth;
8062 sum += StringCheckSum(parseList[i]);
8063 sum += StringCheckSum(commentList[i]);
8066 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8067 return sum + StringCheckSum(commentList[i]);
8068 } // end of save patch
8071 GameEnds(result, resultDetails, whosays)
8073 char *resultDetails;
8076 GameMode nextGameMode;
8080 if(endingGame) return; /* [HGM] crash: forbid recursion */
8083 if (appData.debugMode) {
8084 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8085 result, resultDetails ? resultDetails : "(null)", whosays);
8088 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8089 /* If we are playing on ICS, the server decides when the
8090 game is over, but the engine can offer to draw, claim
8094 if (appData.zippyPlay && first.initDone) {
8095 if (result == GameIsDrawn) {
8096 /* In case draw still needs to be claimed */
8097 SendToICS(ics_prefix);
8098 SendToICS("draw\n");
8099 } else if (StrCaseStr(resultDetails, "resign")) {
8100 SendToICS(ics_prefix);
8101 SendToICS("resign\n");
8105 endingGame = 0; /* [HGM] crash */
8109 /* If we're loading the game from a file, stop */
8110 if (whosays == GE_FILE) {
8111 (void) StopLoadGameTimer();
8115 /* Cancel draw offers */
8116 first.offeredDraw = second.offeredDraw = 0;
8118 /* If this is an ICS game, only ICS can really say it's done;
8119 if not, anyone can. */
8120 isIcsGame = (gameMode == IcsPlayingWhite ||
8121 gameMode == IcsPlayingBlack ||
8122 gameMode == IcsObserving ||
8123 gameMode == IcsExamining);
8125 if (!isIcsGame || whosays == GE_ICS) {
8126 /* OK -- not an ICS game, or ICS said it was done */
8128 if (!isIcsGame && !appData.noChessProgram)
8129 SetUserThinkingEnables();
8131 /* [HGM] if a machine claims the game end we verify this claim */
8132 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8133 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8135 ChessMove trueResult = (ChessMove) -1;
8137 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8138 first.twoMachinesColor[0] :
8139 second.twoMachinesColor[0] ;
8141 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8142 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8143 /* [HGM] verify: engine mate claims accepted if they were flagged */
8144 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8146 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8147 /* [HGM] verify: engine mate claims accepted if they were flagged */
8148 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8150 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8151 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8154 // now verify win claims, but not in drop games, as we don't understand those yet
8155 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8156 || gameInfo.variant == VariantGreat) &&
8157 (result == WhiteWins && claimer == 'w' ||
8158 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8159 if (appData.debugMode) {
8160 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8161 result, epStatus[forwardMostMove], forwardMostMove);
8163 if(result != trueResult) {
8164 sprintf(buf, "False win claim: '%s'", resultDetails);
8165 result = claimer == 'w' ? BlackWins : WhiteWins;
8166 resultDetails = buf;
8169 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8170 && (forwardMostMove <= backwardMostMove ||
8171 epStatus[forwardMostMove-1] > EP_DRAWS ||
8172 (claimer=='b')==(forwardMostMove&1))
8174 /* [HGM] verify: draws that were not flagged are false claims */
8175 sprintf(buf, "False draw claim: '%s'", resultDetails);
8176 result = claimer == 'w' ? BlackWins : WhiteWins;
8177 resultDetails = buf;
8179 /* (Claiming a loss is accepted no questions asked!) */
8181 /* [HGM] bare: don't allow bare King to win */
8182 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8183 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8184 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8185 && result != GameIsDrawn)
8186 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8187 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8188 int p = (int)boards[forwardMostMove][i][j] - color;
8189 if(p >= 0 && p <= (int)WhiteKing) k++;
8191 if (appData.debugMode) {
8192 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8193 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8196 result = GameIsDrawn;
8197 sprintf(buf, "%s but bare king", resultDetails);
8198 resultDetails = buf;
8204 if(serverMoves != NULL && !loadFlag) { char c = '=';
8205 if(result==WhiteWins) c = '+';
8206 if(result==BlackWins) c = '-';
8207 if(resultDetails != NULL)
8208 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8210 if (resultDetails != NULL) {
8211 gameInfo.result = result;
8212 gameInfo.resultDetails = StrSave(resultDetails);
8214 /* display last move only if game was not loaded from file */
8215 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8216 DisplayMove(currentMove - 1);
8218 if (forwardMostMove != 0) {
8219 if (gameMode != PlayFromGameFile && gameMode != EditGame
8220 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8222 if (*appData.saveGameFile != NULLCHAR) {
8223 SaveGameToFile(appData.saveGameFile, TRUE);
8224 } else if (appData.autoSaveGames) {
8227 if (*appData.savePositionFile != NULLCHAR) {
8228 SavePositionToFile(appData.savePositionFile);
8233 /* Tell program how game ended in case it is learning */
8234 /* [HGM] Moved this to after saving the PGN, just in case */
8235 /* engine died and we got here through time loss. In that */
8236 /* case we will get a fatal error writing the pipe, which */
8237 /* would otherwise lose us the PGN. */
8238 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8239 /* output during GameEnds should never be fatal anymore */
8240 if (gameMode == MachinePlaysWhite ||
8241 gameMode == MachinePlaysBlack ||
8242 gameMode == TwoMachinesPlay ||
8243 gameMode == IcsPlayingWhite ||
8244 gameMode == IcsPlayingBlack ||
8245 gameMode == BeginningOfGame) {
8247 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8249 if (first.pr != NoProc) {
8250 SendToProgram(buf, &first);
8252 if (second.pr != NoProc &&
8253 gameMode == TwoMachinesPlay) {
8254 SendToProgram(buf, &second);
8259 if (appData.icsActive) {
8260 if (appData.quietPlay &&
8261 (gameMode == IcsPlayingWhite ||
8262 gameMode == IcsPlayingBlack)) {
8263 SendToICS(ics_prefix);
8264 SendToICS("set shout 1\n");
8266 nextGameMode = IcsIdle;
8267 ics_user_moved = FALSE;
8268 /* clean up premove. It's ugly when the game has ended and the
8269 * premove highlights are still on the board.
8273 ClearPremoveHighlights();
8274 DrawPosition(FALSE, boards[currentMove]);
8276 if (whosays == GE_ICS) {
8279 if (gameMode == IcsPlayingWhite)
8281 else if(gameMode == IcsPlayingBlack)
8285 if (gameMode == IcsPlayingBlack)
8287 else if(gameMode == IcsPlayingWhite)
8294 PlayIcsUnfinishedSound();
8297 } else if (gameMode == EditGame ||
8298 gameMode == PlayFromGameFile ||
8299 gameMode == AnalyzeMode ||
8300 gameMode == AnalyzeFile) {
8301 nextGameMode = gameMode;
8303 nextGameMode = EndOfGame;
8308 nextGameMode = gameMode;
8311 if (appData.noChessProgram) {
8312 gameMode = nextGameMode;
8314 endingGame = 0; /* [HGM] crash */
8319 /* Put first chess program into idle state */
8320 if (first.pr != NoProc &&
8321 (gameMode == MachinePlaysWhite ||
8322 gameMode == MachinePlaysBlack ||
8323 gameMode == TwoMachinesPlay ||
8324 gameMode == IcsPlayingWhite ||
8325 gameMode == IcsPlayingBlack ||
8326 gameMode == BeginningOfGame)) {
8327 SendToProgram("force\n", &first);
8328 if (first.usePing) {
8330 sprintf(buf, "ping %d\n", ++first.lastPing);
8331 SendToProgram(buf, &first);
8334 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8335 /* Kill off first chess program */
8336 if (first.isr != NULL)
8337 RemoveInputSource(first.isr);
8340 if (first.pr != NoProc) {
8342 DoSleep( appData.delayBeforeQuit );
8343 SendToProgram("quit\n", &first);
8344 DoSleep( appData.delayAfterQuit );
8345 DestroyChildProcess(first.pr, first.useSigterm);
8350 /* Put second chess program into idle state */
8351 if (second.pr != NoProc &&
8352 gameMode == TwoMachinesPlay) {
8353 SendToProgram("force\n", &second);
8354 if (second.usePing) {
8356 sprintf(buf, "ping %d\n", ++second.lastPing);
8357 SendToProgram(buf, &second);
8360 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8361 /* Kill off second chess program */
8362 if (second.isr != NULL)
8363 RemoveInputSource(second.isr);
8366 if (second.pr != NoProc) {
8367 DoSleep( appData.delayBeforeQuit );
8368 SendToProgram("quit\n", &second);
8369 DoSleep( appData.delayAfterQuit );
8370 DestroyChildProcess(second.pr, second.useSigterm);
8375 if (matchMode && gameMode == TwoMachinesPlay) {
8378 if (first.twoMachinesColor[0] == 'w') {
8385 if (first.twoMachinesColor[0] == 'b') {
8394 if (matchGame < appData.matchGames) {
8396 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8397 tmp = first.twoMachinesColor;
8398 first.twoMachinesColor = second.twoMachinesColor;
8399 second.twoMachinesColor = tmp;
8401 gameMode = nextGameMode;
8403 if(appData.matchPause>10000 || appData.matchPause<10)
8404 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8405 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8406 endingGame = 0; /* [HGM] crash */
8410 gameMode = nextGameMode;
8411 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8412 first.tidy, second.tidy,
8413 first.matchWins, second.matchWins,
8414 appData.matchGames - (first.matchWins + second.matchWins));
8415 DisplayFatalError(buf, 0, 0);
8418 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8419 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8421 gameMode = nextGameMode;
8423 endingGame = 0; /* [HGM] crash */
8426 /* Assumes program was just initialized (initString sent).
8427 Leaves program in force mode. */
8429 FeedMovesToProgram(cps, upto)
8430 ChessProgramState *cps;
8435 if (appData.debugMode)
8436 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8437 startedFromSetupPosition ? "position and " : "",
8438 backwardMostMove, upto, cps->which);
8439 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8440 // [HGM] variantswitch: make engine aware of new variant
8441 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8442 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8443 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8444 SendToProgram(buf, cps);
8445 currentlyInitializedVariant = gameInfo.variant;
8447 SendToProgram("force\n", cps);
8448 if (startedFromSetupPosition) {
8449 SendBoard(cps, backwardMostMove);
8450 if (appData.debugMode) {
8451 fprintf(debugFP, "feedMoves\n");
8454 for (i = backwardMostMove; i < upto; i++) {
8455 SendMoveToProgram(i, cps);
8461 ResurrectChessProgram()
8463 /* The chess program may have exited.
8464 If so, restart it and feed it all the moves made so far. */
8466 if (appData.noChessProgram || first.pr != NoProc) return;
8468 StartChessProgram(&first);
8469 InitChessProgram(&first, FALSE);
8470 FeedMovesToProgram(&first, currentMove);
8472 if (!first.sendTime) {
8473 /* can't tell gnuchess what its clock should read,
8474 so we bow to its notion. */
8476 timeRemaining[0][currentMove] = whiteTimeRemaining;
8477 timeRemaining[1][currentMove] = blackTimeRemaining;
8480 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8481 appData.icsEngineAnalyze) && first.analysisSupport) {
8482 SendToProgram("analyze\n", &first);
8483 first.analyzing = TRUE;
8496 if (appData.debugMode) {
8497 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8498 redraw, init, gameMode);
8500 pausing = pauseExamInvalid = FALSE;
8501 startedFromSetupPosition = blackPlaysFirst = FALSE;
8503 whiteFlag = blackFlag = FALSE;
8504 userOfferedDraw = FALSE;
8505 hintRequested = bookRequested = FALSE;
8506 first.maybeThinking = FALSE;
8507 second.maybeThinking = FALSE;
8508 first.bookSuspend = FALSE; // [HGM] book
8509 second.bookSuspend = FALSE;
8510 thinkOutput[0] = NULLCHAR;
8511 lastHint[0] = NULLCHAR;
8512 ClearGameInfo(&gameInfo);
8513 gameInfo.variant = StringToVariant(appData.variant);
8514 ics_user_moved = ics_clock_paused = FALSE;
8515 ics_getting_history = H_FALSE;
8517 white_holding[0] = black_holding[0] = NULLCHAR;
8518 ClearProgramStats();
8519 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8523 flipView = appData.flipView;
8524 ClearPremoveHighlights();
8526 alarmSounded = FALSE;
8528 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8529 if(appData.serverMovesName != NULL) {
8530 /* [HGM] prepare to make moves file for broadcasting */
8531 clock_t t = clock();
8532 if(serverMoves != NULL) fclose(serverMoves);
8533 serverMoves = fopen(appData.serverMovesName, "r");
8534 if(serverMoves != NULL) {
8535 fclose(serverMoves);
8536 /* delay 15 sec before overwriting, so all clients can see end */
8537 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8539 serverMoves = fopen(appData.serverMovesName, "w");
8543 gameMode = BeginningOfGame;
8545 if(appData.icsActive) gameInfo.variant = VariantNormal;
8546 currentMove = forwardMostMove = backwardMostMove = 0;
8547 InitPosition(redraw);
8548 for (i = 0; i < MAX_MOVES; i++) {
8549 if (commentList[i] != NULL) {
8550 free(commentList[i]);
8551 commentList[i] = NULL;
8555 timeRemaining[0][0] = whiteTimeRemaining;
8556 timeRemaining[1][0] = blackTimeRemaining;
8557 if (first.pr == NULL) {
8558 StartChessProgram(&first);
8561 InitChessProgram(&first, startedFromSetupPosition);
8564 DisplayMessage("", "");
8565 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8566 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8573 if (!AutoPlayOneMove())
8575 if (matchMode || appData.timeDelay == 0)
8577 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8579 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8588 int fromX, fromY, toX, toY;
8590 if (appData.debugMode) {
8591 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8594 if (gameMode != PlayFromGameFile)
8597 if (currentMove >= forwardMostMove) {
8598 gameMode = EditGame;
8601 /* [AS] Clear current move marker at the end of a game */
8602 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8607 toX = moveList[currentMove][2] - AAA;
8608 toY = moveList[currentMove][3] - ONE;
8610 if (moveList[currentMove][1] == '@') {
8611 if (appData.highlightLastMove) {
8612 SetHighlights(-1, -1, toX, toY);
8615 fromX = moveList[currentMove][0] - AAA;
8616 fromY = moveList[currentMove][1] - ONE;
8618 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8620 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8622 if (appData.highlightLastMove) {
8623 SetHighlights(fromX, fromY, toX, toY);
8626 DisplayMove(currentMove);
8627 SendMoveToProgram(currentMove++, &first);
8628 DisplayBothClocks();
8629 DrawPosition(FALSE, boards[currentMove]);
8630 // [HGM] PV info: always display, routine tests if empty
8631 DisplayComment(currentMove - 1, commentList[currentMove]);
8637 LoadGameOneMove(readAhead)
8638 ChessMove readAhead;
8640 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8641 char promoChar = NULLCHAR;
8646 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8647 gameMode != AnalyzeMode && gameMode != Training) {
8652 yyboardindex = forwardMostMove;
8653 if (readAhead != (ChessMove)0) {
8654 moveType = readAhead;
8656 if (gameFileFP == NULL)
8658 moveType = (ChessMove) yylex();
8664 if (appData.debugMode)
8665 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8667 if (*p == '{' || *p == '[' || *p == '(') {
8668 p[strlen(p) - 1] = NULLCHAR;
8672 /* append the comment but don't display it */
8673 while (*p == '\n') p++;
8674 AppendComment(currentMove, p);
8677 case WhiteCapturesEnPassant:
8678 case BlackCapturesEnPassant:
8679 case WhitePromotionChancellor:
8680 case BlackPromotionChancellor:
8681 case WhitePromotionArchbishop:
8682 case BlackPromotionArchbishop:
8683 case WhitePromotionCentaur:
8684 case BlackPromotionCentaur:
8685 case WhitePromotionQueen:
8686 case BlackPromotionQueen:
8687 case WhitePromotionRook:
8688 case BlackPromotionRook:
8689 case WhitePromotionBishop:
8690 case BlackPromotionBishop:
8691 case WhitePromotionKnight:
8692 case BlackPromotionKnight:
8693 case WhitePromotionKing:
8694 case BlackPromotionKing:
8696 case WhiteKingSideCastle:
8697 case WhiteQueenSideCastle:
8698 case BlackKingSideCastle:
8699 case BlackQueenSideCastle:
8700 case WhiteKingSideCastleWild:
8701 case WhiteQueenSideCastleWild:
8702 case BlackKingSideCastleWild:
8703 case BlackQueenSideCastleWild:
8705 case WhiteHSideCastleFR:
8706 case WhiteASideCastleFR:
8707 case BlackHSideCastleFR:
8708 case BlackASideCastleFR:
8710 if (appData.debugMode)
8711 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8712 fromX = currentMoveString[0] - AAA;
8713 fromY = currentMoveString[1] - ONE;
8714 toX = currentMoveString[2] - AAA;
8715 toY = currentMoveString[3] - ONE;
8716 promoChar = currentMoveString[4];
8721 if (appData.debugMode)
8722 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8723 fromX = moveType == WhiteDrop ?
8724 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8725 (int) CharToPiece(ToLower(currentMoveString[0]));
8727 toX = currentMoveString[2] - AAA;
8728 toY = currentMoveString[3] - ONE;
8734 case GameUnfinished:
8735 if (appData.debugMode)
8736 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8737 p = strchr(yy_text, '{');
8738 if (p == NULL) p = strchr(yy_text, '(');
8741 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8743 q = strchr(p, *p == '{' ? '}' : ')');
8744 if (q != NULL) *q = NULLCHAR;
8747 GameEnds(moveType, p, GE_FILE);
8749 if (cmailMsgLoaded) {
8751 flipView = WhiteOnMove(currentMove);
8752 if (moveType == GameUnfinished) flipView = !flipView;
8753 if (appData.debugMode)
8754 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8758 case (ChessMove) 0: /* end of file */
8759 if (appData.debugMode)
8760 fprintf(debugFP, "Parser hit end of file\n");
8761 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8762 EP_UNKNOWN, castlingRights[currentMove]) ) {
8768 if (WhiteOnMove(currentMove)) {
8769 GameEnds(BlackWins, "Black mates", GE_FILE);
8771 GameEnds(WhiteWins, "White mates", GE_FILE);
8775 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8782 if (lastLoadGameStart == GNUChessGame) {
8783 /* GNUChessGames have numbers, but they aren't move numbers */
8784 if (appData.debugMode)
8785 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8786 yy_text, (int) moveType);
8787 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8789 /* else fall thru */
8794 /* Reached start of next game in file */
8795 if (appData.debugMode)
8796 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8797 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8798 EP_UNKNOWN, castlingRights[currentMove]) ) {
8804 if (WhiteOnMove(currentMove)) {
8805 GameEnds(BlackWins, "Black mates", GE_FILE);
8807 GameEnds(WhiteWins, "White mates", GE_FILE);
8811 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8817 case PositionDiagram: /* should not happen; ignore */
8818 case ElapsedTime: /* ignore */
8819 case NAG: /* ignore */
8820 if (appData.debugMode)
8821 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8822 yy_text, (int) moveType);
8823 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8826 if (appData.testLegality) {
8827 if (appData.debugMode)
8828 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8829 sprintf(move, _("Illegal move: %d.%s%s"),
8830 (forwardMostMove / 2) + 1,
8831 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8832 DisplayError(move, 0);
8835 if (appData.debugMode)
8836 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8837 yy_text, currentMoveString);
8838 fromX = currentMoveString[0] - AAA;
8839 fromY = currentMoveString[1] - ONE;
8840 toX = currentMoveString[2] - AAA;
8841 toY = currentMoveString[3] - ONE;
8842 promoChar = currentMoveString[4];
8847 if (appData.debugMode)
8848 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8849 sprintf(move, _("Ambiguous move: %d.%s%s"),
8850 (forwardMostMove / 2) + 1,
8851 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8852 DisplayError(move, 0);
8857 case ImpossibleMove:
8858 if (appData.debugMode)
8859 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8860 sprintf(move, _("Illegal move: %d.%s%s"),
8861 (forwardMostMove / 2) + 1,
8862 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8863 DisplayError(move, 0);
8869 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8870 DrawPosition(FALSE, boards[currentMove]);
8871 DisplayBothClocks();
8872 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8873 DisplayComment(currentMove - 1, commentList[currentMove]);
8875 (void) StopLoadGameTimer();
8877 cmailOldMove = forwardMostMove;
8880 /* currentMoveString is set as a side-effect of yylex */
8881 strcat(currentMoveString, "\n");
8882 strcpy(moveList[forwardMostMove], currentMoveString);
8884 thinkOutput[0] = NULLCHAR;
8885 MakeMove(fromX, fromY, toX, toY, promoChar);
8886 currentMove = forwardMostMove;
8891 /* Load the nth game from the given file */
8893 LoadGameFromFile(filename, n, title, useList)
8897 /*Boolean*/ int useList;
8902 if (strcmp(filename, "-") == 0) {
8906 f = fopen(filename, "rb");
8908 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8909 DisplayError(buf, errno);
8913 if (fseek(f, 0, 0) == -1) {
8914 /* f is not seekable; probably a pipe */
8917 if (useList && n == 0) {
8918 int error = GameListBuild(f);
8920 DisplayError(_("Cannot build game list"), error);
8921 } else if (!ListEmpty(&gameList) &&
8922 ((ListGame *) gameList.tailPred)->number > 1) {
8923 GameListPopUp(f, title);
8930 return LoadGame(f, n, title, FALSE);
8935 MakeRegisteredMove()
8937 int fromX, fromY, toX, toY;
8939 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8940 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8943 if (appData.debugMode)
8944 fprintf(debugFP, "Restoring %s for game %d\n",
8945 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8947 thinkOutput[0] = NULLCHAR;
8948 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8949 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8950 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8951 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8952 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8953 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8954 MakeMove(fromX, fromY, toX, toY, promoChar);
8955 ShowMove(fromX, fromY, toX, toY);
8957 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8958 EP_UNKNOWN, castlingRights[currentMove]) ) {
8965 if (WhiteOnMove(currentMove)) {
8966 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8968 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8973 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8980 if (WhiteOnMove(currentMove)) {
8981 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8983 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8988 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8999 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9001 CmailLoadGame(f, gameNumber, title, useList)
9009 if (gameNumber > nCmailGames) {
9010 DisplayError(_("No more games in this message"), 0);
9013 if (f == lastLoadGameFP) {
9014 int offset = gameNumber - lastLoadGameNumber;
9016 cmailMsg[0] = NULLCHAR;
9017 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9018 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9019 nCmailMovesRegistered--;
9021 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9022 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9023 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9026 if (! RegisterMove()) return FALSE;
9030 retVal = LoadGame(f, gameNumber, title, useList);
9032 /* Make move registered during previous look at this game, if any */
9033 MakeRegisteredMove();
9035 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9036 commentList[currentMove]
9037 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9038 DisplayComment(currentMove - 1, commentList[currentMove]);
9044 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9049 int gameNumber = lastLoadGameNumber + offset;
9050 if (lastLoadGameFP == NULL) {
9051 DisplayError(_("No game has been loaded yet"), 0);
9054 if (gameNumber <= 0) {
9055 DisplayError(_("Can't back up any further"), 0);
9058 if (cmailMsgLoaded) {
9059 return CmailLoadGame(lastLoadGameFP, gameNumber,
9060 lastLoadGameTitle, lastLoadGameUseList);
9062 return LoadGame(lastLoadGameFP, gameNumber,
9063 lastLoadGameTitle, lastLoadGameUseList);
9069 /* Load the nth game from open file f */
9071 LoadGame(f, gameNumber, title, useList)
9079 int gn = gameNumber;
9080 ListGame *lg = NULL;
9083 GameMode oldGameMode;
9084 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9086 if (appData.debugMode)
9087 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9089 if (gameMode == Training )
9090 SetTrainingModeOff();
9092 oldGameMode = gameMode;
9093 if (gameMode != BeginningOfGame) {
9098 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9099 fclose(lastLoadGameFP);
9103 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9106 fseek(f, lg->offset, 0);
9107 GameListHighlight(gameNumber);
9111 DisplayError(_("Game number out of range"), 0);
9116 if (fseek(f, 0, 0) == -1) {
9117 if (f == lastLoadGameFP ?
9118 gameNumber == lastLoadGameNumber + 1 :
9122 DisplayError(_("Can't seek on game file"), 0);
9128 lastLoadGameNumber = gameNumber;
9129 strcpy(lastLoadGameTitle, title);
9130 lastLoadGameUseList = useList;
9134 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9135 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9136 lg->gameInfo.black);
9138 } else if (*title != NULLCHAR) {
9139 if (gameNumber > 1) {
9140 sprintf(buf, "%s %d", title, gameNumber);
9143 DisplayTitle(title);
9147 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9148 gameMode = PlayFromGameFile;
9152 currentMove = forwardMostMove = backwardMostMove = 0;
9153 CopyBoard(boards[0], initialPosition);
9157 * Skip the first gn-1 games in the file.
9158 * Also skip over anything that precedes an identifiable
9159 * start of game marker, to avoid being confused by
9160 * garbage at the start of the file. Currently
9161 * recognized start of game markers are the move number "1",
9162 * the pattern "gnuchess .* game", the pattern
9163 * "^[#;%] [^ ]* game file", and a PGN tag block.
9164 * A game that starts with one of the latter two patterns
9165 * will also have a move number 1, possibly
9166 * following a position diagram.
9167 * 5-4-02: Let's try being more lenient and allowing a game to
9168 * start with an unnumbered move. Does that break anything?
9170 cm = lastLoadGameStart = (ChessMove) 0;
9172 yyboardindex = forwardMostMove;
9173 cm = (ChessMove) yylex();
9176 if (cmailMsgLoaded) {
9177 nCmailGames = CMAIL_MAX_GAMES - gn;
9180 DisplayError(_("Game not found in file"), 0);
9187 lastLoadGameStart = cm;
9191 switch (lastLoadGameStart) {
9198 gn--; /* count this game */
9199 lastLoadGameStart = cm;
9208 switch (lastLoadGameStart) {
9213 gn--; /* count this game */
9214 lastLoadGameStart = cm;
9217 lastLoadGameStart = cm; /* game counted already */
9225 yyboardindex = forwardMostMove;
9226 cm = (ChessMove) yylex();
9227 } while (cm == PGNTag || cm == Comment);
9234 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9235 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9236 != CMAIL_OLD_RESULT) {
9238 cmailResult[ CMAIL_MAX_GAMES
9239 - gn - 1] = CMAIL_OLD_RESULT;
9245 /* Only a NormalMove can be at the start of a game
9246 * without a position diagram. */
9247 if (lastLoadGameStart == (ChessMove) 0) {
9249 lastLoadGameStart = MoveNumberOne;
9258 if (appData.debugMode)
9259 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9261 if (cm == XBoardGame) {
9262 /* Skip any header junk before position diagram and/or move 1 */
9264 yyboardindex = forwardMostMove;
9265 cm = (ChessMove) yylex();
9267 if (cm == (ChessMove) 0 ||
9268 cm == GNUChessGame || cm == XBoardGame) {
9269 /* Empty game; pretend end-of-file and handle later */
9274 if (cm == MoveNumberOne || cm == PositionDiagram ||
9275 cm == PGNTag || cm == Comment)
9278 } else if (cm == GNUChessGame) {
9279 if (gameInfo.event != NULL) {
9280 free(gameInfo.event);
9282 gameInfo.event = StrSave(yy_text);
9285 startedFromSetupPosition = FALSE;
9286 while (cm == PGNTag) {
9287 if (appData.debugMode)
9288 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9289 err = ParsePGNTag(yy_text, &gameInfo);
9290 if (!err) numPGNTags++;
9292 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9293 if(gameInfo.variant != oldVariant) {
9294 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9296 oldVariant = gameInfo.variant;
9297 if (appData.debugMode)
9298 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9302 if (gameInfo.fen != NULL) {
9303 Board initial_position;
9304 startedFromSetupPosition = TRUE;
9305 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9307 DisplayError(_("Bad FEN position in file"), 0);
9310 CopyBoard(boards[0], initial_position);
9311 if (blackPlaysFirst) {
9312 currentMove = forwardMostMove = backwardMostMove = 1;
9313 CopyBoard(boards[1], initial_position);
9314 strcpy(moveList[0], "");
9315 strcpy(parseList[0], "");
9316 timeRemaining[0][1] = whiteTimeRemaining;
9317 timeRemaining[1][1] = blackTimeRemaining;
9318 if (commentList[0] != NULL) {
9319 commentList[1] = commentList[0];
9320 commentList[0] = NULL;
9323 currentMove = forwardMostMove = backwardMostMove = 0;
9325 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9327 initialRulePlies = FENrulePlies;
9328 epStatus[forwardMostMove] = FENepStatus;
9329 for( i=0; i< nrCastlingRights; i++ )
9330 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9332 yyboardindex = forwardMostMove;
9334 gameInfo.fen = NULL;
9337 yyboardindex = forwardMostMove;
9338 cm = (ChessMove) yylex();
9340 /* Handle comments interspersed among the tags */
9341 while (cm == Comment) {
9343 if (appData.debugMode)
9344 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9346 if (*p == '{' || *p == '[' || *p == '(') {
9347 p[strlen(p) - 1] = NULLCHAR;
9350 while (*p == '\n') p++;
9351 AppendComment(currentMove, p);
9352 yyboardindex = forwardMostMove;
9353 cm = (ChessMove) yylex();
9357 /* don't rely on existence of Event tag since if game was
9358 * pasted from clipboard the Event tag may not exist
9360 if (numPGNTags > 0){
9362 if (gameInfo.variant == VariantNormal) {
9363 gameInfo.variant = StringToVariant(gameInfo.event);
9366 if( appData.autoDisplayTags ) {
9367 tags = PGNTags(&gameInfo);
9368 TagsPopUp(tags, CmailMsg());
9373 /* Make something up, but don't display it now */
9378 if (cm == PositionDiagram) {
9381 Board initial_position;
9383 if (appData.debugMode)
9384 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9386 if (!startedFromSetupPosition) {
9388 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9389 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9399 initial_position[i][j++] = CharToPiece(*p);
9402 while (*p == ' ' || *p == '\t' ||
9403 *p == '\n' || *p == '\r') p++;
9405 if (strncmp(p, "black", strlen("black"))==0)
9406 blackPlaysFirst = TRUE;
9408 blackPlaysFirst = FALSE;
9409 startedFromSetupPosition = TRUE;
9411 CopyBoard(boards[0], initial_position);
9412 if (blackPlaysFirst) {
9413 currentMove = forwardMostMove = backwardMostMove = 1;
9414 CopyBoard(boards[1], initial_position);
9415 strcpy(moveList[0], "");
9416 strcpy(parseList[0], "");
9417 timeRemaining[0][1] = whiteTimeRemaining;
9418 timeRemaining[1][1] = blackTimeRemaining;
9419 if (commentList[0] != NULL) {
9420 commentList[1] = commentList[0];
9421 commentList[0] = NULL;
9424 currentMove = forwardMostMove = backwardMostMove = 0;
9427 yyboardindex = forwardMostMove;
9428 cm = (ChessMove) yylex();
9431 if (first.pr == NoProc) {
9432 StartChessProgram(&first);
9434 InitChessProgram(&first, FALSE);
9435 SendToProgram("force\n", &first);
9436 if (startedFromSetupPosition) {
9437 SendBoard(&first, forwardMostMove);
9438 if (appData.debugMode) {
9439 fprintf(debugFP, "Load Game\n");
9441 DisplayBothClocks();
9444 /* [HGM] server: flag to write setup moves in broadcast file as one */
9445 loadFlag = appData.suppressLoadMoves;
9447 while (cm == Comment) {
9449 if (appData.debugMode)
9450 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9452 if (*p == '{' || *p == '[' || *p == '(') {
9453 p[strlen(p) - 1] = NULLCHAR;
9456 while (*p == '\n') p++;
9457 AppendComment(currentMove, p);
9458 yyboardindex = forwardMostMove;
9459 cm = (ChessMove) yylex();
9462 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9463 cm == WhiteWins || cm == BlackWins ||
9464 cm == GameIsDrawn || cm == GameUnfinished) {
9465 DisplayMessage("", _("No moves in game"));
9466 if (cmailMsgLoaded) {
9467 if (appData.debugMode)
9468 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9472 DrawPosition(FALSE, boards[currentMove]);
9473 DisplayBothClocks();
9474 gameMode = EditGame;
9481 // [HGM] PV info: routine tests if comment empty
9482 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9483 DisplayComment(currentMove - 1, commentList[currentMove]);
9485 if (!matchMode && appData.timeDelay != 0)
9486 DrawPosition(FALSE, boards[currentMove]);
9488 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9489 programStats.ok_to_send = 1;
9492 /* if the first token after the PGN tags is a move
9493 * and not move number 1, retrieve it from the parser
9495 if (cm != MoveNumberOne)
9496 LoadGameOneMove(cm);
9498 /* load the remaining moves from the file */
9499 while (LoadGameOneMove((ChessMove)0)) {
9500 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9501 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9504 /* rewind to the start of the game */
9505 currentMove = backwardMostMove;
9507 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9509 if (oldGameMode == AnalyzeFile ||
9510 oldGameMode == AnalyzeMode) {
9514 if (matchMode || appData.timeDelay == 0) {
9516 gameMode = EditGame;
9518 } else if (appData.timeDelay > 0) {
9522 if (appData.debugMode)
9523 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9525 loadFlag = 0; /* [HGM] true game starts */
9529 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9531 ReloadPosition(offset)
9534 int positionNumber = lastLoadPositionNumber + offset;
9535 if (lastLoadPositionFP == NULL) {
9536 DisplayError(_("No position has been loaded yet"), 0);
9539 if (positionNumber <= 0) {
9540 DisplayError(_("Can't back up any further"), 0);
9543 return LoadPosition(lastLoadPositionFP, positionNumber,
9544 lastLoadPositionTitle);
9547 /* Load the nth position from the given file */
9549 LoadPositionFromFile(filename, n, title)
9557 if (strcmp(filename, "-") == 0) {
9558 return LoadPosition(stdin, n, "stdin");
9560 f = fopen(filename, "rb");
9562 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9563 DisplayError(buf, errno);
9566 return LoadPosition(f, n, title);
9571 /* Load the nth position from the given open file, and close it */
9573 LoadPosition(f, positionNumber, title)
9578 char *p, line[MSG_SIZ];
9579 Board initial_position;
9580 int i, j, fenMode, pn;
9582 if (gameMode == Training )
9583 SetTrainingModeOff();
9585 if (gameMode != BeginningOfGame) {
9588 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9589 fclose(lastLoadPositionFP);
9591 if (positionNumber == 0) positionNumber = 1;
9592 lastLoadPositionFP = f;
9593 lastLoadPositionNumber = positionNumber;
9594 strcpy(lastLoadPositionTitle, title);
9595 if (first.pr == NoProc) {
9596 StartChessProgram(&first);
9597 InitChessProgram(&first, FALSE);
9599 pn = positionNumber;
9600 if (positionNumber < 0) {
9601 /* Negative position number means to seek to that byte offset */
9602 if (fseek(f, -positionNumber, 0) == -1) {
9603 DisplayError(_("Can't seek on position file"), 0);
9608 if (fseek(f, 0, 0) == -1) {
9609 if (f == lastLoadPositionFP ?
9610 positionNumber == lastLoadPositionNumber + 1 :
9611 positionNumber == 1) {
9614 DisplayError(_("Can't seek on position file"), 0);
9619 /* See if this file is FEN or old-style xboard */
9620 if (fgets(line, MSG_SIZ, f) == NULL) {
9621 DisplayError(_("Position not found in file"), 0);
9624 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9625 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9628 if (fenMode || line[0] == '#') pn--;
9630 /* skip positions before number pn */
9631 if (fgets(line, MSG_SIZ, f) == NULL) {
9633 DisplayError(_("Position not found in file"), 0);
9636 if (fenMode || line[0] == '#') pn--;
9641 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9642 DisplayError(_("Bad FEN position in file"), 0);
9646 (void) fgets(line, MSG_SIZ, f);
9647 (void) fgets(line, MSG_SIZ, f);
9649 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9650 (void) fgets(line, MSG_SIZ, f);
9651 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9654 initial_position[i][j++] = CharToPiece(*p);
9658 blackPlaysFirst = FALSE;
9660 (void) fgets(line, MSG_SIZ, f);
9661 if (strncmp(line, "black", strlen("black"))==0)
9662 blackPlaysFirst = TRUE;
9665 startedFromSetupPosition = TRUE;
9667 SendToProgram("force\n", &first);
9668 CopyBoard(boards[0], initial_position);
9669 if (blackPlaysFirst) {
9670 currentMove = forwardMostMove = backwardMostMove = 1;
9671 strcpy(moveList[0], "");
9672 strcpy(parseList[0], "");
9673 CopyBoard(boards[1], initial_position);
9674 DisplayMessage("", _("Black to play"));
9676 currentMove = forwardMostMove = backwardMostMove = 0;
9677 DisplayMessage("", _("White to play"));
9679 /* [HGM] copy FEN attributes as well */
9681 initialRulePlies = FENrulePlies;
9682 epStatus[forwardMostMove] = FENepStatus;
9683 for( i=0; i< nrCastlingRights; i++ )
9684 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9686 SendBoard(&first, forwardMostMove);
9687 if (appData.debugMode) {
9689 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9690 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9691 fprintf(debugFP, "Load Position\n");
9694 if (positionNumber > 1) {
9695 sprintf(line, "%s %d", title, positionNumber);
9698 DisplayTitle(title);
9700 gameMode = EditGame;
9703 timeRemaining[0][1] = whiteTimeRemaining;
9704 timeRemaining[1][1] = blackTimeRemaining;
9705 DrawPosition(FALSE, boards[currentMove]);
9712 CopyPlayerNameIntoFileName(dest, src)
9715 while (*src != NULLCHAR && *src != ',') {
9720 *(*dest)++ = *src++;
9725 char *DefaultFileName(ext)
9728 static char def[MSG_SIZ];
9731 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9733 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9735 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9744 /* Save the current game to the given file */
9746 SaveGameToFile(filename, append)
9753 if (strcmp(filename, "-") == 0) {
9754 return SaveGame(stdout, 0, NULL);
9756 f = fopen(filename, append ? "a" : "w");
9758 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9759 DisplayError(buf, errno);
9762 return SaveGame(f, 0, NULL);
9771 static char buf[MSG_SIZ];
9774 p = strchr(str, ' ');
9775 if (p == NULL) return str;
9776 strncpy(buf, str, p - str);
9777 buf[p - str] = NULLCHAR;
9781 #define PGN_MAX_LINE 75
9783 #define PGN_SIDE_WHITE 0
9784 #define PGN_SIDE_BLACK 1
9787 static int FindFirstMoveOutOfBook( int side )
9791 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9792 int index = backwardMostMove;
9793 int has_book_hit = 0;
9795 if( (index % 2) != side ) {
9799 while( index < forwardMostMove ) {
9800 /* Check to see if engine is in book */
9801 int depth = pvInfoList[index].depth;
9802 int score = pvInfoList[index].score;
9808 else if( score == 0 && depth == 63 ) {
9809 in_book = 1; /* Zappa */
9811 else if( score == 2 && depth == 99 ) {
9812 in_book = 1; /* Abrok */
9815 has_book_hit += in_book;
9831 void GetOutOfBookInfo( char * buf )
9835 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9837 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9838 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9842 if( oob[0] >= 0 || oob[1] >= 0 ) {
9843 for( i=0; i<2; i++ ) {
9847 if( i > 0 && oob[0] >= 0 ) {
9851 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9852 sprintf( buf+strlen(buf), "%s%.2f",
9853 pvInfoList[idx].score >= 0 ? "+" : "",
9854 pvInfoList[idx].score / 100.0 );
9860 /* Save game in PGN style and close the file */
9865 int i, offset, linelen, newblock;
9869 int movelen, numlen, blank;
9870 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9872 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9874 tm = time((time_t *) NULL);
9876 PrintPGNTags(f, &gameInfo);
9878 if (backwardMostMove > 0 || startedFromSetupPosition) {
9879 char *fen = PositionToFEN(backwardMostMove, NULL);
9880 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9881 fprintf(f, "\n{--------------\n");
9882 PrintPosition(f, backwardMostMove);
9883 fprintf(f, "--------------}\n");
9887 /* [AS] Out of book annotation */
9888 if( appData.saveOutOfBookInfo ) {
9891 GetOutOfBookInfo( buf );
9893 if( buf[0] != '\0' ) {
9894 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9901 i = backwardMostMove;
9905 while (i < forwardMostMove) {
9906 /* Print comments preceding this move */
9907 if (commentList[i] != NULL) {
9908 if (linelen > 0) fprintf(f, "\n");
9909 fprintf(f, "{\n%s}\n", commentList[i]);
9914 /* Format move number */
9916 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9919 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9921 numtext[0] = NULLCHAR;
9924 numlen = strlen(numtext);
9927 /* Print move number */
9928 blank = linelen > 0 && numlen > 0;
9929 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9938 fprintf(f, "%s", numtext);
9942 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9943 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9946 blank = linelen > 0 && movelen > 0;
9947 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9956 fprintf(f, "%s", move_buffer);
9959 /* [AS] Add PV info if present */
9960 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9961 /* [HGM] add time */
9962 char buf[MSG_SIZ]; int seconds;
9964 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9966 if( seconds <= 0) buf[0] = 0; else
9967 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9968 seconds = (seconds + 4)/10; // round to full seconds
9969 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9970 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9973 sprintf( move_buffer, "{%s%.2f/%d%s}",
9974 pvInfoList[i].score >= 0 ? "+" : "",
9975 pvInfoList[i].score / 100.0,
9976 pvInfoList[i].depth,
9979 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9981 /* Print score/depth */
9982 blank = linelen > 0 && movelen > 0;
9983 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9992 fprintf(f, "%s", move_buffer);
9999 /* Start a new line */
10000 if (linelen > 0) fprintf(f, "\n");
10002 /* Print comments after last move */
10003 if (commentList[i] != NULL) {
10004 fprintf(f, "{\n%s}\n", commentList[i]);
10008 if (gameInfo.resultDetails != NULL &&
10009 gameInfo.resultDetails[0] != NULLCHAR) {
10010 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10011 PGNResult(gameInfo.result));
10013 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10017 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10021 /* Save game in old style and close the file */
10023 SaveGameOldStyle(f)
10029 tm = time((time_t *) NULL);
10031 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10034 if (backwardMostMove > 0 || startedFromSetupPosition) {
10035 fprintf(f, "\n[--------------\n");
10036 PrintPosition(f, backwardMostMove);
10037 fprintf(f, "--------------]\n");
10042 i = backwardMostMove;
10043 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10045 while (i < forwardMostMove) {
10046 if (commentList[i] != NULL) {
10047 fprintf(f, "[%s]\n", commentList[i]);
10050 if ((i % 2) == 1) {
10051 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10054 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10056 if (commentList[i] != NULL) {
10060 if (i >= forwardMostMove) {
10064 fprintf(f, "%s\n", parseList[i]);
10069 if (commentList[i] != NULL) {
10070 fprintf(f, "[%s]\n", commentList[i]);
10073 /* This isn't really the old style, but it's close enough */
10074 if (gameInfo.resultDetails != NULL &&
10075 gameInfo.resultDetails[0] != NULLCHAR) {
10076 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10077 gameInfo.resultDetails);
10079 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10086 /* Save the current game to open file f and close the file */
10088 SaveGame(f, dummy, dummy2)
10093 if (gameMode == EditPosition) EditPositionDone(TRUE);
10094 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10095 if (appData.oldSaveStyle)
10096 return SaveGameOldStyle(f);
10098 return SaveGamePGN(f);
10101 /* Save the current position to the given file */
10103 SavePositionToFile(filename)
10109 if (strcmp(filename, "-") == 0) {
10110 return SavePosition(stdout, 0, NULL);
10112 f = fopen(filename, "a");
10114 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10115 DisplayError(buf, errno);
10118 SavePosition(f, 0, NULL);
10124 /* Save the current position to the given open file and close the file */
10126 SavePosition(f, dummy, dummy2)
10134 if (gameMode == EditPosition) EditPositionDone(TRUE);
10135 if (appData.oldSaveStyle) {
10136 tm = time((time_t *) NULL);
10138 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10140 fprintf(f, "[--------------\n");
10141 PrintPosition(f, currentMove);
10142 fprintf(f, "--------------]\n");
10144 fen = PositionToFEN(currentMove, NULL);
10145 fprintf(f, "%s\n", fen);
10153 ReloadCmailMsgEvent(unregister)
10157 static char *inFilename = NULL;
10158 static char *outFilename;
10160 struct stat inbuf, outbuf;
10163 /* Any registered moves are unregistered if unregister is set, */
10164 /* i.e. invoked by the signal handler */
10166 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10167 cmailMoveRegistered[i] = FALSE;
10168 if (cmailCommentList[i] != NULL) {
10169 free(cmailCommentList[i]);
10170 cmailCommentList[i] = NULL;
10173 nCmailMovesRegistered = 0;
10176 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10177 cmailResult[i] = CMAIL_NOT_RESULT;
10181 if (inFilename == NULL) {
10182 /* Because the filenames are static they only get malloced once */
10183 /* and they never get freed */
10184 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10185 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10187 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10188 sprintf(outFilename, "%s.out", appData.cmailGameName);
10191 status = stat(outFilename, &outbuf);
10193 cmailMailedMove = FALSE;
10195 status = stat(inFilename, &inbuf);
10196 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10199 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10200 counts the games, notes how each one terminated, etc.
10202 It would be nice to remove this kludge and instead gather all
10203 the information while building the game list. (And to keep it
10204 in the game list nodes instead of having a bunch of fixed-size
10205 parallel arrays.) Note this will require getting each game's
10206 termination from the PGN tags, as the game list builder does
10207 not process the game moves. --mann
10209 cmailMsgLoaded = TRUE;
10210 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10212 /* Load first game in the file or popup game menu */
10213 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10215 #endif /* !WIN32 */
10223 char string[MSG_SIZ];
10225 if ( cmailMailedMove
10226 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10227 return TRUE; /* Allow free viewing */
10230 /* Unregister move to ensure that we don't leave RegisterMove */
10231 /* with the move registered when the conditions for registering no */
10233 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10234 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10235 nCmailMovesRegistered --;
10237 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10239 free(cmailCommentList[lastLoadGameNumber - 1]);
10240 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10244 if (cmailOldMove == -1) {
10245 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10249 if (currentMove > cmailOldMove + 1) {
10250 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10254 if (currentMove < cmailOldMove) {
10255 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10259 if (forwardMostMove > currentMove) {
10260 /* Silently truncate extra moves */
10264 if ( (currentMove == cmailOldMove + 1)
10265 || ( (currentMove == cmailOldMove)
10266 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10267 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10268 if (gameInfo.result != GameUnfinished) {
10269 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10272 if (commentList[currentMove] != NULL) {
10273 cmailCommentList[lastLoadGameNumber - 1]
10274 = StrSave(commentList[currentMove]);
10276 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10278 if (appData.debugMode)
10279 fprintf(debugFP, "Saving %s for game %d\n",
10280 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10283 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10285 f = fopen(string, "w");
10286 if (appData.oldSaveStyle) {
10287 SaveGameOldStyle(f); /* also closes the file */
10289 sprintf(string, "%s.pos.out", appData.cmailGameName);
10290 f = fopen(string, "w");
10291 SavePosition(f, 0, NULL); /* also closes the file */
10293 fprintf(f, "{--------------\n");
10294 PrintPosition(f, currentMove);
10295 fprintf(f, "--------------}\n\n");
10297 SaveGame(f, 0, NULL); /* also closes the file*/
10300 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10301 nCmailMovesRegistered ++;
10302 } else if (nCmailGames == 1) {
10303 DisplayError(_("You have not made a move yet"), 0);
10314 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10315 FILE *commandOutput;
10316 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10317 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10323 if (! cmailMsgLoaded) {
10324 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10328 if (nCmailGames == nCmailResults) {
10329 DisplayError(_("No unfinished games"), 0);
10333 #if CMAIL_PROHIBIT_REMAIL
10334 if (cmailMailedMove) {
10335 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);
10336 DisplayError(msg, 0);
10341 if (! (cmailMailedMove || RegisterMove())) return;
10343 if ( cmailMailedMove
10344 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10345 sprintf(string, partCommandString,
10346 appData.debugMode ? " -v" : "", appData.cmailGameName);
10347 commandOutput = popen(string, "r");
10349 if (commandOutput == NULL) {
10350 DisplayError(_("Failed to invoke cmail"), 0);
10352 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10353 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10355 if (nBuffers > 1) {
10356 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10357 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10358 nBytes = MSG_SIZ - 1;
10360 (void) memcpy(msg, buffer, nBytes);
10362 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10364 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10365 cmailMailedMove = TRUE; /* Prevent >1 moves */
10368 for (i = 0; i < nCmailGames; i ++) {
10369 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10374 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10376 sprintf(buffer, "%s/%s.%s.archive",
10378 appData.cmailGameName,
10380 LoadGameFromFile(buffer, 1, buffer, FALSE);
10381 cmailMsgLoaded = FALSE;
10385 DisplayInformation(msg);
10386 pclose(commandOutput);
10389 if ((*cmailMsg) != '\0') {
10390 DisplayInformation(cmailMsg);
10395 #endif /* !WIN32 */
10404 int prependComma = 0;
10406 char string[MSG_SIZ]; /* Space for game-list */
10409 if (!cmailMsgLoaded) return "";
10411 if (cmailMailedMove) {
10412 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10414 /* Create a list of games left */
10415 sprintf(string, "[");
10416 for (i = 0; i < nCmailGames; i ++) {
10417 if (! ( cmailMoveRegistered[i]
10418 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10419 if (prependComma) {
10420 sprintf(number, ",%d", i + 1);
10422 sprintf(number, "%d", i + 1);
10426 strcat(string, number);
10429 strcat(string, "]");
10431 if (nCmailMovesRegistered + nCmailResults == 0) {
10432 switch (nCmailGames) {
10435 _("Still need to make move for game\n"));
10440 _("Still need to make moves for both games\n"));
10445 _("Still need to make moves for all %d games\n"),
10450 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10453 _("Still need to make a move for game %s\n"),
10458 if (nCmailResults == nCmailGames) {
10459 sprintf(cmailMsg, _("No unfinished games\n"));
10461 sprintf(cmailMsg, _("Ready to send mail\n"));
10467 _("Still need to make moves for games %s\n"),
10479 if (gameMode == Training)
10480 SetTrainingModeOff();
10483 cmailMsgLoaded = FALSE;
10484 if (appData.icsActive) {
10485 SendToICS(ics_prefix);
10486 SendToICS("refresh\n");
10496 /* Give up on clean exit */
10500 /* Keep trying for clean exit */
10504 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10506 if (telnetISR != NULL) {
10507 RemoveInputSource(telnetISR);
10509 if (icsPR != NoProc) {
10510 DestroyChildProcess(icsPR, TRUE);
10513 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10514 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10516 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10517 /* make sure this other one finishes before killing it! */
10518 if(endingGame) { int count = 0;
10519 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10520 while(endingGame && count++ < 10) DoSleep(1);
10521 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10524 /* Kill off chess programs */
10525 if (first.pr != NoProc) {
10528 DoSleep( appData.delayBeforeQuit );
10529 SendToProgram("quit\n", &first);
10530 DoSleep( appData.delayAfterQuit );
10531 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10533 if (second.pr != NoProc) {
10534 DoSleep( appData.delayBeforeQuit );
10535 SendToProgram("quit\n", &second);
10536 DoSleep( appData.delayAfterQuit );
10537 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10539 if (first.isr != NULL) {
10540 RemoveInputSource(first.isr);
10542 if (second.isr != NULL) {
10543 RemoveInputSource(second.isr);
10546 ShutDownFrontEnd();
10553 if (appData.debugMode)
10554 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10558 if (gameMode == MachinePlaysWhite ||
10559 gameMode == MachinePlaysBlack) {
10562 DisplayBothClocks();
10564 if (gameMode == PlayFromGameFile) {
10565 if (appData.timeDelay >= 0)
10566 AutoPlayGameLoop();
10567 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10568 Reset(FALSE, TRUE);
10569 SendToICS(ics_prefix);
10570 SendToICS("refresh\n");
10571 } else if (currentMove < forwardMostMove) {
10572 ForwardInner(forwardMostMove);
10574 pauseExamInvalid = FALSE;
10576 switch (gameMode) {
10580 pauseExamForwardMostMove = forwardMostMove;
10581 pauseExamInvalid = FALSE;
10584 case IcsPlayingWhite:
10585 case IcsPlayingBlack:
10589 case PlayFromGameFile:
10590 (void) StopLoadGameTimer();
10594 case BeginningOfGame:
10595 if (appData.icsActive) return;
10596 /* else fall through */
10597 case MachinePlaysWhite:
10598 case MachinePlaysBlack:
10599 case TwoMachinesPlay:
10600 if (forwardMostMove == 0)
10601 return; /* don't pause if no one has moved */
10602 if ((gameMode == MachinePlaysWhite &&
10603 !WhiteOnMove(forwardMostMove)) ||
10604 (gameMode == MachinePlaysBlack &&
10605 WhiteOnMove(forwardMostMove))) {
10618 char title[MSG_SIZ];
10620 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10621 strcpy(title, _("Edit comment"));
10623 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10624 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10625 parseList[currentMove - 1]);
10628 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10635 char *tags = PGNTags(&gameInfo);
10636 EditTagsPopUp(tags);
10643 if (appData.noChessProgram || gameMode == AnalyzeMode)
10646 if (gameMode != AnalyzeFile) {
10647 if (!appData.icsEngineAnalyze) {
10649 if (gameMode != EditGame) return;
10651 ResurrectChessProgram();
10652 SendToProgram("analyze\n", &first);
10653 first.analyzing = TRUE;
10654 /*first.maybeThinking = TRUE;*/
10655 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10656 EngineOutputPopUp();
10658 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10663 StartAnalysisClock();
10664 GetTimeMark(&lastNodeCountTime);
10671 if (appData.noChessProgram || gameMode == AnalyzeFile)
10674 if (gameMode != AnalyzeMode) {
10676 if (gameMode != EditGame) return;
10677 ResurrectChessProgram();
10678 SendToProgram("analyze\n", &first);
10679 first.analyzing = TRUE;
10680 /*first.maybeThinking = TRUE;*/
10681 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10682 EngineOutputPopUp();
10684 gameMode = AnalyzeFile;
10689 StartAnalysisClock();
10690 GetTimeMark(&lastNodeCountTime);
10695 MachineWhiteEvent()
10698 char *bookHit = NULL;
10700 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10704 if (gameMode == PlayFromGameFile ||
10705 gameMode == TwoMachinesPlay ||
10706 gameMode == Training ||
10707 gameMode == AnalyzeMode ||
10708 gameMode == EndOfGame)
10711 if (gameMode == EditPosition)
10712 EditPositionDone(TRUE);
10714 if (!WhiteOnMove(currentMove)) {
10715 DisplayError(_("It is not White's turn"), 0);
10719 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10722 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10723 gameMode == AnalyzeFile)
10726 ResurrectChessProgram(); /* in case it isn't running */
10727 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10728 gameMode = MachinePlaysWhite;
10731 gameMode = MachinePlaysWhite;
10735 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10737 if (first.sendName) {
10738 sprintf(buf, "name %s\n", gameInfo.black);
10739 SendToProgram(buf, &first);
10741 if (first.sendTime) {
10742 if (first.useColors) {
10743 SendToProgram("black\n", &first); /*gnu kludge*/
10745 SendTimeRemaining(&first, TRUE);
10747 if (first.useColors) {
10748 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10750 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10751 SetMachineThinkingEnables();
10752 first.maybeThinking = TRUE;
10756 if (appData.autoFlipView && !flipView) {
10757 flipView = !flipView;
10758 DrawPosition(FALSE, NULL);
10759 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10762 if(bookHit) { // [HGM] book: simulate book reply
10763 static char bookMove[MSG_SIZ]; // a bit generous?
10765 programStats.nodes = programStats.depth = programStats.time =
10766 programStats.score = programStats.got_only_move = 0;
10767 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10769 strcpy(bookMove, "move ");
10770 strcat(bookMove, bookHit);
10771 HandleMachineMove(bookMove, &first);
10776 MachineBlackEvent()
10779 char *bookHit = NULL;
10781 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10785 if (gameMode == PlayFromGameFile ||
10786 gameMode == TwoMachinesPlay ||
10787 gameMode == Training ||
10788 gameMode == AnalyzeMode ||
10789 gameMode == EndOfGame)
10792 if (gameMode == EditPosition)
10793 EditPositionDone(TRUE);
10795 if (WhiteOnMove(currentMove)) {
10796 DisplayError(_("It is not Black's turn"), 0);
10800 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10803 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10804 gameMode == AnalyzeFile)
10807 ResurrectChessProgram(); /* in case it isn't running */
10808 gameMode = MachinePlaysBlack;
10812 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10814 if (first.sendName) {
10815 sprintf(buf, "name %s\n", gameInfo.white);
10816 SendToProgram(buf, &first);
10818 if (first.sendTime) {
10819 if (first.useColors) {
10820 SendToProgram("white\n", &first); /*gnu kludge*/
10822 SendTimeRemaining(&first, FALSE);
10824 if (first.useColors) {
10825 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10827 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10828 SetMachineThinkingEnables();
10829 first.maybeThinking = TRUE;
10832 if (appData.autoFlipView && flipView) {
10833 flipView = !flipView;
10834 DrawPosition(FALSE, NULL);
10835 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10837 if(bookHit) { // [HGM] book: simulate book reply
10838 static char bookMove[MSG_SIZ]; // a bit generous?
10840 programStats.nodes = programStats.depth = programStats.time =
10841 programStats.score = programStats.got_only_move = 0;
10842 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10844 strcpy(bookMove, "move ");
10845 strcat(bookMove, bookHit);
10846 HandleMachineMove(bookMove, &first);
10852 DisplayTwoMachinesTitle()
10855 if (appData.matchGames > 0) {
10856 if (first.twoMachinesColor[0] == 'w') {
10857 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10858 gameInfo.white, gameInfo.black,
10859 first.matchWins, second.matchWins,
10860 matchGame - 1 - (first.matchWins + second.matchWins));
10862 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10863 gameInfo.white, gameInfo.black,
10864 second.matchWins, first.matchWins,
10865 matchGame - 1 - (first.matchWins + second.matchWins));
10868 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10874 TwoMachinesEvent P((void))
10878 ChessProgramState *onmove;
10879 char *bookHit = NULL;
10881 if (appData.noChessProgram) return;
10883 switch (gameMode) {
10884 case TwoMachinesPlay:
10886 case MachinePlaysWhite:
10887 case MachinePlaysBlack:
10888 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10889 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10893 case BeginningOfGame:
10894 case PlayFromGameFile:
10897 if (gameMode != EditGame) return;
10900 EditPositionDone(TRUE);
10911 forwardMostMove = currentMove;
10912 ResurrectChessProgram(); /* in case first program isn't running */
10914 if (second.pr == NULL) {
10915 StartChessProgram(&second);
10916 if (second.protocolVersion == 1) {
10917 TwoMachinesEventIfReady();
10919 /* kludge: allow timeout for initial "feature" command */
10921 DisplayMessage("", _("Starting second chess program"));
10922 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10926 DisplayMessage("", "");
10927 InitChessProgram(&second, FALSE);
10928 SendToProgram("force\n", &second);
10929 if (startedFromSetupPosition) {
10930 SendBoard(&second, backwardMostMove);
10931 if (appData.debugMode) {
10932 fprintf(debugFP, "Two Machines\n");
10935 for (i = backwardMostMove; i < forwardMostMove; i++) {
10936 SendMoveToProgram(i, &second);
10939 gameMode = TwoMachinesPlay;
10943 DisplayTwoMachinesTitle();
10945 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10951 SendToProgram(first.computerString, &first);
10952 if (first.sendName) {
10953 sprintf(buf, "name %s\n", second.tidy);
10954 SendToProgram(buf, &first);
10956 SendToProgram(second.computerString, &second);
10957 if (second.sendName) {
10958 sprintf(buf, "name %s\n", first.tidy);
10959 SendToProgram(buf, &second);
10963 if (!first.sendTime || !second.sendTime) {
10964 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10965 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10967 if (onmove->sendTime) {
10968 if (onmove->useColors) {
10969 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10971 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10973 if (onmove->useColors) {
10974 SendToProgram(onmove->twoMachinesColor, onmove);
10976 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10977 // SendToProgram("go\n", onmove);
10978 onmove->maybeThinking = TRUE;
10979 SetMachineThinkingEnables();
10983 if(bookHit) { // [HGM] book: simulate book reply
10984 static char bookMove[MSG_SIZ]; // a bit generous?
10986 programStats.nodes = programStats.depth = programStats.time =
10987 programStats.score = programStats.got_only_move = 0;
10988 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10990 strcpy(bookMove, "move ");
10991 strcat(bookMove, bookHit);
10992 savedMessage = bookMove; // args for deferred call
10993 savedState = onmove;
10994 ScheduleDelayedEvent(DeferredBookMove, 1);
11001 if (gameMode == Training) {
11002 SetTrainingModeOff();
11003 gameMode = PlayFromGameFile;
11004 DisplayMessage("", _("Training mode off"));
11006 gameMode = Training;
11007 animateTraining = appData.animate;
11009 /* make sure we are not already at the end of the game */
11010 if (currentMove < forwardMostMove) {
11011 SetTrainingModeOn();
11012 DisplayMessage("", _("Training mode on"));
11014 gameMode = PlayFromGameFile;
11015 DisplayError(_("Already at end of game"), 0);
11024 if (!appData.icsActive) return;
11025 switch (gameMode) {
11026 case IcsPlayingWhite:
11027 case IcsPlayingBlack:
11030 case BeginningOfGame:
11038 EditPositionDone(TRUE);
11051 gameMode = IcsIdle;
11062 switch (gameMode) {
11064 SetTrainingModeOff();
11066 case MachinePlaysWhite:
11067 case MachinePlaysBlack:
11068 case BeginningOfGame:
11069 SendToProgram("force\n", &first);
11070 SetUserThinkingEnables();
11072 case PlayFromGameFile:
11073 (void) StopLoadGameTimer();
11074 if (gameFileFP != NULL) {
11079 EditPositionDone(TRUE);
11084 SendToProgram("force\n", &first);
11086 case TwoMachinesPlay:
11087 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11088 ResurrectChessProgram();
11089 SetUserThinkingEnables();
11092 ResurrectChessProgram();
11094 case IcsPlayingBlack:
11095 case IcsPlayingWhite:
11096 DisplayError(_("Warning: You are still playing a game"), 0);
11099 DisplayError(_("Warning: You are still observing a game"), 0);
11102 DisplayError(_("Warning: You are still examining a game"), 0);
11113 first.offeredDraw = second.offeredDraw = 0;
11115 if (gameMode == PlayFromGameFile) {
11116 whiteTimeRemaining = timeRemaining[0][currentMove];
11117 blackTimeRemaining = timeRemaining[1][currentMove];
11121 if (gameMode == MachinePlaysWhite ||
11122 gameMode == MachinePlaysBlack ||
11123 gameMode == TwoMachinesPlay ||
11124 gameMode == EndOfGame) {
11125 i = forwardMostMove;
11126 while (i > currentMove) {
11127 SendToProgram("undo\n", &first);
11130 whiteTimeRemaining = timeRemaining[0][currentMove];
11131 blackTimeRemaining = timeRemaining[1][currentMove];
11132 DisplayBothClocks();
11133 if (whiteFlag || blackFlag) {
11134 whiteFlag = blackFlag = 0;
11139 gameMode = EditGame;
11146 EditPositionEvent()
11148 if (gameMode == EditPosition) {
11154 if (gameMode != EditGame) return;
11156 gameMode = EditPosition;
11159 if (currentMove > 0)
11160 CopyBoard(boards[0], boards[currentMove]);
11162 blackPlaysFirst = !WhiteOnMove(currentMove);
11164 currentMove = forwardMostMove = backwardMostMove = 0;
11165 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11172 /* [DM] icsEngineAnalyze - possible call from other functions */
11173 if (appData.icsEngineAnalyze) {
11174 appData.icsEngineAnalyze = FALSE;
11176 DisplayMessage("",_("Close ICS engine analyze..."));
11178 if (first.analysisSupport && first.analyzing) {
11179 SendToProgram("exit\n", &first);
11180 first.analyzing = FALSE;
11182 thinkOutput[0] = NULLCHAR;
11186 EditPositionDone(Boolean fakeRights)
11188 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11190 startedFromSetupPosition = TRUE;
11191 InitChessProgram(&first, FALSE);
11193 { /* don't do this if we just pasted FEN */
11194 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11195 if(boards[0][0][BOARD_WIDTH>>1] == king)
11197 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11198 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11201 castlingRights[0][2] = -1;
11202 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11204 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11205 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11208 castlingRights[0][5] = -1;
11210 SendToProgram("force\n", &first);
11211 if (blackPlaysFirst) {
11212 strcpy(moveList[0], "");
11213 strcpy(parseList[0], "");
11214 currentMove = forwardMostMove = backwardMostMove = 1;
11215 CopyBoard(boards[1], boards[0]);
11216 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11218 epStatus[1] = epStatus[0];
11219 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11222 currentMove = forwardMostMove = backwardMostMove = 0;
11224 SendBoard(&first, forwardMostMove);
11225 if (appData.debugMode) {
11226 fprintf(debugFP, "EditPosDone\n");
11229 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11230 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11231 gameMode = EditGame;
11233 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11234 ClearHighlights(); /* [AS] */
11237 /* Pause for `ms' milliseconds */
11238 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11248 } while (SubtractTimeMarks(&m2, &m1) < ms);
11251 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11253 SendMultiLineToICS(buf)
11256 char temp[MSG_SIZ+1], *p;
11263 strncpy(temp, buf, len);
11268 if (*p == '\n' || *p == '\r')
11273 strcat(temp, "\n");
11275 SendToPlayer(temp, strlen(temp));
11279 SetWhiteToPlayEvent()
11281 if (gameMode == EditPosition) {
11282 blackPlaysFirst = FALSE;
11283 DisplayBothClocks(); /* works because currentMove is 0 */
11284 } else if (gameMode == IcsExamining) {
11285 SendToICS(ics_prefix);
11286 SendToICS("tomove white\n");
11291 SetBlackToPlayEvent()
11293 if (gameMode == EditPosition) {
11294 blackPlaysFirst = TRUE;
11295 currentMove = 1; /* kludge */
11296 DisplayBothClocks();
11298 } else if (gameMode == IcsExamining) {
11299 SendToICS(ics_prefix);
11300 SendToICS("tomove black\n");
11305 EditPositionMenuEvent(selection, x, y)
11306 ChessSquare selection;
11310 ChessSquare piece = boards[0][y][x];
11312 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11314 switch (selection) {
11316 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11317 SendToICS(ics_prefix);
11318 SendToICS("bsetup clear\n");
11319 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11320 SendToICS(ics_prefix);
11321 SendToICS("clearboard\n");
11323 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11324 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11325 for (y = 0; y < BOARD_HEIGHT; y++) {
11326 if (gameMode == IcsExamining) {
11327 if (boards[currentMove][y][x] != EmptySquare) {
11328 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11333 boards[0][y][x] = p;
11338 if (gameMode == EditPosition) {
11339 DrawPosition(FALSE, boards[0]);
11344 SetWhiteToPlayEvent();
11348 SetBlackToPlayEvent();
11352 if (gameMode == IcsExamining) {
11353 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11356 boards[0][y][x] = EmptySquare;
11357 DrawPosition(FALSE, boards[0]);
11362 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11363 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11364 selection = (ChessSquare) (PROMOTED piece);
11365 } else if(piece == EmptySquare) selection = WhiteSilver;
11366 else selection = (ChessSquare)((int)piece - 1);
11370 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11371 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11372 selection = (ChessSquare) (DEMOTED piece);
11373 } else if(piece == EmptySquare) selection = BlackSilver;
11374 else selection = (ChessSquare)((int)piece + 1);
11379 if(gameInfo.variant == VariantShatranj ||
11380 gameInfo.variant == VariantXiangqi ||
11381 gameInfo.variant == VariantCourier )
11382 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11387 if(gameInfo.variant == VariantXiangqi)
11388 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11389 if(gameInfo.variant == VariantKnightmate)
11390 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11393 if (gameMode == IcsExamining) {
11394 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11395 PieceToChar(selection), AAA + x, ONE + y);
11398 boards[0][y][x] = selection;
11399 DrawPosition(FALSE, boards[0]);
11407 DropMenuEvent(selection, x, y)
11408 ChessSquare selection;
11411 ChessMove moveType;
11413 switch (gameMode) {
11414 case IcsPlayingWhite:
11415 case MachinePlaysBlack:
11416 if (!WhiteOnMove(currentMove)) {
11417 DisplayMoveError(_("It is Black's turn"));
11420 moveType = WhiteDrop;
11422 case IcsPlayingBlack:
11423 case MachinePlaysWhite:
11424 if (WhiteOnMove(currentMove)) {
11425 DisplayMoveError(_("It is White's turn"));
11428 moveType = BlackDrop;
11431 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11437 if (moveType == BlackDrop && selection < BlackPawn) {
11438 selection = (ChessSquare) ((int) selection
11439 + (int) BlackPawn - (int) WhitePawn);
11441 if (boards[currentMove][y][x] != EmptySquare) {
11442 DisplayMoveError(_("That square is occupied"));
11446 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11452 /* Accept a pending offer of any kind from opponent */
11454 if (appData.icsActive) {
11455 SendToICS(ics_prefix);
11456 SendToICS("accept\n");
11457 } else if (cmailMsgLoaded) {
11458 if (currentMove == cmailOldMove &&
11459 commentList[cmailOldMove] != NULL &&
11460 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11461 "Black offers a draw" : "White offers a draw")) {
11463 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11464 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11466 DisplayError(_("There is no pending offer on this move"), 0);
11467 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11470 /* Not used for offers from chess program */
11477 /* Decline a pending offer of any kind from opponent */
11479 if (appData.icsActive) {
11480 SendToICS(ics_prefix);
11481 SendToICS("decline\n");
11482 } else if (cmailMsgLoaded) {
11483 if (currentMove == cmailOldMove &&
11484 commentList[cmailOldMove] != NULL &&
11485 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11486 "Black offers a draw" : "White offers a draw")) {
11488 AppendComment(cmailOldMove, "Draw declined");
11489 DisplayComment(cmailOldMove - 1, "Draw declined");
11492 DisplayError(_("There is no pending offer on this move"), 0);
11495 /* Not used for offers from chess program */
11502 /* Issue ICS rematch command */
11503 if (appData.icsActive) {
11504 SendToICS(ics_prefix);
11505 SendToICS("rematch\n");
11512 /* Call your opponent's flag (claim a win on time) */
11513 if (appData.icsActive) {
11514 SendToICS(ics_prefix);
11515 SendToICS("flag\n");
11517 switch (gameMode) {
11520 case MachinePlaysWhite:
11523 GameEnds(GameIsDrawn, "Both players ran out of time",
11526 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11528 DisplayError(_("Your opponent is not out of time"), 0);
11531 case MachinePlaysBlack:
11534 GameEnds(GameIsDrawn, "Both players ran out of time",
11537 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11539 DisplayError(_("Your opponent is not out of time"), 0);
11549 /* Offer draw or accept pending draw offer from opponent */
11551 if (appData.icsActive) {
11552 /* Note: tournament rules require draw offers to be
11553 made after you make your move but before you punch
11554 your clock. Currently ICS doesn't let you do that;
11555 instead, you immediately punch your clock after making
11556 a move, but you can offer a draw at any time. */
11558 SendToICS(ics_prefix);
11559 SendToICS("draw\n");
11560 } else if (cmailMsgLoaded) {
11561 if (currentMove == cmailOldMove &&
11562 commentList[cmailOldMove] != NULL &&
11563 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11564 "Black offers a draw" : "White offers a draw")) {
11565 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11566 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11567 } else if (currentMove == cmailOldMove + 1) {
11568 char *offer = WhiteOnMove(cmailOldMove) ?
11569 "White offers a draw" : "Black offers a draw";
11570 AppendComment(currentMove, offer);
11571 DisplayComment(currentMove - 1, offer);
11572 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11574 DisplayError(_("You must make your move before offering a draw"), 0);
11575 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11577 } else if (first.offeredDraw) {
11578 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11580 if (first.sendDrawOffers) {
11581 SendToProgram("draw\n", &first);
11582 userOfferedDraw = TRUE;
11590 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11592 if (appData.icsActive) {
11593 SendToICS(ics_prefix);
11594 SendToICS("adjourn\n");
11596 /* Currently GNU Chess doesn't offer or accept Adjourns */
11604 /* Offer Abort or accept pending Abort offer from opponent */
11606 if (appData.icsActive) {
11607 SendToICS(ics_prefix);
11608 SendToICS("abort\n");
11610 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11617 /* Resign. You can do this even if it's not your turn. */
11619 if (appData.icsActive) {
11620 SendToICS(ics_prefix);
11621 SendToICS("resign\n");
11623 switch (gameMode) {
11624 case MachinePlaysWhite:
11625 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11627 case MachinePlaysBlack:
11628 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11631 if (cmailMsgLoaded) {
11633 if (WhiteOnMove(cmailOldMove)) {
11634 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11636 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11638 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11649 StopObservingEvent()
11651 /* Stop observing current games */
11652 SendToICS(ics_prefix);
11653 SendToICS("unobserve\n");
11657 StopExaminingEvent()
11659 /* Stop observing current game */
11660 SendToICS(ics_prefix);
11661 SendToICS("unexamine\n");
11665 ForwardInner(target)
11670 if (appData.debugMode)
11671 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11672 target, currentMove, forwardMostMove);
11674 if (gameMode == EditPosition)
11677 if (gameMode == PlayFromGameFile && !pausing)
11680 if (gameMode == IcsExamining && pausing)
11681 limit = pauseExamForwardMostMove;
11683 limit = forwardMostMove;
11685 if (target > limit) target = limit;
11687 if (target > 0 && moveList[target - 1][0]) {
11688 int fromX, fromY, toX, toY;
11689 toX = moveList[target - 1][2] - AAA;
11690 toY = moveList[target - 1][3] - ONE;
11691 if (moveList[target - 1][1] == '@') {
11692 if (appData.highlightLastMove) {
11693 SetHighlights(-1, -1, toX, toY);
11696 fromX = moveList[target - 1][0] - AAA;
11697 fromY = moveList[target - 1][1] - ONE;
11698 if (target == currentMove + 1) {
11699 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11701 if (appData.highlightLastMove) {
11702 SetHighlights(fromX, fromY, toX, toY);
11706 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11707 gameMode == Training || gameMode == PlayFromGameFile ||
11708 gameMode == AnalyzeFile) {
11709 while (currentMove < target) {
11710 SendMoveToProgram(currentMove++, &first);
11713 currentMove = target;
11716 if (gameMode == EditGame || gameMode == EndOfGame) {
11717 whiteTimeRemaining = timeRemaining[0][currentMove];
11718 blackTimeRemaining = timeRemaining[1][currentMove];
11720 DisplayBothClocks();
11721 DisplayMove(currentMove - 1);
11722 DrawPosition(FALSE, boards[currentMove]);
11723 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11724 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11725 DisplayComment(currentMove - 1, commentList[currentMove]);
11733 if (gameMode == IcsExamining && !pausing) {
11734 SendToICS(ics_prefix);
11735 SendToICS("forward\n");
11737 ForwardInner(currentMove + 1);
11744 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11745 /* to optimze, we temporarily turn off analysis mode while we feed
11746 * the remaining moves to the engine. Otherwise we get analysis output
11749 if (first.analysisSupport) {
11750 SendToProgram("exit\nforce\n", &first);
11751 first.analyzing = FALSE;
11755 if (gameMode == IcsExamining && !pausing) {
11756 SendToICS(ics_prefix);
11757 SendToICS("forward 999999\n");
11759 ForwardInner(forwardMostMove);
11762 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11763 /* we have fed all the moves, so reactivate analysis mode */
11764 SendToProgram("analyze\n", &first);
11765 first.analyzing = TRUE;
11766 /*first.maybeThinking = TRUE;*/
11767 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11772 BackwardInner(target)
11775 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11777 if (appData.debugMode)
11778 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11779 target, currentMove, forwardMostMove);
11781 if (gameMode == EditPosition) return;
11782 if (currentMove <= backwardMostMove) {
11784 DrawPosition(full_redraw, boards[currentMove]);
11787 if (gameMode == PlayFromGameFile && !pausing)
11790 if (moveList[target][0]) {
11791 int fromX, fromY, toX, toY;
11792 toX = moveList[target][2] - AAA;
11793 toY = moveList[target][3] - ONE;
11794 if (moveList[target][1] == '@') {
11795 if (appData.highlightLastMove) {
11796 SetHighlights(-1, -1, toX, toY);
11799 fromX = moveList[target][0] - AAA;
11800 fromY = moveList[target][1] - ONE;
11801 if (target == currentMove - 1) {
11802 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11804 if (appData.highlightLastMove) {
11805 SetHighlights(fromX, fromY, toX, toY);
11809 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11810 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11811 while (currentMove > target) {
11812 SendToProgram("undo\n", &first);
11816 currentMove = target;
11819 if (gameMode == EditGame || gameMode == EndOfGame) {
11820 whiteTimeRemaining = timeRemaining[0][currentMove];
11821 blackTimeRemaining = timeRemaining[1][currentMove];
11823 DisplayBothClocks();
11824 DisplayMove(currentMove - 1);
11825 DrawPosition(full_redraw, boards[currentMove]);
11826 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11827 // [HGM] PV info: routine tests if comment empty
11828 DisplayComment(currentMove - 1, commentList[currentMove]);
11834 if (gameMode == IcsExamining && !pausing) {
11835 SendToICS(ics_prefix);
11836 SendToICS("backward\n");
11838 BackwardInner(currentMove - 1);
11845 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11846 /* to optimize, we temporarily turn off analysis mode while we undo
11847 * all the moves. Otherwise we get analysis output after each undo.
11849 if (first.analysisSupport) {
11850 SendToProgram("exit\nforce\n", &first);
11851 first.analyzing = FALSE;
11855 if (gameMode == IcsExamining && !pausing) {
11856 SendToICS(ics_prefix);
11857 SendToICS("backward 999999\n");
11859 BackwardInner(backwardMostMove);
11862 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11863 /* we have fed all the moves, so reactivate analysis mode */
11864 SendToProgram("analyze\n", &first);
11865 first.analyzing = TRUE;
11866 /*first.maybeThinking = TRUE;*/
11867 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11874 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11875 if (to >= forwardMostMove) to = forwardMostMove;
11876 if (to <= backwardMostMove) to = backwardMostMove;
11877 if (to < currentMove) {
11887 if (gameMode != IcsExamining) {
11888 DisplayError(_("You are not examining a game"), 0);
11892 DisplayError(_("You can't revert while pausing"), 0);
11895 SendToICS(ics_prefix);
11896 SendToICS("revert\n");
11902 switch (gameMode) {
11903 case MachinePlaysWhite:
11904 case MachinePlaysBlack:
11905 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11906 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11909 if (forwardMostMove < 2) return;
11910 currentMove = forwardMostMove = forwardMostMove - 2;
11911 whiteTimeRemaining = timeRemaining[0][currentMove];
11912 blackTimeRemaining = timeRemaining[1][currentMove];
11913 DisplayBothClocks();
11914 DisplayMove(currentMove - 1);
11915 ClearHighlights();/*!! could figure this out*/
11916 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11917 SendToProgram("remove\n", &first);
11918 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11921 case BeginningOfGame:
11925 case IcsPlayingWhite:
11926 case IcsPlayingBlack:
11927 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11928 SendToICS(ics_prefix);
11929 SendToICS("takeback 2\n");
11931 SendToICS(ics_prefix);
11932 SendToICS("takeback 1\n");
11941 ChessProgramState *cps;
11943 switch (gameMode) {
11944 case MachinePlaysWhite:
11945 if (!WhiteOnMove(forwardMostMove)) {
11946 DisplayError(_("It is your turn"), 0);
11951 case MachinePlaysBlack:
11952 if (WhiteOnMove(forwardMostMove)) {
11953 DisplayError(_("It is your turn"), 0);
11958 case TwoMachinesPlay:
11959 if (WhiteOnMove(forwardMostMove) ==
11960 (first.twoMachinesColor[0] == 'w')) {
11966 case BeginningOfGame:
11970 SendToProgram("?\n", cps);
11974 TruncateGameEvent()
11977 if (gameMode != EditGame) return;
11984 if (forwardMostMove > currentMove) {
11985 if (gameInfo.resultDetails != NULL) {
11986 free(gameInfo.resultDetails);
11987 gameInfo.resultDetails = NULL;
11988 gameInfo.result = GameUnfinished;
11990 forwardMostMove = currentMove;
11991 HistorySet(parseList, backwardMostMove, forwardMostMove,
11999 if (appData.noChessProgram) return;
12000 switch (gameMode) {
12001 case MachinePlaysWhite:
12002 if (WhiteOnMove(forwardMostMove)) {
12003 DisplayError(_("Wait until your turn"), 0);
12007 case BeginningOfGame:
12008 case MachinePlaysBlack:
12009 if (!WhiteOnMove(forwardMostMove)) {
12010 DisplayError(_("Wait until your turn"), 0);
12015 DisplayError(_("No hint available"), 0);
12018 SendToProgram("hint\n", &first);
12019 hintRequested = TRUE;
12025 if (appData.noChessProgram) return;
12026 switch (gameMode) {
12027 case MachinePlaysWhite:
12028 if (WhiteOnMove(forwardMostMove)) {
12029 DisplayError(_("Wait until your turn"), 0);
12033 case BeginningOfGame:
12034 case MachinePlaysBlack:
12035 if (!WhiteOnMove(forwardMostMove)) {
12036 DisplayError(_("Wait until your turn"), 0);
12041 EditPositionDone(TRUE);
12043 case TwoMachinesPlay:
12048 SendToProgram("bk\n", &first);
12049 bookOutput[0] = NULLCHAR;
12050 bookRequested = TRUE;
12056 char *tags = PGNTags(&gameInfo);
12057 TagsPopUp(tags, CmailMsg());
12061 /* end button procedures */
12064 PrintPosition(fp, move)
12070 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12071 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12072 char c = PieceToChar(boards[move][i][j]);
12073 fputc(c == 'x' ? '.' : c, fp);
12074 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12077 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12078 fprintf(fp, "white to play\n");
12080 fprintf(fp, "black to play\n");
12087 if (gameInfo.white != NULL) {
12088 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12094 /* Find last component of program's own name, using some heuristics */
12096 TidyProgramName(prog, host, buf)
12097 char *prog, *host, buf[MSG_SIZ];
12100 int local = (strcmp(host, "localhost") == 0);
12101 while (!local && (p = strchr(prog, ';')) != NULL) {
12103 while (*p == ' ') p++;
12106 if (*prog == '"' || *prog == '\'') {
12107 q = strchr(prog + 1, *prog);
12109 q = strchr(prog, ' ');
12111 if (q == NULL) q = prog + strlen(prog);
12113 while (p >= prog && *p != '/' && *p != '\\') p--;
12115 if(p == prog && *p == '"') p++;
12116 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12117 memcpy(buf, p, q - p);
12118 buf[q - p] = NULLCHAR;
12126 TimeControlTagValue()
12129 if (!appData.clockMode) {
12131 } else if (movesPerSession > 0) {
12132 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12133 } else if (timeIncrement == 0) {
12134 sprintf(buf, "%ld", timeControl/1000);
12136 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12138 return StrSave(buf);
12144 /* This routine is used only for certain modes */
12145 VariantClass v = gameInfo.variant;
12146 ClearGameInfo(&gameInfo);
12147 gameInfo.variant = v;
12149 switch (gameMode) {
12150 case MachinePlaysWhite:
12151 gameInfo.event = StrSave( appData.pgnEventHeader );
12152 gameInfo.site = StrSave(HostName());
12153 gameInfo.date = PGNDate();
12154 gameInfo.round = StrSave("-");
12155 gameInfo.white = StrSave(first.tidy);
12156 gameInfo.black = StrSave(UserName());
12157 gameInfo.timeControl = TimeControlTagValue();
12160 case MachinePlaysBlack:
12161 gameInfo.event = StrSave( appData.pgnEventHeader );
12162 gameInfo.site = StrSave(HostName());
12163 gameInfo.date = PGNDate();
12164 gameInfo.round = StrSave("-");
12165 gameInfo.white = StrSave(UserName());
12166 gameInfo.black = StrSave(first.tidy);
12167 gameInfo.timeControl = TimeControlTagValue();
12170 case TwoMachinesPlay:
12171 gameInfo.event = StrSave( appData.pgnEventHeader );
12172 gameInfo.site = StrSave(HostName());
12173 gameInfo.date = PGNDate();
12174 if (matchGame > 0) {
12176 sprintf(buf, "%d", matchGame);
12177 gameInfo.round = StrSave(buf);
12179 gameInfo.round = StrSave("-");
12181 if (first.twoMachinesColor[0] == 'w') {
12182 gameInfo.white = StrSave(first.tidy);
12183 gameInfo.black = StrSave(second.tidy);
12185 gameInfo.white = StrSave(second.tidy);
12186 gameInfo.black = StrSave(first.tidy);
12188 gameInfo.timeControl = TimeControlTagValue();
12192 gameInfo.event = StrSave("Edited game");
12193 gameInfo.site = StrSave(HostName());
12194 gameInfo.date = PGNDate();
12195 gameInfo.round = StrSave("-");
12196 gameInfo.white = StrSave("-");
12197 gameInfo.black = StrSave("-");
12201 gameInfo.event = StrSave("Edited position");
12202 gameInfo.site = StrSave(HostName());
12203 gameInfo.date = PGNDate();
12204 gameInfo.round = StrSave("-");
12205 gameInfo.white = StrSave("-");
12206 gameInfo.black = StrSave("-");
12209 case IcsPlayingWhite:
12210 case IcsPlayingBlack:
12215 case PlayFromGameFile:
12216 gameInfo.event = StrSave("Game from non-PGN file");
12217 gameInfo.site = StrSave(HostName());
12218 gameInfo.date = PGNDate();
12219 gameInfo.round = StrSave("-");
12220 gameInfo.white = StrSave("?");
12221 gameInfo.black = StrSave("?");
12230 ReplaceComment(index, text)
12236 while (*text == '\n') text++;
12237 len = strlen(text);
12238 while (len > 0 && text[len - 1] == '\n') len--;
12240 if (commentList[index] != NULL)
12241 free(commentList[index]);
12244 commentList[index] = NULL;
12247 commentList[index] = (char *) malloc(len + 2);
12248 strncpy(commentList[index], text, len);
12249 commentList[index][len] = '\n';
12250 commentList[index][len + 1] = NULLCHAR;
12263 if (ch == '\r') continue;
12265 } while (ch != '\0');
12269 AppendComment(index, text)
12276 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12279 while (*text == '\n') text++;
12280 len = strlen(text);
12281 while (len > 0 && text[len - 1] == '\n') len--;
12283 if (len == 0) return;
12285 if (commentList[index] != NULL) {
12286 old = commentList[index];
12287 oldlen = strlen(old);
12288 commentList[index] = (char *) malloc(oldlen + len + 2);
12289 strcpy(commentList[index], old);
12291 strncpy(&commentList[index][oldlen], text, len);
12292 commentList[index][oldlen + len] = '\n';
12293 commentList[index][oldlen + len + 1] = NULLCHAR;
12295 commentList[index] = (char *) malloc(len + 2);
12296 strncpy(commentList[index], text, len);
12297 commentList[index][len] = '\n';
12298 commentList[index][len + 1] = NULLCHAR;
12302 static char * FindStr( char * text, char * sub_text )
12304 char * result = strstr( text, sub_text );
12306 if( result != NULL ) {
12307 result += strlen( sub_text );
12313 /* [AS] Try to extract PV info from PGN comment */
12314 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12315 char *GetInfoFromComment( int index, char * text )
12319 if( text != NULL && index > 0 ) {
12322 int time = -1, sec = 0, deci;
12323 char * s_eval = FindStr( text, "[%eval " );
12324 char * s_emt = FindStr( text, "[%emt " );
12326 if( s_eval != NULL || s_emt != NULL ) {
12330 if( s_eval != NULL ) {
12331 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12335 if( delim != ']' ) {
12340 if( s_emt != NULL ) {
12344 /* We expect something like: [+|-]nnn.nn/dd */
12347 sep = strchr( text, '/' );
12348 if( sep == NULL || sep < (text+4) ) {
12352 time = -1; sec = -1; deci = -1;
12353 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12354 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12355 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12356 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12360 if( score_lo < 0 || score_lo >= 100 ) {
12364 if(sec >= 0) time = 600*time + 10*sec; else
12365 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12367 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12369 /* [HGM] PV time: now locate end of PV info */
12370 while( *++sep >= '0' && *sep <= '9'); // strip depth
12372 while( *++sep >= '0' && *sep <= '9'); // strip time
12374 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12376 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12377 while(*sep == ' ') sep++;
12388 pvInfoList[index-1].depth = depth;
12389 pvInfoList[index-1].score = score;
12390 pvInfoList[index-1].time = 10*time; // centi-sec
12396 SendToProgram(message, cps)
12398 ChessProgramState *cps;
12400 int count, outCount, error;
12403 if (cps->pr == NULL) return;
12406 if (appData.debugMode) {
12409 fprintf(debugFP, "%ld >%-6s: %s",
12410 SubtractTimeMarks(&now, &programStartTime),
12411 cps->which, message);
12414 count = strlen(message);
12415 outCount = OutputToProcess(cps->pr, message, count, &error);
12416 if (outCount < count && !exiting
12417 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12418 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12419 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12420 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12421 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12422 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12424 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12426 gameInfo.resultDetails = StrSave(buf);
12428 DisplayFatalError(buf, error, 1);
12433 ReceiveFromProgram(isr, closure, message, count, error)
12434 InputSourceRef isr;
12442 ChessProgramState *cps = (ChessProgramState *)closure;
12444 if (isr != cps->isr) return; /* Killed intentionally */
12448 _("Error: %s chess program (%s) exited unexpectedly"),
12449 cps->which, cps->program);
12450 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12451 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12452 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12453 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12455 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12457 gameInfo.resultDetails = StrSave(buf);
12459 RemoveInputSource(cps->isr);
12460 DisplayFatalError(buf, 0, 1);
12463 _("Error reading from %s chess program (%s)"),
12464 cps->which, cps->program);
12465 RemoveInputSource(cps->isr);
12467 /* [AS] Program is misbehaving badly... kill it */
12468 if( count == -2 ) {
12469 DestroyChildProcess( cps->pr, 9 );
12473 DisplayFatalError(buf, error, 1);
12478 if ((end_str = strchr(message, '\r')) != NULL)
12479 *end_str = NULLCHAR;
12480 if ((end_str = strchr(message, '\n')) != NULL)
12481 *end_str = NULLCHAR;
12483 if (appData.debugMode) {
12484 TimeMark now; int print = 1;
12485 char *quote = ""; char c; int i;
12487 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12488 char start = message[0];
12489 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12490 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12491 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12492 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12493 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12494 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12495 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12496 sscanf(message, "pong %c", &c)!=1 && start != '#')
12497 { quote = "# "; print = (appData.engineComments == 2); }
12498 message[0] = start; // restore original message
12502 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12503 SubtractTimeMarks(&now, &programStartTime), cps->which,
12509 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12510 if (appData.icsEngineAnalyze) {
12511 if (strstr(message, "whisper") != NULL ||
12512 strstr(message, "kibitz") != NULL ||
12513 strstr(message, "tellics") != NULL) return;
12516 HandleMachineMove(message, cps);
12521 SendTimeControl(cps, mps, tc, inc, sd, st)
12522 ChessProgramState *cps;
12523 int mps, inc, sd, st;
12529 if( timeControl_2 > 0 ) {
12530 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12531 tc = timeControl_2;
12534 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12535 inc /= cps->timeOdds;
12536 st /= cps->timeOdds;
12538 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12541 /* Set exact time per move, normally using st command */
12542 if (cps->stKludge) {
12543 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12545 if (seconds == 0) {
12546 sprintf(buf, "level 1 %d\n", st/60);
12548 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12551 sprintf(buf, "st %d\n", st);
12554 /* Set conventional or incremental time control, using level command */
12555 if (seconds == 0) {
12556 /* Note old gnuchess bug -- minutes:seconds used to not work.
12557 Fixed in later versions, but still avoid :seconds
12558 when seconds is 0. */
12559 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12561 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12562 seconds, inc/1000);
12565 SendToProgram(buf, cps);
12567 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12568 /* Orthogonally, limit search to given depth */
12570 if (cps->sdKludge) {
12571 sprintf(buf, "depth\n%d\n", sd);
12573 sprintf(buf, "sd %d\n", sd);
12575 SendToProgram(buf, cps);
12578 if(cps->nps > 0) { /* [HGM] nps */
12579 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12581 sprintf(buf, "nps %d\n", cps->nps);
12582 SendToProgram(buf, cps);
12587 ChessProgramState *WhitePlayer()
12588 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12590 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12591 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12597 SendTimeRemaining(cps, machineWhite)
12598 ChessProgramState *cps;
12599 int /*boolean*/ machineWhite;
12601 char message[MSG_SIZ];
12604 /* Note: this routine must be called when the clocks are stopped
12605 or when they have *just* been set or switched; otherwise
12606 it will be off by the time since the current tick started.
12608 if (machineWhite) {
12609 time = whiteTimeRemaining / 10;
12610 otime = blackTimeRemaining / 10;
12612 time = blackTimeRemaining / 10;
12613 otime = whiteTimeRemaining / 10;
12615 /* [HGM] translate opponent's time by time-odds factor */
12616 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12617 if (appData.debugMode) {
12618 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12621 if (time <= 0) time = 1;
12622 if (otime <= 0) otime = 1;
12624 sprintf(message, "time %ld\n", time);
12625 SendToProgram(message, cps);
12627 sprintf(message, "otim %ld\n", otime);
12628 SendToProgram(message, cps);
12632 BoolFeature(p, name, loc, cps)
12636 ChessProgramState *cps;
12639 int len = strlen(name);
12641 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12643 sscanf(*p, "%d", &val);
12645 while (**p && **p != ' ') (*p)++;
12646 sprintf(buf, "accepted %s\n", name);
12647 SendToProgram(buf, cps);
12654 IntFeature(p, name, loc, cps)
12658 ChessProgramState *cps;
12661 int len = strlen(name);
12662 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12664 sscanf(*p, "%d", loc);
12665 while (**p && **p != ' ') (*p)++;
12666 sprintf(buf, "accepted %s\n", name);
12667 SendToProgram(buf, cps);
12674 StringFeature(p, name, loc, cps)
12678 ChessProgramState *cps;
12681 int len = strlen(name);
12682 if (strncmp((*p), name, len) == 0
12683 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12685 sscanf(*p, "%[^\"]", loc);
12686 while (**p && **p != '\"') (*p)++;
12687 if (**p == '\"') (*p)++;
12688 sprintf(buf, "accepted %s\n", name);
12689 SendToProgram(buf, cps);
12696 ParseOption(Option *opt, ChessProgramState *cps)
12697 // [HGM] options: process the string that defines an engine option, and determine
12698 // name, type, default value, and allowed value range
12700 char *p, *q, buf[MSG_SIZ];
12701 int n, min = (-1)<<31, max = 1<<31, def;
12703 if(p = strstr(opt->name, " -spin ")) {
12704 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12705 if(max < min) max = min; // enforce consistency
12706 if(def < min) def = min;
12707 if(def > max) def = max;
12712 } else if((p = strstr(opt->name, " -slider "))) {
12713 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12714 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12715 if(max < min) max = min; // enforce consistency
12716 if(def < min) def = min;
12717 if(def > max) def = max;
12721 opt->type = Spin; // Slider;
12722 } else if((p = strstr(opt->name, " -string "))) {
12723 opt->textValue = p+9;
12724 opt->type = TextBox;
12725 } else if((p = strstr(opt->name, " -file "))) {
12726 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12727 opt->textValue = p+7;
12728 opt->type = TextBox; // FileName;
12729 } else if((p = strstr(opt->name, " -path "))) {
12730 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12731 opt->textValue = p+7;
12732 opt->type = TextBox; // PathName;
12733 } else if(p = strstr(opt->name, " -check ")) {
12734 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12735 opt->value = (def != 0);
12736 opt->type = CheckBox;
12737 } else if(p = strstr(opt->name, " -combo ")) {
12738 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12739 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12740 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12741 opt->value = n = 0;
12742 while(q = StrStr(q, " /// ")) {
12743 n++; *q = 0; // count choices, and null-terminate each of them
12745 if(*q == '*') { // remember default, which is marked with * prefix
12749 cps->comboList[cps->comboCnt++] = q;
12751 cps->comboList[cps->comboCnt++] = NULL;
12753 opt->type = ComboBox;
12754 } else if(p = strstr(opt->name, " -button")) {
12755 opt->type = Button;
12756 } else if(p = strstr(opt->name, " -save")) {
12757 opt->type = SaveButton;
12758 } else return FALSE;
12759 *p = 0; // terminate option name
12760 // now look if the command-line options define a setting for this engine option.
12761 if(cps->optionSettings && cps->optionSettings[0])
12762 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12763 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12764 sprintf(buf, "option %s", p);
12765 if(p = strstr(buf, ",")) *p = 0;
12767 SendToProgram(buf, cps);
12773 FeatureDone(cps, val)
12774 ChessProgramState* cps;
12777 DelayedEventCallback cb = GetDelayedEvent();
12778 if ((cb == InitBackEnd3 && cps == &first) ||
12779 (cb == TwoMachinesEventIfReady && cps == &second)) {
12780 CancelDelayedEvent();
12781 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12783 cps->initDone = val;
12786 /* Parse feature command from engine */
12788 ParseFeatures(args, cps)
12790 ChessProgramState *cps;
12798 while (*p == ' ') p++;
12799 if (*p == NULLCHAR) return;
12801 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12802 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12803 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12804 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12805 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12806 if (BoolFeature(&p, "reuse", &val, cps)) {
12807 /* Engine can disable reuse, but can't enable it if user said no */
12808 if (!val) cps->reuse = FALSE;
12811 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12812 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12813 if (gameMode == TwoMachinesPlay) {
12814 DisplayTwoMachinesTitle();
12820 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12821 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12822 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12823 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12824 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12825 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12826 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12827 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12828 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12829 if (IntFeature(&p, "done", &val, cps)) {
12830 FeatureDone(cps, val);
12833 /* Added by Tord: */
12834 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12835 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12836 /* End of additions by Tord */
12838 /* [HGM] added features: */
12839 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12840 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12841 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12842 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12843 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12844 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12845 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12846 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12847 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12848 SendToProgram(buf, cps);
12851 if(cps->nrOptions >= MAX_OPTIONS) {
12853 sprintf(buf, "%s engine has too many options\n", cps->which);
12854 DisplayError(buf, 0);
12858 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12859 /* End of additions by HGM */
12861 /* unknown feature: complain and skip */
12863 while (*q && *q != '=') q++;
12864 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12865 SendToProgram(buf, cps);
12871 while (*p && *p != '\"') p++;
12872 if (*p == '\"') p++;
12874 while (*p && *p != ' ') p++;
12882 PeriodicUpdatesEvent(newState)
12885 if (newState == appData.periodicUpdates)
12888 appData.periodicUpdates=newState;
12890 /* Display type changes, so update it now */
12891 // DisplayAnalysis();
12893 /* Get the ball rolling again... */
12895 AnalysisPeriodicEvent(1);
12896 StartAnalysisClock();
12901 PonderNextMoveEvent(newState)
12904 if (newState == appData.ponderNextMove) return;
12905 if (gameMode == EditPosition) EditPositionDone(TRUE);
12907 SendToProgram("hard\n", &first);
12908 if (gameMode == TwoMachinesPlay) {
12909 SendToProgram("hard\n", &second);
12912 SendToProgram("easy\n", &first);
12913 thinkOutput[0] = NULLCHAR;
12914 if (gameMode == TwoMachinesPlay) {
12915 SendToProgram("easy\n", &second);
12918 appData.ponderNextMove = newState;
12922 NewSettingEvent(option, command, value)
12928 if (gameMode == EditPosition) EditPositionDone(TRUE);
12929 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12930 SendToProgram(buf, &first);
12931 if (gameMode == TwoMachinesPlay) {
12932 SendToProgram(buf, &second);
12937 ShowThinkingEvent()
12938 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12940 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12941 int newState = appData.showThinking
12942 // [HGM] thinking: other features now need thinking output as well
12943 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12945 if (oldState == newState) return;
12946 oldState = newState;
12947 if (gameMode == EditPosition) EditPositionDone(TRUE);
12949 SendToProgram("post\n", &first);
12950 if (gameMode == TwoMachinesPlay) {
12951 SendToProgram("post\n", &second);
12954 SendToProgram("nopost\n", &first);
12955 thinkOutput[0] = NULLCHAR;
12956 if (gameMode == TwoMachinesPlay) {
12957 SendToProgram("nopost\n", &second);
12960 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12964 AskQuestionEvent(title, question, replyPrefix, which)
12965 char *title; char *question; char *replyPrefix; char *which;
12967 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12968 if (pr == NoProc) return;
12969 AskQuestion(title, question, replyPrefix, pr);
12973 DisplayMove(moveNumber)
12976 char message[MSG_SIZ];
12978 char cpThinkOutput[MSG_SIZ];
12980 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12982 if (moveNumber == forwardMostMove - 1 ||
12983 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12985 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12987 if (strchr(cpThinkOutput, '\n')) {
12988 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12991 *cpThinkOutput = NULLCHAR;
12994 /* [AS] Hide thinking from human user */
12995 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12996 *cpThinkOutput = NULLCHAR;
12997 if( thinkOutput[0] != NULLCHAR ) {
13000 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13001 cpThinkOutput[i] = '.';
13003 cpThinkOutput[i] = NULLCHAR;
13004 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13008 if (moveNumber == forwardMostMove - 1 &&
13009 gameInfo.resultDetails != NULL) {
13010 if (gameInfo.resultDetails[0] == NULLCHAR) {
13011 sprintf(res, " %s", PGNResult(gameInfo.result));
13013 sprintf(res, " {%s} %s",
13014 gameInfo.resultDetails, PGNResult(gameInfo.result));
13020 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13021 DisplayMessage(res, cpThinkOutput);
13023 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13024 WhiteOnMove(moveNumber) ? " " : ".. ",
13025 parseList[moveNumber], res);
13026 DisplayMessage(message, cpThinkOutput);
13031 DisplayComment(moveNumber, text)
13035 char title[MSG_SIZ];
13036 char buf[8000]; // comment can be long!
13039 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13040 strcpy(title, "Comment");
13042 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13043 WhiteOnMove(moveNumber) ? " " : ".. ",
13044 parseList[moveNumber]);
13046 // [HGM] PV info: display PV info together with (or as) comment
13047 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13048 if(text == NULL) text = "";
13049 score = pvInfoList[moveNumber].score;
13050 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13051 depth, (pvInfoList[moveNumber].time+50)/100, text);
13054 if (text != NULL && (appData.autoDisplayComment || commentUp))
13055 CommentPopUp(title, text);
13058 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13059 * might be busy thinking or pondering. It can be omitted if your
13060 * gnuchess is configured to stop thinking immediately on any user
13061 * input. However, that gnuchess feature depends on the FIONREAD
13062 * ioctl, which does not work properly on some flavors of Unix.
13066 ChessProgramState *cps;
13069 if (!cps->useSigint) return;
13070 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13071 switch (gameMode) {
13072 case MachinePlaysWhite:
13073 case MachinePlaysBlack:
13074 case TwoMachinesPlay:
13075 case IcsPlayingWhite:
13076 case IcsPlayingBlack:
13079 /* Skip if we know it isn't thinking */
13080 if (!cps->maybeThinking) return;
13081 if (appData.debugMode)
13082 fprintf(debugFP, "Interrupting %s\n", cps->which);
13083 InterruptChildProcess(cps->pr);
13084 cps->maybeThinking = FALSE;
13089 #endif /*ATTENTION*/
13095 if (whiteTimeRemaining <= 0) {
13098 if (appData.icsActive) {
13099 if (appData.autoCallFlag &&
13100 gameMode == IcsPlayingBlack && !blackFlag) {
13101 SendToICS(ics_prefix);
13102 SendToICS("flag\n");
13106 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13108 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13109 if (appData.autoCallFlag) {
13110 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13117 if (blackTimeRemaining <= 0) {
13120 if (appData.icsActive) {
13121 if (appData.autoCallFlag &&
13122 gameMode == IcsPlayingWhite && !whiteFlag) {
13123 SendToICS(ics_prefix);
13124 SendToICS("flag\n");
13128 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13130 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13131 if (appData.autoCallFlag) {
13132 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13145 if (!appData.clockMode || appData.icsActive ||
13146 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13149 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13151 if ( !WhiteOnMove(forwardMostMove) )
13152 /* White made time control */
13153 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13154 /* [HGM] time odds: correct new time quota for time odds! */
13155 / WhitePlayer()->timeOdds;
13157 /* Black made time control */
13158 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13159 / WhitePlayer()->other->timeOdds;
13163 DisplayBothClocks()
13165 int wom = gameMode == EditPosition ?
13166 !blackPlaysFirst : WhiteOnMove(currentMove);
13167 DisplayWhiteClock(whiteTimeRemaining, wom);
13168 DisplayBlackClock(blackTimeRemaining, !wom);
13172 /* Timekeeping seems to be a portability nightmare. I think everyone
13173 has ftime(), but I'm really not sure, so I'm including some ifdefs
13174 to use other calls if you don't. Clocks will be less accurate if
13175 you have neither ftime nor gettimeofday.
13178 /* VS 2008 requires the #include outside of the function */
13179 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13180 #include <sys/timeb.h>
13183 /* Get the current time as a TimeMark */
13188 #if HAVE_GETTIMEOFDAY
13190 struct timeval timeVal;
13191 struct timezone timeZone;
13193 gettimeofday(&timeVal, &timeZone);
13194 tm->sec = (long) timeVal.tv_sec;
13195 tm->ms = (int) (timeVal.tv_usec / 1000L);
13197 #else /*!HAVE_GETTIMEOFDAY*/
13200 // include <sys/timeb.h> / moved to just above start of function
13201 struct timeb timeB;
13204 tm->sec = (long) timeB.time;
13205 tm->ms = (int) timeB.millitm;
13207 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13208 tm->sec = (long) time(NULL);
13214 /* Return the difference in milliseconds between two
13215 time marks. We assume the difference will fit in a long!
13218 SubtractTimeMarks(tm2, tm1)
13219 TimeMark *tm2, *tm1;
13221 return 1000L*(tm2->sec - tm1->sec) +
13222 (long) (tm2->ms - tm1->ms);
13227 * Code to manage the game clocks.
13229 * In tournament play, black starts the clock and then white makes a move.
13230 * We give the human user a slight advantage if he is playing white---the
13231 * clocks don't run until he makes his first move, so it takes zero time.
13232 * Also, we don't account for network lag, so we could get out of sync
13233 * with GNU Chess's clock -- but then, referees are always right.
13236 static TimeMark tickStartTM;
13237 static long intendedTickLength;
13240 NextTickLength(timeRemaining)
13241 long timeRemaining;
13243 long nominalTickLength, nextTickLength;
13245 if (timeRemaining > 0L && timeRemaining <= 10000L)
13246 nominalTickLength = 100L;
13248 nominalTickLength = 1000L;
13249 nextTickLength = timeRemaining % nominalTickLength;
13250 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13252 return nextTickLength;
13255 /* Adjust clock one minute up or down */
13257 AdjustClock(Boolean which, int dir)
13259 if(which) blackTimeRemaining += 60000*dir;
13260 else whiteTimeRemaining += 60000*dir;
13261 DisplayBothClocks();
13264 /* Stop clocks and reset to a fresh time control */
13268 (void) StopClockTimer();
13269 if (appData.icsActive) {
13270 whiteTimeRemaining = blackTimeRemaining = 0;
13271 } else { /* [HGM] correct new time quote for time odds */
13272 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13273 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13275 if (whiteFlag || blackFlag) {
13277 whiteFlag = blackFlag = FALSE;
13279 DisplayBothClocks();
13282 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13284 /* Decrement running clock by amount of time that has passed */
13288 long timeRemaining;
13289 long lastTickLength, fudge;
13292 if (!appData.clockMode) return;
13293 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13297 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13299 /* Fudge if we woke up a little too soon */
13300 fudge = intendedTickLength - lastTickLength;
13301 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13303 if (WhiteOnMove(forwardMostMove)) {
13304 if(whiteNPS >= 0) lastTickLength = 0;
13305 timeRemaining = whiteTimeRemaining -= lastTickLength;
13306 DisplayWhiteClock(whiteTimeRemaining - fudge,
13307 WhiteOnMove(currentMove));
13309 if(blackNPS >= 0) lastTickLength = 0;
13310 timeRemaining = blackTimeRemaining -= lastTickLength;
13311 DisplayBlackClock(blackTimeRemaining - fudge,
13312 !WhiteOnMove(currentMove));
13315 if (CheckFlags()) return;
13318 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13319 StartClockTimer(intendedTickLength);
13321 /* if the time remaining has fallen below the alarm threshold, sound the
13322 * alarm. if the alarm has sounded and (due to a takeback or time control
13323 * with increment) the time remaining has increased to a level above the
13324 * threshold, reset the alarm so it can sound again.
13327 if (appData.icsActive && appData.icsAlarm) {
13329 /* make sure we are dealing with the user's clock */
13330 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13331 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13334 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13335 alarmSounded = FALSE;
13336 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13338 alarmSounded = TRUE;
13344 /* A player has just moved, so stop the previously running
13345 clock and (if in clock mode) start the other one.
13346 We redisplay both clocks in case we're in ICS mode, because
13347 ICS gives us an update to both clocks after every move.
13348 Note that this routine is called *after* forwardMostMove
13349 is updated, so the last fractional tick must be subtracted
13350 from the color that is *not* on move now.
13355 long lastTickLength;
13357 int flagged = FALSE;
13361 if (StopClockTimer() && appData.clockMode) {
13362 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13363 if (WhiteOnMove(forwardMostMove)) {
13364 if(blackNPS >= 0) lastTickLength = 0;
13365 blackTimeRemaining -= lastTickLength;
13366 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13367 // if(pvInfoList[forwardMostMove-1].time == -1)
13368 pvInfoList[forwardMostMove-1].time = // use GUI time
13369 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13371 if(whiteNPS >= 0) lastTickLength = 0;
13372 whiteTimeRemaining -= lastTickLength;
13373 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13374 // if(pvInfoList[forwardMostMove-1].time == -1)
13375 pvInfoList[forwardMostMove-1].time =
13376 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13378 flagged = CheckFlags();
13380 CheckTimeControl();
13382 if (flagged || !appData.clockMode) return;
13384 switch (gameMode) {
13385 case MachinePlaysBlack:
13386 case MachinePlaysWhite:
13387 case BeginningOfGame:
13388 if (pausing) return;
13392 case PlayFromGameFile:
13401 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13402 whiteTimeRemaining : blackTimeRemaining);
13403 StartClockTimer(intendedTickLength);
13407 /* Stop both clocks */
13411 long lastTickLength;
13414 if (!StopClockTimer()) return;
13415 if (!appData.clockMode) return;
13419 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13420 if (WhiteOnMove(forwardMostMove)) {
13421 if(whiteNPS >= 0) lastTickLength = 0;
13422 whiteTimeRemaining -= lastTickLength;
13423 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13425 if(blackNPS >= 0) lastTickLength = 0;
13426 blackTimeRemaining -= lastTickLength;
13427 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13432 /* Start clock of player on move. Time may have been reset, so
13433 if clock is already running, stop and restart it. */
13437 (void) StopClockTimer(); /* in case it was running already */
13438 DisplayBothClocks();
13439 if (CheckFlags()) return;
13441 if (!appData.clockMode) return;
13442 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13444 GetTimeMark(&tickStartTM);
13445 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13446 whiteTimeRemaining : blackTimeRemaining);
13448 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13449 whiteNPS = blackNPS = -1;
13450 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13451 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13452 whiteNPS = first.nps;
13453 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13454 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13455 blackNPS = first.nps;
13456 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13457 whiteNPS = second.nps;
13458 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13459 blackNPS = second.nps;
13460 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13462 StartClockTimer(intendedTickLength);
13469 long second, minute, hour, day;
13471 static char buf[32];
13473 if (ms > 0 && ms <= 9900) {
13474 /* convert milliseconds to tenths, rounding up */
13475 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13477 sprintf(buf, " %03.1f ", tenths/10.0);
13481 /* convert milliseconds to seconds, rounding up */
13482 /* use floating point to avoid strangeness of integer division
13483 with negative dividends on many machines */
13484 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13491 day = second / (60 * 60 * 24);
13492 second = second % (60 * 60 * 24);
13493 hour = second / (60 * 60);
13494 second = second % (60 * 60);
13495 minute = second / 60;
13496 second = second % 60;
13499 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13500 sign, day, hour, minute, second);
13502 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13504 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13511 * This is necessary because some C libraries aren't ANSI C compliant yet.
13514 StrStr(string, match)
13515 char *string, *match;
13519 length = strlen(match);
13521 for (i = strlen(string) - length; i >= 0; i--, string++)
13522 if (!strncmp(match, string, length))
13529 StrCaseStr(string, match)
13530 char *string, *match;
13534 length = strlen(match);
13536 for (i = strlen(string) - length; i >= 0; i--, string++) {
13537 for (j = 0; j < length; j++) {
13538 if (ToLower(match[j]) != ToLower(string[j]))
13541 if (j == length) return string;
13555 c1 = ToLower(*s1++);
13556 c2 = ToLower(*s2++);
13557 if (c1 > c2) return 1;
13558 if (c1 < c2) return -1;
13559 if (c1 == NULLCHAR) return 0;
13568 return isupper(c) ? tolower(c) : c;
13576 return islower(c) ? toupper(c) : c;
13578 #endif /* !_amigados */
13586 if ((ret = (char *) malloc(strlen(s) + 1))) {
13593 StrSavePtr(s, savePtr)
13594 char *s, **savePtr;
13599 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13600 strcpy(*savePtr, s);
13612 clock = time((time_t *)NULL);
13613 tm = localtime(&clock);
13614 sprintf(buf, "%04d.%02d.%02d",
13615 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13616 return StrSave(buf);
13621 PositionToFEN(move, overrideCastling)
13623 char *overrideCastling;
13625 int i, j, fromX, fromY, toX, toY;
13632 whiteToPlay = (gameMode == EditPosition) ?
13633 !blackPlaysFirst : (move % 2 == 0);
13636 /* Piece placement data */
13637 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13639 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13640 if (boards[move][i][j] == EmptySquare) {
13642 } else { ChessSquare piece = boards[move][i][j];
13643 if (emptycount > 0) {
13644 if(emptycount<10) /* [HGM] can be >= 10 */
13645 *p++ = '0' + emptycount;
13646 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13649 if(PieceToChar(piece) == '+') {
13650 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13652 piece = (ChessSquare)(DEMOTED piece);
13654 *p++ = PieceToChar(piece);
13656 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13657 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13662 if (emptycount > 0) {
13663 if(emptycount<10) /* [HGM] can be >= 10 */
13664 *p++ = '0' + emptycount;
13665 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13672 /* [HGM] print Crazyhouse or Shogi holdings */
13673 if( gameInfo.holdingsWidth ) {
13674 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13676 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13677 piece = boards[move][i][BOARD_WIDTH-1];
13678 if( piece != EmptySquare )
13679 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13680 *p++ = PieceToChar(piece);
13682 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13683 piece = boards[move][BOARD_HEIGHT-i-1][0];
13684 if( piece != EmptySquare )
13685 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13686 *p++ = PieceToChar(piece);
13689 if( q == p ) *p++ = '-';
13695 *p++ = whiteToPlay ? 'w' : 'b';
13698 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13699 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13701 if(nrCastlingRights) {
13703 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13704 /* [HGM] write directly from rights */
13705 if(castlingRights[move][2] >= 0 &&
13706 castlingRights[move][0] >= 0 )
13707 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13708 if(castlingRights[move][2] >= 0 &&
13709 castlingRights[move][1] >= 0 )
13710 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13711 if(castlingRights[move][5] >= 0 &&
13712 castlingRights[move][3] >= 0 )
13713 *p++ = castlingRights[move][3] + AAA;
13714 if(castlingRights[move][5] >= 0 &&
13715 castlingRights[move][4] >= 0 )
13716 *p++ = castlingRights[move][4] + AAA;
13719 /* [HGM] write true castling rights */
13720 if( nrCastlingRights == 6 ) {
13721 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13722 castlingRights[move][2] >= 0 ) *p++ = 'K';
13723 if(castlingRights[move][1] == BOARD_LEFT &&
13724 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13725 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13726 castlingRights[move][5] >= 0 ) *p++ = 'k';
13727 if(castlingRights[move][4] == BOARD_LEFT &&
13728 castlingRights[move][5] >= 0 ) *p++ = 'q';
13731 if (q == p) *p++ = '-'; /* No castling rights */
13735 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13736 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13737 /* En passant target square */
13738 if (move > backwardMostMove) {
13739 fromX = moveList[move - 1][0] - AAA;
13740 fromY = moveList[move - 1][1] - ONE;
13741 toX = moveList[move - 1][2] - AAA;
13742 toY = moveList[move - 1][3] - ONE;
13743 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13744 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13745 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13747 /* 2-square pawn move just happened */
13749 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13753 } else if(move == backwardMostMove) {
13754 // [HGM] perhaps we should always do it like this, and forget the above?
13755 if(epStatus[move] >= 0) {
13756 *p++ = epStatus[move] + AAA;
13757 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13768 /* [HGM] find reversible plies */
13769 { int i = 0, j=move;
13771 if (appData.debugMode) { int k;
13772 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13773 for(k=backwardMostMove; k<=forwardMostMove; k++)
13774 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13778 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13779 if( j == backwardMostMove ) i += initialRulePlies;
13780 sprintf(p, "%d ", i);
13781 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13783 /* Fullmove number */
13784 sprintf(p, "%d", (move / 2) + 1);
13786 return StrSave(buf);
13790 ParseFEN(board, blackPlaysFirst, fen)
13792 int *blackPlaysFirst;
13802 /* [HGM] by default clear Crazyhouse holdings, if present */
13803 if(gameInfo.holdingsWidth) {
13804 for(i=0; i<BOARD_HEIGHT; i++) {
13805 board[i][0] = EmptySquare; /* black holdings */
13806 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13807 board[i][1] = (ChessSquare) 0; /* black counts */
13808 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13812 /* Piece placement data */
13813 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13816 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13817 if (*p == '/') p++;
13818 emptycount = gameInfo.boardWidth - j;
13819 while (emptycount--)
13820 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13822 #if(BOARD_SIZE >= 10)
13823 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13824 p++; emptycount=10;
13825 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13826 while (emptycount--)
13827 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13829 } else if (isdigit(*p)) {
13830 emptycount = *p++ - '0';
13831 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13832 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13833 while (emptycount--)
13834 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13835 } else if (*p == '+' || isalpha(*p)) {
13836 if (j >= gameInfo.boardWidth) return FALSE;
13838 piece = CharToPiece(*++p);
13839 if(piece == EmptySquare) return FALSE; /* unknown piece */
13840 piece = (ChessSquare) (PROMOTED piece ); p++;
13841 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13842 } else piece = CharToPiece(*p++);
13844 if(piece==EmptySquare) return FALSE; /* unknown piece */
13845 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13846 piece = (ChessSquare) (PROMOTED piece);
13847 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13850 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13856 while (*p == '/' || *p == ' ') p++;
13858 /* [HGM] look for Crazyhouse holdings here */
13859 while(*p==' ') p++;
13860 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13862 if(*p == '-' ) *p++; /* empty holdings */ else {
13863 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13864 /* if we would allow FEN reading to set board size, we would */
13865 /* have to add holdings and shift the board read so far here */
13866 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13868 if((int) piece >= (int) BlackPawn ) {
13869 i = (int)piece - (int)BlackPawn;
13870 i = PieceToNumber((ChessSquare)i);
13871 if( i >= gameInfo.holdingsSize ) return FALSE;
13872 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13873 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13875 i = (int)piece - (int)WhitePawn;
13876 i = PieceToNumber((ChessSquare)i);
13877 if( i >= gameInfo.holdingsSize ) return FALSE;
13878 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13879 board[i][BOARD_WIDTH-2]++; /* black holdings */
13883 if(*p == ']') *p++;
13886 while(*p == ' ') p++;
13891 *blackPlaysFirst = FALSE;
13894 *blackPlaysFirst = TRUE;
13900 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13901 /* return the extra info in global variiables */
13903 /* set defaults in case FEN is incomplete */
13904 FENepStatus = EP_UNKNOWN;
13905 for(i=0; i<nrCastlingRights; i++ ) {
13906 FENcastlingRights[i] =
13907 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13908 } /* assume possible unless obviously impossible */
13909 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13910 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13911 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13912 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13913 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13914 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13915 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13916 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13919 while(*p==' ') p++;
13920 if(nrCastlingRights) {
13921 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13922 /* castling indicator present, so default becomes no castlings */
13923 for(i=0; i<nrCastlingRights; i++ ) {
13924 FENcastlingRights[i] = -1;
13927 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13928 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13929 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13930 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13931 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13933 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13934 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13935 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13937 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13938 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13939 if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13940 && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13941 if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13942 && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13945 for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13946 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13947 FENcastlingRights[2] = whiteKingFile;
13950 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13951 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13952 FENcastlingRights[2] = whiteKingFile;
13955 for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13956 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13957 FENcastlingRights[5] = blackKingFile;
13960 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13961 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13962 FENcastlingRights[5] = blackKingFile;
13965 default: /* FRC castlings */
13966 if(c >= 'a') { /* black rights */
13967 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13968 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13969 if(i == BOARD_RGHT) break;
13970 FENcastlingRights[5] = i;
13972 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13973 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13975 FENcastlingRights[3] = c;
13977 FENcastlingRights[4] = c;
13978 } else { /* white rights */
13979 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13980 if(board[0][i] == WhiteKing) break;
13981 if(i == BOARD_RGHT) break;
13982 FENcastlingRights[2] = i;
13983 c -= AAA - 'a' + 'A';
13984 if(board[0][c] >= WhiteKing) break;
13986 FENcastlingRights[0] = c;
13988 FENcastlingRights[1] = c;
13992 for(i=0; i<nrCastlingRights; i++)
13993 if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
13994 if (appData.debugMode) {
13995 fprintf(debugFP, "FEN castling rights:");
13996 for(i=0; i<nrCastlingRights; i++)
13997 fprintf(debugFP, " %d", FENcastlingRights[i]);
13998 fprintf(debugFP, "\n");
14001 while(*p==' ') p++;
14004 /* read e.p. field in games that know e.p. capture */
14005 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14006 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14008 p++; FENepStatus = EP_NONE;
14010 char c = *p++ - AAA;
14012 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14013 if(*p >= '0' && *p <='9') *p++;
14019 if(sscanf(p, "%d", &i) == 1) {
14020 FENrulePlies = i; /* 50-move ply counter */
14021 /* (The move number is still ignored) */
14028 EditPositionPasteFEN(char *fen)
14031 Board initial_position;
14033 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14034 DisplayError(_("Bad FEN position in clipboard"), 0);
14037 int savedBlackPlaysFirst = blackPlaysFirst;
14038 EditPositionEvent();
14039 blackPlaysFirst = savedBlackPlaysFirst;
14040 CopyBoard(boards[0], initial_position);
14041 /* [HGM] copy FEN attributes as well */
14043 initialRulePlies = FENrulePlies;
14044 epStatus[0] = FENepStatus;
14045 for( i=0; i<nrCastlingRights; i++ )
14046 castlingRights[0][i] = FENcastlingRights[i];
14048 EditPositionDone(FALSE);
14049 DisplayBothClocks();
14050 DrawPosition(FALSE, boards[currentMove]);
14055 static char cseq[12] = "\\ ";
14057 Boolean set_cont_sequence(char *new_seq)
14062 // handle bad attempts to set the sequence
14064 return 0; // acceptable error - no debug
14066 len = strlen(new_seq);
14067 ret = (len > 0) && (len < sizeof(cseq));
14069 strcpy(cseq, new_seq);
14070 else if (appData.debugMode)
14071 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14076 reformat a source message so words don't cross the width boundary. internal
14077 newlines are not removed. returns the wrapped size (no null character unless
14078 included in source message). If dest is NULL, only calculate the size required
14079 for the dest buffer. lp argument indicats line position upon entry, and it's
14080 passed back upon exit.
14082 int wrap(char *dest, char *src, int count, int width, int *lp)
14084 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14086 cseq_len = strlen(cseq);
14087 old_line = line = *lp;
14088 ansi = len = clen = 0;
14090 for (i=0; i < count; i++)
14092 if (src[i] == '\033')
14095 // if we hit the width, back up
14096 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14098 // store i & len in case the word is too long
14099 old_i = i, old_len = len;
14101 // find the end of the last word
14102 while (i && src[i] != ' ' && src[i] != '\n')
14108 // word too long? restore i & len before splitting it
14109 if ((old_i-i+clen) >= width)
14116 if (i && src[i-1] == ' ')
14119 if (src[i] != ' ' && src[i] != '\n')
14126 // now append the newline and continuation sequence
14131 strncpy(dest+len, cseq, cseq_len);
14139 dest[len] = src[i];
14143 if (src[i] == '\n')
14148 if (dest && appData.debugMode)
14150 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14151 count, width, line, len, *lp);
14152 show_bytes(debugFP, src, count);
14153 fprintf(debugFP, "\ndest: ");
14154 show_bytes(debugFP, dest, len);
14155 fprintf(debugFP, "\n");
14157 *lp = dest ? line : old_line;