2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163 Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void KeepAlive P((void));
191 void StartClocks P((void));
192 void SwitchClocks P((int nr));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
223 extern void ConsoleCreate();
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
242 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
246 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
253 /* States for ics_getting_history */
255 #define H_REQUESTED 1
256 #define H_GOT_REQ_HEADER 2
257 #define H_GOT_UNREQ_HEADER 3
258 #define H_GETTING_MOVES 4
259 #define H_GOT_UNWANTED_HEADER 5
261 /* whosays values for GameEnds */
270 /* Maximum number of games in a cmail message */
271 #define CMAIL_MAX_GAMES 20
273 /* Different types of move when calling RegisterMove */
275 #define CMAIL_RESIGN 1
277 #define CMAIL_ACCEPT 3
279 /* Different types of result to remember for each game */
280 #define CMAIL_NOT_RESULT 0
281 #define CMAIL_OLD_RESULT 1
282 #define CMAIL_NEW_RESULT 2
284 /* Telnet protocol constants */
295 static char * safeStrCpy( char * dst, const char * src, size_t count )
297 assert( dst != NULL );
298 assert( src != NULL );
301 strncpy( dst, src, count );
302 dst[ count-1 ] = '\0';
306 /* Some compiler can't cast u64 to double
307 * This function do the job for us:
309 * We use the highest bit for cast, this only
310 * works if the highest bit is not
311 * in use (This should not happen)
313 * We used this for all compiler
316 u64ToDouble(u64 value)
319 u64 tmp = value & u64Const(0x7fffffffffffffff);
320 r = (double)(s64)tmp;
321 if (value & u64Const(0x8000000000000000))
322 r += 9.2233720368547758080e18; /* 2^63 */
326 /* Fake up flags for now, as we aren't keeping track of castling
327 availability yet. [HGM] Change of logic: the flag now only
328 indicates the type of castlings allowed by the rule of the game.
329 The actual rights themselves are maintained in the array
330 castlingRights, as part of the game history, and are not probed
336 int flags = F_ALL_CASTLE_OK;
337 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
338 switch (gameInfo.variant) {
340 flags &= ~F_ALL_CASTLE_OK;
341 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
342 flags |= F_IGNORE_CHECK;
344 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
347 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
349 case VariantKriegspiel:
350 flags |= F_KRIEGSPIEL_CAPTURE;
352 case VariantCapaRandom:
353 case VariantFischeRandom:
354 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
355 case VariantNoCastle:
356 case VariantShatranj:
359 flags &= ~F_ALL_CASTLE_OK;
367 FILE *gameFileFP, *debugFP;
370 [AS] Note: sometimes, the sscanf() function is used to parse the input
371 into a fixed-size buffer. Because of this, we must be prepared to
372 receive strings as long as the size of the input buffer, which is currently
373 set to 4K for Windows and 8K for the rest.
374 So, we must either allocate sufficiently large buffers here, or
375 reduce the size of the input buffer in the input reading part.
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
382 ChessProgramState first, second;
384 /* premove variables */
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
434 /* animateTraining preserves the state of appData.animate
435 * when Training mode is activated. This allows the
436 * response to be animated when appData.animate == TRUE and
437 * appData.animateDragging == TRUE.
439 Boolean animateTraining;
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char epStatus[MAX_MOVES];
448 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
449 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
450 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
451 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
452 int initialRulePlies, FENrulePlies;
454 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
457 int mute; // mute all sounds
459 ChessSquare FIDEArray[2][BOARD_SIZE] = {
460 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
461 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
462 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
463 BlackKing, BlackBishop, BlackKnight, BlackRook }
466 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
467 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
468 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
469 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
470 BlackKing, BlackKing, BlackKnight, BlackRook }
473 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
474 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
475 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
476 { BlackRook, BlackMan, BlackBishop, BlackQueen,
477 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
480 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] white and black different armies! */
481 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
483 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
484 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
487 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
488 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
489 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
490 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
491 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
494 ChessSquare makrukArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
495 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
496 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
497 { BlackRook, BlackKnight, BlackMan, BlackFerz,
498 BlackKing, BlackMan, BlackKnight, BlackRook }
503 ChessSquare ShogiArray[2][BOARD_SIZE] = {
504 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
505 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
506 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
507 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
510 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
511 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
512 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
513 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
514 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
517 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
518 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
519 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
521 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
524 ChessSquare GreatArray[2][BOARD_SIZE] = {
525 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
526 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
527 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
528 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
531 ChessSquare JanusArray[2][BOARD_SIZE] = {
532 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
533 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
534 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
535 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
539 ChessSquare GothicArray[2][BOARD_SIZE] = {
540 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
541 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
542 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
543 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
546 #define GothicArray CapablancaArray
550 ChessSquare FalconArray[2][BOARD_SIZE] = {
551 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
552 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
554 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
557 #define FalconArray CapablancaArray
560 #else // !(BOARD_SIZE>=10)
561 #define XiangqiPosition FIDEArray
562 #define CapablancaArray FIDEArray
563 #define GothicArray FIDEArray
564 #define GreatArray FIDEArray
565 #endif // !(BOARD_SIZE>=10)
568 ChessSquare CourierArray[2][BOARD_SIZE] = {
569 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
570 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
571 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
572 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
574 #else // !(BOARD_SIZE>=12)
575 #define CourierArray CapablancaArray
576 #endif // !(BOARD_SIZE>=12)
579 Board initialPosition;
582 /* Convert str to a rating. Checks for special cases of "----",
584 "++++", etc. Also strips ()'s */
586 string_to_rating(str)
589 while(*str && !isdigit(*str)) ++str;
591 return 0; /* One of the special "no rating" cases */
599 /* Init programStats */
600 programStats.movelist[0] = 0;
601 programStats.depth = 0;
602 programStats.nr_moves = 0;
603 programStats.moves_left = 0;
604 programStats.nodes = 0;
605 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
606 programStats.score = 0;
607 programStats.got_only_move = 0;
608 programStats.got_fail = 0;
609 programStats.line_is_book = 0;
615 int matched, min, sec;
617 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
619 GetTimeMark(&programStartTime);
620 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
623 programStats.ok_to_send = 1;
624 programStats.seen_stat = 0;
627 * Initialize game list
633 * Internet chess server status
635 if (appData.icsActive) {
636 appData.matchMode = FALSE;
637 appData.matchGames = 0;
639 appData.noChessProgram = !appData.zippyPlay;
641 appData.zippyPlay = FALSE;
642 appData.zippyTalk = FALSE;
643 appData.noChessProgram = TRUE;
645 if (*appData.icsHelper != NULLCHAR) {
646 appData.useTelnet = TRUE;
647 appData.telnetProgram = appData.icsHelper;
650 appData.zippyTalk = appData.zippyPlay = FALSE;
653 /* [AS] Initialize pv info list [HGM] and game state */
657 for( i=0; i<MAX_MOVES; i++ ) {
658 pvInfoList[i].depth = -1;
660 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
665 * Parse timeControl resource
667 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
668 appData.movesPerSession)) {
670 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
671 DisplayFatalError(buf, 0, 2);
675 * Parse searchTime resource
677 if (*appData.searchTime != NULLCHAR) {
678 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
680 searchTime = min * 60;
681 } else if (matched == 2) {
682 searchTime = min * 60 + sec;
685 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
686 DisplayFatalError(buf, 0, 2);
690 /* [AS] Adjudication threshold */
691 adjudicateLossThreshold = appData.adjudicateLossThreshold;
693 first.which = "first";
694 second.which = "second";
695 first.maybeThinking = second.maybeThinking = FALSE;
696 first.pr = second.pr = NoProc;
697 first.isr = second.isr = NULL;
698 first.sendTime = second.sendTime = 2;
699 first.sendDrawOffers = 1;
700 if (appData.firstPlaysBlack) {
701 first.twoMachinesColor = "black\n";
702 second.twoMachinesColor = "white\n";
704 first.twoMachinesColor = "white\n";
705 second.twoMachinesColor = "black\n";
707 first.program = appData.firstChessProgram;
708 second.program = appData.secondChessProgram;
709 first.host = appData.firstHost;
710 second.host = appData.secondHost;
711 first.dir = appData.firstDirectory;
712 second.dir = appData.secondDirectory;
713 first.other = &second;
714 second.other = &first;
715 first.initString = appData.initString;
716 second.initString = appData.secondInitString;
717 first.computerString = appData.firstComputerString;
718 second.computerString = appData.secondComputerString;
719 first.useSigint = second.useSigint = TRUE;
720 first.useSigterm = second.useSigterm = TRUE;
721 first.reuse = appData.reuseFirst;
722 second.reuse = appData.reuseSecond;
723 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
724 second.nps = appData.secondNPS;
725 first.useSetboard = second.useSetboard = FALSE;
726 first.useSAN = second.useSAN = FALSE;
727 first.usePing = second.usePing = FALSE;
728 first.lastPing = second.lastPing = 0;
729 first.lastPong = second.lastPong = 0;
730 first.usePlayother = second.usePlayother = FALSE;
731 first.useColors = second.useColors = TRUE;
732 first.useUsermove = second.useUsermove = FALSE;
733 first.sendICS = second.sendICS = FALSE;
734 first.sendName = second.sendName = appData.icsActive;
735 first.sdKludge = second.sdKludge = FALSE;
736 first.stKludge = second.stKludge = FALSE;
737 TidyProgramName(first.program, first.host, first.tidy);
738 TidyProgramName(second.program, second.host, second.tidy);
739 first.matchWins = second.matchWins = 0;
740 strcpy(first.variants, appData.variant);
741 strcpy(second.variants, appData.variant);
742 first.analysisSupport = second.analysisSupport = 2; /* detect */
743 first.analyzing = second.analyzing = FALSE;
744 first.initDone = second.initDone = FALSE;
746 /* New features added by Tord: */
747 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
748 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
749 /* End of new features added by Tord. */
750 first.fenOverride = appData.fenOverride1;
751 second.fenOverride = appData.fenOverride2;
753 /* [HGM] time odds: set factor for each machine */
754 first.timeOdds = appData.firstTimeOdds;
755 second.timeOdds = appData.secondTimeOdds;
757 if(appData.timeOddsMode) {
758 norm = first.timeOdds;
759 if(norm > second.timeOdds) norm = second.timeOdds;
761 first.timeOdds /= norm;
762 second.timeOdds /= norm;
765 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
766 first.accumulateTC = appData.firstAccumulateTC;
767 second.accumulateTC = appData.secondAccumulateTC;
768 first.maxNrOfSessions = second.maxNrOfSessions = 1;
771 first.debug = second.debug = FALSE;
772 first.supportsNPS = second.supportsNPS = UNKNOWN;
775 first.optionSettings = appData.firstOptions;
776 second.optionSettings = appData.secondOptions;
778 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
779 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
780 first.isUCI = appData.firstIsUCI; /* [AS] */
781 second.isUCI = appData.secondIsUCI; /* [AS] */
782 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
783 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
785 if (appData.firstProtocolVersion > PROTOVER ||
786 appData.firstProtocolVersion < 1) {
788 sprintf(buf, _("protocol version %d not supported"),
789 appData.firstProtocolVersion);
790 DisplayFatalError(buf, 0, 2);
792 first.protocolVersion = appData.firstProtocolVersion;
795 if (appData.secondProtocolVersion > PROTOVER ||
796 appData.secondProtocolVersion < 1) {
798 sprintf(buf, _("protocol version %d not supported"),
799 appData.secondProtocolVersion);
800 DisplayFatalError(buf, 0, 2);
802 second.protocolVersion = appData.secondProtocolVersion;
805 if (appData.icsActive) {
806 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
807 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
808 appData.clockMode = FALSE;
809 first.sendTime = second.sendTime = 0;
813 /* Override some settings from environment variables, for backward
814 compatibility. Unfortunately it's not feasible to have the env
815 vars just set defaults, at least in xboard. Ugh.
817 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
822 if (appData.noChessProgram) {
823 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
824 sprintf(programVersion, "%s", PACKAGE_STRING);
826 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
827 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
828 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
831 if (!appData.icsActive) {
833 /* Check for variants that are supported only in ICS mode,
834 or not at all. Some that are accepted here nevertheless
835 have bugs; see comments below.
837 VariantClass variant = StringToVariant(appData.variant);
839 case VariantBughouse: /* need four players and two boards */
840 case VariantKriegspiel: /* need to hide pieces and move details */
841 /* case VariantFischeRandom: (Fabien: moved below) */
842 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
843 DisplayFatalError(buf, 0, 2);
847 case VariantLoadable:
857 sprintf(buf, _("Unknown variant name %s"), appData.variant);
858 DisplayFatalError(buf, 0, 2);
861 case VariantXiangqi: /* [HGM] repetition rules not implemented */
862 case VariantFairy: /* [HGM] TestLegality definitely off! */
863 case VariantGothic: /* [HGM] should work */
864 case VariantCapablanca: /* [HGM] should work */
865 case VariantCourier: /* [HGM] initial forced moves not implemented */
866 case VariantShogi: /* [HGM] drops not tested for legality */
867 case VariantKnightmate: /* [HGM] should work */
868 case VariantCylinder: /* [HGM] untested */
869 case VariantFalcon: /* [HGM] untested */
870 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
871 offboard interposition not understood */
872 case VariantNormal: /* definitely works! */
873 case VariantWildCastle: /* pieces not automatically shuffled */
874 case VariantNoCastle: /* pieces not automatically shuffled */
875 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
876 case VariantLosers: /* should work except for win condition,
877 and doesn't know captures are mandatory */
878 case VariantSuicide: /* should work except for win condition,
879 and doesn't know captures are mandatory */
880 case VariantGiveaway: /* should work except for win condition,
881 and doesn't know captures are mandatory */
882 case VariantTwoKings: /* should work */
883 case VariantAtomic: /* should work except for win condition */
884 case Variant3Check: /* should work except for win condition */
885 case VariantShatranj: /* should work except for all win conditions */
886 case VariantMakruk: /* should work except for daw countdown */
887 case VariantBerolina: /* might work if TestLegality is off */
888 case VariantCapaRandom: /* should work */
889 case VariantJanus: /* should work */
890 case VariantSuper: /* experimental */
891 case VariantGreat: /* experimental, requires legality testing to be off */
896 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
897 InitEngineUCI( installDir, &second );
900 int NextIntegerFromString( char ** str, long * value )
905 while( *s == ' ' || *s == '\t' ) {
911 if( *s >= '0' && *s <= '9' ) {
912 while( *s >= '0' && *s <= '9' ) {
913 *value = *value * 10 + (*s - '0');
925 int NextTimeControlFromString( char ** str, long * value )
928 int result = NextIntegerFromString( str, &temp );
931 *value = temp * 60; /* Minutes */
934 result = NextIntegerFromString( str, &temp );
935 *value += temp; /* Seconds */
942 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
943 { /* [HGM] routine added to read '+moves/time' for secondary time control */
944 int result = -1; long temp, temp2;
946 if(**str != '+') return -1; // old params remain in force!
948 if( NextTimeControlFromString( str, &temp ) ) return -1;
951 /* time only: incremental or sudden-death time control */
952 if(**str == '+') { /* increment follows; read it */
954 if(result = NextIntegerFromString( str, &temp2)) return -1;
957 *moves = 0; *tc = temp * 1000;
959 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
961 (*str)++; /* classical time control */
962 result = NextTimeControlFromString( str, &temp2);
971 int GetTimeQuota(int movenr)
972 { /* [HGM] get time to add from the multi-session time-control string */
973 int moves=1; /* kludge to force reading of first session */
974 long time, increment;
975 char *s = fullTimeControlString;
977 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
979 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
980 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
981 if(movenr == -1) return time; /* last move before new session */
982 if(!moves) return increment; /* current session is incremental */
983 if(movenr >= 0) movenr -= moves; /* we already finished this session */
984 } while(movenr >= -1); /* try again for next session */
986 return 0; // no new time quota on this move
990 ParseTimeControl(tc, ti, mps)
999 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1002 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1003 else sprintf(buf, "+%s+%d", tc, ti);
1006 sprintf(buf, "+%d/%s", mps, tc);
1007 else sprintf(buf, "+%s", tc);
1009 fullTimeControlString = StrSave(buf);
1011 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1016 /* Parse second time control */
1019 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1027 timeControl_2 = tc2 * 1000;
1037 timeControl = tc1 * 1000;
1040 timeIncrement = ti * 1000; /* convert to ms */
1041 movesPerSession = 0;
1044 movesPerSession = mps;
1052 if (appData.debugMode) {
1053 fprintf(debugFP, "%s\n", programVersion);
1056 set_cont_sequence(appData.wrapContSeq);
1057 if (appData.matchGames > 0) {
1058 appData.matchMode = TRUE;
1059 } else if (appData.matchMode) {
1060 appData.matchGames = 1;
1062 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1063 appData.matchGames = appData.sameColorGames;
1064 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1065 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1066 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1069 if (appData.noChessProgram || first.protocolVersion == 1) {
1072 /* kludge: allow timeout for initial "feature" commands */
1074 DisplayMessage("", _("Starting chess program"));
1075 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1080 InitBackEnd3 P((void))
1082 GameMode initialMode;
1086 InitChessProgram(&first, startedFromSetupPosition);
1089 if (appData.icsActive) {
1091 /* [DM] Make a console window if needed [HGM] merged ifs */
1096 if (*appData.icsCommPort != NULLCHAR) {
1097 sprintf(buf, _("Could not open comm port %s"),
1098 appData.icsCommPort);
1100 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1101 appData.icsHost, appData.icsPort);
1103 DisplayFatalError(buf, err, 1);
1108 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1110 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1111 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1112 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1113 } else if (appData.noChessProgram) {
1119 if (*appData.cmailGameName != NULLCHAR) {
1121 OpenLoopback(&cmailPR);
1123 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1127 DisplayMessage("", "");
1128 if (StrCaseCmp(appData.initialMode, "") == 0) {
1129 initialMode = BeginningOfGame;
1130 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1131 initialMode = TwoMachinesPlay;
1132 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1133 initialMode = AnalyzeFile;
1134 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1135 initialMode = AnalyzeMode;
1136 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1137 initialMode = MachinePlaysWhite;
1138 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1139 initialMode = MachinePlaysBlack;
1140 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1141 initialMode = EditGame;
1142 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1143 initialMode = EditPosition;
1144 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1145 initialMode = Training;
1147 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1148 DisplayFatalError(buf, 0, 2);
1152 if (appData.matchMode) {
1153 /* Set up machine vs. machine match */
1154 if (appData.noChessProgram) {
1155 DisplayFatalError(_("Can't have a match with no chess programs"),
1161 if (*appData.loadGameFile != NULLCHAR) {
1162 int index = appData.loadGameIndex; // [HGM] autoinc
1163 if(index<0) lastIndex = index = 1;
1164 if (!LoadGameFromFile(appData.loadGameFile,
1166 appData.loadGameFile, FALSE)) {
1167 DisplayFatalError(_("Bad game file"), 0, 1);
1170 } else if (*appData.loadPositionFile != NULLCHAR) {
1171 int index = appData.loadPositionIndex; // [HGM] autoinc
1172 if(index<0) lastIndex = index = 1;
1173 if (!LoadPositionFromFile(appData.loadPositionFile,
1175 appData.loadPositionFile)) {
1176 DisplayFatalError(_("Bad position file"), 0, 1);
1181 } else if (*appData.cmailGameName != NULLCHAR) {
1182 /* Set up cmail mode */
1183 ReloadCmailMsgEvent(TRUE);
1185 /* Set up other modes */
1186 if (initialMode == AnalyzeFile) {
1187 if (*appData.loadGameFile == NULLCHAR) {
1188 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1192 if (*appData.loadGameFile != NULLCHAR) {
1193 (void) LoadGameFromFile(appData.loadGameFile,
1194 appData.loadGameIndex,
1195 appData.loadGameFile, TRUE);
1196 } else if (*appData.loadPositionFile != NULLCHAR) {
1197 (void) LoadPositionFromFile(appData.loadPositionFile,
1198 appData.loadPositionIndex,
1199 appData.loadPositionFile);
1200 /* [HGM] try to make self-starting even after FEN load */
1201 /* to allow automatic setup of fairy variants with wtm */
1202 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1203 gameMode = BeginningOfGame;
1204 setboardSpoiledMachineBlack = 1;
1206 /* [HGM] loadPos: make that every new game uses the setup */
1207 /* from file as long as we do not switch variant */
1208 if(!blackPlaysFirst) { int i;
1209 startedFromPositionFile = TRUE;
1210 CopyBoard(filePosition, boards[0]);
1211 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1214 if (initialMode == AnalyzeMode) {
1215 if (appData.noChessProgram) {
1216 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1219 if (appData.icsActive) {
1220 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1224 } else if (initialMode == AnalyzeFile) {
1225 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1226 ShowThinkingEvent();
1228 AnalysisPeriodicEvent(1);
1229 } else if (initialMode == MachinePlaysWhite) {
1230 if (appData.noChessProgram) {
1231 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1235 if (appData.icsActive) {
1236 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1240 MachineWhiteEvent();
1241 } else if (initialMode == MachinePlaysBlack) {
1242 if (appData.noChessProgram) {
1243 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1247 if (appData.icsActive) {
1248 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1252 MachineBlackEvent();
1253 } else if (initialMode == TwoMachinesPlay) {
1254 if (appData.noChessProgram) {
1255 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1259 if (appData.icsActive) {
1260 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1265 } else if (initialMode == EditGame) {
1267 } else if (initialMode == EditPosition) {
1268 EditPositionEvent();
1269 } else if (initialMode == Training) {
1270 if (*appData.loadGameFile == NULLCHAR) {
1271 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1280 * Establish will establish a contact to a remote host.port.
1281 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1282 * used to talk to the host.
1283 * Returns 0 if okay, error code if not.
1290 if (*appData.icsCommPort != NULLCHAR) {
1291 /* Talk to the host through a serial comm port */
1292 return OpenCommPort(appData.icsCommPort, &icsPR);
1294 } else if (*appData.gateway != NULLCHAR) {
1295 if (*appData.remoteShell == NULLCHAR) {
1296 /* Use the rcmd protocol to run telnet program on a gateway host */
1297 snprintf(buf, sizeof(buf), "%s %s %s",
1298 appData.telnetProgram, appData.icsHost, appData.icsPort);
1299 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1302 /* Use the rsh program to run telnet program on a gateway host */
1303 if (*appData.remoteUser == NULLCHAR) {
1304 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1305 appData.gateway, appData.telnetProgram,
1306 appData.icsHost, appData.icsPort);
1308 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1309 appData.remoteShell, appData.gateway,
1310 appData.remoteUser, appData.telnetProgram,
1311 appData.icsHost, appData.icsPort);
1313 return StartChildProcess(buf, "", &icsPR);
1316 } else if (appData.useTelnet) {
1317 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1320 /* TCP socket interface differs somewhat between
1321 Unix and NT; handle details in the front end.
1323 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1328 show_bytes(fp, buf, count)
1334 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1335 fprintf(fp, "\\%03o", *buf & 0xff);
1344 /* Returns an errno value */
1346 OutputMaybeTelnet(pr, message, count, outError)
1352 char buf[8192], *p, *q, *buflim;
1353 int left, newcount, outcount;
1355 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1356 *appData.gateway != NULLCHAR) {
1357 if (appData.debugMode) {
1358 fprintf(debugFP, ">ICS: ");
1359 show_bytes(debugFP, message, count);
1360 fprintf(debugFP, "\n");
1362 return OutputToProcess(pr, message, count, outError);
1365 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1372 if (appData.debugMode) {
1373 fprintf(debugFP, ">ICS: ");
1374 show_bytes(debugFP, buf, newcount);
1375 fprintf(debugFP, "\n");
1377 outcount = OutputToProcess(pr, buf, newcount, outError);
1378 if (outcount < newcount) return -1; /* to be sure */
1385 } else if (((unsigned char) *p) == TN_IAC) {
1386 *q++ = (char) TN_IAC;
1393 if (appData.debugMode) {
1394 fprintf(debugFP, ">ICS: ");
1395 show_bytes(debugFP, buf, newcount);
1396 fprintf(debugFP, "\n");
1398 outcount = OutputToProcess(pr, buf, newcount, outError);
1399 if (outcount < newcount) return -1; /* to be sure */
1404 read_from_player(isr, closure, message, count, error)
1411 int outError, outCount;
1412 static int gotEof = 0;
1414 /* Pass data read from player on to ICS */
1417 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1418 if (outCount < count) {
1419 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1421 } else if (count < 0) {
1422 RemoveInputSource(isr);
1423 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1424 } else if (gotEof++ > 0) {
1425 RemoveInputSource(isr);
1426 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1432 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1433 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1434 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1435 SendToICS("date\n");
1436 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1439 /* added routine for printf style output to ics */
1440 void ics_printf(char *format, ...)
1442 char buffer[MSG_SIZ];
1445 va_start(args, format);
1446 vsnprintf(buffer, sizeof(buffer), format, args);
1447 buffer[sizeof(buffer)-1] = '\0';
1456 int count, outCount, outError;
1458 if (icsPR == NULL) return;
1461 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1462 if (outCount < count) {
1463 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1467 /* This is used for sending logon scripts to the ICS. Sending
1468 without a delay causes problems when using timestamp on ICC
1469 (at least on my machine). */
1471 SendToICSDelayed(s,msdelay)
1475 int count, outCount, outError;
1477 if (icsPR == NULL) return;
1480 if (appData.debugMode) {
1481 fprintf(debugFP, ">ICS: ");
1482 show_bytes(debugFP, s, count);
1483 fprintf(debugFP, "\n");
1485 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1487 if (outCount < count) {
1488 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1493 /* Remove all highlighting escape sequences in s
1494 Also deletes any suffix starting with '('
1497 StripHighlightAndTitle(s)
1500 static char retbuf[MSG_SIZ];
1503 while (*s != NULLCHAR) {
1504 while (*s == '\033') {
1505 while (*s != NULLCHAR && !isalpha(*s)) s++;
1506 if (*s != NULLCHAR) s++;
1508 while (*s != NULLCHAR && *s != '\033') {
1509 if (*s == '(' || *s == '[') {
1520 /* Remove all highlighting escape sequences in s */
1525 static char retbuf[MSG_SIZ];
1528 while (*s != NULLCHAR) {
1529 while (*s == '\033') {
1530 while (*s != NULLCHAR && !isalpha(*s)) s++;
1531 if (*s != NULLCHAR) s++;
1533 while (*s != NULLCHAR && *s != '\033') {
1541 char *variantNames[] = VARIANT_NAMES;
1546 return variantNames[v];
1550 /* Identify a variant from the strings the chess servers use or the
1551 PGN Variant tag names we use. */
1558 VariantClass v = VariantNormal;
1559 int i, found = FALSE;
1564 /* [HGM] skip over optional board-size prefixes */
1565 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1566 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1567 while( *e++ != '_');
1570 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1574 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1575 if (StrCaseStr(e, variantNames[i])) {
1576 v = (VariantClass) i;
1583 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1584 || StrCaseStr(e, "wild/fr")
1585 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1586 v = VariantFischeRandom;
1587 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1588 (i = 1, p = StrCaseStr(e, "w"))) {
1590 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1597 case 0: /* FICS only, actually */
1599 /* Castling legal even if K starts on d-file */
1600 v = VariantWildCastle;
1605 /* Castling illegal even if K & R happen to start in
1606 normal positions. */
1607 v = VariantNoCastle;
1620 /* Castling legal iff K & R start in normal positions */
1626 /* Special wilds for position setup; unclear what to do here */
1627 v = VariantLoadable;
1630 /* Bizarre ICC game */
1631 v = VariantTwoKings;
1634 v = VariantKriegspiel;
1640 v = VariantFischeRandom;
1643 v = VariantCrazyhouse;
1646 v = VariantBughouse;
1652 /* Not quite the same as FICS suicide! */
1653 v = VariantGiveaway;
1659 v = VariantShatranj;
1662 /* Temporary names for future ICC types. The name *will* change in
1663 the next xboard/WinBoard release after ICC defines it. */
1701 v = VariantCapablanca;
1704 v = VariantKnightmate;
1710 v = VariantCylinder;
1716 v = VariantCapaRandom;
1719 v = VariantBerolina;
1731 /* Found "wild" or "w" in the string but no number;
1732 must assume it's normal chess. */
1736 sprintf(buf, _("Unknown wild type %d"), wnum);
1737 DisplayError(buf, 0);
1743 if (appData.debugMode) {
1744 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1745 e, wnum, VariantName(v));
1750 static int leftover_start = 0, leftover_len = 0;
1751 char star_match[STAR_MATCH_N][MSG_SIZ];
1753 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1754 advance *index beyond it, and set leftover_start to the new value of
1755 *index; else return FALSE. If pattern contains the character '*', it
1756 matches any sequence of characters not containing '\r', '\n', or the
1757 character following the '*' (if any), and the matched sequence(s) are
1758 copied into star_match.
1761 looking_at(buf, index, pattern)
1766 char *bufp = &buf[*index], *patternp = pattern;
1768 char *matchp = star_match[0];
1771 if (*patternp == NULLCHAR) {
1772 *index = leftover_start = bufp - buf;
1776 if (*bufp == NULLCHAR) return FALSE;
1777 if (*patternp == '*') {
1778 if (*bufp == *(patternp + 1)) {
1780 matchp = star_match[++star_count];
1784 } else if (*bufp == '\n' || *bufp == '\r') {
1786 if (*patternp == NULLCHAR)
1791 *matchp++ = *bufp++;
1795 if (*patternp != *bufp) return FALSE;
1802 SendToPlayer(data, length)
1806 int error, outCount;
1807 outCount = OutputToProcess(NoProc, data, length, &error);
1808 if (outCount < length) {
1809 DisplayFatalError(_("Error writing to display"), error, 1);
1814 PackHolding(packed, holding)
1826 switch (runlength) {
1837 sprintf(q, "%d", runlength);
1849 /* Telnet protocol requests from the front end */
1851 TelnetRequest(ddww, option)
1852 unsigned char ddww, option;
1854 unsigned char msg[3];
1855 int outCount, outError;
1857 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1859 if (appData.debugMode) {
1860 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1876 sprintf(buf1, "%d", ddww);
1885 sprintf(buf2, "%d", option);
1888 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1893 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1895 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1902 if (!appData.icsActive) return;
1903 TelnetRequest(TN_DO, TN_ECHO);
1909 if (!appData.icsActive) return;
1910 TelnetRequest(TN_DONT, TN_ECHO);
1914 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1916 /* put the holdings sent to us by the server on the board holdings area */
1917 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1921 if(gameInfo.holdingsWidth < 2) return;
1922 if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1923 return; // prevent overwriting by pre-board holdings
1925 if( (int)lowestPiece >= BlackPawn ) {
1928 holdingsStartRow = BOARD_HEIGHT-1;
1931 holdingsColumn = BOARD_WIDTH-1;
1932 countsColumn = BOARD_WIDTH-2;
1933 holdingsStartRow = 0;
1937 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1938 board[i][holdingsColumn] = EmptySquare;
1939 board[i][countsColumn] = (ChessSquare) 0;
1941 while( (p=*holdings++) != NULLCHAR ) {
1942 piece = CharToPiece( ToUpper(p) );
1943 if(piece == EmptySquare) continue;
1944 /*j = (int) piece - (int) WhitePawn;*/
1945 j = PieceToNumber(piece);
1946 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1947 if(j < 0) continue; /* should not happen */
1948 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1949 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1950 board[holdingsStartRow+j*direction][countsColumn]++;
1956 VariantSwitch(Board board, VariantClass newVariant)
1958 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1961 startedFromPositionFile = FALSE;
1962 if(gameInfo.variant == newVariant) return;
1964 /* [HGM] This routine is called each time an assignment is made to
1965 * gameInfo.variant during a game, to make sure the board sizes
1966 * are set to match the new variant. If that means adding or deleting
1967 * holdings, we shift the playing board accordingly
1968 * This kludge is needed because in ICS observe mode, we get boards
1969 * of an ongoing game without knowing the variant, and learn about the
1970 * latter only later. This can be because of the move list we requested,
1971 * in which case the game history is refilled from the beginning anyway,
1972 * but also when receiving holdings of a crazyhouse game. In the latter
1973 * case we want to add those holdings to the already received position.
1977 if (appData.debugMode) {
1978 fprintf(debugFP, "Switch board from %s to %s\n",
1979 VariantName(gameInfo.variant), VariantName(newVariant));
1980 setbuf(debugFP, NULL);
1982 shuffleOpenings = 0; /* [HGM] shuffle */
1983 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1987 newWidth = 9; newHeight = 9;
1988 gameInfo.holdingsSize = 7;
1989 case VariantBughouse:
1990 case VariantCrazyhouse:
1991 newHoldingsWidth = 2; break;
1995 newHoldingsWidth = 2;
1996 gameInfo.holdingsSize = 8;
1999 case VariantCapablanca:
2000 case VariantCapaRandom:
2003 newHoldingsWidth = gameInfo.holdingsSize = 0;
2006 if(newWidth != gameInfo.boardWidth ||
2007 newHeight != gameInfo.boardHeight ||
2008 newHoldingsWidth != gameInfo.holdingsWidth ) {
2010 /* shift position to new playing area, if needed */
2011 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2012 for(i=0; i<BOARD_HEIGHT; i++)
2013 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2014 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2016 for(i=0; i<newHeight; i++) {
2017 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2018 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2020 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2021 for(i=0; i<BOARD_HEIGHT; i++)
2022 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2023 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2026 gameInfo.boardWidth = newWidth;
2027 gameInfo.boardHeight = newHeight;
2028 gameInfo.holdingsWidth = newHoldingsWidth;
2029 gameInfo.variant = newVariant;
2030 InitDrawingSizes(-2, 0);
2031 } else gameInfo.variant = newVariant;
2032 CopyBoard(oldBoard, board); // remember correctly formatted board
2033 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2034 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2037 static int loggedOn = FALSE;
2039 /*-- Game start info cache: --*/
2041 char gs_kind[MSG_SIZ];
2042 static char player1Name[128] = "";
2043 static char player2Name[128] = "";
2044 static char cont_seq[] = "\n\\ ";
2045 static int player1Rating = -1;
2046 static int player2Rating = -1;
2047 /*----------------------------*/
2049 ColorClass curColor = ColorNormal;
2050 int suppressKibitz = 0;
2053 read_from_ics(isr, closure, data, count, error)
2060 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2061 #define STARTED_NONE 0
2062 #define STARTED_MOVES 1
2063 #define STARTED_BOARD 2
2064 #define STARTED_OBSERVE 3
2065 #define STARTED_HOLDINGS 4
2066 #define STARTED_CHATTER 5
2067 #define STARTED_COMMENT 6
2068 #define STARTED_MOVES_NOHIDE 7
2070 static int started = STARTED_NONE;
2071 static char parse[20000];
2072 static int parse_pos = 0;
2073 static char buf[BUF_SIZE + 1];
2074 static int firstTime = TRUE, intfSet = FALSE;
2075 static ColorClass prevColor = ColorNormal;
2076 static int savingComment = FALSE;
2077 static int cmatch = 0; // continuation sequence match
2084 int backup; /* [DM] For zippy color lines */
2086 char talker[MSG_SIZ]; // [HGM] chat
2089 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2091 if (appData.debugMode) {
2093 fprintf(debugFP, "<ICS: ");
2094 show_bytes(debugFP, data, count);
2095 fprintf(debugFP, "\n");
2099 if (appData.debugMode) { int f = forwardMostMove;
2100 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2101 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2104 /* If last read ended with a partial line that we couldn't parse,
2105 prepend it to the new read and try again. */
2106 if (leftover_len > 0) {
2107 for (i=0; i<leftover_len; i++)
2108 buf[i] = buf[leftover_start + i];
2111 /* copy new characters into the buffer */
2112 bp = buf + leftover_len;
2113 buf_len=leftover_len;
2114 for (i=0; i<count; i++)
2117 if (data[i] == '\r')
2120 // join lines split by ICS?
2121 if (!appData.noJoin)
2124 Joining just consists of finding matches against the
2125 continuation sequence, and discarding that sequence
2126 if found instead of copying it. So, until a match
2127 fails, there's nothing to do since it might be the
2128 complete sequence, and thus, something we don't want
2131 if (data[i] == cont_seq[cmatch])
2134 if (cmatch == strlen(cont_seq))
2136 cmatch = 0; // complete match. just reset the counter
2139 it's possible for the ICS to not include the space
2140 at the end of the last word, making our [correct]
2141 join operation fuse two separate words. the server
2142 does this when the space occurs at the width setting.
2144 if (!buf_len || buf[buf_len-1] != ' ')
2155 match failed, so we have to copy what matched before
2156 falling through and copying this character. In reality,
2157 this will only ever be just the newline character, but
2158 it doesn't hurt to be precise.
2160 strncpy(bp, cont_seq, cmatch);
2172 buf[buf_len] = NULLCHAR;
2173 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2178 while (i < buf_len) {
2179 /* Deal with part of the TELNET option negotiation
2180 protocol. We refuse to do anything beyond the
2181 defaults, except that we allow the WILL ECHO option,
2182 which ICS uses to turn off password echoing when we are
2183 directly connected to it. We reject this option
2184 if localLineEditing mode is on (always on in xboard)
2185 and we are talking to port 23, which might be a real
2186 telnet server that will try to keep WILL ECHO on permanently.
2188 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2189 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2190 unsigned char option;
2192 switch ((unsigned char) buf[++i]) {
2194 if (appData.debugMode)
2195 fprintf(debugFP, "\n<WILL ");
2196 switch (option = (unsigned char) buf[++i]) {
2198 if (appData.debugMode)
2199 fprintf(debugFP, "ECHO ");
2200 /* Reply only if this is a change, according
2201 to the protocol rules. */
2202 if (remoteEchoOption) break;
2203 if (appData.localLineEditing &&
2204 atoi(appData.icsPort) == TN_PORT) {
2205 TelnetRequest(TN_DONT, TN_ECHO);
2208 TelnetRequest(TN_DO, TN_ECHO);
2209 remoteEchoOption = TRUE;
2213 if (appData.debugMode)
2214 fprintf(debugFP, "%d ", option);
2215 /* Whatever this is, we don't want it. */
2216 TelnetRequest(TN_DONT, option);
2221 if (appData.debugMode)
2222 fprintf(debugFP, "\n<WONT ");
2223 switch (option = (unsigned char) buf[++i]) {
2225 if (appData.debugMode)
2226 fprintf(debugFP, "ECHO ");
2227 /* Reply only if this is a change, according
2228 to the protocol rules. */
2229 if (!remoteEchoOption) break;
2231 TelnetRequest(TN_DONT, TN_ECHO);
2232 remoteEchoOption = FALSE;
2235 if (appData.debugMode)
2236 fprintf(debugFP, "%d ", (unsigned char) option);
2237 /* Whatever this is, it must already be turned
2238 off, because we never agree to turn on
2239 anything non-default, so according to the
2240 protocol rules, we don't reply. */
2245 if (appData.debugMode)
2246 fprintf(debugFP, "\n<DO ");
2247 switch (option = (unsigned char) buf[++i]) {
2249 /* Whatever this is, we refuse to do it. */
2250 if (appData.debugMode)
2251 fprintf(debugFP, "%d ", option);
2252 TelnetRequest(TN_WONT, option);
2257 if (appData.debugMode)
2258 fprintf(debugFP, "\n<DONT ");
2259 switch (option = (unsigned char) buf[++i]) {
2261 if (appData.debugMode)
2262 fprintf(debugFP, "%d ", option);
2263 /* Whatever this is, we are already not doing
2264 it, because we never agree to do anything
2265 non-default, so according to the protocol
2266 rules, we don't reply. */
2271 if (appData.debugMode)
2272 fprintf(debugFP, "\n<IAC ");
2273 /* Doubled IAC; pass it through */
2277 if (appData.debugMode)
2278 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2279 /* Drop all other telnet commands on the floor */
2282 if (oldi > next_out)
2283 SendToPlayer(&buf[next_out], oldi - next_out);
2289 /* OK, this at least will *usually* work */
2290 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2294 if (loggedOn && !intfSet) {
2295 if (ics_type == ICS_ICC) {
2297 "/set-quietly interface %s\n/set-quietly style 12\n",
2299 } else if (ics_type == ICS_CHESSNET) {
2300 sprintf(str, "/style 12\n");
2302 strcpy(str, "alias $ @\n$set interface ");
2303 strcat(str, programVersion);
2304 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2306 strcat(str, "$iset nohighlight 1\n");
2308 strcat(str, "$iset lock 1\n$style 12\n");
2311 NotifyFrontendLogin();
2315 if (started == STARTED_COMMENT) {
2316 /* Accumulate characters in comment */
2317 parse[parse_pos++] = buf[i];
2318 if (buf[i] == '\n') {
2319 parse[parse_pos] = NULLCHAR;
2320 if(chattingPartner>=0) {
2322 sprintf(mess, "%s%s", talker, parse);
2323 OutputChatMessage(chattingPartner, mess);
2324 chattingPartner = -1;
2326 if(!suppressKibitz) // [HGM] kibitz
2327 AppendComment(forwardMostMove, StripHighlight(parse));
2328 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2329 int nrDigit = 0, nrAlph = 0, j;
2330 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2331 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2332 parse[parse_pos] = NULLCHAR;
2333 // try to be smart: if it does not look like search info, it should go to
2334 // ICS interaction window after all, not to engine-output window.
2335 for(j=0; j<parse_pos; j++) { // count letters and digits
2336 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2337 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2338 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2340 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2341 int depth=0; float score;
2342 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2343 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2344 pvInfoList[forwardMostMove-1].depth = depth;
2345 pvInfoList[forwardMostMove-1].score = 100*score;
2347 OutputKibitz(suppressKibitz, parse);
2348 next_out = i+1; // [HGM] suppress printing in ICS window
2351 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2352 SendToPlayer(tmp, strlen(tmp));
2355 started = STARTED_NONE;
2357 /* Don't match patterns against characters in comment */
2362 if (started == STARTED_CHATTER) {
2363 if (buf[i] != '\n') {
2364 /* Don't match patterns against characters in chatter */
2368 started = STARTED_NONE;
2371 /* Kludge to deal with rcmd protocol */
2372 if (firstTime && looking_at(buf, &i, "\001*")) {
2373 DisplayFatalError(&buf[1], 0, 1);
2379 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2382 if (appData.debugMode)
2383 fprintf(debugFP, "ics_type %d\n", ics_type);
2386 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2387 ics_type = ICS_FICS;
2389 if (appData.debugMode)
2390 fprintf(debugFP, "ics_type %d\n", ics_type);
2393 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2394 ics_type = ICS_CHESSNET;
2396 if (appData.debugMode)
2397 fprintf(debugFP, "ics_type %d\n", ics_type);
2402 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2403 looking_at(buf, &i, "Logging you in as \"*\"") ||
2404 looking_at(buf, &i, "will be \"*\""))) {
2405 strcpy(ics_handle, star_match[0]);
2409 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2411 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2412 DisplayIcsInteractionTitle(buf);
2413 have_set_title = TRUE;
2416 /* skip finger notes */
2417 if (started == STARTED_NONE &&
2418 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2419 (buf[i] == '1' && buf[i+1] == '0')) &&
2420 buf[i+2] == ':' && buf[i+3] == ' ') {
2421 started = STARTED_CHATTER;
2426 /* skip formula vars */
2427 if (started == STARTED_NONE &&
2428 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2429 started = STARTED_CHATTER;
2435 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2436 if (appData.autoKibitz && started == STARTED_NONE &&
2437 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2438 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2439 if(looking_at(buf, &i, "* kibitzes: ") &&
2440 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2441 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2442 suppressKibitz = TRUE;
2443 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2444 && (gameMode == IcsPlayingWhite)) ||
2445 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2446 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2447 started = STARTED_CHATTER; // own kibitz we simply discard
2449 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2450 parse_pos = 0; parse[0] = NULLCHAR;
2451 savingComment = TRUE;
2452 suppressKibitz = gameMode != IcsObserving ? 2 :
2453 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2457 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2458 // suppress the acknowledgements of our own autoKibitz
2460 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2461 SendToPlayer(star_match[0], strlen(star_match[0]));
2462 looking_at(buf, &i, "*% "); // eat prompt
2465 } // [HGM] kibitz: end of patch
2467 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2469 // [HGM] chat: intercept tells by users for which we have an open chat window
2471 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2472 looking_at(buf, &i, "* whispers:") ||
2473 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2474 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2476 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2477 chattingPartner = -1;
2479 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2480 for(p=0; p<MAX_CHAT; p++) {
2481 if(channel == atoi(chatPartner[p])) {
2482 talker[0] = '['; strcat(talker, "] ");
2483 chattingPartner = p; break;
2486 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2487 for(p=0; p<MAX_CHAT; p++) {
2488 if(!strcmp("WHISPER", chatPartner[p])) {
2489 talker[0] = '['; strcat(talker, "] ");
2490 chattingPartner = p; break;
2493 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2494 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2496 chattingPartner = p; break;
2498 if(chattingPartner<0) i = oldi; else {
2499 started = STARTED_COMMENT;
2500 parse_pos = 0; parse[0] = NULLCHAR;
2501 savingComment = 3 + chattingPartner; // counts as TRUE
2502 suppressKibitz = TRUE;
2504 } // [HGM] chat: end of patch
2506 if (appData.zippyTalk || appData.zippyPlay) {
2507 /* [DM] Backup address for color zippy lines */
2511 if (loggedOn == TRUE)
2512 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2513 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2515 if (ZippyControl(buf, &i) ||
2516 ZippyConverse(buf, &i) ||
2517 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2519 if (!appData.colorize) continue;
2523 } // [DM] 'else { ' deleted
2525 /* Regular tells and says */
2526 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2527 looking_at(buf, &i, "* (your partner) tells you: ") ||
2528 looking_at(buf, &i, "* says: ") ||
2529 /* Don't color "message" or "messages" output */
2530 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2531 looking_at(buf, &i, "*. * at *:*: ") ||
2532 looking_at(buf, &i, "--* (*:*): ") ||
2533 /* Message notifications (same color as tells) */
2534 looking_at(buf, &i, "* has left a message ") ||
2535 looking_at(buf, &i, "* just sent you a message:\n") ||
2536 /* Whispers and kibitzes */
2537 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2538 looking_at(buf, &i, "* kibitzes: ") ||
2540 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2542 if (tkind == 1 && strchr(star_match[0], ':')) {
2543 /* Avoid "tells you:" spoofs in channels */
2546 if (star_match[0][0] == NULLCHAR ||
2547 strchr(star_match[0], ' ') ||
2548 (tkind == 3 && strchr(star_match[1], ' '))) {
2549 /* Reject bogus matches */
2552 if (appData.colorize) {
2553 if (oldi > next_out) {
2554 SendToPlayer(&buf[next_out], oldi - next_out);
2559 Colorize(ColorTell, FALSE);
2560 curColor = ColorTell;
2563 Colorize(ColorKibitz, FALSE);
2564 curColor = ColorKibitz;
2567 p = strrchr(star_match[1], '(');
2574 Colorize(ColorChannel1, FALSE);
2575 curColor = ColorChannel1;
2577 Colorize(ColorChannel, FALSE);
2578 curColor = ColorChannel;
2582 curColor = ColorNormal;
2586 if (started == STARTED_NONE && appData.autoComment &&
2587 (gameMode == IcsObserving ||
2588 gameMode == IcsPlayingWhite ||
2589 gameMode == IcsPlayingBlack)) {
2590 parse_pos = i - oldi;
2591 memcpy(parse, &buf[oldi], parse_pos);
2592 parse[parse_pos] = NULLCHAR;
2593 started = STARTED_COMMENT;
2594 savingComment = TRUE;
2596 started = STARTED_CHATTER;
2597 savingComment = FALSE;
2604 if (looking_at(buf, &i, "* s-shouts: ") ||
2605 looking_at(buf, &i, "* c-shouts: ")) {
2606 if (appData.colorize) {
2607 if (oldi > next_out) {
2608 SendToPlayer(&buf[next_out], oldi - next_out);
2611 Colorize(ColorSShout, FALSE);
2612 curColor = ColorSShout;
2615 started = STARTED_CHATTER;
2619 if (looking_at(buf, &i, "--->")) {
2624 if (looking_at(buf, &i, "* shouts: ") ||
2625 looking_at(buf, &i, "--> ")) {
2626 if (appData.colorize) {
2627 if (oldi > next_out) {
2628 SendToPlayer(&buf[next_out], oldi - next_out);
2631 Colorize(ColorShout, FALSE);
2632 curColor = ColorShout;
2635 started = STARTED_CHATTER;
2639 if (looking_at( buf, &i, "Challenge:")) {
2640 if (appData.colorize) {
2641 if (oldi > next_out) {
2642 SendToPlayer(&buf[next_out], oldi - next_out);
2645 Colorize(ColorChallenge, FALSE);
2646 curColor = ColorChallenge;
2652 if (looking_at(buf, &i, "* offers you") ||
2653 looking_at(buf, &i, "* offers to be") ||
2654 looking_at(buf, &i, "* would like to") ||
2655 looking_at(buf, &i, "* requests to") ||
2656 looking_at(buf, &i, "Your opponent offers") ||
2657 looking_at(buf, &i, "Your opponent requests")) {
2659 if (appData.colorize) {
2660 if (oldi > next_out) {
2661 SendToPlayer(&buf[next_out], oldi - next_out);
2664 Colorize(ColorRequest, FALSE);
2665 curColor = ColorRequest;
2670 if (looking_at(buf, &i, "* (*) seeking")) {
2671 if (appData.colorize) {
2672 if (oldi > next_out) {
2673 SendToPlayer(&buf[next_out], oldi - next_out);
2676 Colorize(ColorSeek, FALSE);
2677 curColor = ColorSeek;
2682 if (looking_at(buf, &i, "\\ ")) {
2683 if (prevColor != ColorNormal) {
2684 if (oldi > next_out) {
2685 SendToPlayer(&buf[next_out], oldi - next_out);
2688 Colorize(prevColor, TRUE);
2689 curColor = prevColor;
2691 if (savingComment) {
2692 parse_pos = i - oldi;
2693 memcpy(parse, &buf[oldi], parse_pos);
2694 parse[parse_pos] = NULLCHAR;
2695 started = STARTED_COMMENT;
2696 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2697 chattingPartner = savingComment - 3; // kludge to remember the box
2699 started = STARTED_CHATTER;
2704 if (looking_at(buf, &i, "Black Strength :") ||
2705 looking_at(buf, &i, "<<< style 10 board >>>") ||
2706 looking_at(buf, &i, "<10>") ||
2707 looking_at(buf, &i, "#@#")) {
2708 /* Wrong board style */
2710 SendToICS(ics_prefix);
2711 SendToICS("set style 12\n");
2712 SendToICS(ics_prefix);
2713 SendToICS("refresh\n");
2717 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2719 have_sent_ICS_logon = 1;
2723 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2724 (looking_at(buf, &i, "\n<12> ") ||
2725 looking_at(buf, &i, "<12> "))) {
2727 if (oldi > next_out) {
2728 SendToPlayer(&buf[next_out], oldi - next_out);
2731 started = STARTED_BOARD;
2736 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2737 looking_at(buf, &i, "<b1> ")) {
2738 if (oldi > next_out) {
2739 SendToPlayer(&buf[next_out], oldi - next_out);
2742 started = STARTED_HOLDINGS;
2747 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2749 /* Header for a move list -- first line */
2751 switch (ics_getting_history) {
2755 case BeginningOfGame:
2756 /* User typed "moves" or "oldmoves" while we
2757 were idle. Pretend we asked for these
2758 moves and soak them up so user can step
2759 through them and/or save them.
2762 gameMode = IcsObserving;
2765 ics_getting_history = H_GOT_UNREQ_HEADER;
2767 case EditGame: /*?*/
2768 case EditPosition: /*?*/
2769 /* Should above feature work in these modes too? */
2770 /* For now it doesn't */
2771 ics_getting_history = H_GOT_UNWANTED_HEADER;
2774 ics_getting_history = H_GOT_UNWANTED_HEADER;
2779 /* Is this the right one? */
2780 if (gameInfo.white && gameInfo.black &&
2781 strcmp(gameInfo.white, star_match[0]) == 0 &&
2782 strcmp(gameInfo.black, star_match[2]) == 0) {
2784 ics_getting_history = H_GOT_REQ_HEADER;
2787 case H_GOT_REQ_HEADER:
2788 case H_GOT_UNREQ_HEADER:
2789 case H_GOT_UNWANTED_HEADER:
2790 case H_GETTING_MOVES:
2791 /* Should not happen */
2792 DisplayError(_("Error gathering move list: two headers"), 0);
2793 ics_getting_history = H_FALSE;
2797 /* Save player ratings into gameInfo if needed */
2798 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2799 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2800 (gameInfo.whiteRating == -1 ||
2801 gameInfo.blackRating == -1)) {
2803 gameInfo.whiteRating = string_to_rating(star_match[1]);
2804 gameInfo.blackRating = string_to_rating(star_match[3]);
2805 if (appData.debugMode)
2806 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2807 gameInfo.whiteRating, gameInfo.blackRating);
2812 if (looking_at(buf, &i,
2813 "* * match, initial time: * minute*, increment: * second")) {
2814 /* Header for a move list -- second line */
2815 /* Initial board will follow if this is a wild game */
2816 if (gameInfo.event != NULL) free(gameInfo.event);
2817 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2818 gameInfo.event = StrSave(str);
2819 /* [HGM] we switched variant. Translate boards if needed. */
2820 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2824 if (looking_at(buf, &i, "Move ")) {
2825 /* Beginning of a move list */
2826 switch (ics_getting_history) {
2828 /* Normally should not happen */
2829 /* Maybe user hit reset while we were parsing */
2832 /* Happens if we are ignoring a move list that is not
2833 * the one we just requested. Common if the user
2834 * tries to observe two games without turning off
2837 case H_GETTING_MOVES:
2838 /* Should not happen */
2839 DisplayError(_("Error gathering move list: nested"), 0);
2840 ics_getting_history = H_FALSE;
2842 case H_GOT_REQ_HEADER:
2843 ics_getting_history = H_GETTING_MOVES;
2844 started = STARTED_MOVES;
2846 if (oldi > next_out) {
2847 SendToPlayer(&buf[next_out], oldi - next_out);
2850 case H_GOT_UNREQ_HEADER:
2851 ics_getting_history = H_GETTING_MOVES;
2852 started = STARTED_MOVES_NOHIDE;
2855 case H_GOT_UNWANTED_HEADER:
2856 ics_getting_history = H_FALSE;
2862 if (looking_at(buf, &i, "% ") ||
2863 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2864 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2865 if(suppressKibitz) next_out = i;
2866 savingComment = FALSE;
2870 case STARTED_MOVES_NOHIDE:
2871 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2872 parse[parse_pos + i - oldi] = NULLCHAR;
2873 ParseGameHistory(parse);
2875 if (appData.zippyPlay && first.initDone) {
2876 FeedMovesToProgram(&first, forwardMostMove);
2877 if (gameMode == IcsPlayingWhite) {
2878 if (WhiteOnMove(forwardMostMove)) {
2879 if (first.sendTime) {
2880 if (first.useColors) {
2881 SendToProgram("black\n", &first);
2883 SendTimeRemaining(&first, TRUE);
2885 if (first.useColors) {
2886 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2888 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2889 first.maybeThinking = TRUE;
2891 if (first.usePlayother) {
2892 if (first.sendTime) {
2893 SendTimeRemaining(&first, TRUE);
2895 SendToProgram("playother\n", &first);
2901 } else if (gameMode == IcsPlayingBlack) {
2902 if (!WhiteOnMove(forwardMostMove)) {
2903 if (first.sendTime) {
2904 if (first.useColors) {
2905 SendToProgram("white\n", &first);
2907 SendTimeRemaining(&first, FALSE);
2909 if (first.useColors) {
2910 SendToProgram("black\n", &first);
2912 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2913 first.maybeThinking = TRUE;
2915 if (first.usePlayother) {
2916 if (first.sendTime) {
2917 SendTimeRemaining(&first, FALSE);
2919 SendToProgram("playother\n", &first);
2928 if (gameMode == IcsObserving && ics_gamenum == -1) {
2929 /* Moves came from oldmoves or moves command
2930 while we weren't doing anything else.
2932 currentMove = forwardMostMove;
2933 ClearHighlights();/*!!could figure this out*/
2934 flipView = appData.flipView;
2935 DrawPosition(TRUE, boards[currentMove]);
2936 DisplayBothClocks();
2937 sprintf(str, "%s vs. %s",
2938 gameInfo.white, gameInfo.black);
2942 /* Moves were history of an active game */
2943 if (gameInfo.resultDetails != NULL) {
2944 free(gameInfo.resultDetails);
2945 gameInfo.resultDetails = NULL;
2948 HistorySet(parseList, backwardMostMove,
2949 forwardMostMove, currentMove-1);
2950 DisplayMove(currentMove - 1);
2951 if (started == STARTED_MOVES) next_out = i;
2952 started = STARTED_NONE;
2953 ics_getting_history = H_FALSE;
2956 case STARTED_OBSERVE:
2957 started = STARTED_NONE;
2958 SendToICS(ics_prefix);
2959 SendToICS("refresh\n");
2965 if(bookHit) { // [HGM] book: simulate book reply
2966 static char bookMove[MSG_SIZ]; // a bit generous?
2968 programStats.nodes = programStats.depth = programStats.time =
2969 programStats.score = programStats.got_only_move = 0;
2970 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2972 strcpy(bookMove, "move ");
2973 strcat(bookMove, bookHit);
2974 HandleMachineMove(bookMove, &first);
2979 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2980 started == STARTED_HOLDINGS ||
2981 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2982 /* Accumulate characters in move list or board */
2983 parse[parse_pos++] = buf[i];
2986 /* Start of game messages. Mostly we detect start of game
2987 when the first board image arrives. On some versions
2988 of the ICS, though, we need to do a "refresh" after starting
2989 to observe in order to get the current board right away. */
2990 if (looking_at(buf, &i, "Adding game * to observation list")) {
2991 started = STARTED_OBSERVE;
2995 /* Handle auto-observe */
2996 if (appData.autoObserve &&
2997 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2998 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3000 /* Choose the player that was highlighted, if any. */
3001 if (star_match[0][0] == '\033' ||
3002 star_match[1][0] != '\033') {
3003 player = star_match[0];
3005 player = star_match[2];
3007 sprintf(str, "%sobserve %s\n",
3008 ics_prefix, StripHighlightAndTitle(player));
3011 /* Save ratings from notify string */
3012 strcpy(player1Name, star_match[0]);
3013 player1Rating = string_to_rating(star_match[1]);
3014 strcpy(player2Name, star_match[2]);
3015 player2Rating = string_to_rating(star_match[3]);
3017 if (appData.debugMode)
3019 "Ratings from 'Game notification:' %s %d, %s %d\n",
3020 player1Name, player1Rating,
3021 player2Name, player2Rating);
3026 /* Deal with automatic examine mode after a game,
3027 and with IcsObserving -> IcsExamining transition */
3028 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3029 looking_at(buf, &i, "has made you an examiner of game *")) {
3031 int gamenum = atoi(star_match[0]);
3032 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3033 gamenum == ics_gamenum) {
3034 /* We were already playing or observing this game;
3035 no need to refetch history */
3036 gameMode = IcsExamining;
3038 pauseExamForwardMostMove = forwardMostMove;
3039 } else if (currentMove < forwardMostMove) {
3040 ForwardInner(forwardMostMove);
3043 /* I don't think this case really can happen */
3044 SendToICS(ics_prefix);
3045 SendToICS("refresh\n");
3050 /* Error messages */
3051 // if (ics_user_moved) {
3052 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3053 if (looking_at(buf, &i, "Illegal move") ||
3054 looking_at(buf, &i, "Not a legal move") ||
3055 looking_at(buf, &i, "Your king is in check") ||
3056 looking_at(buf, &i, "It isn't your turn") ||
3057 looking_at(buf, &i, "It is not your move")) {
3059 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3060 currentMove = forwardMostMove-1;
3061 DisplayMove(currentMove - 1); /* before DMError */
3062 DrawPosition(FALSE, boards[currentMove]);
3063 SwitchClocks(forwardMostMove-1); // [HGM] race
3064 DisplayBothClocks();
3066 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3072 if (looking_at(buf, &i, "still have time") ||
3073 looking_at(buf, &i, "not out of time") ||
3074 looking_at(buf, &i, "either player is out of time") ||
3075 looking_at(buf, &i, "has timeseal; checking")) {
3076 /* We must have called his flag a little too soon */
3077 whiteFlag = blackFlag = FALSE;
3081 if (looking_at(buf, &i, "added * seconds to") ||
3082 looking_at(buf, &i, "seconds were added to")) {
3083 /* Update the clocks */
3084 SendToICS(ics_prefix);
3085 SendToICS("refresh\n");
3089 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3090 ics_clock_paused = TRUE;
3095 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3096 ics_clock_paused = FALSE;
3101 /* Grab player ratings from the Creating: message.
3102 Note we have to check for the special case when
3103 the ICS inserts things like [white] or [black]. */
3104 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3105 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3107 0 player 1 name (not necessarily white)
3109 2 empty, white, or black (IGNORED)
3110 3 player 2 name (not necessarily black)
3113 The names/ratings are sorted out when the game
3114 actually starts (below).
3116 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3117 player1Rating = string_to_rating(star_match[1]);
3118 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3119 player2Rating = string_to_rating(star_match[4]);
3121 if (appData.debugMode)
3123 "Ratings from 'Creating:' %s %d, %s %d\n",
3124 player1Name, player1Rating,
3125 player2Name, player2Rating);
3130 /* Improved generic start/end-of-game messages */
3131 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3132 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3133 /* If tkind == 0: */
3134 /* star_match[0] is the game number */
3135 /* [1] is the white player's name */
3136 /* [2] is the black player's name */
3137 /* For end-of-game: */
3138 /* [3] is the reason for the game end */
3139 /* [4] is a PGN end game-token, preceded by " " */
3140 /* For start-of-game: */
3141 /* [3] begins with "Creating" or "Continuing" */
3142 /* [4] is " *" or empty (don't care). */
3143 int gamenum = atoi(star_match[0]);
3144 char *whitename, *blackname, *why, *endtoken;
3145 ChessMove endtype = (ChessMove) 0;
3148 whitename = star_match[1];
3149 blackname = star_match[2];
3150 why = star_match[3];
3151 endtoken = star_match[4];
3153 whitename = star_match[1];
3154 blackname = star_match[3];
3155 why = star_match[5];
3156 endtoken = star_match[6];
3159 /* Game start messages */
3160 if (strncmp(why, "Creating ", 9) == 0 ||
3161 strncmp(why, "Continuing ", 11) == 0) {
3162 gs_gamenum = gamenum;
3163 strcpy(gs_kind, strchr(why, ' ') + 1);
3164 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3166 if (appData.zippyPlay) {
3167 ZippyGameStart(whitename, blackname);
3173 /* Game end messages */
3174 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3175 ics_gamenum != gamenum) {
3178 while (endtoken[0] == ' ') endtoken++;
3179 switch (endtoken[0]) {
3182 endtype = GameUnfinished;
3185 endtype = BlackWins;
3188 if (endtoken[1] == '/')
3189 endtype = GameIsDrawn;
3191 endtype = WhiteWins;
3194 GameEnds(endtype, why, GE_ICS);
3196 if (appData.zippyPlay && first.initDone) {
3197 ZippyGameEnd(endtype, why);
3198 if (first.pr == NULL) {
3199 /* Start the next process early so that we'll
3200 be ready for the next challenge */
3201 StartChessProgram(&first);
3203 /* Send "new" early, in case this command takes
3204 a long time to finish, so that we'll be ready
3205 for the next challenge. */
3206 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3213 if (looking_at(buf, &i, "Removing game * from observation") ||
3214 looking_at(buf, &i, "no longer observing game *") ||
3215 looking_at(buf, &i, "Game * (*) has no examiners")) {
3216 if (gameMode == IcsObserving &&
3217 atoi(star_match[0]) == ics_gamenum)
3219 /* icsEngineAnalyze */
3220 if (appData.icsEngineAnalyze) {
3227 ics_user_moved = FALSE;
3232 if (looking_at(buf, &i, "no longer examining game *")) {
3233 if (gameMode == IcsExamining &&
3234 atoi(star_match[0]) == ics_gamenum)
3238 ics_user_moved = FALSE;
3243 /* Advance leftover_start past any newlines we find,
3244 so only partial lines can get reparsed */
3245 if (looking_at(buf, &i, "\n")) {
3246 prevColor = curColor;
3247 if (curColor != ColorNormal) {
3248 if (oldi > next_out) {
3249 SendToPlayer(&buf[next_out], oldi - next_out);
3252 Colorize(ColorNormal, FALSE);
3253 curColor = ColorNormal;
3255 if (started == STARTED_BOARD) {
3256 started = STARTED_NONE;
3257 parse[parse_pos] = NULLCHAR;
3258 ParseBoard12(parse);
3261 /* Send premove here */
3262 if (appData.premove) {
3264 if (currentMove == 0 &&
3265 gameMode == IcsPlayingWhite &&
3266 appData.premoveWhite) {
3267 sprintf(str, "%s\n", appData.premoveWhiteText);
3268 if (appData.debugMode)
3269 fprintf(debugFP, "Sending premove:\n");
3271 } else if (currentMove == 1 &&
3272 gameMode == IcsPlayingBlack &&
3273 appData.premoveBlack) {
3274 sprintf(str, "%s\n", appData.premoveBlackText);
3275 if (appData.debugMode)
3276 fprintf(debugFP, "Sending premove:\n");
3278 } else if (gotPremove) {
3280 ClearPremoveHighlights();
3281 if (appData.debugMode)
3282 fprintf(debugFP, "Sending premove:\n");
3283 UserMoveEvent(premoveFromX, premoveFromY,
3284 premoveToX, premoveToY,
3289 /* Usually suppress following prompt */
3290 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3291 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3292 if (looking_at(buf, &i, "*% ")) {
3293 savingComment = FALSE;
3298 } else if (started == STARTED_HOLDINGS) {
3300 char new_piece[MSG_SIZ];
3301 started = STARTED_NONE;
3302 parse[parse_pos] = NULLCHAR;
3303 if (appData.debugMode)
3304 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3305 parse, currentMove);
3306 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3307 gamenum == ics_gamenum) {
3308 if (gameInfo.variant == VariantNormal) {
3309 /* [HGM] We seem to switch variant during a game!
3310 * Presumably no holdings were displayed, so we have
3311 * to move the position two files to the right to
3312 * create room for them!
3314 VariantClass newVariant;
3315 switch(gameInfo.boardWidth) { // base guess on board width
3316 case 9: newVariant = VariantShogi; break;
3317 case 10: newVariant = VariantGreat; break;
3318 default: newVariant = VariantCrazyhouse; break;
3320 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3321 /* Get a move list just to see the header, which
3322 will tell us whether this is really bug or zh */
3323 if (ics_getting_history == H_FALSE) {
3324 ics_getting_history = H_REQUESTED;
3325 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3329 new_piece[0] = NULLCHAR;
3330 sscanf(parse, "game %d white [%s black [%s <- %s",
3331 &gamenum, white_holding, black_holding,
3333 white_holding[strlen(white_holding)-1] = NULLCHAR;
3334 black_holding[strlen(black_holding)-1] = NULLCHAR;
3335 /* [HGM] copy holdings to board holdings area */
3336 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3337 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3338 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3340 if (appData.zippyPlay && first.initDone) {
3341 ZippyHoldings(white_holding, black_holding,
3345 if (tinyLayout || smallLayout) {
3346 char wh[16], bh[16];
3347 PackHolding(wh, white_holding);
3348 PackHolding(bh, black_holding);
3349 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3350 gameInfo.white, gameInfo.black);
3352 sprintf(str, "%s [%s] vs. %s [%s]",
3353 gameInfo.white, white_holding,
3354 gameInfo.black, black_holding);
3357 DrawPosition(FALSE, boards[currentMove]);
3360 /* Suppress following prompt */
3361 if (looking_at(buf, &i, "*% ")) {
3362 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3363 savingComment = FALSE;
3371 i++; /* skip unparsed character and loop back */
3374 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3375 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3376 // SendToPlayer(&buf[next_out], i - next_out);
3377 started != STARTED_HOLDINGS && leftover_start > next_out) {
3378 SendToPlayer(&buf[next_out], leftover_start - next_out);
3382 leftover_len = buf_len - leftover_start;
3383 /* if buffer ends with something we couldn't parse,
3384 reparse it after appending the next read */
3386 } else if (count == 0) {
3387 RemoveInputSource(isr);
3388 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3390 DisplayFatalError(_("Error reading from ICS"), error, 1);
3395 /* Board style 12 looks like this:
3397 <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
3399 * The "<12> " is stripped before it gets to this routine. The two
3400 * trailing 0's (flip state and clock ticking) are later addition, and
3401 * some chess servers may not have them, or may have only the first.
3402 * Additional trailing fields may be added in the future.
3405 #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"
3407 #define RELATION_OBSERVING_PLAYED 0
3408 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3409 #define RELATION_PLAYING_MYMOVE 1
3410 #define RELATION_PLAYING_NOTMYMOVE -1
3411 #define RELATION_EXAMINING 2
3412 #define RELATION_ISOLATED_BOARD -3
3413 #define RELATION_STARTING_POSITION -4 /* FICS only */
3416 ParseBoard12(string)
3419 GameMode newGameMode;
3420 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3421 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3422 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3423 char to_play, board_chars[200];
3424 char move_str[500], str[500], elapsed_time[500];
3425 char black[32], white[32];
3427 int prevMove = currentMove;
3430 int fromX, fromY, toX, toY;
3432 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3433 char *bookHit = NULL; // [HGM] book
3434 Boolean weird = FALSE, reqFlag = FALSE;
3436 fromX = fromY = toX = toY = -1;
3440 if (appData.debugMode)
3441 fprintf(debugFP, _("Parsing board: %s\n"), string);
3443 move_str[0] = NULLCHAR;
3444 elapsed_time[0] = NULLCHAR;
3445 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3447 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3448 if(string[i] == ' ') { ranks++; files = 0; }
3450 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3453 for(j = 0; j <i; j++) board_chars[j] = string[j];
3454 board_chars[i] = '\0';
3457 n = sscanf(string, PATTERN, &to_play, &double_push,
3458 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3459 &gamenum, white, black, &relation, &basetime, &increment,
3460 &white_stren, &black_stren, &white_time, &black_time,
3461 &moveNum, str, elapsed_time, move_str, &ics_flip,
3465 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3466 DisplayError(str, 0);
3470 /* Convert the move number to internal form */
3471 moveNum = (moveNum - 1) * 2;
3472 if (to_play == 'B') moveNum++;
3473 if (moveNum >= MAX_MOVES) {
3474 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3480 case RELATION_OBSERVING_PLAYED:
3481 case RELATION_OBSERVING_STATIC:
3482 if (gamenum == -1) {
3483 /* Old ICC buglet */
3484 relation = RELATION_OBSERVING_STATIC;
3486 newGameMode = IcsObserving;
3488 case RELATION_PLAYING_MYMOVE:
3489 case RELATION_PLAYING_NOTMYMOVE:
3491 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3492 IcsPlayingWhite : IcsPlayingBlack;
3494 case RELATION_EXAMINING:
3495 newGameMode = IcsExamining;
3497 case RELATION_ISOLATED_BOARD:
3499 /* Just display this board. If user was doing something else,
3500 we will forget about it until the next board comes. */
3501 newGameMode = IcsIdle;
3503 case RELATION_STARTING_POSITION:
3504 newGameMode = gameMode;
3508 /* Modify behavior for initial board display on move listing
3511 switch (ics_getting_history) {
3515 case H_GOT_REQ_HEADER:
3516 case H_GOT_UNREQ_HEADER:
3517 /* This is the initial position of the current game */
3518 gamenum = ics_gamenum;
3519 moveNum = 0; /* old ICS bug workaround */
3520 if (to_play == 'B') {
3521 startedFromSetupPosition = TRUE;
3522 blackPlaysFirst = TRUE;
3524 if (forwardMostMove == 0) forwardMostMove = 1;
3525 if (backwardMostMove == 0) backwardMostMove = 1;
3526 if (currentMove == 0) currentMove = 1;
3528 newGameMode = gameMode;
3529 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3531 case H_GOT_UNWANTED_HEADER:
3532 /* This is an initial board that we don't want */
3534 case H_GETTING_MOVES:
3535 /* Should not happen */
3536 DisplayError(_("Error gathering move list: extra board"), 0);
3537 ics_getting_history = H_FALSE;
3541 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3542 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3543 /* [HGM] We seem to have switched variant unexpectedly
3544 * Try to guess new variant from board size
3546 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3547 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3548 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3549 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3550 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3551 if(!weird) newVariant = VariantNormal;
3552 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3553 /* Get a move list just to see the header, which
3554 will tell us whether this is really bug or zh */
3555 if (ics_getting_history == H_FALSE) {
3556 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3557 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3562 /* Take action if this is the first board of a new game, or of a
3563 different game than is currently being displayed. */
3564 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3565 relation == RELATION_ISOLATED_BOARD) {
3567 /* Forget the old game and get the history (if any) of the new one */
3568 if (gameMode != BeginningOfGame) {
3572 if (appData.autoRaiseBoard) BoardToTop();
3574 if (gamenum == -1) {
3575 newGameMode = IcsIdle;
3576 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3577 appData.getMoveList && !reqFlag) {
3578 /* Need to get game history */
3579 ics_getting_history = H_REQUESTED;
3580 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3584 /* Initially flip the board to have black on the bottom if playing
3585 black or if the ICS flip flag is set, but let the user change
3586 it with the Flip View button. */
3587 flipView = appData.autoFlipView ?
3588 (newGameMode == IcsPlayingBlack) || ics_flip :
3591 /* Done with values from previous mode; copy in new ones */
3592 gameMode = newGameMode;
3594 ics_gamenum = gamenum;
3595 if (gamenum == gs_gamenum) {
3596 int klen = strlen(gs_kind);
3597 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3598 sprintf(str, "ICS %s", gs_kind);
3599 gameInfo.event = StrSave(str);
3601 gameInfo.event = StrSave("ICS game");
3603 gameInfo.site = StrSave(appData.icsHost);
3604 gameInfo.date = PGNDate();
3605 gameInfo.round = StrSave("-");
3606 gameInfo.white = StrSave(white);
3607 gameInfo.black = StrSave(black);
3608 timeControl = basetime * 60 * 1000;
3610 timeIncrement = increment * 1000;
3611 movesPerSession = 0;
3612 gameInfo.timeControl = TimeControlTagValue();
3613 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3614 if (appData.debugMode) {
3615 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3616 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3617 setbuf(debugFP, NULL);
3620 gameInfo.outOfBook = NULL;
3622 /* Do we have the ratings? */
3623 if (strcmp(player1Name, white) == 0 &&
3624 strcmp(player2Name, black) == 0) {
3625 if (appData.debugMode)
3626 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3627 player1Rating, player2Rating);
3628 gameInfo.whiteRating = player1Rating;
3629 gameInfo.blackRating = player2Rating;
3630 } else if (strcmp(player2Name, white) == 0 &&
3631 strcmp(player1Name, black) == 0) {
3632 if (appData.debugMode)
3633 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3634 player2Rating, player1Rating);
3635 gameInfo.whiteRating = player2Rating;
3636 gameInfo.blackRating = player1Rating;
3638 player1Name[0] = player2Name[0] = NULLCHAR;
3640 /* Silence shouts if requested */
3641 if (appData.quietPlay &&
3642 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3643 SendToICS(ics_prefix);
3644 SendToICS("set shout 0\n");
3648 /* Deal with midgame name changes */
3650 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3651 if (gameInfo.white) free(gameInfo.white);
3652 gameInfo.white = StrSave(white);
3654 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3655 if (gameInfo.black) free(gameInfo.black);
3656 gameInfo.black = StrSave(black);
3660 /* Throw away game result if anything actually changes in examine mode */
3661 if (gameMode == IcsExamining && !newGame) {
3662 gameInfo.result = GameUnfinished;
3663 if (gameInfo.resultDetails != NULL) {
3664 free(gameInfo.resultDetails);
3665 gameInfo.resultDetails = NULL;
3669 /* In pausing && IcsExamining mode, we ignore boards coming
3670 in if they are in a different variation than we are. */
3671 if (pauseExamInvalid) return;
3672 if (pausing && gameMode == IcsExamining) {
3673 if (moveNum <= pauseExamForwardMostMove) {
3674 pauseExamInvalid = TRUE;
3675 forwardMostMove = pauseExamForwardMostMove;
3680 if (appData.debugMode) {
3681 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3683 /* Parse the board */
3684 for (k = 0; k < ranks; k++) {
3685 for (j = 0; j < files; j++)
3686 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3687 if(gameInfo.holdingsWidth > 1) {
3688 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3689 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3692 CopyBoard(boards[moveNum], board);
3693 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3695 startedFromSetupPosition =
3696 !CompareBoards(board, initialPosition);
3697 if(startedFromSetupPosition)
3698 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3701 /* [HGM] Set castling rights. Take the outermost Rooks,
3702 to make it also work for FRC opening positions. Note that board12
3703 is really defective for later FRC positions, as it has no way to
3704 indicate which Rook can castle if they are on the same side of King.
3705 For the initial position we grant rights to the outermost Rooks,
3706 and remember thos rights, and we then copy them on positions
3707 later in an FRC game. This means WB might not recognize castlings with
3708 Rooks that have moved back to their original position as illegal,
3709 but in ICS mode that is not its job anyway.
3711 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3712 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3714 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3715 if(board[0][i] == WhiteRook) j = i;
3716 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3717 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3718 if(board[0][i] == WhiteRook) j = i;
3719 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3720 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3721 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3722 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3723 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3724 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3725 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3727 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3728 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3729 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3730 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3731 if(board[BOARD_HEIGHT-1][k] == bKing)
3732 initialRights[5] = castlingRights[moveNum][5] = k;
3733 if(gameInfo.variant == VariantTwoKings) {
3734 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3735 if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3736 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3739 r = castlingRights[moveNum][0] = initialRights[0];
3740 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3741 r = castlingRights[moveNum][1] = initialRights[1];
3742 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3743 r = castlingRights[moveNum][3] = initialRights[3];
3744 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3745 r = castlingRights[moveNum][4] = initialRights[4];
3746 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3747 /* wildcastle kludge: always assume King has rights */
3748 r = castlingRights[moveNum][2] = initialRights[2];
3749 r = castlingRights[moveNum][5] = initialRights[5];
3751 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3752 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3755 if (ics_getting_history == H_GOT_REQ_HEADER ||
3756 ics_getting_history == H_GOT_UNREQ_HEADER) {
3757 /* This was an initial position from a move list, not
3758 the current position */
3762 /* Update currentMove and known move number limits */
3763 newMove = newGame || moveNum > forwardMostMove;
3766 forwardMostMove = backwardMostMove = currentMove = moveNum;
3767 if (gameMode == IcsExamining && moveNum == 0) {
3768 /* Workaround for ICS limitation: we are not told the wild
3769 type when starting to examine a game. But if we ask for
3770 the move list, the move list header will tell us */
3771 ics_getting_history = H_REQUESTED;
3772 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3775 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3776 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3778 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3779 /* [HGM] applied this also to an engine that is silently watching */
3780 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3781 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3782 gameInfo.variant == currentlyInitializedVariant) {
3783 takeback = forwardMostMove - moveNum;
3784 for (i = 0; i < takeback; i++) {
3785 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3786 SendToProgram("undo\n", &first);
3791 forwardMostMove = moveNum;
3792 if (!pausing || currentMove > forwardMostMove)
3793 currentMove = forwardMostMove;
3795 /* New part of history that is not contiguous with old part */
3796 if (pausing && gameMode == IcsExamining) {
3797 pauseExamInvalid = TRUE;
3798 forwardMostMove = pauseExamForwardMostMove;
3801 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3803 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3804 // [HGM] when we will receive the move list we now request, it will be
3805 // fed to the engine from the first move on. So if the engine is not
3806 // in the initial position now, bring it there.
3807 InitChessProgram(&first, 0);
3810 ics_getting_history = H_REQUESTED;
3811 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3814 forwardMostMove = backwardMostMove = currentMove = moveNum;
3817 /* Update the clocks */
3818 if (strchr(elapsed_time, '.')) {
3820 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3821 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3823 /* Time is in seconds */
3824 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3825 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3830 if (appData.zippyPlay && newGame &&
3831 gameMode != IcsObserving && gameMode != IcsIdle &&
3832 gameMode != IcsExamining)
3833 ZippyFirstBoard(moveNum, basetime, increment);
3836 /* Put the move on the move list, first converting
3837 to canonical algebraic form. */
3839 if (appData.debugMode) {
3840 if (appData.debugMode) { int f = forwardMostMove;
3841 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3842 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3844 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3845 fprintf(debugFP, "moveNum = %d\n", moveNum);
3846 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3847 setbuf(debugFP, NULL);
3849 if (moveNum <= backwardMostMove) {
3850 /* We don't know what the board looked like before
3852 strcpy(parseList[moveNum - 1], move_str);
3853 strcat(parseList[moveNum - 1], " ");
3854 strcat(parseList[moveNum - 1], elapsed_time);
3855 moveList[moveNum - 1][0] = NULLCHAR;
3856 } else if (strcmp(move_str, "none") == 0) {
3857 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3858 /* Again, we don't know what the board looked like;
3859 this is really the start of the game. */
3860 parseList[moveNum - 1][0] = NULLCHAR;
3861 moveList[moveNum - 1][0] = NULLCHAR;
3862 backwardMostMove = moveNum;
3863 startedFromSetupPosition = TRUE;
3864 fromX = fromY = toX = toY = -1;
3866 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3867 // So we parse the long-algebraic move string in stead of the SAN move
3868 int valid; char buf[MSG_SIZ], *prom;
3870 // str looks something like "Q/a1-a2"; kill the slash
3872 sprintf(buf, "%c%s", str[0], str+2);
3873 else strcpy(buf, str); // might be castling
3874 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3875 strcat(buf, prom); // long move lacks promo specification!
3876 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3877 if(appData.debugMode)
3878 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3879 strcpy(move_str, buf);
3881 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3882 &fromX, &fromY, &toX, &toY, &promoChar)
3883 || ParseOneMove(buf, moveNum - 1, &moveType,
3884 &fromX, &fromY, &toX, &toY, &promoChar);
3885 // end of long SAN patch
3887 (void) CoordsToAlgebraic(boards[moveNum - 1],
3888 PosFlags(moveNum - 1), EP_UNKNOWN,
3889 fromY, fromX, toY, toX, promoChar,
3890 parseList[moveNum-1]);
3891 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3892 castlingRights[moveNum]) ) {
3898 if(gameInfo.variant != VariantShogi)
3899 strcat(parseList[moveNum - 1], "+");
3902 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3903 strcat(parseList[moveNum - 1], "#");
3906 strcat(parseList[moveNum - 1], " ");
3907 strcat(parseList[moveNum - 1], elapsed_time);
3908 /* currentMoveString is set as a side-effect of ParseOneMove */
3909 strcpy(moveList[moveNum - 1], currentMoveString);
3910 strcat(moveList[moveNum - 1], "\n");
3912 /* Move from ICS was illegal!? Punt. */
3913 if (appData.debugMode) {
3914 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3915 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3917 strcpy(parseList[moveNum - 1], move_str);
3918 strcat(parseList[moveNum - 1], " ");
3919 strcat(parseList[moveNum - 1], elapsed_time);
3920 moveList[moveNum - 1][0] = NULLCHAR;
3921 fromX = fromY = toX = toY = -1;
3924 if (appData.debugMode) {
3925 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3926 setbuf(debugFP, NULL);
3930 /* Send move to chess program (BEFORE animating it). */
3931 if (appData.zippyPlay && !newGame && newMove &&
3932 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3934 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3935 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3936 if (moveList[moveNum - 1][0] == NULLCHAR) {
3937 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3939 DisplayError(str, 0);
3941 if (first.sendTime) {
3942 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3944 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3945 if (firstMove && !bookHit) {
3947 if (first.useColors) {
3948 SendToProgram(gameMode == IcsPlayingWhite ?
3950 "black\ngo\n", &first);
3952 SendToProgram("go\n", &first);
3954 first.maybeThinking = TRUE;
3957 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3958 if (moveList[moveNum - 1][0] == NULLCHAR) {
3959 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3960 DisplayError(str, 0);
3962 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3963 SendMoveToProgram(moveNum - 1, &first);
3970 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3971 /* If move comes from a remote source, animate it. If it
3972 isn't remote, it will have already been animated. */
3973 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3974 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3976 if (!pausing && appData.highlightLastMove) {
3977 SetHighlights(fromX, fromY, toX, toY);
3981 /* Start the clocks */
3982 whiteFlag = blackFlag = FALSE;
3983 appData.clockMode = !(basetime == 0 && increment == 0);
3985 ics_clock_paused = TRUE;
3987 } else if (ticking == 1) {
3988 ics_clock_paused = FALSE;
3990 if (gameMode == IcsIdle ||
3991 relation == RELATION_OBSERVING_STATIC ||
3992 relation == RELATION_EXAMINING ||
3994 DisplayBothClocks();
3998 /* Display opponents and material strengths */
3999 if (gameInfo.variant != VariantBughouse &&
4000 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4001 if (tinyLayout || smallLayout) {
4002 if(gameInfo.variant == VariantNormal)
4003 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4004 gameInfo.white, white_stren, gameInfo.black, black_stren,
4005 basetime, increment);
4007 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4008 gameInfo.white, white_stren, gameInfo.black, black_stren,
4009 basetime, increment, (int) gameInfo.variant);
4011 if(gameInfo.variant == VariantNormal)
4012 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4013 gameInfo.white, white_stren, gameInfo.black, black_stren,
4014 basetime, increment);
4016 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4017 gameInfo.white, white_stren, gameInfo.black, black_stren,
4018 basetime, increment, VariantName(gameInfo.variant));
4021 if (appData.debugMode) {
4022 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4027 /* Display the board */
4028 if (!pausing && !appData.noGUI) {
4030 if (appData.premove)
4032 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4033 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4034 ClearPremoveHighlights();
4036 DrawPosition(FALSE, boards[currentMove]);
4037 DisplayMove(moveNum - 1);
4038 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4039 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4040 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4041 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4045 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4047 if(bookHit) { // [HGM] book: simulate book reply
4048 static char bookMove[MSG_SIZ]; // a bit generous?
4050 programStats.nodes = programStats.depth = programStats.time =
4051 programStats.score = programStats.got_only_move = 0;
4052 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4054 strcpy(bookMove, "move ");
4055 strcat(bookMove, bookHit);
4056 HandleMachineMove(bookMove, &first);
4065 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4066 ics_getting_history = H_REQUESTED;
4067 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4073 AnalysisPeriodicEvent(force)
4076 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4077 && !force) || !appData.periodicUpdates)
4080 /* Send . command to Crafty to collect stats */
4081 SendToProgram(".\n", &first);
4083 /* Don't send another until we get a response (this makes
4084 us stop sending to old Crafty's which don't understand
4085 the "." command (sending illegal cmds resets node count & time,
4086 which looks bad)) */
4087 programStats.ok_to_send = 0;
4090 void ics_update_width(new_width)
4093 ics_printf("set width %d\n", new_width);
4097 SendMoveToProgram(moveNum, cps)
4099 ChessProgramState *cps;
4103 if (cps->useUsermove) {
4104 SendToProgram("usermove ", cps);
4108 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4109 int len = space - parseList[moveNum];
4110 memcpy(buf, parseList[moveNum], len);
4112 buf[len] = NULLCHAR;
4114 sprintf(buf, "%s\n", parseList[moveNum]);
4116 SendToProgram(buf, cps);
4118 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4119 AlphaRank(moveList[moveNum], 4);
4120 SendToProgram(moveList[moveNum], cps);
4121 AlphaRank(moveList[moveNum], 4); // and back
4123 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4124 * the engine. It would be nice to have a better way to identify castle
4126 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4127 && cps->useOOCastle) {
4128 int fromX = moveList[moveNum][0] - AAA;
4129 int fromY = moveList[moveNum][1] - ONE;
4130 int toX = moveList[moveNum][2] - AAA;
4131 int toY = moveList[moveNum][3] - ONE;
4132 if((boards[moveNum][fromY][fromX] == WhiteKing
4133 && boards[moveNum][toY][toX] == WhiteRook)
4134 || (boards[moveNum][fromY][fromX] == BlackKing
4135 && boards[moveNum][toY][toX] == BlackRook)) {
4136 if(toX > fromX) SendToProgram("O-O\n", cps);
4137 else SendToProgram("O-O-O\n", cps);
4139 else SendToProgram(moveList[moveNum], cps);
4141 else SendToProgram(moveList[moveNum], cps);
4142 /* End of additions by Tord */
4145 /* [HGM] setting up the opening has brought engine in force mode! */
4146 /* Send 'go' if we are in a mode where machine should play. */
4147 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4148 (gameMode == TwoMachinesPlay ||
4150 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4152 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4153 SendToProgram("go\n", cps);
4154 if (appData.debugMode) {
4155 fprintf(debugFP, "(extra)\n");
4158 setboardSpoiledMachineBlack = 0;
4162 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4164 int fromX, fromY, toX, toY;
4166 char user_move[MSG_SIZ];
4170 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4171 (int)moveType, fromX, fromY, toX, toY);
4172 DisplayError(user_move + strlen("say "), 0);
4174 case WhiteKingSideCastle:
4175 case BlackKingSideCastle:
4176 case WhiteQueenSideCastleWild:
4177 case BlackQueenSideCastleWild:
4179 case WhiteHSideCastleFR:
4180 case BlackHSideCastleFR:
4182 sprintf(user_move, "o-o\n");
4184 case WhiteQueenSideCastle:
4185 case BlackQueenSideCastle:
4186 case WhiteKingSideCastleWild:
4187 case BlackKingSideCastleWild:
4189 case WhiteASideCastleFR:
4190 case BlackASideCastleFR:
4192 sprintf(user_move, "o-o-o\n");
4194 case WhitePromotionQueen:
4195 case BlackPromotionQueen:
4196 case WhitePromotionRook:
4197 case BlackPromotionRook:
4198 case WhitePromotionBishop:
4199 case BlackPromotionBishop:
4200 case WhitePromotionKnight:
4201 case BlackPromotionKnight:
4202 case WhitePromotionKing:
4203 case BlackPromotionKing:
4204 case WhitePromotionChancellor:
4205 case BlackPromotionChancellor:
4206 case WhitePromotionArchbishop:
4207 case BlackPromotionArchbishop:
4208 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4209 sprintf(user_move, "%c%c%c%c=%c\n",
4210 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4211 PieceToChar(WhiteFerz));
4212 else if(gameInfo.variant == VariantGreat)
4213 sprintf(user_move, "%c%c%c%c=%c\n",
4214 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4215 PieceToChar(WhiteMan));
4217 sprintf(user_move, "%c%c%c%c=%c\n",
4218 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4219 PieceToChar(PromoPiece(moveType)));
4223 sprintf(user_move, "%c@%c%c\n",
4224 ToUpper(PieceToChar((ChessSquare) fromX)),
4225 AAA + toX, ONE + toY);
4228 case WhiteCapturesEnPassant:
4229 case BlackCapturesEnPassant:
4230 case IllegalMove: /* could be a variant we don't quite understand */
4231 sprintf(user_move, "%c%c%c%c\n",
4232 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4235 SendToICS(user_move);
4236 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4237 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4241 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4246 if (rf == DROP_RANK) {
4247 sprintf(move, "%c@%c%c\n",
4248 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4250 if (promoChar == 'x' || promoChar == NULLCHAR) {
4251 sprintf(move, "%c%c%c%c\n",
4252 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4254 sprintf(move, "%c%c%c%c%c\n",
4255 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4261 ProcessICSInitScript(f)
4266 while (fgets(buf, MSG_SIZ, f)) {
4267 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4274 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4276 AlphaRank(char *move, int n)
4278 // char *p = move, c; int x, y;
4280 if (appData.debugMode) {
4281 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4285 move[2]>='0' && move[2]<='9' &&
4286 move[3]>='a' && move[3]<='x' ) {
4288 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4289 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4291 if(move[0]>='0' && move[0]<='9' &&
4292 move[1]>='a' && move[1]<='x' &&
4293 move[2]>='0' && move[2]<='9' &&
4294 move[3]>='a' && move[3]<='x' ) {
4295 /* input move, Shogi -> normal */
4296 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4297 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4298 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4299 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4302 move[3]>='0' && move[3]<='9' &&
4303 move[2]>='a' && move[2]<='x' ) {
4305 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4306 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4309 move[0]>='a' && move[0]<='x' &&
4310 move[3]>='0' && move[3]<='9' &&
4311 move[2]>='a' && move[2]<='x' ) {
4312 /* output move, normal -> Shogi */
4313 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4314 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4315 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4316 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4317 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4319 if (appData.debugMode) {
4320 fprintf(debugFP, " out = '%s'\n", move);
4324 /* Parser for moves from gnuchess, ICS, or user typein box */
4326 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4329 ChessMove *moveType;
4330 int *fromX, *fromY, *toX, *toY;
4333 if (appData.debugMode) {
4334 fprintf(debugFP, "move to parse: %s\n", move);
4336 *moveType = yylexstr(moveNum, move);
4338 switch (*moveType) {
4339 case WhitePromotionChancellor:
4340 case BlackPromotionChancellor:
4341 case WhitePromotionArchbishop:
4342 case BlackPromotionArchbishop:
4343 case WhitePromotionQueen:
4344 case BlackPromotionQueen:
4345 case WhitePromotionRook:
4346 case BlackPromotionRook:
4347 case WhitePromotionBishop:
4348 case BlackPromotionBishop:
4349 case WhitePromotionKnight:
4350 case BlackPromotionKnight:
4351 case WhitePromotionKing:
4352 case BlackPromotionKing:
4354 case WhiteCapturesEnPassant:
4355 case BlackCapturesEnPassant:
4356 case WhiteKingSideCastle:
4357 case WhiteQueenSideCastle:
4358 case BlackKingSideCastle:
4359 case BlackQueenSideCastle:
4360 case WhiteKingSideCastleWild:
4361 case WhiteQueenSideCastleWild:
4362 case BlackKingSideCastleWild:
4363 case BlackQueenSideCastleWild:
4364 /* Code added by Tord: */
4365 case WhiteHSideCastleFR:
4366 case WhiteASideCastleFR:
4367 case BlackHSideCastleFR:
4368 case BlackASideCastleFR:
4369 /* End of code added by Tord */
4370 case IllegalMove: /* bug or odd chess variant */
4371 *fromX = currentMoveString[0] - AAA;
4372 *fromY = currentMoveString[1] - ONE;
4373 *toX = currentMoveString[2] - AAA;
4374 *toY = currentMoveString[3] - ONE;
4375 *promoChar = currentMoveString[4];
4376 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4377 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4378 if (appData.debugMode) {
4379 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4381 *fromX = *fromY = *toX = *toY = 0;
4384 if (appData.testLegality) {
4385 return (*moveType != IllegalMove);
4387 return !(*fromX == *toX && *fromY == *toY);
4392 *fromX = *moveType == WhiteDrop ?
4393 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4394 (int) CharToPiece(ToLower(currentMoveString[0]));
4396 *toX = currentMoveString[2] - AAA;
4397 *toY = currentMoveString[3] - ONE;
4398 *promoChar = NULLCHAR;
4402 case ImpossibleMove:
4403 case (ChessMove) 0: /* end of file */
4412 if (appData.debugMode) {
4413 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4416 *fromX = *fromY = *toX = *toY = 0;
4417 *promoChar = NULLCHAR;
4422 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4423 // All positions will have equal probability, but the current method will not provide a unique
4424 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4430 int piecesLeft[(int)BlackPawn];
4431 int seed, nrOfShuffles;
4433 void GetPositionNumber()
4434 { // sets global variable seed
4437 seed = appData.defaultFrcPosition;
4438 if(seed < 0) { // randomize based on time for negative FRC position numbers
4439 for(i=0; i<50; i++) seed += random();
4440 seed = random() ^ random() >> 8 ^ random() << 8;
4441 if(seed<0) seed = -seed;
4445 int put(Board board, int pieceType, int rank, int n, int shade)
4446 // put the piece on the (n-1)-th empty squares of the given shade
4450 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4451 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4452 board[rank][i] = (ChessSquare) pieceType;
4453 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4455 piecesLeft[pieceType]--;
4463 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4464 // calculate where the next piece goes, (any empty square), and put it there
4468 i = seed % squaresLeft[shade];
4469 nrOfShuffles *= squaresLeft[shade];
4470 seed /= squaresLeft[shade];
4471 put(board, pieceType, rank, i, shade);
4474 void AddTwoPieces(Board board, int pieceType, int rank)
4475 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4477 int i, n=squaresLeft[ANY], j=n-1, k;
4479 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4480 i = seed % k; // pick one
4483 while(i >= j) i -= j--;
4484 j = n - 1 - j; i += j;
4485 put(board, pieceType, rank, j, ANY);
4486 put(board, pieceType, rank, i, ANY);
4489 void SetUpShuffle(Board board, int number)
4493 GetPositionNumber(); nrOfShuffles = 1;
4495 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4496 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4497 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4499 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4501 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4502 p = (int) board[0][i];
4503 if(p < (int) BlackPawn) piecesLeft[p] ++;
4504 board[0][i] = EmptySquare;
4507 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4508 // shuffles restricted to allow normal castling put KRR first
4509 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4510 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4511 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4512 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4513 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4514 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4515 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4516 put(board, WhiteRook, 0, 0, ANY);
4517 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4520 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4521 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4522 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4523 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4524 while(piecesLeft[p] >= 2) {
4525 AddOnePiece(board, p, 0, LITE);
4526 AddOnePiece(board, p, 0, DARK);
4528 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4531 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4532 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4533 // but we leave King and Rooks for last, to possibly obey FRC restriction
4534 if(p == (int)WhiteRook) continue;
4535 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4536 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4539 // now everything is placed, except perhaps King (Unicorn) and Rooks
4541 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4542 // Last King gets castling rights
4543 while(piecesLeft[(int)WhiteUnicorn]) {
4544 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4545 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4548 while(piecesLeft[(int)WhiteKing]) {
4549 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4550 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4555 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4556 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4559 // Only Rooks can be left; simply place them all
4560 while(piecesLeft[(int)WhiteRook]) {
4561 i = put(board, WhiteRook, 0, 0, ANY);
4562 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4565 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4567 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4570 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4571 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4574 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4577 int SetCharTable( char *table, const char * map )
4578 /* [HGM] moved here from winboard.c because of its general usefulness */
4579 /* Basically a safe strcpy that uses the last character as King */
4581 int result = FALSE; int NrPieces;
4583 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4584 && NrPieces >= 12 && !(NrPieces&1)) {
4585 int i; /* [HGM] Accept even length from 12 to 34 */
4587 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4588 for( i=0; i<NrPieces/2-1; i++ ) {
4590 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4592 table[(int) WhiteKing] = map[NrPieces/2-1];
4593 table[(int) BlackKing] = map[NrPieces-1];
4601 void Prelude(Board board)
4602 { // [HGM] superchess: random selection of exo-pieces
4603 int i, j, k; ChessSquare p;
4604 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4606 GetPositionNumber(); // use FRC position number
4608 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4609 SetCharTable(pieceToChar, appData.pieceToCharTable);
4610 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4611 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4614 j = seed%4; seed /= 4;
4615 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4616 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4617 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4618 j = seed%3 + (seed%3 >= j); seed /= 3;
4619 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4620 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4621 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4622 j = seed%3; seed /= 3;
4623 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4624 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4625 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4626 j = seed%2 + (seed%2 >= j); seed /= 2;
4627 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4628 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4629 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4630 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4631 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4632 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4633 put(board, exoPieces[0], 0, 0, ANY);
4634 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4638 InitPosition(redraw)
4641 ChessSquare (* pieces)[BOARD_SIZE];
4642 int i, j, pawnRow, overrule,
4643 oldx = gameInfo.boardWidth,
4644 oldy = gameInfo.boardHeight,
4645 oldh = gameInfo.holdingsWidth,
4646 oldv = gameInfo.variant;
4648 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4650 /* [AS] Initialize pv info list [HGM] and game status */
4652 for( i=0; i<MAX_MOVES; i++ ) {
4653 pvInfoList[i].depth = 0;
4654 epStatus[i]=EP_NONE;
4655 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4658 initialRulePlies = 0; /* 50-move counter start */
4660 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4661 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4665 /* [HGM] logic here is completely changed. In stead of full positions */
4666 /* the initialized data only consist of the two backranks. The switch */
4667 /* selects which one we will use, which is than copied to the Board */
4668 /* initialPosition, which for the rest is initialized by Pawns and */
4669 /* empty squares. This initial position is then copied to boards[0], */
4670 /* possibly after shuffling, so that it remains available. */
4672 gameInfo.holdingsWidth = 0; /* default board sizes */
4673 gameInfo.boardWidth = 8;
4674 gameInfo.boardHeight = 8;
4675 gameInfo.holdingsSize = 0;
4676 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4677 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4678 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4680 switch (gameInfo.variant) {
4681 case VariantFischeRandom:
4682 shuffleOpenings = TRUE;
4686 case VariantShatranj:
4687 pieces = ShatranjArray;
4688 nrCastlingRights = 0;
4689 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4692 pieces = makrukArray;
4693 nrCastlingRights = 0;
4694 startedFromSetupPosition = TRUE;
4695 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4697 case VariantTwoKings:
4698 pieces = twoKingsArray;
4700 case VariantCapaRandom:
4701 shuffleOpenings = TRUE;
4702 case VariantCapablanca:
4703 pieces = CapablancaArray;
4704 gameInfo.boardWidth = 10;
4705 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4708 pieces = GothicArray;
4709 gameInfo.boardWidth = 10;
4710 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4713 pieces = JanusArray;
4714 gameInfo.boardWidth = 10;
4715 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4716 nrCastlingRights = 6;
4717 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4718 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4719 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4720 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4721 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4722 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4725 pieces = FalconArray;
4726 gameInfo.boardWidth = 10;
4727 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4729 case VariantXiangqi:
4730 pieces = XiangqiArray;
4731 gameInfo.boardWidth = 9;
4732 gameInfo.boardHeight = 10;
4733 nrCastlingRights = 0;
4734 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4737 pieces = ShogiArray;
4738 gameInfo.boardWidth = 9;
4739 gameInfo.boardHeight = 9;
4740 gameInfo.holdingsSize = 7;
4741 nrCastlingRights = 0;
4742 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4744 case VariantCourier:
4745 pieces = CourierArray;
4746 gameInfo.boardWidth = 12;
4747 nrCastlingRights = 0;
4748 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4749 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4751 case VariantKnightmate:
4752 pieces = KnightmateArray;
4753 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4756 pieces = fairyArray;
4757 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4760 pieces = GreatArray;
4761 gameInfo.boardWidth = 10;
4762 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4763 gameInfo.holdingsSize = 8;
4767 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4768 gameInfo.holdingsSize = 8;
4769 startedFromSetupPosition = TRUE;
4771 case VariantCrazyhouse:
4772 case VariantBughouse:
4774 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4775 gameInfo.holdingsSize = 5;
4777 case VariantWildCastle:
4779 /* !!?shuffle with kings guaranteed to be on d or e file */
4780 shuffleOpenings = 1;
4782 case VariantNoCastle:
4784 nrCastlingRights = 0;
4785 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4786 /* !!?unconstrained back-rank shuffle */
4787 shuffleOpenings = 1;
4792 if(appData.NrFiles >= 0) {
4793 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4794 gameInfo.boardWidth = appData.NrFiles;
4796 if(appData.NrRanks >= 0) {
4797 gameInfo.boardHeight = appData.NrRanks;
4799 if(appData.holdingsSize >= 0) {
4800 i = appData.holdingsSize;
4801 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4802 gameInfo.holdingsSize = i;
4804 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4805 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4806 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4808 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4809 if(pawnRow < 1) pawnRow = 1;
4810 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4812 /* User pieceToChar list overrules defaults */
4813 if(appData.pieceToCharTable != NULL)
4814 SetCharTable(pieceToChar, appData.pieceToCharTable);
4816 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4818 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4819 s = (ChessSquare) 0; /* account holding counts in guard band */
4820 for( i=0; i<BOARD_HEIGHT; i++ )
4821 initialPosition[i][j] = s;
4823 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4824 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4825 initialPosition[pawnRow][j] = WhitePawn;
4826 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4827 if(gameInfo.variant == VariantXiangqi) {
4829 initialPosition[pawnRow][j] =
4830 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4831 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4832 initialPosition[2][j] = WhiteCannon;
4833 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4837 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4839 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4842 initialPosition[1][j] = WhiteBishop;
4843 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4845 initialPosition[1][j] = WhiteRook;
4846 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4849 if( nrCastlingRights == -1) {
4850 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4851 /* This sets default castling rights from none to normal corners */
4852 /* Variants with other castling rights must set them themselves above */
4853 nrCastlingRights = 6;
4855 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4856 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4857 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4858 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4859 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4860 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4863 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4864 if(gameInfo.variant == VariantGreat) { // promotion commoners
4865 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4866 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4867 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4868 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4870 if (appData.debugMode) {
4871 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4873 if(shuffleOpenings) {
4874 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4875 startedFromSetupPosition = TRUE;
4877 if(startedFromPositionFile) {
4878 /* [HGM] loadPos: use PositionFile for every new game */
4879 CopyBoard(initialPosition, filePosition);
4880 for(i=0; i<nrCastlingRights; i++)
4881 castlingRights[0][i] = initialRights[i] = fileRights[i];
4882 startedFromSetupPosition = TRUE;
4885 CopyBoard(boards[0], initialPosition);
4887 if(oldx != gameInfo.boardWidth ||
4888 oldy != gameInfo.boardHeight ||
4889 oldh != gameInfo.holdingsWidth
4891 || oldv == VariantGothic || // For licensing popups
4892 gameInfo.variant == VariantGothic
4895 || oldv == VariantFalcon ||
4896 gameInfo.variant == VariantFalcon
4899 InitDrawingSizes(-2 ,0);
4902 DrawPosition(TRUE, boards[currentMove]);
4906 SendBoard(cps, moveNum)
4907 ChessProgramState *cps;
4910 char message[MSG_SIZ];
4912 if (cps->useSetboard) {
4913 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4914 sprintf(message, "setboard %s\n", fen);
4915 SendToProgram(message, cps);
4921 /* Kludge to set black to move, avoiding the troublesome and now
4922 * deprecated "black" command.
4924 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4926 SendToProgram("edit\n", cps);
4927 SendToProgram("#\n", cps);
4928 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4929 bp = &boards[moveNum][i][BOARD_LEFT];
4930 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4931 if ((int) *bp < (int) BlackPawn) {
4932 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4934 if(message[0] == '+' || message[0] == '~') {
4935 sprintf(message, "%c%c%c+\n",
4936 PieceToChar((ChessSquare)(DEMOTED *bp)),
4939 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4940 message[1] = BOARD_RGHT - 1 - j + '1';
4941 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4943 SendToProgram(message, cps);
4948 SendToProgram("c\n", cps);
4949 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4950 bp = &boards[moveNum][i][BOARD_LEFT];
4951 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4952 if (((int) *bp != (int) EmptySquare)
4953 && ((int) *bp >= (int) BlackPawn)) {
4954 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4956 if(message[0] == '+' || message[0] == '~') {
4957 sprintf(message, "%c%c%c+\n",
4958 PieceToChar((ChessSquare)(DEMOTED *bp)),
4961 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4962 message[1] = BOARD_RGHT - 1 - j + '1';
4963 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4965 SendToProgram(message, cps);
4970 SendToProgram(".\n", cps);
4972 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4976 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4978 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4979 /* [HGM] add Shogi promotions */
4980 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4985 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4986 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4988 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4989 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4992 piece = boards[currentMove][fromY][fromX];
4993 if(gameInfo.variant == VariantShogi) {
4994 promotionZoneSize = 3;
4995 highestPromotingPiece = (int)WhiteFerz;
4996 } else if(gameInfo.variant == VariantMakruk) {
4997 promotionZoneSize = 3;
5000 // next weed out all moves that do not touch the promotion zone at all
5001 if((int)piece >= BlackPawn) {
5002 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5004 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5006 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5007 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5010 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5012 // weed out mandatory Shogi promotions
5013 if(gameInfo.variant == VariantShogi) {
5014 if(piece >= BlackPawn) {
5015 if(toY == 0 && piece == BlackPawn ||
5016 toY == 0 && piece == BlackQueen ||
5017 toY <= 1 && piece == BlackKnight) {
5022 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5023 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5024 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5031 // weed out obviously illegal Pawn moves
5032 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5033 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5034 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5035 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5036 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5037 // note we are not allowed to test for valid (non-)capture, due to premove
5040 // we either have a choice what to promote to, or (in Shogi) whether to promote
5041 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5042 *promoChoice = PieceToChar(BlackFerz); // no choice
5045 if(appData.alwaysPromoteToQueen) { // predetermined
5046 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5047 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5048 else *promoChoice = PieceToChar(BlackQueen);
5052 // suppress promotion popup on illegal moves that are not premoves
5053 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5054 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5055 if(appData.testLegality && !premove) {
5056 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5057 epStatus[currentMove], castlingRights[currentMove],
5058 fromY, fromX, toY, toX, NULLCHAR);
5059 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5060 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5068 InPalace(row, column)
5070 { /* [HGM] for Xiangqi */
5071 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5072 column < (BOARD_WIDTH + 4)/2 &&
5073 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5078 PieceForSquare (x, y)
5082 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5085 return boards[currentMove][y][x];
5089 OKToStartUserMove(x, y)
5092 ChessSquare from_piece;
5095 if (matchMode) return FALSE;
5096 if (gameMode == EditPosition) return TRUE;
5098 if (x >= 0 && y >= 0)
5099 from_piece = boards[currentMove][y][x];
5101 from_piece = EmptySquare;
5103 if (from_piece == EmptySquare) return FALSE;
5105 white_piece = (int)from_piece >= (int)WhitePawn &&
5106 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5109 case PlayFromGameFile:
5111 case TwoMachinesPlay:
5119 case MachinePlaysWhite:
5120 case IcsPlayingBlack:
5121 if (appData.zippyPlay) return FALSE;
5123 DisplayMoveError(_("You are playing Black"));
5128 case MachinePlaysBlack:
5129 case IcsPlayingWhite:
5130 if (appData.zippyPlay) return FALSE;
5132 DisplayMoveError(_("You are playing White"));
5138 if (!white_piece && WhiteOnMove(currentMove)) {
5139 DisplayMoveError(_("It is White's turn"));
5142 if (white_piece && !WhiteOnMove(currentMove)) {
5143 DisplayMoveError(_("It is Black's turn"));
5146 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5147 /* Editing correspondence game history */
5148 /* Could disallow this or prompt for confirmation */
5151 if (currentMove < forwardMostMove) {
5152 /* Discarding moves */
5153 /* Could prompt for confirmation here,
5154 but I don't think that's such a good idea */
5155 forwardMostMove = currentMove;
5159 case BeginningOfGame:
5160 if (appData.icsActive) return FALSE;
5161 if (!appData.noChessProgram) {
5163 DisplayMoveError(_("You are playing White"));
5170 if (!white_piece && WhiteOnMove(currentMove)) {
5171 DisplayMoveError(_("It is White's turn"));
5174 if (white_piece && !WhiteOnMove(currentMove)) {
5175 DisplayMoveError(_("It is Black's turn"));
5184 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5185 && gameMode != AnalyzeFile && gameMode != Training) {
5186 DisplayMoveError(_("Displayed position is not current"));
5192 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5193 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5194 int lastLoadGameUseList = FALSE;
5195 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5196 ChessMove lastLoadGameStart = (ChessMove) 0;
5199 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5200 int fromX, fromY, toX, toY;
5205 ChessSquare pdown, pup;
5207 /* Check if the user is playing in turn. This is complicated because we
5208 let the user "pick up" a piece before it is his turn. So the piece he
5209 tried to pick up may have been captured by the time he puts it down!
5210 Therefore we use the color the user is supposed to be playing in this
5211 test, not the color of the piece that is currently on the starting
5212 square---except in EditGame mode, where the user is playing both
5213 sides; fortunately there the capture race can't happen. (It can
5214 now happen in IcsExamining mode, but that's just too bad. The user
5215 will get a somewhat confusing message in that case.)
5219 case PlayFromGameFile:
5221 case TwoMachinesPlay:
5225 /* We switched into a game mode where moves are not accepted,
5226 perhaps while the mouse button was down. */
5227 return ImpossibleMove;
5229 case MachinePlaysWhite:
5230 /* User is moving for Black */
5231 if (WhiteOnMove(currentMove)) {
5232 DisplayMoveError(_("It is White's turn"));
5233 return ImpossibleMove;
5237 case MachinePlaysBlack:
5238 /* User is moving for White */
5239 if (!WhiteOnMove(currentMove)) {
5240 DisplayMoveError(_("It is Black's turn"));
5241 return ImpossibleMove;
5247 case BeginningOfGame:
5250 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5251 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5252 /* User is moving for Black */
5253 if (WhiteOnMove(currentMove)) {
5254 DisplayMoveError(_("It is White's turn"));
5255 return ImpossibleMove;
5258 /* User is moving for White */
5259 if (!WhiteOnMove(currentMove)) {
5260 DisplayMoveError(_("It is Black's turn"));
5261 return ImpossibleMove;
5266 case IcsPlayingBlack:
5267 /* User is moving for Black */
5268 if (WhiteOnMove(currentMove)) {
5269 if (!appData.premove) {
5270 DisplayMoveError(_("It is White's turn"));
5271 } else if (toX >= 0 && toY >= 0) {
5274 premoveFromX = fromX;
5275 premoveFromY = fromY;
5276 premovePromoChar = promoChar;
5278 if (appData.debugMode)
5279 fprintf(debugFP, "Got premove: fromX %d,"
5280 "fromY %d, toX %d, toY %d\n",
5281 fromX, fromY, toX, toY);
5283 return ImpossibleMove;
5287 case IcsPlayingWhite:
5288 /* User is moving for White */
5289 if (!WhiteOnMove(currentMove)) {
5290 if (!appData.premove) {
5291 DisplayMoveError(_("It is Black's turn"));
5292 } else if (toX >= 0 && toY >= 0) {
5295 premoveFromX = fromX;
5296 premoveFromY = fromY;
5297 premovePromoChar = promoChar;
5299 if (appData.debugMode)
5300 fprintf(debugFP, "Got premove: fromX %d,"
5301 "fromY %d, toX %d, toY %d\n",
5302 fromX, fromY, toX, toY);
5304 return ImpossibleMove;
5312 /* EditPosition, empty square, or different color piece;
5313 click-click move is possible */
5314 if (toX == -2 || toY == -2) {
5315 boards[0][fromY][fromX] = EmptySquare;
5316 return AmbiguousMove;
5317 } else if (toX >= 0 && toY >= 0) {
5318 boards[0][toY][toX] = boards[0][fromY][fromX];
5319 boards[0][fromY][fromX] = EmptySquare;
5320 return AmbiguousMove;
5322 return ImpossibleMove;
5325 if(toX < 0 || toY < 0) return ImpossibleMove;
5326 pdown = boards[currentMove][fromY][fromX];
5327 pup = boards[currentMove][toY][toX];
5329 /* [HGM] If move started in holdings, it means a drop */
5330 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5331 if( pup != EmptySquare ) return ImpossibleMove;
5332 if(appData.testLegality) {
5333 /* it would be more logical if LegalityTest() also figured out
5334 * which drops are legal. For now we forbid pawns on back rank.
5335 * Shogi is on its own here...
5337 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5338 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5339 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5341 return WhiteDrop; /* Not needed to specify white or black yet */
5344 userOfferedDraw = FALSE;
5346 /* [HGM] always test for legality, to get promotion info */
5347 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5348 epStatus[currentMove], castlingRights[currentMove],
5349 fromY, fromX, toY, toX, promoChar);
5350 /* [HGM] but possibly ignore an IllegalMove result */
5351 if (appData.testLegality) {
5352 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5353 DisplayMoveError(_("Illegal move"));
5354 return ImpossibleMove;
5359 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5360 function is made into one that returns an OK move type if FinishMove
5361 should be called. This to give the calling driver routine the
5362 opportunity to finish the userMove input with a promotion popup,
5363 without bothering the user with this for invalid or illegal moves */
5365 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5368 /* Common tail of UserMoveEvent and DropMenuEvent */
5370 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5372 int fromX, fromY, toX, toY;
5373 /*char*/int promoChar;
5377 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5378 // [HGM] superchess: suppress promotions to non-available piece
5379 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5380 if(WhiteOnMove(currentMove)) {
5381 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5383 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5387 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5388 move type in caller when we know the move is a legal promotion */
5389 if(moveType == NormalMove && promoChar)
5390 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5392 /* [HGM] convert drag-and-drop piece drops to standard form */
5393 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5394 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5395 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5396 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5397 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5398 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5399 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5400 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5404 /* [HGM] <popupFix> The following if has been moved here from
5405 UserMoveEvent(). Because it seemed to belong here (why not allow
5406 piece drops in training games?), and because it can only be
5407 performed after it is known to what we promote. */
5408 if (gameMode == Training) {
5409 /* compare the move played on the board to the next move in the
5410 * game. If they match, display the move and the opponent's response.
5411 * If they don't match, display an error message.
5414 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5415 CopyBoard(testBoard, boards[currentMove]);
5416 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5418 if (CompareBoards(testBoard, boards[currentMove+1])) {
5419 ForwardInner(currentMove+1);
5421 /* Autoplay the opponent's response.
5422 * if appData.animate was TRUE when Training mode was entered,
5423 * the response will be animated.
5425 saveAnimate = appData.animate;
5426 appData.animate = animateTraining;
5427 ForwardInner(currentMove+1);
5428 appData.animate = saveAnimate;
5430 /* check for the end of the game */
5431 if (currentMove >= forwardMostMove) {
5432 gameMode = PlayFromGameFile;
5434 SetTrainingModeOff();
5435 DisplayInformation(_("End of game"));
5438 DisplayError(_("Incorrect move"), 0);
5443 /* Ok, now we know that the move is good, so we can kill
5444 the previous line in Analysis Mode */
5445 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5446 forwardMostMove = currentMove;
5449 /* If we need the chess program but it's dead, restart it */
5450 ResurrectChessProgram();
5452 /* A user move restarts a paused game*/
5456 thinkOutput[0] = NULLCHAR;
5458 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5460 if (gameMode == BeginningOfGame) {
5461 if (appData.noChessProgram) {
5462 gameMode = EditGame;
5466 gameMode = MachinePlaysBlack;
5469 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5471 if (first.sendName) {
5472 sprintf(buf, "name %s\n", gameInfo.white);
5473 SendToProgram(buf, &first);
5480 /* Relay move to ICS or chess engine */
5481 if (appData.icsActive) {
5482 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5483 gameMode == IcsExamining) {
5484 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5488 if (first.sendTime && (gameMode == BeginningOfGame ||
5489 gameMode == MachinePlaysWhite ||
5490 gameMode == MachinePlaysBlack)) {
5491 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5493 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5494 // [HGM] book: if program might be playing, let it use book
5495 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5496 first.maybeThinking = TRUE;
5497 } else SendMoveToProgram(forwardMostMove-1, &first);
5498 if (currentMove == cmailOldMove + 1) {
5499 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5503 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5507 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5508 EP_UNKNOWN, castlingRights[currentMove]) ) {
5514 if (WhiteOnMove(currentMove)) {
5515 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5517 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5521 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5526 case MachinePlaysBlack:
5527 case MachinePlaysWhite:
5528 /* disable certain menu options while machine is thinking */
5529 SetMachineThinkingEnables();
5536 if(bookHit) { // [HGM] book: simulate book reply
5537 static char bookMove[MSG_SIZ]; // a bit generous?
5539 programStats.nodes = programStats.depth = programStats.time =
5540 programStats.score = programStats.got_only_move = 0;
5541 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5543 strcpy(bookMove, "move ");
5544 strcat(bookMove, bookHit);
5545 HandleMachineMove(bookMove, &first);
5551 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5552 int fromX, fromY, toX, toY;
5555 /* [HGM] This routine was added to allow calling of its two logical
5556 parts from other modules in the old way. Before, UserMoveEvent()
5557 automatically called FinishMove() if the move was OK, and returned
5558 otherwise. I separated the two, in order to make it possible to
5559 slip a promotion popup in between. But that it always needs two
5560 calls, to the first part, (now called UserMoveTest() ), and to
5561 FinishMove if the first part succeeded. Calls that do not need
5562 to do anything in between, can call this routine the old way.
5564 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5565 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5566 if(moveType == AmbiguousMove)
5567 DrawPosition(FALSE, boards[currentMove]);
5568 else if(moveType != ImpossibleMove && moveType != Comment)
5569 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5572 void LeftClick(ClickType clickType, int xPix, int yPix)
5575 Boolean saveAnimate;
5576 static int second = 0, promotionChoice = 0;
5577 char promoChoice = NULLCHAR;
5579 if (clickType == Press) ErrorPopDown();
5581 x = EventToSquare(xPix, BOARD_WIDTH);
5582 y = EventToSquare(yPix, BOARD_HEIGHT);
5583 if (!flipView && y >= 0) {
5584 y = BOARD_HEIGHT - 1 - y;
5586 if (flipView && x >= 0) {
5587 x = BOARD_WIDTH - 1 - x;
5590 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5591 if(clickType == Release) return; // ignore upclick of click-click destination
5592 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5593 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5594 if(gameInfo.holdingsWidth &&
5595 (WhiteOnMove(currentMove)
5596 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5597 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5598 // click in right holdings, for determining promotion piece
5599 ChessSquare p = boards[currentMove][y][x];
5600 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5601 if(p != EmptySquare) {
5602 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5607 DrawPosition(FALSE, boards[currentMove]);
5611 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5612 if(clickType == Press
5613 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5614 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5615 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5619 if (clickType == Press) {
5621 if (OKToStartUserMove(x, y)) {
5625 DragPieceBegin(xPix, yPix);
5626 if (appData.highlightDragging) {
5627 SetHighlights(x, y, -1, -1);
5635 if (clickType == Press && gameMode != EditPosition) {
5640 // ignore off-board to clicks
5641 if(y < 0 || x < 0) return;
5643 /* Check if clicking again on the same color piece */
5644 fromP = boards[currentMove][fromY][fromX];
5645 toP = boards[currentMove][y][x];
5646 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5647 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5648 WhitePawn <= toP && toP <= WhiteKing &&
5649 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5650 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5651 (BlackPawn <= fromP && fromP <= BlackKing &&
5652 BlackPawn <= toP && toP <= BlackKing &&
5653 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5654 !(fromP == BlackKing && toP == BlackRook && frc))) {
5655 /* Clicked again on same color piece -- changed his mind */
5656 second = (x == fromX && y == fromY);
5657 if (appData.highlightDragging) {
5658 SetHighlights(x, y, -1, -1);
5662 if (OKToStartUserMove(x, y)) {
5665 DragPieceBegin(xPix, yPix);
5669 // ignore clicks on holdings
5670 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5673 if (clickType == Release && x == fromX && y == fromY) {
5674 DragPieceEnd(xPix, yPix);
5675 if (appData.animateDragging) {
5676 /* Undo animation damage if any */
5677 DrawPosition(FALSE, NULL);
5680 /* Second up/down in same square; just abort move */
5685 ClearPremoveHighlights();
5687 /* First upclick in same square; start click-click mode */
5688 SetHighlights(x, y, -1, -1);
5693 /* we now have a different from- and (possibly off-board) to-square */
5694 /* Completed move */
5697 saveAnimate = appData.animate;
5698 if (clickType == Press) {
5699 /* Finish clickclick move */
5700 if (appData.animate || appData.highlightLastMove) {
5701 SetHighlights(fromX, fromY, toX, toY);
5706 /* Finish drag move */
5707 if (appData.highlightLastMove) {
5708 SetHighlights(fromX, fromY, toX, toY);
5712 DragPieceEnd(xPix, yPix);
5713 /* Don't animate move and drag both */
5714 appData.animate = FALSE;
5717 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5718 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5721 DrawPosition(TRUE, NULL);
5725 // off-board moves should not be highlighted
5726 if(x < 0 || x < 0) ClearHighlights();
5728 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5729 SetHighlights(fromX, fromY, toX, toY);
5730 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5731 // [HGM] super: promotion to captured piece selected from holdings
5732 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5733 promotionChoice = TRUE;
5734 // kludge follows to temporarily execute move on display, without promoting yet
5735 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5736 boards[currentMove][toY][toX] = p;
5737 DrawPosition(FALSE, boards[currentMove]);
5738 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5739 boards[currentMove][toY][toX] = q;
5740 DisplayMessage("Click in holdings to choose piece", "");
5745 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5746 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5747 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5750 appData.animate = saveAnimate;
5751 if (appData.animate || appData.animateDragging) {
5752 /* Undo animation damage if needed */
5753 DrawPosition(FALSE, NULL);
5757 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5759 // char * hint = lastHint;
5760 FrontEndProgramStats stats;
5762 stats.which = cps == &first ? 0 : 1;
5763 stats.depth = cpstats->depth;
5764 stats.nodes = cpstats->nodes;
5765 stats.score = cpstats->score;
5766 stats.time = cpstats->time;
5767 stats.pv = cpstats->movelist;
5768 stats.hint = lastHint;
5769 stats.an_move_index = 0;
5770 stats.an_move_count = 0;
5772 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5773 stats.hint = cpstats->move_name;
5774 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5775 stats.an_move_count = cpstats->nr_moves;
5778 SetProgramStats( &stats );
5781 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5782 { // [HGM] book: this routine intercepts moves to simulate book replies
5783 char *bookHit = NULL;
5785 //first determine if the incoming move brings opponent into his book
5786 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5787 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5788 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5789 if(bookHit != NULL && !cps->bookSuspend) {
5790 // make sure opponent is not going to reply after receiving move to book position
5791 SendToProgram("force\n", cps);
5792 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5794 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5795 // now arrange restart after book miss
5797 // after a book hit we never send 'go', and the code after the call to this routine
5798 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5800 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5801 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5802 SendToProgram(buf, cps);
5803 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5804 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5805 SendToProgram("go\n", cps);
5806 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5807 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5808 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5809 SendToProgram("go\n", cps);
5810 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5812 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5816 ChessProgramState *savedState;
5817 void DeferredBookMove(void)
5819 if(savedState->lastPing != savedState->lastPong)
5820 ScheduleDelayedEvent(DeferredBookMove, 10);
5822 HandleMachineMove(savedMessage, savedState);
5826 HandleMachineMove(message, cps)
5828 ChessProgramState *cps;
5830 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5831 char realname[MSG_SIZ];
5832 int fromX, fromY, toX, toY;
5839 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5841 * Kludge to ignore BEL characters
5843 while (*message == '\007') message++;
5846 * [HGM] engine debug message: ignore lines starting with '#' character
5848 if(cps->debug && *message == '#') return;
5851 * Look for book output
5853 if (cps == &first && bookRequested) {
5854 if (message[0] == '\t' || message[0] == ' ') {
5855 /* Part of the book output is here; append it */
5856 strcat(bookOutput, message);
5857 strcat(bookOutput, " \n");
5859 } else if (bookOutput[0] != NULLCHAR) {
5860 /* All of book output has arrived; display it */
5861 char *p = bookOutput;
5862 while (*p != NULLCHAR) {
5863 if (*p == '\t') *p = ' ';
5866 DisplayInformation(bookOutput);
5867 bookRequested = FALSE;
5868 /* Fall through to parse the current output */
5873 * Look for machine move.
5875 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5876 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5878 /* This method is only useful on engines that support ping */
5879 if (cps->lastPing != cps->lastPong) {
5880 if (gameMode == BeginningOfGame) {
5881 /* Extra move from before last new; ignore */
5882 if (appData.debugMode) {
5883 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5886 if (appData.debugMode) {
5887 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5888 cps->which, gameMode);
5891 SendToProgram("undo\n", cps);
5897 case BeginningOfGame:
5898 /* Extra move from before last reset; ignore */
5899 if (appData.debugMode) {
5900 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5907 /* Extra move after we tried to stop. The mode test is
5908 not a reliable way of detecting this problem, but it's
5909 the best we can do on engines that don't support ping.
5911 if (appData.debugMode) {
5912 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5913 cps->which, gameMode);
5915 SendToProgram("undo\n", cps);
5918 case MachinePlaysWhite:
5919 case IcsPlayingWhite:
5920 machineWhite = TRUE;
5923 case MachinePlaysBlack:
5924 case IcsPlayingBlack:
5925 machineWhite = FALSE;
5928 case TwoMachinesPlay:
5929 machineWhite = (cps->twoMachinesColor[0] == 'w');
5932 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5933 if (appData.debugMode) {
5935 "Ignoring move out of turn by %s, gameMode %d"
5936 ", forwardMost %d\n",
5937 cps->which, gameMode, forwardMostMove);
5942 if (appData.debugMode) { int f = forwardMostMove;
5943 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5944 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5946 if(cps->alphaRank) AlphaRank(machineMove, 4);
5947 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5948 &fromX, &fromY, &toX, &toY, &promoChar)) {
5949 /* Machine move could not be parsed; ignore it. */
5950 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5951 machineMove, cps->which);
5952 DisplayError(buf1, 0);
5953 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5954 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5955 if (gameMode == TwoMachinesPlay) {
5956 GameEnds(machineWhite ? BlackWins : WhiteWins,
5962 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5963 /* So we have to redo legality test with true e.p. status here, */
5964 /* to make sure an illegal e.p. capture does not slip through, */
5965 /* to cause a forfeit on a justified illegal-move complaint */
5966 /* of the opponent. */
5967 if( gameMode==TwoMachinesPlay && appData.testLegality
5968 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5971 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5972 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5973 fromY, fromX, toY, toX, promoChar);
5974 if (appData.debugMode) {
5976 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5977 castlingRights[forwardMostMove][i], castlingRank[i]);
5978 fprintf(debugFP, "castling rights\n");
5980 if(moveType == IllegalMove) {
5981 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5982 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5983 GameEnds(machineWhite ? BlackWins : WhiteWins,
5986 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5987 /* [HGM] Kludge to handle engines that send FRC-style castling
5988 when they shouldn't (like TSCP-Gothic) */
5990 case WhiteASideCastleFR:
5991 case BlackASideCastleFR:
5993 currentMoveString[2]++;
5995 case WhiteHSideCastleFR:
5996 case BlackHSideCastleFR:
5998 currentMoveString[2]--;
6000 default: ; // nothing to do, but suppresses warning of pedantic compilers
6003 hintRequested = FALSE;
6004 lastHint[0] = NULLCHAR;
6005 bookRequested = FALSE;
6006 /* Program may be pondering now */
6007 cps->maybeThinking = TRUE;
6008 if (cps->sendTime == 2) cps->sendTime = 1;
6009 if (cps->offeredDraw) cps->offeredDraw--;
6012 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6014 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6016 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6017 char buf[3*MSG_SIZ];
6019 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6020 programStats.score / 100.,
6022 programStats.time / 100.,
6023 (unsigned int)programStats.nodes,
6024 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6025 programStats.movelist);
6027 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6031 /* currentMoveString is set as a side-effect of ParseOneMove */
6032 strcpy(machineMove, currentMoveString);
6033 strcat(machineMove, "\n");
6034 strcpy(moveList[forwardMostMove], machineMove);
6036 /* [AS] Save move info and clear stats for next move */
6037 pvInfoList[ forwardMostMove ].score = programStats.score;
6038 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6039 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6040 ClearProgramStats();
6041 thinkOutput[0] = NULLCHAR;
6042 hiddenThinkOutputState = 0;
6044 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6046 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6047 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6050 while( count < adjudicateLossPlies ) {
6051 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6054 score = -score; /* Flip score for winning side */
6057 if( score > adjudicateLossThreshold ) {
6064 if( count >= adjudicateLossPlies ) {
6065 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6067 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6068 "Xboard adjudication",
6075 if( gameMode == TwoMachinesPlay ) {
6076 // [HGM] some adjudications useful with buggy engines
6077 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6078 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6081 if( appData.testLegality )
6082 { /* [HGM] Some more adjudications for obstinate engines */
6083 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6084 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6085 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6086 static int moveCount = 6;
6088 char *reason = NULL;
6090 /* Count what is on board. */
6091 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6092 { ChessSquare p = boards[forwardMostMove][i][j];
6096 { /* count B,N,R and other of each side */
6099 NrK++; break; // [HGM] atomic: count Kings
6103 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6104 bishopsColor |= 1 << ((i^j)&1);
6109 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6110 bishopsColor |= 1 << ((i^j)&1);
6125 PawnAdvance += m; NrPawns++;
6127 NrPieces += (p != EmptySquare);
6128 NrW += ((int)p < (int)BlackPawn);
6129 if(gameInfo.variant == VariantXiangqi &&
6130 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6131 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6132 NrW -= ((int)p < (int)BlackPawn);
6136 /* Some material-based adjudications that have to be made before stalemate test */
6137 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6138 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6139 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6140 if(appData.checkMates) {
6141 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6142 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6143 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6144 "Xboard adjudication: King destroyed", GE_XBOARD );
6149 /* Bare King in Shatranj (loses) or Losers (wins) */
6150 if( NrW == 1 || NrPieces - NrW == 1) {
6151 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6152 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6153 if(appData.checkMates) {
6154 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6155 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6156 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6157 "Xboard adjudication: Bare king", GE_XBOARD );
6161 if( gameInfo.variant == VariantShatranj && --bare < 0)
6163 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6164 if(appData.checkMates) {
6165 /* but only adjudicate if adjudication enabled */
6166 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6167 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6168 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6169 "Xboard adjudication: Bare king", GE_XBOARD );
6176 // don't wait for engine to announce game end if we can judge ourselves
6177 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6178 castlingRights[forwardMostMove]) ) {
6180 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6181 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6182 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6183 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6186 reason = "Xboard adjudication: 3rd check";
6187 epStatus[forwardMostMove] = EP_CHECKMATE;
6197 reason = "Xboard adjudication: Stalemate";
6198 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6199 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6200 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6201 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6202 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6203 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6204 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6205 EP_CHECKMATE : EP_WINS);
6206 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6207 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6211 reason = "Xboard adjudication: Checkmate";
6212 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6216 switch(i = epStatus[forwardMostMove]) {
6218 result = GameIsDrawn; break;
6220 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6222 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6224 result = (ChessMove) 0;
6226 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6227 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6228 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6229 GameEnds( result, reason, GE_XBOARD );
6233 /* Next absolutely insufficient mating material. */
6234 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6235 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6236 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6237 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6238 { /* KBK, KNK, KK of KBKB with like Bishops */
6240 /* always flag draws, for judging claims */
6241 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6243 if(appData.materialDraws) {
6244 /* but only adjudicate them if adjudication enabled */
6245 SendToProgram("force\n", cps->other); // suppress reply
6246 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6247 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6248 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6253 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6255 ( NrWR == 1 && NrBR == 1 /* KRKR */
6256 || NrWQ==1 && NrBQ==1 /* KQKQ */
6257 || NrWN==2 || NrBN==2 /* KNNK */
6258 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6260 if(--moveCount < 0 && appData.trivialDraws)
6261 { /* if the first 3 moves do not show a tactical win, declare draw */
6262 SendToProgram("force\n", cps->other); // suppress reply
6263 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6264 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6265 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6268 } else moveCount = 6;
6272 /* Check for rep-draws */
6274 for(k = forwardMostMove-2;
6275 k>=backwardMostMove && k>=forwardMostMove-100 &&
6276 epStatus[k] < EP_UNKNOWN &&
6277 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6280 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6281 /* compare castling rights */
6282 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6283 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6284 rights++; /* King lost rights, while rook still had them */
6285 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6286 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6287 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6288 rights++; /* but at least one rook lost them */
6290 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6291 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6293 if( castlingRights[forwardMostMove][5] >= 0 ) {
6294 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6295 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6298 if( rights == 0 && ++count > appData.drawRepeats-2
6299 && appData.drawRepeats > 1) {
6300 /* adjudicate after user-specified nr of repeats */
6301 SendToProgram("force\n", cps->other); // suppress reply
6302 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6303 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6304 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6305 // [HGM] xiangqi: check for forbidden perpetuals
6306 int m, ourPerpetual = 1, hisPerpetual = 1;
6307 for(m=forwardMostMove; m>k; m-=2) {
6308 if(MateTest(boards[m], PosFlags(m),
6309 EP_NONE, castlingRights[m]) != MT_CHECK)
6310 ourPerpetual = 0; // the current mover did not always check
6311 if(MateTest(boards[m-1], PosFlags(m-1),
6312 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6313 hisPerpetual = 0; // the opponent did not always check
6315 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6316 ourPerpetual, hisPerpetual);
6317 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6318 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6319 "Xboard adjudication: perpetual checking", GE_XBOARD );
6322 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6323 break; // (or we would have caught him before). Abort repetition-checking loop.
6324 // Now check for perpetual chases
6325 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6326 hisPerpetual = PerpetualChase(k, forwardMostMove);
6327 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6328 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6329 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6330 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6333 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6334 break; // Abort repetition-checking loop.
6336 // if neither of us is checking or chasing all the time, or both are, it is draw
6338 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6341 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6342 epStatus[forwardMostMove] = EP_REP_DRAW;
6346 /* Now we test for 50-move draws. Determine ply count */
6347 count = forwardMostMove;
6348 /* look for last irreversble move */
6349 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6351 /* if we hit starting position, add initial plies */
6352 if( count == backwardMostMove )
6353 count -= initialRulePlies;
6354 count = forwardMostMove - count;
6356 epStatus[forwardMostMove] = EP_RULE_DRAW;
6357 /* this is used to judge if draw claims are legal */
6358 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
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*/
6362 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6366 /* if draw offer is pending, treat it as a draw claim
6367 * when draw condition present, to allow engines a way to
6368 * claim draws before making their move to avoid a race
6369 * condition occurring after their move
6371 if( cps->other->offeredDraw || cps->offeredDraw ) {
6373 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6374 p = "Draw claim: 50-move rule";
6375 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6376 p = "Draw claim: 3-fold repetition";
6377 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6378 p = "Draw claim: insufficient mating material";
6380 SendToProgram("force\n", cps->other); // suppress reply
6381 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6382 GameEnds( GameIsDrawn, p, GE_XBOARD );
6383 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6389 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6390 SendToProgram("force\n", cps->other); // suppress reply
6391 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6392 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6394 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6401 if (gameMode == TwoMachinesPlay) {
6402 /* [HGM] relaying draw offers moved to after reception of move */
6403 /* and interpreting offer as claim if it brings draw condition */
6404 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6405 SendToProgram("draw\n", cps->other);
6407 if (cps->other->sendTime) {
6408 SendTimeRemaining(cps->other,
6409 cps->other->twoMachinesColor[0] == 'w');
6411 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6412 if (firstMove && !bookHit) {
6414 if (cps->other->useColors) {
6415 SendToProgram(cps->other->twoMachinesColor, cps->other);
6417 SendToProgram("go\n", cps->other);
6419 cps->other->maybeThinking = TRUE;
6422 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6424 if (!pausing && appData.ringBellAfterMoves) {
6429 * Reenable menu items that were disabled while
6430 * machine was thinking
6432 if (gameMode != TwoMachinesPlay)
6433 SetUserThinkingEnables();
6435 // [HGM] book: after book hit opponent has received move and is now in force mode
6436 // force the book reply into it, and then fake that it outputted this move by jumping
6437 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6439 static char bookMove[MSG_SIZ]; // a bit generous?
6441 strcpy(bookMove, "move ");
6442 strcat(bookMove, bookHit);
6445 programStats.nodes = programStats.depth = programStats.time =
6446 programStats.score = programStats.got_only_move = 0;
6447 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6449 if(cps->lastPing != cps->lastPong) {
6450 savedMessage = message; // args for deferred call
6452 ScheduleDelayedEvent(DeferredBookMove, 10);
6461 /* Set special modes for chess engines. Later something general
6462 * could be added here; for now there is just one kludge feature,
6463 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6464 * when "xboard" is given as an interactive command.
6466 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6467 cps->useSigint = FALSE;
6468 cps->useSigterm = FALSE;
6470 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6471 ParseFeatures(message+8, cps);
6472 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6475 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6476 * want this, I was asked to put it in, and obliged.
6478 if (!strncmp(message, "setboard ", 9)) {
6479 Board initial_position; int i;
6481 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6483 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6484 DisplayError(_("Bad FEN received from engine"), 0);
6488 CopyBoard(boards[0], initial_position);
6489 initialRulePlies = FENrulePlies;
6490 epStatus[0] = FENepStatus;
6491 for( i=0; i<nrCastlingRights; i++ )
6492 castlingRights[0][i] = FENcastlingRights[i];
6493 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6494 else gameMode = MachinePlaysBlack;
6495 DrawPosition(FALSE, boards[currentMove]);
6501 * Look for communication commands
6503 if (!strncmp(message, "telluser ", 9)) {
6504 DisplayNote(message + 9);
6507 if (!strncmp(message, "tellusererror ", 14)) {
6508 DisplayError(message + 14, 0);
6511 if (!strncmp(message, "tellopponent ", 13)) {
6512 if (appData.icsActive) {
6514 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6518 DisplayNote(message + 13);
6522 if (!strncmp(message, "tellothers ", 11)) {
6523 if (appData.icsActive) {
6525 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6531 if (!strncmp(message, "tellall ", 8)) {
6532 if (appData.icsActive) {
6534 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6538 DisplayNote(message + 8);
6542 if (strncmp(message, "warning", 7) == 0) {
6543 /* Undocumented feature, use tellusererror in new code */
6544 DisplayError(message, 0);
6547 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6548 strcpy(realname, cps->tidy);
6549 strcat(realname, " query");
6550 AskQuestion(realname, buf2, buf1, cps->pr);
6553 /* Commands from the engine directly to ICS. We don't allow these to be
6554 * sent until we are logged on. Crafty kibitzes have been known to
6555 * interfere with the login process.
6558 if (!strncmp(message, "tellics ", 8)) {
6559 SendToICS(message + 8);
6563 if (!strncmp(message, "tellicsnoalias ", 15)) {
6564 SendToICS(ics_prefix);
6565 SendToICS(message + 15);
6569 /* The following are for backward compatibility only */
6570 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6571 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6572 SendToICS(ics_prefix);
6578 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6582 * If the move is illegal, cancel it and redraw the board.
6583 * Also deal with other error cases. Matching is rather loose
6584 * here to accommodate engines written before the spec.
6586 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6587 strncmp(message, "Error", 5) == 0) {
6588 if (StrStr(message, "name") ||
6589 StrStr(message, "rating") || StrStr(message, "?") ||
6590 StrStr(message, "result") || StrStr(message, "board") ||
6591 StrStr(message, "bk") || StrStr(message, "computer") ||
6592 StrStr(message, "variant") || StrStr(message, "hint") ||
6593 StrStr(message, "random") || StrStr(message, "depth") ||
6594 StrStr(message, "accepted")) {
6597 if (StrStr(message, "protover")) {
6598 /* Program is responding to input, so it's apparently done
6599 initializing, and this error message indicates it is
6600 protocol version 1. So we don't need to wait any longer
6601 for it to initialize and send feature commands. */
6602 FeatureDone(cps, 1);
6603 cps->protocolVersion = 1;
6606 cps->maybeThinking = FALSE;
6608 if (StrStr(message, "draw")) {
6609 /* Program doesn't have "draw" command */
6610 cps->sendDrawOffers = 0;
6613 if (cps->sendTime != 1 &&
6614 (StrStr(message, "time") || StrStr(message, "otim"))) {
6615 /* Program apparently doesn't have "time" or "otim" command */
6619 if (StrStr(message, "analyze")) {
6620 cps->analysisSupport = FALSE;
6621 cps->analyzing = FALSE;
6623 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6624 DisplayError(buf2, 0);
6627 if (StrStr(message, "(no matching move)st")) {
6628 /* Special kludge for GNU Chess 4 only */
6629 cps->stKludge = TRUE;
6630 SendTimeControl(cps, movesPerSession, timeControl,
6631 timeIncrement, appData.searchDepth,
6635 if (StrStr(message, "(no matching move)sd")) {
6636 /* Special kludge for GNU Chess 4 only */
6637 cps->sdKludge = TRUE;
6638 SendTimeControl(cps, movesPerSession, timeControl,
6639 timeIncrement, appData.searchDepth,
6643 if (!StrStr(message, "llegal")) {
6646 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6647 gameMode == IcsIdle) return;
6648 if (forwardMostMove <= backwardMostMove) return;
6649 if (pausing) PauseEvent();
6650 if(appData.forceIllegal) {
6651 // [HGM] illegal: machine refused move; force position after move into it
6652 SendToProgram("force\n", cps);
6653 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6654 // we have a real problem now, as SendBoard will use the a2a3 kludge
6655 // when black is to move, while there might be nothing on a2 or black
6656 // might already have the move. So send the board as if white has the move.
6657 // But first we must change the stm of the engine, as it refused the last move
6658 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6659 if(WhiteOnMove(forwardMostMove)) {
6660 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6661 SendBoard(cps, forwardMostMove); // kludgeless board
6663 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6664 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6665 SendBoard(cps, forwardMostMove+1); // kludgeless board
6667 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6668 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6669 gameMode == TwoMachinesPlay)
6670 SendToProgram("go\n", cps);
6673 if (gameMode == PlayFromGameFile) {
6674 /* Stop reading this game file */
6675 gameMode = EditGame;
6678 currentMove = forwardMostMove-1;
6679 DisplayMove(currentMove-1); /* before DisplayMoveError */
6680 SwitchClocks(forwardMostMove-1); // [HGM] race
6681 DisplayBothClocks();
6682 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6683 parseList[currentMove], cps->which);
6684 DisplayMoveError(buf1);
6685 DrawPosition(FALSE, boards[currentMove]);
6687 /* [HGM] illegal-move claim should forfeit game when Xboard */
6688 /* only passes fully legal moves */
6689 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6690 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6691 "False illegal-move claim", GE_XBOARD );
6695 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6696 /* Program has a broken "time" command that
6697 outputs a string not ending in newline.
6703 * If chess program startup fails, exit with an error message.
6704 * Attempts to recover here are futile.
6706 if ((StrStr(message, "unknown host") != NULL)
6707 || (StrStr(message, "No remote directory") != NULL)
6708 || (StrStr(message, "not found") != NULL)
6709 || (StrStr(message, "No such file") != NULL)
6710 || (StrStr(message, "can't alloc") != NULL)
6711 || (StrStr(message, "Permission denied") != NULL)) {
6713 cps->maybeThinking = FALSE;
6714 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6715 cps->which, cps->program, cps->host, message);
6716 RemoveInputSource(cps->isr);
6717 DisplayFatalError(buf1, 0, 1);
6722 * Look for hint output
6724 if (sscanf(message, "Hint: %s", buf1) == 1) {
6725 if (cps == &first && hintRequested) {
6726 hintRequested = FALSE;
6727 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6728 &fromX, &fromY, &toX, &toY, &promoChar)) {
6729 (void) CoordsToAlgebraic(boards[forwardMostMove],
6730 PosFlags(forwardMostMove), EP_UNKNOWN,
6731 fromY, fromX, toY, toX, promoChar, buf1);
6732 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6733 DisplayInformation(buf2);
6735 /* Hint move could not be parsed!? */
6736 snprintf(buf2, sizeof(buf2),
6737 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6739 DisplayError(buf2, 0);
6742 strcpy(lastHint, buf1);
6748 * Ignore other messages if game is not in progress
6750 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6751 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6754 * look for win, lose, draw, or draw offer
6756 if (strncmp(message, "1-0", 3) == 0) {
6757 char *p, *q, *r = "";
6758 p = strchr(message, '{');
6766 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6768 } else if (strncmp(message, "0-1", 3) == 0) {
6769 char *p, *q, *r = "";
6770 p = strchr(message, '{');
6778 /* Kludge for Arasan 4.1 bug */
6779 if (strcmp(r, "Black resigns") == 0) {
6780 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6783 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6785 } else if (strncmp(message, "1/2", 3) == 0) {
6786 char *p, *q, *r = "";
6787 p = strchr(message, '{');
6796 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6799 } else if (strncmp(message, "White resign", 12) == 0) {
6800 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6802 } else if (strncmp(message, "Black resign", 12) == 0) {
6803 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6805 } else if (strncmp(message, "White matches", 13) == 0 ||
6806 strncmp(message, "Black matches", 13) == 0 ) {
6807 /* [HGM] ignore GNUShogi noises */
6809 } else if (strncmp(message, "White", 5) == 0 &&
6810 message[5] != '(' &&
6811 StrStr(message, "Black") == NULL) {
6812 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6814 } else if (strncmp(message, "Black", 5) == 0 &&
6815 message[5] != '(') {
6816 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6818 } else if (strcmp(message, "resign") == 0 ||
6819 strcmp(message, "computer resigns") == 0) {
6821 case MachinePlaysBlack:
6822 case IcsPlayingBlack:
6823 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6825 case MachinePlaysWhite:
6826 case IcsPlayingWhite:
6827 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6829 case TwoMachinesPlay:
6830 if (cps->twoMachinesColor[0] == 'w')
6831 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6833 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6840 } else if (strncmp(message, "opponent mates", 14) == 0) {
6842 case MachinePlaysBlack:
6843 case IcsPlayingBlack:
6844 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6846 case MachinePlaysWhite:
6847 case IcsPlayingWhite:
6848 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6850 case TwoMachinesPlay:
6851 if (cps->twoMachinesColor[0] == 'w')
6852 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6854 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6861 } else if (strncmp(message, "computer mates", 14) == 0) {
6863 case MachinePlaysBlack:
6864 case IcsPlayingBlack:
6865 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6867 case MachinePlaysWhite:
6868 case IcsPlayingWhite:
6869 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6871 case TwoMachinesPlay:
6872 if (cps->twoMachinesColor[0] == 'w')
6873 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6875 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6882 } else if (strncmp(message, "checkmate", 9) == 0) {
6883 if (WhiteOnMove(forwardMostMove)) {
6884 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6886 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6889 } else if (strstr(message, "Draw") != NULL ||
6890 strstr(message, "game is a draw") != NULL) {
6891 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6893 } else if (strstr(message, "offer") != NULL &&
6894 strstr(message, "draw") != NULL) {
6896 if (appData.zippyPlay && first.initDone) {
6897 /* Relay offer to ICS */
6898 SendToICS(ics_prefix);
6899 SendToICS("draw\n");
6902 cps->offeredDraw = 2; /* valid until this engine moves twice */
6903 if (gameMode == TwoMachinesPlay) {
6904 if (cps->other->offeredDraw) {
6905 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6906 /* [HGM] in two-machine mode we delay relaying draw offer */
6907 /* until after we also have move, to see if it is really claim */
6909 } else if (gameMode == MachinePlaysWhite ||
6910 gameMode == MachinePlaysBlack) {
6911 if (userOfferedDraw) {
6912 DisplayInformation(_("Machine accepts your draw offer"));
6913 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6915 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6922 * Look for thinking output
6924 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6925 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6927 int plylev, mvleft, mvtot, curscore, time;
6928 char mvname[MOVE_LEN];
6932 int prefixHint = FALSE;
6933 mvname[0] = NULLCHAR;
6936 case MachinePlaysBlack:
6937 case IcsPlayingBlack:
6938 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6940 case MachinePlaysWhite:
6941 case IcsPlayingWhite:
6942 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6947 case IcsObserving: /* [DM] icsEngineAnalyze */
6948 if (!appData.icsEngineAnalyze) ignore = TRUE;
6950 case TwoMachinesPlay:
6951 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6962 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6963 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6965 if (plyext != ' ' && plyext != '\t') {
6969 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6970 if( cps->scoreIsAbsolute &&
6971 ( gameMode == MachinePlaysBlack ||
6972 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6973 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6974 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6975 !WhiteOnMove(currentMove)
6978 curscore = -curscore;
6982 programStats.depth = plylev;
6983 programStats.nodes = nodes;
6984 programStats.time = time;
6985 programStats.score = curscore;
6986 programStats.got_only_move = 0;
6988 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6991 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6992 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6993 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6994 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6995 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6996 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6997 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6998 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7001 /* Buffer overflow protection */
7002 if (buf1[0] != NULLCHAR) {
7003 if (strlen(buf1) >= sizeof(programStats.movelist)
7004 && appData.debugMode) {
7006 "PV is too long; using the first %u bytes.\n",
7007 (unsigned) sizeof(programStats.movelist) - 1);
7010 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7012 sprintf(programStats.movelist, " no PV\n");
7015 if (programStats.seen_stat) {
7016 programStats.ok_to_send = 1;
7019 if (strchr(programStats.movelist, '(') != NULL) {
7020 programStats.line_is_book = 1;
7021 programStats.nr_moves = 0;
7022 programStats.moves_left = 0;
7024 programStats.line_is_book = 0;
7027 SendProgramStatsToFrontend( cps, &programStats );
7030 [AS] Protect the thinkOutput buffer from overflow... this
7031 is only useful if buf1 hasn't overflowed first!
7033 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7035 (gameMode == TwoMachinesPlay ?
7036 ToUpper(cps->twoMachinesColor[0]) : ' '),
7037 ((double) curscore) / 100.0,
7038 prefixHint ? lastHint : "",
7039 prefixHint ? " " : "" );
7041 if( buf1[0] != NULLCHAR ) {
7042 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7044 if( strlen(buf1) > max_len ) {
7045 if( appData.debugMode) {
7046 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7048 buf1[max_len+1] = '\0';
7051 strcat( thinkOutput, buf1 );
7054 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7055 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7056 DisplayMove(currentMove - 1);
7060 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7061 /* crafty (9.25+) says "(only move) <move>"
7062 * if there is only 1 legal move
7064 sscanf(p, "(only move) %s", buf1);
7065 sprintf(thinkOutput, "%s (only move)", buf1);
7066 sprintf(programStats.movelist, "%s (only move)", buf1);
7067 programStats.depth = 1;
7068 programStats.nr_moves = 1;
7069 programStats.moves_left = 1;
7070 programStats.nodes = 1;
7071 programStats.time = 1;
7072 programStats.got_only_move = 1;
7074 /* Not really, but we also use this member to
7075 mean "line isn't going to change" (Crafty
7076 isn't searching, so stats won't change) */
7077 programStats.line_is_book = 1;
7079 SendProgramStatsToFrontend( cps, &programStats );
7081 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7082 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7083 DisplayMove(currentMove - 1);
7086 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7087 &time, &nodes, &plylev, &mvleft,
7088 &mvtot, mvname) >= 5) {
7089 /* The stat01: line is from Crafty (9.29+) in response
7090 to the "." command */
7091 programStats.seen_stat = 1;
7092 cps->maybeThinking = TRUE;
7094 if (programStats.got_only_move || !appData.periodicUpdates)
7097 programStats.depth = plylev;
7098 programStats.time = time;
7099 programStats.nodes = nodes;
7100 programStats.moves_left = mvleft;
7101 programStats.nr_moves = mvtot;
7102 strcpy(programStats.move_name, mvname);
7103 programStats.ok_to_send = 1;
7104 programStats.movelist[0] = '\0';
7106 SendProgramStatsToFrontend( cps, &programStats );
7110 } else if (strncmp(message,"++",2) == 0) {
7111 /* Crafty 9.29+ outputs this */
7112 programStats.got_fail = 2;
7115 } else if (strncmp(message,"--",2) == 0) {
7116 /* Crafty 9.29+ outputs this */
7117 programStats.got_fail = 1;
7120 } else if (thinkOutput[0] != NULLCHAR &&
7121 strncmp(message, " ", 4) == 0) {
7122 unsigned message_len;
7125 while (*p && *p == ' ') p++;
7127 message_len = strlen( p );
7129 /* [AS] Avoid buffer overflow */
7130 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7131 strcat(thinkOutput, " ");
7132 strcat(thinkOutput, p);
7135 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7136 strcat(programStats.movelist, " ");
7137 strcat(programStats.movelist, p);
7140 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7141 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7142 DisplayMove(currentMove - 1);
7150 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7151 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7153 ChessProgramStats cpstats;
7155 if (plyext != ' ' && plyext != '\t') {
7159 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7160 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7161 curscore = -curscore;
7164 cpstats.depth = plylev;
7165 cpstats.nodes = nodes;
7166 cpstats.time = time;
7167 cpstats.score = curscore;
7168 cpstats.got_only_move = 0;
7169 cpstats.movelist[0] = '\0';
7171 if (buf1[0] != NULLCHAR) {
7172 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7175 cpstats.ok_to_send = 0;
7176 cpstats.line_is_book = 0;
7177 cpstats.nr_moves = 0;
7178 cpstats.moves_left = 0;
7180 SendProgramStatsToFrontend( cps, &cpstats );
7187 /* Parse a game score from the character string "game", and
7188 record it as the history of the current game. The game
7189 score is NOT assumed to start from the standard position.
7190 The display is not updated in any way.
7193 ParseGameHistory(game)
7197 int fromX, fromY, toX, toY, boardIndex;
7202 if (appData.debugMode)
7203 fprintf(debugFP, "Parsing game history: %s\n", game);
7205 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7206 gameInfo.site = StrSave(appData.icsHost);
7207 gameInfo.date = PGNDate();
7208 gameInfo.round = StrSave("-");
7210 /* Parse out names of players */
7211 while (*game == ' ') game++;
7213 while (*game != ' ') *p++ = *game++;
7215 gameInfo.white = StrSave(buf);
7216 while (*game == ' ') game++;
7218 while (*game != ' ' && *game != '\n') *p++ = *game++;
7220 gameInfo.black = StrSave(buf);
7223 boardIndex = blackPlaysFirst ? 1 : 0;
7226 yyboardindex = boardIndex;
7227 moveType = (ChessMove) yylex();
7229 case IllegalMove: /* maybe suicide chess, etc. */
7230 if (appData.debugMode) {
7231 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7232 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7233 setbuf(debugFP, NULL);
7235 case WhitePromotionChancellor:
7236 case BlackPromotionChancellor:
7237 case WhitePromotionArchbishop:
7238 case BlackPromotionArchbishop:
7239 case WhitePromotionQueen:
7240 case BlackPromotionQueen:
7241 case WhitePromotionRook:
7242 case BlackPromotionRook:
7243 case WhitePromotionBishop:
7244 case BlackPromotionBishop:
7245 case WhitePromotionKnight:
7246 case BlackPromotionKnight:
7247 case WhitePromotionKing:
7248 case BlackPromotionKing:
7250 case WhiteCapturesEnPassant:
7251 case BlackCapturesEnPassant:
7252 case WhiteKingSideCastle:
7253 case WhiteQueenSideCastle:
7254 case BlackKingSideCastle:
7255 case BlackQueenSideCastle:
7256 case WhiteKingSideCastleWild:
7257 case WhiteQueenSideCastleWild:
7258 case BlackKingSideCastleWild:
7259 case BlackQueenSideCastleWild:
7261 case WhiteHSideCastleFR:
7262 case WhiteASideCastleFR:
7263 case BlackHSideCastleFR:
7264 case BlackASideCastleFR:
7266 fromX = currentMoveString[0] - AAA;
7267 fromY = currentMoveString[1] - ONE;
7268 toX = currentMoveString[2] - AAA;
7269 toY = currentMoveString[3] - ONE;
7270 promoChar = currentMoveString[4];
7274 fromX = moveType == WhiteDrop ?
7275 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7276 (int) CharToPiece(ToLower(currentMoveString[0]));
7278 toX = currentMoveString[2] - AAA;
7279 toY = currentMoveString[3] - ONE;
7280 promoChar = NULLCHAR;
7284 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7285 if (appData.debugMode) {
7286 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7287 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7288 setbuf(debugFP, NULL);
7290 DisplayError(buf, 0);
7292 case ImpossibleMove:
7294 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7295 if (appData.debugMode) {
7296 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7297 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7298 setbuf(debugFP, NULL);
7300 DisplayError(buf, 0);
7302 case (ChessMove) 0: /* end of file */
7303 if (boardIndex < backwardMostMove) {
7304 /* Oops, gap. How did that happen? */
7305 DisplayError(_("Gap in move list"), 0);
7308 backwardMostMove = blackPlaysFirst ? 1 : 0;
7309 if (boardIndex > forwardMostMove) {
7310 forwardMostMove = boardIndex;
7314 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7315 strcat(parseList[boardIndex-1], " ");
7316 strcat(parseList[boardIndex-1], yy_text);
7328 case GameUnfinished:
7329 if (gameMode == IcsExamining) {
7330 if (boardIndex < backwardMostMove) {
7331 /* Oops, gap. How did that happen? */
7334 backwardMostMove = blackPlaysFirst ? 1 : 0;
7337 gameInfo.result = moveType;
7338 p = strchr(yy_text, '{');
7339 if (p == NULL) p = strchr(yy_text, '(');
7342 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7344 q = strchr(p, *p == '{' ? '}' : ')');
7345 if (q != NULL) *q = NULLCHAR;
7348 gameInfo.resultDetails = StrSave(p);
7351 if (boardIndex >= forwardMostMove &&
7352 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7353 backwardMostMove = blackPlaysFirst ? 1 : 0;
7356 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7357 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7358 parseList[boardIndex]);
7359 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7360 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7361 /* currentMoveString is set as a side-effect of yylex */
7362 strcpy(moveList[boardIndex], currentMoveString);
7363 strcat(moveList[boardIndex], "\n");
7365 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7366 castlingRights[boardIndex], &epStatus[boardIndex]);
7367 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7368 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7374 if(gameInfo.variant != VariantShogi)
7375 strcat(parseList[boardIndex - 1], "+");
7379 strcat(parseList[boardIndex - 1], "#");
7386 /* Apply a move to the given board */
7388 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7389 int fromX, fromY, toX, toY;
7395 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7396 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7398 /* [HGM] compute & store e.p. status and castling rights for new position */
7399 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7402 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7406 if( board[toY][toX] != EmptySquare )
7409 if( board[fromY][fromX] == WhitePawn ) {
7410 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7413 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7414 gameInfo.variant != VariantBerolina || toX < fromX)
7415 *ep = toX | berolina;
7416 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7417 gameInfo.variant != VariantBerolina || toX > fromX)
7421 if( board[fromY][fromX] == BlackPawn ) {
7422 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7424 if( toY-fromY== -2) {
7425 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7426 gameInfo.variant != VariantBerolina || toX < fromX)
7427 *ep = toX | berolina;
7428 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7429 gameInfo.variant != VariantBerolina || toX > fromX)
7434 for(i=0; i<nrCastlingRights; i++) {
7435 if(castling[i] == fromX && castlingRank[i] == fromY ||
7436 castling[i] == toX && castlingRank[i] == toY
7437 ) castling[i] = -1; // revoke for moved or captured piece
7442 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7443 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7444 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7446 if (fromX == toX && fromY == toY) return;
7448 if (fromY == DROP_RANK) {
7450 piece = board[toY][toX] = (ChessSquare) fromX;
7452 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7453 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7454 if(gameInfo.variant == VariantKnightmate)
7455 king += (int) WhiteUnicorn - (int) WhiteKing;
7457 /* Code added by Tord: */
7458 /* FRC castling assumed when king captures friendly rook. */
7459 if (board[fromY][fromX] == WhiteKing &&
7460 board[toY][toX] == WhiteRook) {
7461 board[fromY][fromX] = EmptySquare;
7462 board[toY][toX] = EmptySquare;
7464 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7466 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7468 } else if (board[fromY][fromX] == BlackKing &&
7469 board[toY][toX] == BlackRook) {
7470 board[fromY][fromX] = EmptySquare;
7471 board[toY][toX] = EmptySquare;
7473 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7475 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7477 /* End of code added by Tord */
7479 } else if (board[fromY][fromX] == king
7480 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7481 && toY == fromY && toX > fromX+1) {
7482 board[fromY][fromX] = EmptySquare;
7483 board[toY][toX] = king;
7484 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7485 board[fromY][BOARD_RGHT-1] = EmptySquare;
7486 } else if (board[fromY][fromX] == king
7487 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7488 && toY == fromY && toX < fromX-1) {
7489 board[fromY][fromX] = EmptySquare;
7490 board[toY][toX] = king;
7491 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7492 board[fromY][BOARD_LEFT] = EmptySquare;
7493 } else if (board[fromY][fromX] == WhitePawn
7494 && toY >= BOARD_HEIGHT-promoRank
7495 && gameInfo.variant != VariantXiangqi
7497 /* white pawn promotion */
7498 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7499 if (board[toY][toX] == EmptySquare) {
7500 board[toY][toX] = WhiteQueen;
7502 if(gameInfo.variant==VariantBughouse ||
7503 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7504 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7505 board[fromY][fromX] = EmptySquare;
7506 } else if ((fromY == BOARD_HEIGHT-4)
7508 && gameInfo.variant != VariantXiangqi
7509 && gameInfo.variant != VariantBerolina
7510 && (board[fromY][fromX] == WhitePawn)
7511 && (board[toY][toX] == EmptySquare)) {
7512 board[fromY][fromX] = EmptySquare;
7513 board[toY][toX] = WhitePawn;
7514 captured = board[toY - 1][toX];
7515 board[toY - 1][toX] = EmptySquare;
7516 } else if ((fromY == BOARD_HEIGHT-4)
7518 && gameInfo.variant == VariantBerolina
7519 && (board[fromY][fromX] == WhitePawn)
7520 && (board[toY][toX] == EmptySquare)) {
7521 board[fromY][fromX] = EmptySquare;
7522 board[toY][toX] = WhitePawn;
7523 if(oldEP & EP_BEROLIN_A) {
7524 captured = board[fromY][fromX-1];
7525 board[fromY][fromX-1] = EmptySquare;
7526 }else{ captured = board[fromY][fromX+1];
7527 board[fromY][fromX+1] = EmptySquare;
7529 } else if (board[fromY][fromX] == king
7530 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7531 && toY == fromY && toX > fromX+1) {
7532 board[fromY][fromX] = EmptySquare;
7533 board[toY][toX] = king;
7534 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7535 board[fromY][BOARD_RGHT-1] = EmptySquare;
7536 } else if (board[fromY][fromX] == king
7537 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7538 && toY == fromY && toX < fromX-1) {
7539 board[fromY][fromX] = EmptySquare;
7540 board[toY][toX] = king;
7541 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7542 board[fromY][BOARD_LEFT] = EmptySquare;
7543 } else if (fromY == 7 && fromX == 3
7544 && board[fromY][fromX] == BlackKing
7545 && toY == 7 && toX == 5) {
7546 board[fromY][fromX] = EmptySquare;
7547 board[toY][toX] = BlackKing;
7548 board[fromY][7] = EmptySquare;
7549 board[toY][4] = BlackRook;
7550 } else if (fromY == 7 && fromX == 3
7551 && board[fromY][fromX] == BlackKing
7552 && toY == 7 && toX == 1) {
7553 board[fromY][fromX] = EmptySquare;
7554 board[toY][toX] = BlackKing;
7555 board[fromY][0] = EmptySquare;
7556 board[toY][2] = BlackRook;
7557 } else if (board[fromY][fromX] == BlackPawn
7559 && gameInfo.variant != VariantXiangqi
7561 /* black pawn promotion */
7562 board[toY][toX] = CharToPiece(ToLower(promoChar));
7563 if (board[toY][toX] == EmptySquare) {
7564 board[toY][toX] = BlackQueen;
7566 if(gameInfo.variant==VariantBughouse ||
7567 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7568 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7569 board[fromY][fromX] = EmptySquare;
7570 } else if ((fromY == 3)
7572 && gameInfo.variant != VariantXiangqi
7573 && gameInfo.variant != VariantBerolina
7574 && (board[fromY][fromX] == BlackPawn)
7575 && (board[toY][toX] == EmptySquare)) {
7576 board[fromY][fromX] = EmptySquare;
7577 board[toY][toX] = BlackPawn;
7578 captured = board[toY + 1][toX];
7579 board[toY + 1][toX] = EmptySquare;
7580 } else if ((fromY == 3)
7582 && gameInfo.variant == VariantBerolina
7583 && (board[fromY][fromX] == BlackPawn)
7584 && (board[toY][toX] == EmptySquare)) {
7585 board[fromY][fromX] = EmptySquare;
7586 board[toY][toX] = BlackPawn;
7587 if(oldEP & EP_BEROLIN_A) {
7588 captured = board[fromY][fromX-1];
7589 board[fromY][fromX-1] = EmptySquare;
7590 }else{ captured = board[fromY][fromX+1];
7591 board[fromY][fromX+1] = EmptySquare;
7594 board[toY][toX] = board[fromY][fromX];
7595 board[fromY][fromX] = EmptySquare;
7598 /* [HGM] now we promote for Shogi, if needed */
7599 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7600 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7603 if (gameInfo.holdingsWidth != 0) {
7605 /* !!A lot more code needs to be written to support holdings */
7606 /* [HGM] OK, so I have written it. Holdings are stored in the */
7607 /* penultimate board files, so they are automaticlly stored */
7608 /* in the game history. */
7609 if (fromY == DROP_RANK) {
7610 /* Delete from holdings, by decreasing count */
7611 /* and erasing image if necessary */
7613 if(p < (int) BlackPawn) { /* white drop */
7614 p -= (int)WhitePawn;
7615 p = PieceToNumber((ChessSquare)p);
7616 if(p >= gameInfo.holdingsSize) p = 0;
7617 if(--board[p][BOARD_WIDTH-2] <= 0)
7618 board[p][BOARD_WIDTH-1] = EmptySquare;
7619 if((int)board[p][BOARD_WIDTH-2] < 0)
7620 board[p][BOARD_WIDTH-2] = 0;
7621 } else { /* black drop */
7622 p -= (int)BlackPawn;
7623 p = PieceToNumber((ChessSquare)p);
7624 if(p >= gameInfo.holdingsSize) p = 0;
7625 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7626 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7627 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7628 board[BOARD_HEIGHT-1-p][1] = 0;
7631 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7632 && gameInfo.variant != VariantBughouse ) {
7633 /* [HGM] holdings: Add to holdings, if holdings exist */
7634 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7635 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7636 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7639 if (p >= (int) BlackPawn) {
7640 p -= (int)BlackPawn;
7641 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7642 /* in Shogi restore piece to its original first */
7643 captured = (ChessSquare) (DEMOTED captured);
7646 p = PieceToNumber((ChessSquare)p);
7647 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7648 board[p][BOARD_WIDTH-2]++;
7649 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7651 p -= (int)WhitePawn;
7652 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7653 captured = (ChessSquare) (DEMOTED captured);
7656 p = PieceToNumber((ChessSquare)p);
7657 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7658 board[BOARD_HEIGHT-1-p][1]++;
7659 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7662 } else if (gameInfo.variant == VariantAtomic) {
7663 if (captured != EmptySquare) {
7665 for (y = toY-1; y <= toY+1; y++) {
7666 for (x = toX-1; x <= toX+1; x++) {
7667 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7668 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7669 board[y][x] = EmptySquare;
7673 board[toY][toX] = EmptySquare;
7676 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7677 /* [HGM] Shogi promotions */
7678 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7681 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7682 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7683 // [HGM] superchess: take promotion piece out of holdings
7684 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7685 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7686 if(!--board[k][BOARD_WIDTH-2])
7687 board[k][BOARD_WIDTH-1] = EmptySquare;
7689 if(!--board[BOARD_HEIGHT-1-k][1])
7690 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7696 /* Updates forwardMostMove */
7698 MakeMove(fromX, fromY, toX, toY, promoChar)
7699 int fromX, fromY, toX, toY;
7702 // forwardMostMove++; // [HGM] bare: moved downstream
7704 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7705 int timeLeft; static int lastLoadFlag=0; int king, piece;
7706 piece = boards[forwardMostMove][fromY][fromX];
7707 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7708 if(gameInfo.variant == VariantKnightmate)
7709 king += (int) WhiteUnicorn - (int) WhiteKing;
7710 if(forwardMostMove == 0) {
7712 fprintf(serverMoves, "%s;", second.tidy);
7713 fprintf(serverMoves, "%s;", first.tidy);
7714 if(!blackPlaysFirst)
7715 fprintf(serverMoves, "%s;", second.tidy);
7716 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7717 lastLoadFlag = loadFlag;
7719 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7720 // print castling suffix
7721 if( toY == fromY && piece == king ) {
7723 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7725 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7728 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7729 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7730 boards[forwardMostMove][toY][toX] == EmptySquare
7732 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7734 if(promoChar != NULLCHAR)
7735 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7737 fprintf(serverMoves, "/%d/%d",
7738 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7739 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7740 else timeLeft = blackTimeRemaining/1000;
7741 fprintf(serverMoves, "/%d", timeLeft);
7743 fflush(serverMoves);
7746 if (forwardMostMove+1 >= MAX_MOVES) {
7747 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7751 if (commentList[forwardMostMove+1] != NULL) {
7752 free(commentList[forwardMostMove+1]);
7753 commentList[forwardMostMove+1] = NULL;
7755 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7756 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7757 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7758 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7759 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7760 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
7761 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7762 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7763 gameInfo.result = GameUnfinished;
7764 if (gameInfo.resultDetails != NULL) {
7765 free(gameInfo.resultDetails);
7766 gameInfo.resultDetails = NULL;
7768 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7769 moveList[forwardMostMove - 1]);
7770 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7771 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7772 fromY, fromX, toY, toX, promoChar,
7773 parseList[forwardMostMove - 1]);
7774 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7775 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7776 castlingRights[forwardMostMove]) ) {
7782 if(gameInfo.variant != VariantShogi)
7783 strcat(parseList[forwardMostMove - 1], "+");
7787 strcat(parseList[forwardMostMove - 1], "#");
7790 if (appData.debugMode) {
7791 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7796 /* Updates currentMove if not pausing */
7798 ShowMove(fromX, fromY, toX, toY)
7800 int instant = (gameMode == PlayFromGameFile) ?
7801 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7802 if(appData.noGUI) return;
7803 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7805 if (forwardMostMove == currentMove + 1) {
7806 AnimateMove(boards[forwardMostMove - 1],
7807 fromX, fromY, toX, toY);
7809 if (appData.highlightLastMove) {
7810 SetHighlights(fromX, fromY, toX, toY);
7813 currentMove = forwardMostMove;
7816 if (instant) return;
7818 DisplayMove(currentMove - 1);
7819 DrawPosition(FALSE, boards[currentMove]);
7820 DisplayBothClocks();
7821 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7824 void SendEgtPath(ChessProgramState *cps)
7825 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7826 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7828 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7831 char c, *q = name+1, *r, *s;
7833 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7834 while(*p && *p != ',') *q++ = *p++;
7836 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7837 strcmp(name, ",nalimov:") == 0 ) {
7838 // take nalimov path from the menu-changeable option first, if it is defined
7839 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7840 SendToProgram(buf,cps); // send egtbpath command for nalimov
7842 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7843 (s = StrStr(appData.egtFormats, name)) != NULL) {
7844 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7845 s = r = StrStr(s, ":") + 1; // beginning of path info
7846 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7847 c = *r; *r = 0; // temporarily null-terminate path info
7848 *--q = 0; // strip of trailig ':' from name
7849 sprintf(buf, "egtpath %s %s\n", name+1, s);
7851 SendToProgram(buf,cps); // send egtbpath command for this format
7853 if(*p == ',') p++; // read away comma to position for next format name
7858 InitChessProgram(cps, setup)
7859 ChessProgramState *cps;
7860 int setup; /* [HGM] needed to setup FRC opening position */
7862 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7863 if (appData.noChessProgram) return;
7864 hintRequested = FALSE;
7865 bookRequested = FALSE;
7867 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7868 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7869 if(cps->memSize) { /* [HGM] memory */
7870 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7871 SendToProgram(buf, cps);
7873 SendEgtPath(cps); /* [HGM] EGT */
7874 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7875 sprintf(buf, "cores %d\n", appData.smpCores);
7876 SendToProgram(buf, cps);
7879 SendToProgram(cps->initString, cps);
7880 if (gameInfo.variant != VariantNormal &&
7881 gameInfo.variant != VariantLoadable
7882 /* [HGM] also send variant if board size non-standard */
7883 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7885 char *v = VariantName(gameInfo.variant);
7886 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7887 /* [HGM] in protocol 1 we have to assume all variants valid */
7888 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7889 DisplayFatalError(buf, 0, 1);
7893 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7894 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7895 if( gameInfo.variant == VariantXiangqi )
7896 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7897 if( gameInfo.variant == VariantShogi )
7898 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7899 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7900 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7901 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7902 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7903 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7904 if( gameInfo.variant == VariantCourier )
7905 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7906 if( gameInfo.variant == VariantSuper )
7907 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7908 if( gameInfo.variant == VariantGreat )
7909 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7912 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7913 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7914 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7915 if(StrStr(cps->variants, b) == NULL) {
7916 // specific sized variant not known, check if general sizing allowed
7917 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7918 if(StrStr(cps->variants, "boardsize") == NULL) {
7919 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7920 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7921 DisplayFatalError(buf, 0, 1);
7924 /* [HGM] here we really should compare with the maximum supported board size */
7927 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7928 sprintf(buf, "variant %s\n", b);
7929 SendToProgram(buf, cps);
7931 currentlyInitializedVariant = gameInfo.variant;
7933 /* [HGM] send opening position in FRC to first engine */
7935 SendToProgram("force\n", cps);
7937 /* engine is now in force mode! Set flag to wake it up after first move. */
7938 setboardSpoiledMachineBlack = 1;
7942 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7943 SendToProgram(buf, cps);
7945 cps->maybeThinking = FALSE;
7946 cps->offeredDraw = 0;
7947 if (!appData.icsActive) {
7948 SendTimeControl(cps, movesPerSession, timeControl,
7949 timeIncrement, appData.searchDepth,
7952 if (appData.showThinking
7953 // [HGM] thinking: four options require thinking output to be sent
7954 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7956 SendToProgram("post\n", cps);
7958 SendToProgram("hard\n", cps);
7959 if (!appData.ponderNextMove) {
7960 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7961 it without being sure what state we are in first. "hard"
7962 is not a toggle, so that one is OK.
7964 SendToProgram("easy\n", cps);
7967 sprintf(buf, "ping %d\n", ++cps->lastPing);
7968 SendToProgram(buf, cps);
7970 cps->initDone = TRUE;
7975 StartChessProgram(cps)
7976 ChessProgramState *cps;
7981 if (appData.noChessProgram) return;
7982 cps->initDone = FALSE;
7984 if (strcmp(cps->host, "localhost") == 0) {
7985 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7986 } else if (*appData.remoteShell == NULLCHAR) {
7987 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7989 if (*appData.remoteUser == NULLCHAR) {
7990 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7993 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7994 cps->host, appData.remoteUser, cps->program);
7996 err = StartChildProcess(buf, "", &cps->pr);
8000 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8001 DisplayFatalError(buf, err, 1);
8007 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8008 if (cps->protocolVersion > 1) {
8009 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8010 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8011 cps->comboCnt = 0; // and values of combo boxes
8012 SendToProgram(buf, cps);
8014 SendToProgram("xboard\n", cps);
8020 TwoMachinesEventIfReady P((void))
8022 if (first.lastPing != first.lastPong) {
8023 DisplayMessage("", _("Waiting for first chess program"));
8024 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8027 if (second.lastPing != second.lastPong) {
8028 DisplayMessage("", _("Waiting for second chess program"));
8029 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8037 NextMatchGame P((void))
8039 int index; /* [HGM] autoinc: step load index during match */
8041 if (*appData.loadGameFile != NULLCHAR) {
8042 index = appData.loadGameIndex;
8043 if(index < 0) { // [HGM] autoinc
8044 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8045 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8047 LoadGameFromFile(appData.loadGameFile,
8049 appData.loadGameFile, FALSE);
8050 } else if (*appData.loadPositionFile != NULLCHAR) {
8051 index = appData.loadPositionIndex;
8052 if(index < 0) { // [HGM] autoinc
8053 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8054 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8056 LoadPositionFromFile(appData.loadPositionFile,
8058 appData.loadPositionFile);
8060 TwoMachinesEventIfReady();
8063 void UserAdjudicationEvent( int result )
8065 ChessMove gameResult = GameIsDrawn;
8068 gameResult = WhiteWins;
8070 else if( result < 0 ) {
8071 gameResult = BlackWins;
8074 if( gameMode == TwoMachinesPlay ) {
8075 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8080 // [HGM] save: calculate checksum of game to make games easily identifiable
8081 int StringCheckSum(char *s)
8084 if(s==NULL) return 0;
8085 while(*s) i = i*259 + *s++;
8092 for(i=backwardMostMove; i<forwardMostMove; i++) {
8093 sum += pvInfoList[i].depth;
8094 sum += StringCheckSum(parseList[i]);
8095 sum += StringCheckSum(commentList[i]);
8098 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8099 return sum + StringCheckSum(commentList[i]);
8100 } // end of save patch
8103 GameEnds(result, resultDetails, whosays)
8105 char *resultDetails;
8108 GameMode nextGameMode;
8112 if(endingGame) return; /* [HGM] crash: forbid recursion */
8115 if (appData.debugMode) {
8116 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8117 result, resultDetails ? resultDetails : "(null)", whosays);
8120 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8121 /* If we are playing on ICS, the server decides when the
8122 game is over, but the engine can offer to draw, claim
8126 if (appData.zippyPlay && first.initDone) {
8127 if (result == GameIsDrawn) {
8128 /* In case draw still needs to be claimed */
8129 SendToICS(ics_prefix);
8130 SendToICS("draw\n");
8131 } else if (StrCaseStr(resultDetails, "resign")) {
8132 SendToICS(ics_prefix);
8133 SendToICS("resign\n");
8137 endingGame = 0; /* [HGM] crash */
8141 /* If we're loading the game from a file, stop */
8142 if (whosays == GE_FILE) {
8143 (void) StopLoadGameTimer();
8147 /* Cancel draw offers */
8148 first.offeredDraw = second.offeredDraw = 0;
8150 /* If this is an ICS game, only ICS can really say it's done;
8151 if not, anyone can. */
8152 isIcsGame = (gameMode == IcsPlayingWhite ||
8153 gameMode == IcsPlayingBlack ||
8154 gameMode == IcsObserving ||
8155 gameMode == IcsExamining);
8157 if (!isIcsGame || whosays == GE_ICS) {
8158 /* OK -- not an ICS game, or ICS said it was done */
8160 if (!isIcsGame && !appData.noChessProgram)
8161 SetUserThinkingEnables();
8163 /* [HGM] if a machine claims the game end we verify this claim */
8164 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8165 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8167 ChessMove trueResult = (ChessMove) -1;
8169 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8170 first.twoMachinesColor[0] :
8171 second.twoMachinesColor[0] ;
8173 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8174 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8175 /* [HGM] verify: engine mate claims accepted if they were flagged */
8176 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8178 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8179 /* [HGM] verify: engine mate claims accepted if they were flagged */
8180 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8182 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8183 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8186 // now verify win claims, but not in drop games, as we don't understand those yet
8187 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8188 || gameInfo.variant == VariantGreat) &&
8189 (result == WhiteWins && claimer == 'w' ||
8190 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8191 if (appData.debugMode) {
8192 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8193 result, epStatus[forwardMostMove], forwardMostMove);
8195 if(result != trueResult) {
8196 sprintf(buf, "False win claim: '%s'", resultDetails);
8197 result = claimer == 'w' ? BlackWins : WhiteWins;
8198 resultDetails = buf;
8201 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8202 && (forwardMostMove <= backwardMostMove ||
8203 epStatus[forwardMostMove-1] > EP_DRAWS ||
8204 (claimer=='b')==(forwardMostMove&1))
8206 /* [HGM] verify: draws that were not flagged are false claims */
8207 sprintf(buf, "False draw claim: '%s'", resultDetails);
8208 result = claimer == 'w' ? BlackWins : WhiteWins;
8209 resultDetails = buf;
8211 /* (Claiming a loss is accepted no questions asked!) */
8213 /* [HGM] bare: don't allow bare King to win */
8214 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8215 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8216 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8217 && result != GameIsDrawn)
8218 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8219 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8220 int p = (int)boards[forwardMostMove][i][j] - color;
8221 if(p >= 0 && p <= (int)WhiteKing) k++;
8223 if (appData.debugMode) {
8224 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8225 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8228 result = GameIsDrawn;
8229 sprintf(buf, "%s but bare king", resultDetails);
8230 resultDetails = buf;
8236 if(serverMoves != NULL && !loadFlag) { char c = '=';
8237 if(result==WhiteWins) c = '+';
8238 if(result==BlackWins) c = '-';
8239 if(resultDetails != NULL)
8240 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8242 if (resultDetails != NULL) {
8243 gameInfo.result = result;
8244 gameInfo.resultDetails = StrSave(resultDetails);
8246 /* display last move only if game was not loaded from file */
8247 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8248 DisplayMove(currentMove - 1);
8250 if (forwardMostMove != 0) {
8251 if (gameMode != PlayFromGameFile && gameMode != EditGame
8252 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8254 if (*appData.saveGameFile != NULLCHAR) {
8255 SaveGameToFile(appData.saveGameFile, TRUE);
8256 } else if (appData.autoSaveGames) {
8259 if (*appData.savePositionFile != NULLCHAR) {
8260 SavePositionToFile(appData.savePositionFile);
8265 /* Tell program how game ended in case it is learning */
8266 /* [HGM] Moved this to after saving the PGN, just in case */
8267 /* engine died and we got here through time loss. In that */
8268 /* case we will get a fatal error writing the pipe, which */
8269 /* would otherwise lose us the PGN. */
8270 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8271 /* output during GameEnds should never be fatal anymore */
8272 if (gameMode == MachinePlaysWhite ||
8273 gameMode == MachinePlaysBlack ||
8274 gameMode == TwoMachinesPlay ||
8275 gameMode == IcsPlayingWhite ||
8276 gameMode == IcsPlayingBlack ||
8277 gameMode == BeginningOfGame) {
8279 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8281 if (first.pr != NoProc) {
8282 SendToProgram(buf, &first);
8284 if (second.pr != NoProc &&
8285 gameMode == TwoMachinesPlay) {
8286 SendToProgram(buf, &second);
8291 if (appData.icsActive) {
8292 if (appData.quietPlay &&
8293 (gameMode == IcsPlayingWhite ||
8294 gameMode == IcsPlayingBlack)) {
8295 SendToICS(ics_prefix);
8296 SendToICS("set shout 1\n");
8298 nextGameMode = IcsIdle;
8299 ics_user_moved = FALSE;
8300 /* clean up premove. It's ugly when the game has ended and the
8301 * premove highlights are still on the board.
8305 ClearPremoveHighlights();
8306 DrawPosition(FALSE, boards[currentMove]);
8308 if (whosays == GE_ICS) {
8311 if (gameMode == IcsPlayingWhite)
8313 else if(gameMode == IcsPlayingBlack)
8317 if (gameMode == IcsPlayingBlack)
8319 else if(gameMode == IcsPlayingWhite)
8326 PlayIcsUnfinishedSound();
8329 } else if (gameMode == EditGame ||
8330 gameMode == PlayFromGameFile ||
8331 gameMode == AnalyzeMode ||
8332 gameMode == AnalyzeFile) {
8333 nextGameMode = gameMode;
8335 nextGameMode = EndOfGame;
8340 nextGameMode = gameMode;
8343 if (appData.noChessProgram) {
8344 gameMode = nextGameMode;
8346 endingGame = 0; /* [HGM] crash */
8351 /* Put first chess program into idle state */
8352 if (first.pr != NoProc &&
8353 (gameMode == MachinePlaysWhite ||
8354 gameMode == MachinePlaysBlack ||
8355 gameMode == TwoMachinesPlay ||
8356 gameMode == IcsPlayingWhite ||
8357 gameMode == IcsPlayingBlack ||
8358 gameMode == BeginningOfGame)) {
8359 SendToProgram("force\n", &first);
8360 if (first.usePing) {
8362 sprintf(buf, "ping %d\n", ++first.lastPing);
8363 SendToProgram(buf, &first);
8366 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8367 /* Kill off first chess program */
8368 if (first.isr != NULL)
8369 RemoveInputSource(first.isr);
8372 if (first.pr != NoProc) {
8374 DoSleep( appData.delayBeforeQuit );
8375 SendToProgram("quit\n", &first);
8376 DoSleep( appData.delayAfterQuit );
8377 DestroyChildProcess(first.pr, first.useSigterm);
8382 /* Put second chess program into idle state */
8383 if (second.pr != NoProc &&
8384 gameMode == TwoMachinesPlay) {
8385 SendToProgram("force\n", &second);
8386 if (second.usePing) {
8388 sprintf(buf, "ping %d\n", ++second.lastPing);
8389 SendToProgram(buf, &second);
8392 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8393 /* Kill off second chess program */
8394 if (second.isr != NULL)
8395 RemoveInputSource(second.isr);
8398 if (second.pr != NoProc) {
8399 DoSleep( appData.delayBeforeQuit );
8400 SendToProgram("quit\n", &second);
8401 DoSleep( appData.delayAfterQuit );
8402 DestroyChildProcess(second.pr, second.useSigterm);
8407 if (matchMode && gameMode == TwoMachinesPlay) {
8410 if (first.twoMachinesColor[0] == 'w') {
8417 if (first.twoMachinesColor[0] == 'b') {
8426 if (matchGame < appData.matchGames) {
8428 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8429 tmp = first.twoMachinesColor;
8430 first.twoMachinesColor = second.twoMachinesColor;
8431 second.twoMachinesColor = tmp;
8433 gameMode = nextGameMode;
8435 if(appData.matchPause>10000 || appData.matchPause<10)
8436 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8437 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8438 endingGame = 0; /* [HGM] crash */
8442 gameMode = nextGameMode;
8443 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8444 first.tidy, second.tidy,
8445 first.matchWins, second.matchWins,
8446 appData.matchGames - (first.matchWins + second.matchWins));
8447 DisplayFatalError(buf, 0, 0);
8450 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8451 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8453 gameMode = nextGameMode;
8455 endingGame = 0; /* [HGM] crash */
8458 /* Assumes program was just initialized (initString sent).
8459 Leaves program in force mode. */
8461 FeedMovesToProgram(cps, upto)
8462 ChessProgramState *cps;
8467 if (appData.debugMode)
8468 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8469 startedFromSetupPosition ? "position and " : "",
8470 backwardMostMove, upto, cps->which);
8471 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8472 // [HGM] variantswitch: make engine aware of new variant
8473 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8474 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8475 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8476 SendToProgram(buf, cps);
8477 currentlyInitializedVariant = gameInfo.variant;
8479 SendToProgram("force\n", cps);
8480 if (startedFromSetupPosition) {
8481 SendBoard(cps, backwardMostMove);
8482 if (appData.debugMode) {
8483 fprintf(debugFP, "feedMoves\n");
8486 for (i = backwardMostMove; i < upto; i++) {
8487 SendMoveToProgram(i, cps);
8493 ResurrectChessProgram()
8495 /* The chess program may have exited.
8496 If so, restart it and feed it all the moves made so far. */
8498 if (appData.noChessProgram || first.pr != NoProc) return;
8500 StartChessProgram(&first);
8501 InitChessProgram(&first, FALSE);
8502 FeedMovesToProgram(&first, currentMove);
8504 if (!first.sendTime) {
8505 /* can't tell gnuchess what its clock should read,
8506 so we bow to its notion. */
8508 timeRemaining[0][currentMove] = whiteTimeRemaining;
8509 timeRemaining[1][currentMove] = blackTimeRemaining;
8512 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8513 appData.icsEngineAnalyze) && first.analysisSupport) {
8514 SendToProgram("analyze\n", &first);
8515 first.analyzing = TRUE;
8528 if (appData.debugMode) {
8529 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8530 redraw, init, gameMode);
8532 pausing = pauseExamInvalid = FALSE;
8533 startedFromSetupPosition = blackPlaysFirst = FALSE;
8535 whiteFlag = blackFlag = FALSE;
8536 userOfferedDraw = FALSE;
8537 hintRequested = bookRequested = FALSE;
8538 first.maybeThinking = FALSE;
8539 second.maybeThinking = FALSE;
8540 first.bookSuspend = FALSE; // [HGM] book
8541 second.bookSuspend = FALSE;
8542 thinkOutput[0] = NULLCHAR;
8543 lastHint[0] = NULLCHAR;
8544 ClearGameInfo(&gameInfo);
8545 gameInfo.variant = StringToVariant(appData.variant);
8546 ics_user_moved = ics_clock_paused = FALSE;
8547 ics_getting_history = H_FALSE;
8549 white_holding[0] = black_holding[0] = NULLCHAR;
8550 ClearProgramStats();
8551 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8555 flipView = appData.flipView;
8556 ClearPremoveHighlights();
8558 alarmSounded = FALSE;
8560 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8561 if(appData.serverMovesName != NULL) {
8562 /* [HGM] prepare to make moves file for broadcasting */
8563 clock_t t = clock();
8564 if(serverMoves != NULL) fclose(serverMoves);
8565 serverMoves = fopen(appData.serverMovesName, "r");
8566 if(serverMoves != NULL) {
8567 fclose(serverMoves);
8568 /* delay 15 sec before overwriting, so all clients can see end */
8569 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8571 serverMoves = fopen(appData.serverMovesName, "w");
8575 gameMode = BeginningOfGame;
8577 if(appData.icsActive) gameInfo.variant = VariantNormal;
8578 currentMove = forwardMostMove = backwardMostMove = 0;
8579 InitPosition(redraw);
8580 for (i = 0; i < MAX_MOVES; i++) {
8581 if (commentList[i] != NULL) {
8582 free(commentList[i]);
8583 commentList[i] = NULL;
8587 timeRemaining[0][0] = whiteTimeRemaining;
8588 timeRemaining[1][0] = blackTimeRemaining;
8589 if (first.pr == NULL) {
8590 StartChessProgram(&first);
8593 InitChessProgram(&first, startedFromSetupPosition);
8596 DisplayMessage("", "");
8597 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8598 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8605 if (!AutoPlayOneMove())
8607 if (matchMode || appData.timeDelay == 0)
8609 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8611 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8620 int fromX, fromY, toX, toY;
8622 if (appData.debugMode) {
8623 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8626 if (gameMode != PlayFromGameFile)
8629 if (currentMove >= forwardMostMove) {
8630 gameMode = EditGame;
8633 /* [AS] Clear current move marker at the end of a game */
8634 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8639 toX = moveList[currentMove][2] - AAA;
8640 toY = moveList[currentMove][3] - ONE;
8642 if (moveList[currentMove][1] == '@') {
8643 if (appData.highlightLastMove) {
8644 SetHighlights(-1, -1, toX, toY);
8647 fromX = moveList[currentMove][0] - AAA;
8648 fromY = moveList[currentMove][1] - ONE;
8650 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8652 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8654 if (appData.highlightLastMove) {
8655 SetHighlights(fromX, fromY, toX, toY);
8658 DisplayMove(currentMove);
8659 SendMoveToProgram(currentMove++, &first);
8660 DisplayBothClocks();
8661 DrawPosition(FALSE, boards[currentMove]);
8662 // [HGM] PV info: always display, routine tests if empty
8663 DisplayComment(currentMove - 1, commentList[currentMove]);
8669 LoadGameOneMove(readAhead)
8670 ChessMove readAhead;
8672 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8673 char promoChar = NULLCHAR;
8678 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8679 gameMode != AnalyzeMode && gameMode != Training) {
8684 yyboardindex = forwardMostMove;
8685 if (readAhead != (ChessMove)0) {
8686 moveType = readAhead;
8688 if (gameFileFP == NULL)
8690 moveType = (ChessMove) yylex();
8696 if (appData.debugMode)
8697 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8699 if (*p == '{' || *p == '[' || *p == '(') {
8700 p[strlen(p) - 1] = NULLCHAR;
8704 /* append the comment but don't display it */
8705 while (*p == '\n') p++;
8706 AppendComment(currentMove, p);
8709 case WhiteCapturesEnPassant:
8710 case BlackCapturesEnPassant:
8711 case WhitePromotionChancellor:
8712 case BlackPromotionChancellor:
8713 case WhitePromotionArchbishop:
8714 case BlackPromotionArchbishop:
8715 case WhitePromotionCentaur:
8716 case BlackPromotionCentaur:
8717 case WhitePromotionQueen:
8718 case BlackPromotionQueen:
8719 case WhitePromotionRook:
8720 case BlackPromotionRook:
8721 case WhitePromotionBishop:
8722 case BlackPromotionBishop:
8723 case WhitePromotionKnight:
8724 case BlackPromotionKnight:
8725 case WhitePromotionKing:
8726 case BlackPromotionKing:
8728 case WhiteKingSideCastle:
8729 case WhiteQueenSideCastle:
8730 case BlackKingSideCastle:
8731 case BlackQueenSideCastle:
8732 case WhiteKingSideCastleWild:
8733 case WhiteQueenSideCastleWild:
8734 case BlackKingSideCastleWild:
8735 case BlackQueenSideCastleWild:
8737 case WhiteHSideCastleFR:
8738 case WhiteASideCastleFR:
8739 case BlackHSideCastleFR:
8740 case BlackASideCastleFR:
8742 if (appData.debugMode)
8743 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8744 fromX = currentMoveString[0] - AAA;
8745 fromY = currentMoveString[1] - ONE;
8746 toX = currentMoveString[2] - AAA;
8747 toY = currentMoveString[3] - ONE;
8748 promoChar = currentMoveString[4];
8753 if (appData.debugMode)
8754 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8755 fromX = moveType == WhiteDrop ?
8756 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8757 (int) CharToPiece(ToLower(currentMoveString[0]));
8759 toX = currentMoveString[2] - AAA;
8760 toY = currentMoveString[3] - ONE;
8766 case GameUnfinished:
8767 if (appData.debugMode)
8768 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8769 p = strchr(yy_text, '{');
8770 if (p == NULL) p = strchr(yy_text, '(');
8773 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8775 q = strchr(p, *p == '{' ? '}' : ')');
8776 if (q != NULL) *q = NULLCHAR;
8779 GameEnds(moveType, p, GE_FILE);
8781 if (cmailMsgLoaded) {
8783 flipView = WhiteOnMove(currentMove);
8784 if (moveType == GameUnfinished) flipView = !flipView;
8785 if (appData.debugMode)
8786 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8790 case (ChessMove) 0: /* end of file */
8791 if (appData.debugMode)
8792 fprintf(debugFP, "Parser hit end of file\n");
8793 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8794 EP_UNKNOWN, castlingRights[currentMove]) ) {
8800 if (WhiteOnMove(currentMove)) {
8801 GameEnds(BlackWins, "Black mates", GE_FILE);
8803 GameEnds(WhiteWins, "White mates", GE_FILE);
8807 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8814 if (lastLoadGameStart == GNUChessGame) {
8815 /* GNUChessGames have numbers, but they aren't move numbers */
8816 if (appData.debugMode)
8817 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8818 yy_text, (int) moveType);
8819 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8821 /* else fall thru */
8826 /* Reached start of next game in file */
8827 if (appData.debugMode)
8828 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8829 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8830 EP_UNKNOWN, castlingRights[currentMove]) ) {
8836 if (WhiteOnMove(currentMove)) {
8837 GameEnds(BlackWins, "Black mates", GE_FILE);
8839 GameEnds(WhiteWins, "White mates", GE_FILE);
8843 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8849 case PositionDiagram: /* should not happen; ignore */
8850 case ElapsedTime: /* ignore */
8851 case NAG: /* ignore */
8852 if (appData.debugMode)
8853 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8854 yy_text, (int) moveType);
8855 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8858 if (appData.testLegality) {
8859 if (appData.debugMode)
8860 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8861 sprintf(move, _("Illegal move: %d.%s%s"),
8862 (forwardMostMove / 2) + 1,
8863 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8864 DisplayError(move, 0);
8867 if (appData.debugMode)
8868 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8869 yy_text, currentMoveString);
8870 fromX = currentMoveString[0] - AAA;
8871 fromY = currentMoveString[1] - ONE;
8872 toX = currentMoveString[2] - AAA;
8873 toY = currentMoveString[3] - ONE;
8874 promoChar = currentMoveString[4];
8879 if (appData.debugMode)
8880 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8881 sprintf(move, _("Ambiguous move: %d.%s%s"),
8882 (forwardMostMove / 2) + 1,
8883 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8884 DisplayError(move, 0);
8889 case ImpossibleMove:
8890 if (appData.debugMode)
8891 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8892 sprintf(move, _("Illegal move: %d.%s%s"),
8893 (forwardMostMove / 2) + 1,
8894 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8895 DisplayError(move, 0);
8901 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8902 DrawPosition(FALSE, boards[currentMove]);
8903 DisplayBothClocks();
8904 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8905 DisplayComment(currentMove - 1, commentList[currentMove]);
8907 (void) StopLoadGameTimer();
8909 cmailOldMove = forwardMostMove;
8912 /* currentMoveString is set as a side-effect of yylex */
8913 strcat(currentMoveString, "\n");
8914 strcpy(moveList[forwardMostMove], currentMoveString);
8916 thinkOutput[0] = NULLCHAR;
8917 MakeMove(fromX, fromY, toX, toY, promoChar);
8918 currentMove = forwardMostMove;
8923 /* Load the nth game from the given file */
8925 LoadGameFromFile(filename, n, title, useList)
8929 /*Boolean*/ int useList;
8934 if (strcmp(filename, "-") == 0) {
8938 f = fopen(filename, "rb");
8940 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8941 DisplayError(buf, errno);
8945 if (fseek(f, 0, 0) == -1) {
8946 /* f is not seekable; probably a pipe */
8949 if (useList && n == 0) {
8950 int error = GameListBuild(f);
8952 DisplayError(_("Cannot build game list"), error);
8953 } else if (!ListEmpty(&gameList) &&
8954 ((ListGame *) gameList.tailPred)->number > 1) {
8955 GameListPopUp(f, title);
8962 return LoadGame(f, n, title, FALSE);
8967 MakeRegisteredMove()
8969 int fromX, fromY, toX, toY;
8971 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8972 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8975 if (appData.debugMode)
8976 fprintf(debugFP, "Restoring %s for game %d\n",
8977 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8979 thinkOutput[0] = NULLCHAR;
8980 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8981 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8982 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8983 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8984 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8985 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8986 MakeMove(fromX, fromY, toX, toY, promoChar);
8987 ShowMove(fromX, fromY, toX, toY);
8989 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8990 EP_UNKNOWN, castlingRights[currentMove]) ) {
8997 if (WhiteOnMove(currentMove)) {
8998 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9000 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9005 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9012 if (WhiteOnMove(currentMove)) {
9013 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9015 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9020 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9031 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9033 CmailLoadGame(f, gameNumber, title, useList)
9041 if (gameNumber > nCmailGames) {
9042 DisplayError(_("No more games in this message"), 0);
9045 if (f == lastLoadGameFP) {
9046 int offset = gameNumber - lastLoadGameNumber;
9048 cmailMsg[0] = NULLCHAR;
9049 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9050 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9051 nCmailMovesRegistered--;
9053 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9054 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9055 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9058 if (! RegisterMove()) return FALSE;
9062 retVal = LoadGame(f, gameNumber, title, useList);
9064 /* Make move registered during previous look at this game, if any */
9065 MakeRegisteredMove();
9067 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9068 commentList[currentMove]
9069 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9070 DisplayComment(currentMove - 1, commentList[currentMove]);
9076 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9081 int gameNumber = lastLoadGameNumber + offset;
9082 if (lastLoadGameFP == NULL) {
9083 DisplayError(_("No game has been loaded yet"), 0);
9086 if (gameNumber <= 0) {
9087 DisplayError(_("Can't back up any further"), 0);
9090 if (cmailMsgLoaded) {
9091 return CmailLoadGame(lastLoadGameFP, gameNumber,
9092 lastLoadGameTitle, lastLoadGameUseList);
9094 return LoadGame(lastLoadGameFP, gameNumber,
9095 lastLoadGameTitle, lastLoadGameUseList);
9101 /* Load the nth game from open file f */
9103 LoadGame(f, gameNumber, title, useList)
9111 int gn = gameNumber;
9112 ListGame *lg = NULL;
9115 GameMode oldGameMode;
9116 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9118 if (appData.debugMode)
9119 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9121 if (gameMode == Training )
9122 SetTrainingModeOff();
9124 oldGameMode = gameMode;
9125 if (gameMode != BeginningOfGame) {
9130 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9131 fclose(lastLoadGameFP);
9135 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9138 fseek(f, lg->offset, 0);
9139 GameListHighlight(gameNumber);
9143 DisplayError(_("Game number out of range"), 0);
9148 if (fseek(f, 0, 0) == -1) {
9149 if (f == lastLoadGameFP ?
9150 gameNumber == lastLoadGameNumber + 1 :
9154 DisplayError(_("Can't seek on game file"), 0);
9160 lastLoadGameNumber = gameNumber;
9161 strcpy(lastLoadGameTitle, title);
9162 lastLoadGameUseList = useList;
9166 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9167 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9168 lg->gameInfo.black);
9170 } else if (*title != NULLCHAR) {
9171 if (gameNumber > 1) {
9172 sprintf(buf, "%s %d", title, gameNumber);
9175 DisplayTitle(title);
9179 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9180 gameMode = PlayFromGameFile;
9184 currentMove = forwardMostMove = backwardMostMove = 0;
9185 CopyBoard(boards[0], initialPosition);
9189 * Skip the first gn-1 games in the file.
9190 * Also skip over anything that precedes an identifiable
9191 * start of game marker, to avoid being confused by
9192 * garbage at the start of the file. Currently
9193 * recognized start of game markers are the move number "1",
9194 * the pattern "gnuchess .* game", the pattern
9195 * "^[#;%] [^ ]* game file", and a PGN tag block.
9196 * A game that starts with one of the latter two patterns
9197 * will also have a move number 1, possibly
9198 * following a position diagram.
9199 * 5-4-02: Let's try being more lenient and allowing a game to
9200 * start with an unnumbered move. Does that break anything?
9202 cm = lastLoadGameStart = (ChessMove) 0;
9204 yyboardindex = forwardMostMove;
9205 cm = (ChessMove) yylex();
9208 if (cmailMsgLoaded) {
9209 nCmailGames = CMAIL_MAX_GAMES - gn;
9212 DisplayError(_("Game not found in file"), 0);
9219 lastLoadGameStart = cm;
9223 switch (lastLoadGameStart) {
9230 gn--; /* count this game */
9231 lastLoadGameStart = cm;
9240 switch (lastLoadGameStart) {
9245 gn--; /* count this game */
9246 lastLoadGameStart = cm;
9249 lastLoadGameStart = cm; /* game counted already */
9257 yyboardindex = forwardMostMove;
9258 cm = (ChessMove) yylex();
9259 } while (cm == PGNTag || cm == Comment);
9266 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9267 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9268 != CMAIL_OLD_RESULT) {
9270 cmailResult[ CMAIL_MAX_GAMES
9271 - gn - 1] = CMAIL_OLD_RESULT;
9277 /* Only a NormalMove can be at the start of a game
9278 * without a position diagram. */
9279 if (lastLoadGameStart == (ChessMove) 0) {
9281 lastLoadGameStart = MoveNumberOne;
9290 if (appData.debugMode)
9291 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9293 if (cm == XBoardGame) {
9294 /* Skip any header junk before position diagram and/or move 1 */
9296 yyboardindex = forwardMostMove;
9297 cm = (ChessMove) yylex();
9299 if (cm == (ChessMove) 0 ||
9300 cm == GNUChessGame || cm == XBoardGame) {
9301 /* Empty game; pretend end-of-file and handle later */
9306 if (cm == MoveNumberOne || cm == PositionDiagram ||
9307 cm == PGNTag || cm == Comment)
9310 } else if (cm == GNUChessGame) {
9311 if (gameInfo.event != NULL) {
9312 free(gameInfo.event);
9314 gameInfo.event = StrSave(yy_text);
9317 startedFromSetupPosition = FALSE;
9318 while (cm == PGNTag) {
9319 if (appData.debugMode)
9320 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9321 err = ParsePGNTag(yy_text, &gameInfo);
9322 if (!err) numPGNTags++;
9324 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9325 if(gameInfo.variant != oldVariant) {
9326 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9328 oldVariant = gameInfo.variant;
9329 if (appData.debugMode)
9330 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9334 if (gameInfo.fen != NULL) {
9335 Board initial_position;
9336 startedFromSetupPosition = TRUE;
9337 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9339 DisplayError(_("Bad FEN position in file"), 0);
9342 CopyBoard(boards[0], initial_position);
9343 if (blackPlaysFirst) {
9344 currentMove = forwardMostMove = backwardMostMove = 1;
9345 CopyBoard(boards[1], initial_position);
9346 strcpy(moveList[0], "");
9347 strcpy(parseList[0], "");
9348 timeRemaining[0][1] = whiteTimeRemaining;
9349 timeRemaining[1][1] = blackTimeRemaining;
9350 if (commentList[0] != NULL) {
9351 commentList[1] = commentList[0];
9352 commentList[0] = NULL;
9355 currentMove = forwardMostMove = backwardMostMove = 0;
9357 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9359 initialRulePlies = FENrulePlies;
9360 epStatus[forwardMostMove] = FENepStatus;
9361 for( i=0; i< nrCastlingRights; i++ )
9362 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9364 yyboardindex = forwardMostMove;
9366 gameInfo.fen = NULL;
9369 yyboardindex = forwardMostMove;
9370 cm = (ChessMove) yylex();
9372 /* Handle comments interspersed among the tags */
9373 while (cm == Comment) {
9375 if (appData.debugMode)
9376 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9378 if (*p == '{' || *p == '[' || *p == '(') {
9379 p[strlen(p) - 1] = NULLCHAR;
9382 while (*p == '\n') p++;
9383 AppendComment(currentMove, p);
9384 yyboardindex = forwardMostMove;
9385 cm = (ChessMove) yylex();
9389 /* don't rely on existence of Event tag since if game was
9390 * pasted from clipboard the Event tag may not exist
9392 if (numPGNTags > 0){
9394 if (gameInfo.variant == VariantNormal) {
9395 gameInfo.variant = StringToVariant(gameInfo.event);
9398 if( appData.autoDisplayTags ) {
9399 tags = PGNTags(&gameInfo);
9400 TagsPopUp(tags, CmailMsg());
9405 /* Make something up, but don't display it now */
9410 if (cm == PositionDiagram) {
9413 Board initial_position;
9415 if (appData.debugMode)
9416 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9418 if (!startedFromSetupPosition) {
9420 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9421 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9431 initial_position[i][j++] = CharToPiece(*p);
9434 while (*p == ' ' || *p == '\t' ||
9435 *p == '\n' || *p == '\r') p++;
9437 if (strncmp(p, "black", strlen("black"))==0)
9438 blackPlaysFirst = TRUE;
9440 blackPlaysFirst = FALSE;
9441 startedFromSetupPosition = TRUE;
9443 CopyBoard(boards[0], initial_position);
9444 if (blackPlaysFirst) {
9445 currentMove = forwardMostMove = backwardMostMove = 1;
9446 CopyBoard(boards[1], initial_position);
9447 strcpy(moveList[0], "");
9448 strcpy(parseList[0], "");
9449 timeRemaining[0][1] = whiteTimeRemaining;
9450 timeRemaining[1][1] = blackTimeRemaining;
9451 if (commentList[0] != NULL) {
9452 commentList[1] = commentList[0];
9453 commentList[0] = NULL;
9456 currentMove = forwardMostMove = backwardMostMove = 0;
9459 yyboardindex = forwardMostMove;
9460 cm = (ChessMove) yylex();
9463 if (first.pr == NoProc) {
9464 StartChessProgram(&first);
9466 InitChessProgram(&first, FALSE);
9467 SendToProgram("force\n", &first);
9468 if (startedFromSetupPosition) {
9469 SendBoard(&first, forwardMostMove);
9470 if (appData.debugMode) {
9471 fprintf(debugFP, "Load Game\n");
9473 DisplayBothClocks();
9476 /* [HGM] server: flag to write setup moves in broadcast file as one */
9477 loadFlag = appData.suppressLoadMoves;
9479 while (cm == Comment) {
9481 if (appData.debugMode)
9482 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9484 if (*p == '{' || *p == '[' || *p == '(') {
9485 p[strlen(p) - 1] = NULLCHAR;
9488 while (*p == '\n') p++;
9489 AppendComment(currentMove, p);
9490 yyboardindex = forwardMostMove;
9491 cm = (ChessMove) yylex();
9494 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9495 cm == WhiteWins || cm == BlackWins ||
9496 cm == GameIsDrawn || cm == GameUnfinished) {
9497 DisplayMessage("", _("No moves in game"));
9498 if (cmailMsgLoaded) {
9499 if (appData.debugMode)
9500 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9504 DrawPosition(FALSE, boards[currentMove]);
9505 DisplayBothClocks();
9506 gameMode = EditGame;
9513 // [HGM] PV info: routine tests if comment empty
9514 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9515 DisplayComment(currentMove - 1, commentList[currentMove]);
9517 if (!matchMode && appData.timeDelay != 0)
9518 DrawPosition(FALSE, boards[currentMove]);
9520 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9521 programStats.ok_to_send = 1;
9524 /* if the first token after the PGN tags is a move
9525 * and not move number 1, retrieve it from the parser
9527 if (cm != MoveNumberOne)
9528 LoadGameOneMove(cm);
9530 /* load the remaining moves from the file */
9531 while (LoadGameOneMove((ChessMove)0)) {
9532 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9533 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9536 /* rewind to the start of the game */
9537 currentMove = backwardMostMove;
9539 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9541 if (oldGameMode == AnalyzeFile ||
9542 oldGameMode == AnalyzeMode) {
9546 if (matchMode || appData.timeDelay == 0) {
9548 gameMode = EditGame;
9550 } else if (appData.timeDelay > 0) {
9554 if (appData.debugMode)
9555 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9557 loadFlag = 0; /* [HGM] true game starts */
9561 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9563 ReloadPosition(offset)
9566 int positionNumber = lastLoadPositionNumber + offset;
9567 if (lastLoadPositionFP == NULL) {
9568 DisplayError(_("No position has been loaded yet"), 0);
9571 if (positionNumber <= 0) {
9572 DisplayError(_("Can't back up any further"), 0);
9575 return LoadPosition(lastLoadPositionFP, positionNumber,
9576 lastLoadPositionTitle);
9579 /* Load the nth position from the given file */
9581 LoadPositionFromFile(filename, n, title)
9589 if (strcmp(filename, "-") == 0) {
9590 return LoadPosition(stdin, n, "stdin");
9592 f = fopen(filename, "rb");
9594 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9595 DisplayError(buf, errno);
9598 return LoadPosition(f, n, title);
9603 /* Load the nth position from the given open file, and close it */
9605 LoadPosition(f, positionNumber, title)
9610 char *p, line[MSG_SIZ];
9611 Board initial_position;
9612 int i, j, fenMode, pn;
9614 if (gameMode == Training )
9615 SetTrainingModeOff();
9617 if (gameMode != BeginningOfGame) {
9620 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9621 fclose(lastLoadPositionFP);
9623 if (positionNumber == 0) positionNumber = 1;
9624 lastLoadPositionFP = f;
9625 lastLoadPositionNumber = positionNumber;
9626 strcpy(lastLoadPositionTitle, title);
9627 if (first.pr == NoProc) {
9628 StartChessProgram(&first);
9629 InitChessProgram(&first, FALSE);
9631 pn = positionNumber;
9632 if (positionNumber < 0) {
9633 /* Negative position number means to seek to that byte offset */
9634 if (fseek(f, -positionNumber, 0) == -1) {
9635 DisplayError(_("Can't seek on position file"), 0);
9640 if (fseek(f, 0, 0) == -1) {
9641 if (f == lastLoadPositionFP ?
9642 positionNumber == lastLoadPositionNumber + 1 :
9643 positionNumber == 1) {
9646 DisplayError(_("Can't seek on position file"), 0);
9651 /* See if this file is FEN or old-style xboard */
9652 if (fgets(line, MSG_SIZ, f) == NULL) {
9653 DisplayError(_("Position not found in file"), 0);
9656 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9657 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9660 if (fenMode || line[0] == '#') pn--;
9662 /* skip positions before number pn */
9663 if (fgets(line, MSG_SIZ, f) == NULL) {
9665 DisplayError(_("Position not found in file"), 0);
9668 if (fenMode || line[0] == '#') pn--;
9673 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9674 DisplayError(_("Bad FEN position in file"), 0);
9678 (void) fgets(line, MSG_SIZ, f);
9679 (void) fgets(line, MSG_SIZ, f);
9681 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9682 (void) fgets(line, MSG_SIZ, f);
9683 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9686 initial_position[i][j++] = CharToPiece(*p);
9690 blackPlaysFirst = FALSE;
9692 (void) fgets(line, MSG_SIZ, f);
9693 if (strncmp(line, "black", strlen("black"))==0)
9694 blackPlaysFirst = TRUE;
9697 startedFromSetupPosition = TRUE;
9699 SendToProgram("force\n", &first);
9700 CopyBoard(boards[0], initial_position);
9701 if (blackPlaysFirst) {
9702 currentMove = forwardMostMove = backwardMostMove = 1;
9703 strcpy(moveList[0], "");
9704 strcpy(parseList[0], "");
9705 CopyBoard(boards[1], initial_position);
9706 DisplayMessage("", _("Black to play"));
9708 currentMove = forwardMostMove = backwardMostMove = 0;
9709 DisplayMessage("", _("White to play"));
9711 /* [HGM] copy FEN attributes as well */
9713 initialRulePlies = FENrulePlies;
9714 epStatus[forwardMostMove] = FENepStatus;
9715 for( i=0; i< nrCastlingRights; i++ )
9716 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9718 SendBoard(&first, forwardMostMove);
9719 if (appData.debugMode) {
9721 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9722 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9723 fprintf(debugFP, "Load Position\n");
9726 if (positionNumber > 1) {
9727 sprintf(line, "%s %d", title, positionNumber);
9730 DisplayTitle(title);
9732 gameMode = EditGame;
9735 timeRemaining[0][1] = whiteTimeRemaining;
9736 timeRemaining[1][1] = blackTimeRemaining;
9737 DrawPosition(FALSE, boards[currentMove]);
9744 CopyPlayerNameIntoFileName(dest, src)
9747 while (*src != NULLCHAR && *src != ',') {
9752 *(*dest)++ = *src++;
9757 char *DefaultFileName(ext)
9760 static char def[MSG_SIZ];
9763 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9765 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9767 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9776 /* Save the current game to the given file */
9778 SaveGameToFile(filename, append)
9785 if (strcmp(filename, "-") == 0) {
9786 return SaveGame(stdout, 0, NULL);
9788 f = fopen(filename, append ? "a" : "w");
9790 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9791 DisplayError(buf, errno);
9794 return SaveGame(f, 0, NULL);
9803 static char buf[MSG_SIZ];
9806 p = strchr(str, ' ');
9807 if (p == NULL) return str;
9808 strncpy(buf, str, p - str);
9809 buf[p - str] = NULLCHAR;
9813 #define PGN_MAX_LINE 75
9815 #define PGN_SIDE_WHITE 0
9816 #define PGN_SIDE_BLACK 1
9819 static int FindFirstMoveOutOfBook( int side )
9823 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9824 int index = backwardMostMove;
9825 int has_book_hit = 0;
9827 if( (index % 2) != side ) {
9831 while( index < forwardMostMove ) {
9832 /* Check to see if engine is in book */
9833 int depth = pvInfoList[index].depth;
9834 int score = pvInfoList[index].score;
9840 else if( score == 0 && depth == 63 ) {
9841 in_book = 1; /* Zappa */
9843 else if( score == 2 && depth == 99 ) {
9844 in_book = 1; /* Abrok */
9847 has_book_hit += in_book;
9863 void GetOutOfBookInfo( char * buf )
9867 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9869 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9870 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9874 if( oob[0] >= 0 || oob[1] >= 0 ) {
9875 for( i=0; i<2; i++ ) {
9879 if( i > 0 && oob[0] >= 0 ) {
9883 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9884 sprintf( buf+strlen(buf), "%s%.2f",
9885 pvInfoList[idx].score >= 0 ? "+" : "",
9886 pvInfoList[idx].score / 100.0 );
9892 /* Save game in PGN style and close the file */
9897 int i, offset, linelen, newblock;
9901 int movelen, numlen, blank;
9902 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9904 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9906 tm = time((time_t *) NULL);
9908 PrintPGNTags(f, &gameInfo);
9910 if (backwardMostMove > 0 || startedFromSetupPosition) {
9911 char *fen = PositionToFEN(backwardMostMove, NULL);
9912 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9913 fprintf(f, "\n{--------------\n");
9914 PrintPosition(f, backwardMostMove);
9915 fprintf(f, "--------------}\n");
9919 /* [AS] Out of book annotation */
9920 if( appData.saveOutOfBookInfo ) {
9923 GetOutOfBookInfo( buf );
9925 if( buf[0] != '\0' ) {
9926 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9933 i = backwardMostMove;
9937 while (i < forwardMostMove) {
9938 /* Print comments preceding this move */
9939 if (commentList[i] != NULL) {
9940 if (linelen > 0) fprintf(f, "\n");
9941 fprintf(f, "{\n%s}\n", commentList[i]);
9946 /* Format move number */
9948 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9951 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9953 numtext[0] = NULLCHAR;
9956 numlen = strlen(numtext);
9959 /* Print move number */
9960 blank = linelen > 0 && numlen > 0;
9961 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9970 fprintf(f, "%s", numtext);
9974 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9975 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9978 blank = linelen > 0 && movelen > 0;
9979 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9988 fprintf(f, "%s", move_buffer);
9991 /* [AS] Add PV info if present */
9992 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9993 /* [HGM] add time */
9994 char buf[MSG_SIZ]; int seconds;
9996 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9998 if( seconds <= 0) buf[0] = 0; else
9999 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10000 seconds = (seconds + 4)/10; // round to full seconds
10001 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10002 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10005 sprintf( move_buffer, "{%s%.2f/%d%s}",
10006 pvInfoList[i].score >= 0 ? "+" : "",
10007 pvInfoList[i].score / 100.0,
10008 pvInfoList[i].depth,
10011 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10013 /* Print score/depth */
10014 blank = linelen > 0 && movelen > 0;
10015 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10024 fprintf(f, "%s", move_buffer);
10025 linelen += movelen;
10031 /* Start a new line */
10032 if (linelen > 0) fprintf(f, "\n");
10034 /* Print comments after last move */
10035 if (commentList[i] != NULL) {
10036 fprintf(f, "{\n%s}\n", commentList[i]);
10040 if (gameInfo.resultDetails != NULL &&
10041 gameInfo.resultDetails[0] != NULLCHAR) {
10042 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10043 PGNResult(gameInfo.result));
10045 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10049 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10053 /* Save game in old style and close the file */
10055 SaveGameOldStyle(f)
10061 tm = time((time_t *) NULL);
10063 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10066 if (backwardMostMove > 0 || startedFromSetupPosition) {
10067 fprintf(f, "\n[--------------\n");
10068 PrintPosition(f, backwardMostMove);
10069 fprintf(f, "--------------]\n");
10074 i = backwardMostMove;
10075 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10077 while (i < forwardMostMove) {
10078 if (commentList[i] != NULL) {
10079 fprintf(f, "[%s]\n", commentList[i]);
10082 if ((i % 2) == 1) {
10083 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10086 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10088 if (commentList[i] != NULL) {
10092 if (i >= forwardMostMove) {
10096 fprintf(f, "%s\n", parseList[i]);
10101 if (commentList[i] != NULL) {
10102 fprintf(f, "[%s]\n", commentList[i]);
10105 /* This isn't really the old style, but it's close enough */
10106 if (gameInfo.resultDetails != NULL &&
10107 gameInfo.resultDetails[0] != NULLCHAR) {
10108 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10109 gameInfo.resultDetails);
10111 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10118 /* Save the current game to open file f and close the file */
10120 SaveGame(f, dummy, dummy2)
10125 if (gameMode == EditPosition) EditPositionDone(TRUE);
10126 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10127 if (appData.oldSaveStyle)
10128 return SaveGameOldStyle(f);
10130 return SaveGamePGN(f);
10133 /* Save the current position to the given file */
10135 SavePositionToFile(filename)
10141 if (strcmp(filename, "-") == 0) {
10142 return SavePosition(stdout, 0, NULL);
10144 f = fopen(filename, "a");
10146 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10147 DisplayError(buf, errno);
10150 SavePosition(f, 0, NULL);
10156 /* Save the current position to the given open file and close the file */
10158 SavePosition(f, dummy, dummy2)
10166 if (gameMode == EditPosition) EditPositionDone(TRUE);
10167 if (appData.oldSaveStyle) {
10168 tm = time((time_t *) NULL);
10170 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10172 fprintf(f, "[--------------\n");
10173 PrintPosition(f, currentMove);
10174 fprintf(f, "--------------]\n");
10176 fen = PositionToFEN(currentMove, NULL);
10177 fprintf(f, "%s\n", fen);
10185 ReloadCmailMsgEvent(unregister)
10189 static char *inFilename = NULL;
10190 static char *outFilename;
10192 struct stat inbuf, outbuf;
10195 /* Any registered moves are unregistered if unregister is set, */
10196 /* i.e. invoked by the signal handler */
10198 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10199 cmailMoveRegistered[i] = FALSE;
10200 if (cmailCommentList[i] != NULL) {
10201 free(cmailCommentList[i]);
10202 cmailCommentList[i] = NULL;
10205 nCmailMovesRegistered = 0;
10208 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10209 cmailResult[i] = CMAIL_NOT_RESULT;
10213 if (inFilename == NULL) {
10214 /* Because the filenames are static they only get malloced once */
10215 /* and they never get freed */
10216 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10217 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10219 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10220 sprintf(outFilename, "%s.out", appData.cmailGameName);
10223 status = stat(outFilename, &outbuf);
10225 cmailMailedMove = FALSE;
10227 status = stat(inFilename, &inbuf);
10228 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10231 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10232 counts the games, notes how each one terminated, etc.
10234 It would be nice to remove this kludge and instead gather all
10235 the information while building the game list. (And to keep it
10236 in the game list nodes instead of having a bunch of fixed-size
10237 parallel arrays.) Note this will require getting each game's
10238 termination from the PGN tags, as the game list builder does
10239 not process the game moves. --mann
10241 cmailMsgLoaded = TRUE;
10242 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10244 /* Load first game in the file or popup game menu */
10245 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10247 #endif /* !WIN32 */
10255 char string[MSG_SIZ];
10257 if ( cmailMailedMove
10258 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10259 return TRUE; /* Allow free viewing */
10262 /* Unregister move to ensure that we don't leave RegisterMove */
10263 /* with the move registered when the conditions for registering no */
10265 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10266 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10267 nCmailMovesRegistered --;
10269 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10271 free(cmailCommentList[lastLoadGameNumber - 1]);
10272 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10276 if (cmailOldMove == -1) {
10277 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10281 if (currentMove > cmailOldMove + 1) {
10282 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10286 if (currentMove < cmailOldMove) {
10287 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10291 if (forwardMostMove > currentMove) {
10292 /* Silently truncate extra moves */
10296 if ( (currentMove == cmailOldMove + 1)
10297 || ( (currentMove == cmailOldMove)
10298 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10299 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10300 if (gameInfo.result != GameUnfinished) {
10301 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10304 if (commentList[currentMove] != NULL) {
10305 cmailCommentList[lastLoadGameNumber - 1]
10306 = StrSave(commentList[currentMove]);
10308 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10310 if (appData.debugMode)
10311 fprintf(debugFP, "Saving %s for game %d\n",
10312 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10315 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10317 f = fopen(string, "w");
10318 if (appData.oldSaveStyle) {
10319 SaveGameOldStyle(f); /* also closes the file */
10321 sprintf(string, "%s.pos.out", appData.cmailGameName);
10322 f = fopen(string, "w");
10323 SavePosition(f, 0, NULL); /* also closes the file */
10325 fprintf(f, "{--------------\n");
10326 PrintPosition(f, currentMove);
10327 fprintf(f, "--------------}\n\n");
10329 SaveGame(f, 0, NULL); /* also closes the file*/
10332 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10333 nCmailMovesRegistered ++;
10334 } else if (nCmailGames == 1) {
10335 DisplayError(_("You have not made a move yet"), 0);
10346 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10347 FILE *commandOutput;
10348 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10349 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10355 if (! cmailMsgLoaded) {
10356 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10360 if (nCmailGames == nCmailResults) {
10361 DisplayError(_("No unfinished games"), 0);
10365 #if CMAIL_PROHIBIT_REMAIL
10366 if (cmailMailedMove) {
10367 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);
10368 DisplayError(msg, 0);
10373 if (! (cmailMailedMove || RegisterMove())) return;
10375 if ( cmailMailedMove
10376 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10377 sprintf(string, partCommandString,
10378 appData.debugMode ? " -v" : "", appData.cmailGameName);
10379 commandOutput = popen(string, "r");
10381 if (commandOutput == NULL) {
10382 DisplayError(_("Failed to invoke cmail"), 0);
10384 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10385 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10387 if (nBuffers > 1) {
10388 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10389 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10390 nBytes = MSG_SIZ - 1;
10392 (void) memcpy(msg, buffer, nBytes);
10394 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10396 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10397 cmailMailedMove = TRUE; /* Prevent >1 moves */
10400 for (i = 0; i < nCmailGames; i ++) {
10401 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10406 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10408 sprintf(buffer, "%s/%s.%s.archive",
10410 appData.cmailGameName,
10412 LoadGameFromFile(buffer, 1, buffer, FALSE);
10413 cmailMsgLoaded = FALSE;
10417 DisplayInformation(msg);
10418 pclose(commandOutput);
10421 if ((*cmailMsg) != '\0') {
10422 DisplayInformation(cmailMsg);
10427 #endif /* !WIN32 */
10436 int prependComma = 0;
10438 char string[MSG_SIZ]; /* Space for game-list */
10441 if (!cmailMsgLoaded) return "";
10443 if (cmailMailedMove) {
10444 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10446 /* Create a list of games left */
10447 sprintf(string, "[");
10448 for (i = 0; i < nCmailGames; i ++) {
10449 if (! ( cmailMoveRegistered[i]
10450 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10451 if (prependComma) {
10452 sprintf(number, ",%d", i + 1);
10454 sprintf(number, "%d", i + 1);
10458 strcat(string, number);
10461 strcat(string, "]");
10463 if (nCmailMovesRegistered + nCmailResults == 0) {
10464 switch (nCmailGames) {
10467 _("Still need to make move for game\n"));
10472 _("Still need to make moves for both games\n"));
10477 _("Still need to make moves for all %d games\n"),
10482 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10485 _("Still need to make a move for game %s\n"),
10490 if (nCmailResults == nCmailGames) {
10491 sprintf(cmailMsg, _("No unfinished games\n"));
10493 sprintf(cmailMsg, _("Ready to send mail\n"));
10499 _("Still need to make moves for games %s\n"),
10511 if (gameMode == Training)
10512 SetTrainingModeOff();
10515 cmailMsgLoaded = FALSE;
10516 if (appData.icsActive) {
10517 SendToICS(ics_prefix);
10518 SendToICS("refresh\n");
10528 /* Give up on clean exit */
10532 /* Keep trying for clean exit */
10536 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10538 if (telnetISR != NULL) {
10539 RemoveInputSource(telnetISR);
10541 if (icsPR != NoProc) {
10542 DestroyChildProcess(icsPR, TRUE);
10545 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10546 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10548 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10549 /* make sure this other one finishes before killing it! */
10550 if(endingGame) { int count = 0;
10551 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10552 while(endingGame && count++ < 10) DoSleep(1);
10553 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10556 /* Kill off chess programs */
10557 if (first.pr != NoProc) {
10560 DoSleep( appData.delayBeforeQuit );
10561 SendToProgram("quit\n", &first);
10562 DoSleep( appData.delayAfterQuit );
10563 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10565 if (second.pr != NoProc) {
10566 DoSleep( appData.delayBeforeQuit );
10567 SendToProgram("quit\n", &second);
10568 DoSleep( appData.delayAfterQuit );
10569 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10571 if (first.isr != NULL) {
10572 RemoveInputSource(first.isr);
10574 if (second.isr != NULL) {
10575 RemoveInputSource(second.isr);
10578 ShutDownFrontEnd();
10585 if (appData.debugMode)
10586 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10590 if (gameMode == MachinePlaysWhite ||
10591 gameMode == MachinePlaysBlack) {
10594 DisplayBothClocks();
10596 if (gameMode == PlayFromGameFile) {
10597 if (appData.timeDelay >= 0)
10598 AutoPlayGameLoop();
10599 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10600 Reset(FALSE, TRUE);
10601 SendToICS(ics_prefix);
10602 SendToICS("refresh\n");
10603 } else if (currentMove < forwardMostMove) {
10604 ForwardInner(forwardMostMove);
10606 pauseExamInvalid = FALSE;
10608 switch (gameMode) {
10612 pauseExamForwardMostMove = forwardMostMove;
10613 pauseExamInvalid = FALSE;
10616 case IcsPlayingWhite:
10617 case IcsPlayingBlack:
10621 case PlayFromGameFile:
10622 (void) StopLoadGameTimer();
10626 case BeginningOfGame:
10627 if (appData.icsActive) return;
10628 /* else fall through */
10629 case MachinePlaysWhite:
10630 case MachinePlaysBlack:
10631 case TwoMachinesPlay:
10632 if (forwardMostMove == 0)
10633 return; /* don't pause if no one has moved */
10634 if ((gameMode == MachinePlaysWhite &&
10635 !WhiteOnMove(forwardMostMove)) ||
10636 (gameMode == MachinePlaysBlack &&
10637 WhiteOnMove(forwardMostMove))) {
10650 char title[MSG_SIZ];
10652 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10653 strcpy(title, _("Edit comment"));
10655 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10656 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10657 parseList[currentMove - 1]);
10660 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10667 char *tags = PGNTags(&gameInfo);
10668 EditTagsPopUp(tags);
10675 if (appData.noChessProgram || gameMode == AnalyzeMode)
10678 if (gameMode != AnalyzeFile) {
10679 if (!appData.icsEngineAnalyze) {
10681 if (gameMode != EditGame) return;
10683 ResurrectChessProgram();
10684 SendToProgram("analyze\n", &first);
10685 first.analyzing = TRUE;
10686 /*first.maybeThinking = TRUE;*/
10687 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10688 EngineOutputPopUp();
10690 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10695 StartAnalysisClock();
10696 GetTimeMark(&lastNodeCountTime);
10703 if (appData.noChessProgram || gameMode == AnalyzeFile)
10706 if (gameMode != AnalyzeMode) {
10708 if (gameMode != EditGame) return;
10709 ResurrectChessProgram();
10710 SendToProgram("analyze\n", &first);
10711 first.analyzing = TRUE;
10712 /*first.maybeThinking = TRUE;*/
10713 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10714 EngineOutputPopUp();
10716 gameMode = AnalyzeFile;
10721 StartAnalysisClock();
10722 GetTimeMark(&lastNodeCountTime);
10727 MachineWhiteEvent()
10730 char *bookHit = NULL;
10732 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10736 if (gameMode == PlayFromGameFile ||
10737 gameMode == TwoMachinesPlay ||
10738 gameMode == Training ||
10739 gameMode == AnalyzeMode ||
10740 gameMode == EndOfGame)
10743 if (gameMode == EditPosition)
10744 EditPositionDone(TRUE);
10746 if (!WhiteOnMove(currentMove)) {
10747 DisplayError(_("It is not White's turn"), 0);
10751 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10754 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10755 gameMode == AnalyzeFile)
10758 ResurrectChessProgram(); /* in case it isn't running */
10759 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10760 gameMode = MachinePlaysWhite;
10763 gameMode = MachinePlaysWhite;
10767 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10769 if (first.sendName) {
10770 sprintf(buf, "name %s\n", gameInfo.black);
10771 SendToProgram(buf, &first);
10773 if (first.sendTime) {
10774 if (first.useColors) {
10775 SendToProgram("black\n", &first); /*gnu kludge*/
10777 SendTimeRemaining(&first, TRUE);
10779 if (first.useColors) {
10780 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10782 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10783 SetMachineThinkingEnables();
10784 first.maybeThinking = TRUE;
10788 if (appData.autoFlipView && !flipView) {
10789 flipView = !flipView;
10790 DrawPosition(FALSE, NULL);
10791 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10794 if(bookHit) { // [HGM] book: simulate book reply
10795 static char bookMove[MSG_SIZ]; // a bit generous?
10797 programStats.nodes = programStats.depth = programStats.time =
10798 programStats.score = programStats.got_only_move = 0;
10799 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10801 strcpy(bookMove, "move ");
10802 strcat(bookMove, bookHit);
10803 HandleMachineMove(bookMove, &first);
10808 MachineBlackEvent()
10811 char *bookHit = NULL;
10813 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10817 if (gameMode == PlayFromGameFile ||
10818 gameMode == TwoMachinesPlay ||
10819 gameMode == Training ||
10820 gameMode == AnalyzeMode ||
10821 gameMode == EndOfGame)
10824 if (gameMode == EditPosition)
10825 EditPositionDone(TRUE);
10827 if (WhiteOnMove(currentMove)) {
10828 DisplayError(_("It is not Black's turn"), 0);
10832 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10835 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10836 gameMode == AnalyzeFile)
10839 ResurrectChessProgram(); /* in case it isn't running */
10840 gameMode = MachinePlaysBlack;
10844 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10846 if (first.sendName) {
10847 sprintf(buf, "name %s\n", gameInfo.white);
10848 SendToProgram(buf, &first);
10850 if (first.sendTime) {
10851 if (first.useColors) {
10852 SendToProgram("white\n", &first); /*gnu kludge*/
10854 SendTimeRemaining(&first, FALSE);
10856 if (first.useColors) {
10857 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10859 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10860 SetMachineThinkingEnables();
10861 first.maybeThinking = TRUE;
10864 if (appData.autoFlipView && flipView) {
10865 flipView = !flipView;
10866 DrawPosition(FALSE, NULL);
10867 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10869 if(bookHit) { // [HGM] book: simulate book reply
10870 static char bookMove[MSG_SIZ]; // a bit generous?
10872 programStats.nodes = programStats.depth = programStats.time =
10873 programStats.score = programStats.got_only_move = 0;
10874 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10876 strcpy(bookMove, "move ");
10877 strcat(bookMove, bookHit);
10878 HandleMachineMove(bookMove, &first);
10884 DisplayTwoMachinesTitle()
10887 if (appData.matchGames > 0) {
10888 if (first.twoMachinesColor[0] == 'w') {
10889 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10890 gameInfo.white, gameInfo.black,
10891 first.matchWins, second.matchWins,
10892 matchGame - 1 - (first.matchWins + second.matchWins));
10894 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10895 gameInfo.white, gameInfo.black,
10896 second.matchWins, first.matchWins,
10897 matchGame - 1 - (first.matchWins + second.matchWins));
10900 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10906 TwoMachinesEvent P((void))
10910 ChessProgramState *onmove;
10911 char *bookHit = NULL;
10913 if (appData.noChessProgram) return;
10915 switch (gameMode) {
10916 case TwoMachinesPlay:
10918 case MachinePlaysWhite:
10919 case MachinePlaysBlack:
10920 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10921 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10925 case BeginningOfGame:
10926 case PlayFromGameFile:
10929 if (gameMode != EditGame) return;
10932 EditPositionDone(TRUE);
10943 forwardMostMove = currentMove;
10944 ResurrectChessProgram(); /* in case first program isn't running */
10946 if (second.pr == NULL) {
10947 StartChessProgram(&second);
10948 if (second.protocolVersion == 1) {
10949 TwoMachinesEventIfReady();
10951 /* kludge: allow timeout for initial "feature" command */
10953 DisplayMessage("", _("Starting second chess program"));
10954 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10958 DisplayMessage("", "");
10959 InitChessProgram(&second, FALSE);
10960 SendToProgram("force\n", &second);
10961 if (startedFromSetupPosition) {
10962 SendBoard(&second, backwardMostMove);
10963 if (appData.debugMode) {
10964 fprintf(debugFP, "Two Machines\n");
10967 for (i = backwardMostMove; i < forwardMostMove; i++) {
10968 SendMoveToProgram(i, &second);
10971 gameMode = TwoMachinesPlay;
10975 DisplayTwoMachinesTitle();
10977 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10983 SendToProgram(first.computerString, &first);
10984 if (first.sendName) {
10985 sprintf(buf, "name %s\n", second.tidy);
10986 SendToProgram(buf, &first);
10988 SendToProgram(second.computerString, &second);
10989 if (second.sendName) {
10990 sprintf(buf, "name %s\n", first.tidy);
10991 SendToProgram(buf, &second);
10995 if (!first.sendTime || !second.sendTime) {
10996 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10997 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10999 if (onmove->sendTime) {
11000 if (onmove->useColors) {
11001 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11003 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11005 if (onmove->useColors) {
11006 SendToProgram(onmove->twoMachinesColor, onmove);
11008 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11009 // SendToProgram("go\n", onmove);
11010 onmove->maybeThinking = TRUE;
11011 SetMachineThinkingEnables();
11015 if(bookHit) { // [HGM] book: simulate book reply
11016 static char bookMove[MSG_SIZ]; // a bit generous?
11018 programStats.nodes = programStats.depth = programStats.time =
11019 programStats.score = programStats.got_only_move = 0;
11020 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11022 strcpy(bookMove, "move ");
11023 strcat(bookMove, bookHit);
11024 savedMessage = bookMove; // args for deferred call
11025 savedState = onmove;
11026 ScheduleDelayedEvent(DeferredBookMove, 1);
11033 if (gameMode == Training) {
11034 SetTrainingModeOff();
11035 gameMode = PlayFromGameFile;
11036 DisplayMessage("", _("Training mode off"));
11038 gameMode = Training;
11039 animateTraining = appData.animate;
11041 /* make sure we are not already at the end of the game */
11042 if (currentMove < forwardMostMove) {
11043 SetTrainingModeOn();
11044 DisplayMessage("", _("Training mode on"));
11046 gameMode = PlayFromGameFile;
11047 DisplayError(_("Already at end of game"), 0);
11056 if (!appData.icsActive) return;
11057 switch (gameMode) {
11058 case IcsPlayingWhite:
11059 case IcsPlayingBlack:
11062 case BeginningOfGame:
11070 EditPositionDone(TRUE);
11083 gameMode = IcsIdle;
11094 switch (gameMode) {
11096 SetTrainingModeOff();
11098 case MachinePlaysWhite:
11099 case MachinePlaysBlack:
11100 case BeginningOfGame:
11101 SendToProgram("force\n", &first);
11102 SetUserThinkingEnables();
11104 case PlayFromGameFile:
11105 (void) StopLoadGameTimer();
11106 if (gameFileFP != NULL) {
11111 EditPositionDone(TRUE);
11116 SendToProgram("force\n", &first);
11118 case TwoMachinesPlay:
11119 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11120 ResurrectChessProgram();
11121 SetUserThinkingEnables();
11124 ResurrectChessProgram();
11126 case IcsPlayingBlack:
11127 case IcsPlayingWhite:
11128 DisplayError(_("Warning: You are still playing a game"), 0);
11131 DisplayError(_("Warning: You are still observing a game"), 0);
11134 DisplayError(_("Warning: You are still examining a game"), 0);
11145 first.offeredDraw = second.offeredDraw = 0;
11147 if (gameMode == PlayFromGameFile) {
11148 whiteTimeRemaining = timeRemaining[0][currentMove];
11149 blackTimeRemaining = timeRemaining[1][currentMove];
11153 if (gameMode == MachinePlaysWhite ||
11154 gameMode == MachinePlaysBlack ||
11155 gameMode == TwoMachinesPlay ||
11156 gameMode == EndOfGame) {
11157 i = forwardMostMove;
11158 while (i > currentMove) {
11159 SendToProgram("undo\n", &first);
11162 whiteTimeRemaining = timeRemaining[0][currentMove];
11163 blackTimeRemaining = timeRemaining[1][currentMove];
11164 DisplayBothClocks();
11165 if (whiteFlag || blackFlag) {
11166 whiteFlag = blackFlag = 0;
11171 gameMode = EditGame;
11178 EditPositionEvent()
11180 if (gameMode == EditPosition) {
11186 if (gameMode != EditGame) return;
11188 gameMode = EditPosition;
11191 if (currentMove > 0)
11192 CopyBoard(boards[0], boards[currentMove]);
11194 blackPlaysFirst = !WhiteOnMove(currentMove);
11196 currentMove = forwardMostMove = backwardMostMove = 0;
11197 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11204 /* [DM] icsEngineAnalyze - possible call from other functions */
11205 if (appData.icsEngineAnalyze) {
11206 appData.icsEngineAnalyze = FALSE;
11208 DisplayMessage("",_("Close ICS engine analyze..."));
11210 if (first.analysisSupport && first.analyzing) {
11211 SendToProgram("exit\n", &first);
11212 first.analyzing = FALSE;
11214 thinkOutput[0] = NULLCHAR;
11218 EditPositionDone(Boolean fakeRights)
11220 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11222 startedFromSetupPosition = TRUE;
11223 InitChessProgram(&first, FALSE);
11225 { /* don't do this if we just pasted FEN */
11226 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11227 if(boards[0][0][BOARD_WIDTH>>1] == king)
11229 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11230 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11233 castlingRights[0][2] = -1;
11234 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11236 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11237 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11240 castlingRights[0][5] = -1;
11242 SendToProgram("force\n", &first);
11243 if (blackPlaysFirst) {
11244 strcpy(moveList[0], "");
11245 strcpy(parseList[0], "");
11246 currentMove = forwardMostMove = backwardMostMove = 1;
11247 CopyBoard(boards[1], boards[0]);
11248 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11250 epStatus[1] = epStatus[0];
11251 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11254 currentMove = forwardMostMove = backwardMostMove = 0;
11256 SendBoard(&first, forwardMostMove);
11257 if (appData.debugMode) {
11258 fprintf(debugFP, "EditPosDone\n");
11261 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11262 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11263 gameMode = EditGame;
11265 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11266 ClearHighlights(); /* [AS] */
11269 /* Pause for `ms' milliseconds */
11270 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11280 } while (SubtractTimeMarks(&m2, &m1) < ms);
11283 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11285 SendMultiLineToICS(buf)
11288 char temp[MSG_SIZ+1], *p;
11295 strncpy(temp, buf, len);
11300 if (*p == '\n' || *p == '\r')
11305 strcat(temp, "\n");
11307 SendToPlayer(temp, strlen(temp));
11311 SetWhiteToPlayEvent()
11313 if (gameMode == EditPosition) {
11314 blackPlaysFirst = FALSE;
11315 DisplayBothClocks(); /* works because currentMove is 0 */
11316 } else if (gameMode == IcsExamining) {
11317 SendToICS(ics_prefix);
11318 SendToICS("tomove white\n");
11323 SetBlackToPlayEvent()
11325 if (gameMode == EditPosition) {
11326 blackPlaysFirst = TRUE;
11327 currentMove = 1; /* kludge */
11328 DisplayBothClocks();
11330 } else if (gameMode == IcsExamining) {
11331 SendToICS(ics_prefix);
11332 SendToICS("tomove black\n");
11337 EditPositionMenuEvent(selection, x, y)
11338 ChessSquare selection;
11342 ChessSquare piece = boards[0][y][x];
11344 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11346 switch (selection) {
11348 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11349 SendToICS(ics_prefix);
11350 SendToICS("bsetup clear\n");
11351 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11352 SendToICS(ics_prefix);
11353 SendToICS("clearboard\n");
11355 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11356 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11357 for (y = 0; y < BOARD_HEIGHT; y++) {
11358 if (gameMode == IcsExamining) {
11359 if (boards[currentMove][y][x] != EmptySquare) {
11360 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11365 boards[0][y][x] = p;
11370 if (gameMode == EditPosition) {
11371 DrawPosition(FALSE, boards[0]);
11376 SetWhiteToPlayEvent();
11380 SetBlackToPlayEvent();
11384 if (gameMode == IcsExamining) {
11385 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11388 boards[0][y][x] = EmptySquare;
11389 DrawPosition(FALSE, boards[0]);
11394 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11395 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11396 selection = (ChessSquare) (PROMOTED piece);
11397 } else if(piece == EmptySquare) selection = WhiteSilver;
11398 else selection = (ChessSquare)((int)piece - 1);
11402 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11403 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11404 selection = (ChessSquare) (DEMOTED piece);
11405 } else if(piece == EmptySquare) selection = BlackSilver;
11406 else selection = (ChessSquare)((int)piece + 1);
11411 if(gameInfo.variant == VariantShatranj ||
11412 gameInfo.variant == VariantXiangqi ||
11413 gameInfo.variant == VariantCourier ||
11414 gameInfo.variant == VariantMakruk )
11415 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11420 if(gameInfo.variant == VariantXiangqi)
11421 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11422 if(gameInfo.variant == VariantKnightmate)
11423 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11426 if (gameMode == IcsExamining) {
11427 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11428 PieceToChar(selection), AAA + x, ONE + y);
11431 boards[0][y][x] = selection;
11432 DrawPosition(FALSE, boards[0]);
11440 DropMenuEvent(selection, x, y)
11441 ChessSquare selection;
11444 ChessMove moveType;
11446 switch (gameMode) {
11447 case IcsPlayingWhite:
11448 case MachinePlaysBlack:
11449 if (!WhiteOnMove(currentMove)) {
11450 DisplayMoveError(_("It is Black's turn"));
11453 moveType = WhiteDrop;
11455 case IcsPlayingBlack:
11456 case MachinePlaysWhite:
11457 if (WhiteOnMove(currentMove)) {
11458 DisplayMoveError(_("It is White's turn"));
11461 moveType = BlackDrop;
11464 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11470 if (moveType == BlackDrop && selection < BlackPawn) {
11471 selection = (ChessSquare) ((int) selection
11472 + (int) BlackPawn - (int) WhitePawn);
11474 if (boards[currentMove][y][x] != EmptySquare) {
11475 DisplayMoveError(_("That square is occupied"));
11479 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11485 /* Accept a pending offer of any kind from opponent */
11487 if (appData.icsActive) {
11488 SendToICS(ics_prefix);
11489 SendToICS("accept\n");
11490 } else if (cmailMsgLoaded) {
11491 if (currentMove == cmailOldMove &&
11492 commentList[cmailOldMove] != NULL &&
11493 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11494 "Black offers a draw" : "White offers a draw")) {
11496 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11497 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11499 DisplayError(_("There is no pending offer on this move"), 0);
11500 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11503 /* Not used for offers from chess program */
11510 /* Decline a pending offer of any kind from opponent */
11512 if (appData.icsActive) {
11513 SendToICS(ics_prefix);
11514 SendToICS("decline\n");
11515 } else if (cmailMsgLoaded) {
11516 if (currentMove == cmailOldMove &&
11517 commentList[cmailOldMove] != NULL &&
11518 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11519 "Black offers a draw" : "White offers a draw")) {
11521 AppendComment(cmailOldMove, "Draw declined");
11522 DisplayComment(cmailOldMove - 1, "Draw declined");
11525 DisplayError(_("There is no pending offer on this move"), 0);
11528 /* Not used for offers from chess program */
11535 /* Issue ICS rematch command */
11536 if (appData.icsActive) {
11537 SendToICS(ics_prefix);
11538 SendToICS("rematch\n");
11545 /* Call your opponent's flag (claim a win on time) */
11546 if (appData.icsActive) {
11547 SendToICS(ics_prefix);
11548 SendToICS("flag\n");
11550 switch (gameMode) {
11553 case MachinePlaysWhite:
11556 GameEnds(GameIsDrawn, "Both players ran out of time",
11559 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11561 DisplayError(_("Your opponent is not out of time"), 0);
11564 case MachinePlaysBlack:
11567 GameEnds(GameIsDrawn, "Both players ran out of time",
11570 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11572 DisplayError(_("Your opponent is not out of time"), 0);
11582 /* Offer draw or accept pending draw offer from opponent */
11584 if (appData.icsActive) {
11585 /* Note: tournament rules require draw offers to be
11586 made after you make your move but before you punch
11587 your clock. Currently ICS doesn't let you do that;
11588 instead, you immediately punch your clock after making
11589 a move, but you can offer a draw at any time. */
11591 SendToICS(ics_prefix);
11592 SendToICS("draw\n");
11593 } else if (cmailMsgLoaded) {
11594 if (currentMove == cmailOldMove &&
11595 commentList[cmailOldMove] != NULL &&
11596 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11597 "Black offers a draw" : "White offers a draw")) {
11598 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11599 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11600 } else if (currentMove == cmailOldMove + 1) {
11601 char *offer = WhiteOnMove(cmailOldMove) ?
11602 "White offers a draw" : "Black offers a draw";
11603 AppendComment(currentMove, offer);
11604 DisplayComment(currentMove - 1, offer);
11605 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11607 DisplayError(_("You must make your move before offering a draw"), 0);
11608 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11610 } else if (first.offeredDraw) {
11611 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11613 if (first.sendDrawOffers) {
11614 SendToProgram("draw\n", &first);
11615 userOfferedDraw = TRUE;
11623 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11625 if (appData.icsActive) {
11626 SendToICS(ics_prefix);
11627 SendToICS("adjourn\n");
11629 /* Currently GNU Chess doesn't offer or accept Adjourns */
11637 /* Offer Abort or accept pending Abort offer from opponent */
11639 if (appData.icsActive) {
11640 SendToICS(ics_prefix);
11641 SendToICS("abort\n");
11643 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11650 /* Resign. You can do this even if it's not your turn. */
11652 if (appData.icsActive) {
11653 SendToICS(ics_prefix);
11654 SendToICS("resign\n");
11656 switch (gameMode) {
11657 case MachinePlaysWhite:
11658 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11660 case MachinePlaysBlack:
11661 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11664 if (cmailMsgLoaded) {
11666 if (WhiteOnMove(cmailOldMove)) {
11667 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11669 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11671 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11682 StopObservingEvent()
11684 /* Stop observing current games */
11685 SendToICS(ics_prefix);
11686 SendToICS("unobserve\n");
11690 StopExaminingEvent()
11692 /* Stop observing current game */
11693 SendToICS(ics_prefix);
11694 SendToICS("unexamine\n");
11698 ForwardInner(target)
11703 if (appData.debugMode)
11704 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11705 target, currentMove, forwardMostMove);
11707 if (gameMode == EditPosition)
11710 if (gameMode == PlayFromGameFile && !pausing)
11713 if (gameMode == IcsExamining && pausing)
11714 limit = pauseExamForwardMostMove;
11716 limit = forwardMostMove;
11718 if (target > limit) target = limit;
11720 if (target > 0 && moveList[target - 1][0]) {
11721 int fromX, fromY, toX, toY;
11722 toX = moveList[target - 1][2] - AAA;
11723 toY = moveList[target - 1][3] - ONE;
11724 if (moveList[target - 1][1] == '@') {
11725 if (appData.highlightLastMove) {
11726 SetHighlights(-1, -1, toX, toY);
11729 fromX = moveList[target - 1][0] - AAA;
11730 fromY = moveList[target - 1][1] - ONE;
11731 if (target == currentMove + 1) {
11732 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11734 if (appData.highlightLastMove) {
11735 SetHighlights(fromX, fromY, toX, toY);
11739 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11740 gameMode == Training || gameMode == PlayFromGameFile ||
11741 gameMode == AnalyzeFile) {
11742 while (currentMove < target) {
11743 SendMoveToProgram(currentMove++, &first);
11746 currentMove = target;
11749 if (gameMode == EditGame || gameMode == EndOfGame) {
11750 whiteTimeRemaining = timeRemaining[0][currentMove];
11751 blackTimeRemaining = timeRemaining[1][currentMove];
11753 DisplayBothClocks();
11754 DisplayMove(currentMove - 1);
11755 DrawPosition(FALSE, boards[currentMove]);
11756 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11757 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11758 DisplayComment(currentMove - 1, commentList[currentMove]);
11766 if (gameMode == IcsExamining && !pausing) {
11767 SendToICS(ics_prefix);
11768 SendToICS("forward\n");
11770 ForwardInner(currentMove + 1);
11777 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11778 /* to optimze, we temporarily turn off analysis mode while we feed
11779 * the remaining moves to the engine. Otherwise we get analysis output
11782 if (first.analysisSupport) {
11783 SendToProgram("exit\nforce\n", &first);
11784 first.analyzing = FALSE;
11788 if (gameMode == IcsExamining && !pausing) {
11789 SendToICS(ics_prefix);
11790 SendToICS("forward 999999\n");
11792 ForwardInner(forwardMostMove);
11795 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11796 /* we have fed all the moves, so reactivate analysis mode */
11797 SendToProgram("analyze\n", &first);
11798 first.analyzing = TRUE;
11799 /*first.maybeThinking = TRUE;*/
11800 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11805 BackwardInner(target)
11808 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11810 if (appData.debugMode)
11811 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11812 target, currentMove, forwardMostMove);
11814 if (gameMode == EditPosition) return;
11815 if (currentMove <= backwardMostMove) {
11817 DrawPosition(full_redraw, boards[currentMove]);
11820 if (gameMode == PlayFromGameFile && !pausing)
11823 if (moveList[target][0]) {
11824 int fromX, fromY, toX, toY;
11825 toX = moveList[target][2] - AAA;
11826 toY = moveList[target][3] - ONE;
11827 if (moveList[target][1] == '@') {
11828 if (appData.highlightLastMove) {
11829 SetHighlights(-1, -1, toX, toY);
11832 fromX = moveList[target][0] - AAA;
11833 fromY = moveList[target][1] - ONE;
11834 if (target == currentMove - 1) {
11835 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11837 if (appData.highlightLastMove) {
11838 SetHighlights(fromX, fromY, toX, toY);
11842 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11843 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11844 while (currentMove > target) {
11845 SendToProgram("undo\n", &first);
11849 currentMove = target;
11852 if (gameMode == EditGame || gameMode == EndOfGame) {
11853 whiteTimeRemaining = timeRemaining[0][currentMove];
11854 blackTimeRemaining = timeRemaining[1][currentMove];
11856 DisplayBothClocks();
11857 DisplayMove(currentMove - 1);
11858 DrawPosition(full_redraw, boards[currentMove]);
11859 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11860 // [HGM] PV info: routine tests if comment empty
11861 DisplayComment(currentMove - 1, commentList[currentMove]);
11867 if (gameMode == IcsExamining && !pausing) {
11868 SendToICS(ics_prefix);
11869 SendToICS("backward\n");
11871 BackwardInner(currentMove - 1);
11878 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11879 /* to optimize, we temporarily turn off analysis mode while we undo
11880 * all the moves. Otherwise we get analysis output after each undo.
11882 if (first.analysisSupport) {
11883 SendToProgram("exit\nforce\n", &first);
11884 first.analyzing = FALSE;
11888 if (gameMode == IcsExamining && !pausing) {
11889 SendToICS(ics_prefix);
11890 SendToICS("backward 999999\n");
11892 BackwardInner(backwardMostMove);
11895 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11896 /* we have fed all the moves, so reactivate analysis mode */
11897 SendToProgram("analyze\n", &first);
11898 first.analyzing = TRUE;
11899 /*first.maybeThinking = TRUE;*/
11900 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11907 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11908 if (to >= forwardMostMove) to = forwardMostMove;
11909 if (to <= backwardMostMove) to = backwardMostMove;
11910 if (to < currentMove) {
11920 if (gameMode != IcsExamining) {
11921 DisplayError(_("You are not examining a game"), 0);
11925 DisplayError(_("You can't revert while pausing"), 0);
11928 SendToICS(ics_prefix);
11929 SendToICS("revert\n");
11935 switch (gameMode) {
11936 case MachinePlaysWhite:
11937 case MachinePlaysBlack:
11938 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11939 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11942 if (forwardMostMove < 2) return;
11943 currentMove = forwardMostMove = forwardMostMove - 2;
11944 whiteTimeRemaining = timeRemaining[0][currentMove];
11945 blackTimeRemaining = timeRemaining[1][currentMove];
11946 DisplayBothClocks();
11947 DisplayMove(currentMove - 1);
11948 ClearHighlights();/*!! could figure this out*/
11949 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11950 SendToProgram("remove\n", &first);
11951 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11954 case BeginningOfGame:
11958 case IcsPlayingWhite:
11959 case IcsPlayingBlack:
11960 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11961 SendToICS(ics_prefix);
11962 SendToICS("takeback 2\n");
11964 SendToICS(ics_prefix);
11965 SendToICS("takeback 1\n");
11974 ChessProgramState *cps;
11976 switch (gameMode) {
11977 case MachinePlaysWhite:
11978 if (!WhiteOnMove(forwardMostMove)) {
11979 DisplayError(_("It is your turn"), 0);
11984 case MachinePlaysBlack:
11985 if (WhiteOnMove(forwardMostMove)) {
11986 DisplayError(_("It is your turn"), 0);
11991 case TwoMachinesPlay:
11992 if (WhiteOnMove(forwardMostMove) ==
11993 (first.twoMachinesColor[0] == 'w')) {
11999 case BeginningOfGame:
12003 SendToProgram("?\n", cps);
12007 TruncateGameEvent()
12010 if (gameMode != EditGame) return;
12017 if (forwardMostMove > currentMove) {
12018 if (gameInfo.resultDetails != NULL) {
12019 free(gameInfo.resultDetails);
12020 gameInfo.resultDetails = NULL;
12021 gameInfo.result = GameUnfinished;
12023 forwardMostMove = currentMove;
12024 HistorySet(parseList, backwardMostMove, forwardMostMove,
12032 if (appData.noChessProgram) return;
12033 switch (gameMode) {
12034 case MachinePlaysWhite:
12035 if (WhiteOnMove(forwardMostMove)) {
12036 DisplayError(_("Wait until your turn"), 0);
12040 case BeginningOfGame:
12041 case MachinePlaysBlack:
12042 if (!WhiteOnMove(forwardMostMove)) {
12043 DisplayError(_("Wait until your turn"), 0);
12048 DisplayError(_("No hint available"), 0);
12051 SendToProgram("hint\n", &first);
12052 hintRequested = TRUE;
12058 if (appData.noChessProgram) return;
12059 switch (gameMode) {
12060 case MachinePlaysWhite:
12061 if (WhiteOnMove(forwardMostMove)) {
12062 DisplayError(_("Wait until your turn"), 0);
12066 case BeginningOfGame:
12067 case MachinePlaysBlack:
12068 if (!WhiteOnMove(forwardMostMove)) {
12069 DisplayError(_("Wait until your turn"), 0);
12074 EditPositionDone(TRUE);
12076 case TwoMachinesPlay:
12081 SendToProgram("bk\n", &first);
12082 bookOutput[0] = NULLCHAR;
12083 bookRequested = TRUE;
12089 char *tags = PGNTags(&gameInfo);
12090 TagsPopUp(tags, CmailMsg());
12094 /* end button procedures */
12097 PrintPosition(fp, move)
12103 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12104 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12105 char c = PieceToChar(boards[move][i][j]);
12106 fputc(c == 'x' ? '.' : c, fp);
12107 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12110 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12111 fprintf(fp, "white to play\n");
12113 fprintf(fp, "black to play\n");
12120 if (gameInfo.white != NULL) {
12121 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12127 /* Find last component of program's own name, using some heuristics */
12129 TidyProgramName(prog, host, buf)
12130 char *prog, *host, buf[MSG_SIZ];
12133 int local = (strcmp(host, "localhost") == 0);
12134 while (!local && (p = strchr(prog, ';')) != NULL) {
12136 while (*p == ' ') p++;
12139 if (*prog == '"' || *prog == '\'') {
12140 q = strchr(prog + 1, *prog);
12142 q = strchr(prog, ' ');
12144 if (q == NULL) q = prog + strlen(prog);
12146 while (p >= prog && *p != '/' && *p != '\\') p--;
12148 if(p == prog && *p == '"') p++;
12149 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12150 memcpy(buf, p, q - p);
12151 buf[q - p] = NULLCHAR;
12159 TimeControlTagValue()
12162 if (!appData.clockMode) {
12164 } else if (movesPerSession > 0) {
12165 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12166 } else if (timeIncrement == 0) {
12167 sprintf(buf, "%ld", timeControl/1000);
12169 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12171 return StrSave(buf);
12177 /* This routine is used only for certain modes */
12178 VariantClass v = gameInfo.variant;
12179 ClearGameInfo(&gameInfo);
12180 gameInfo.variant = v;
12182 switch (gameMode) {
12183 case MachinePlaysWhite:
12184 gameInfo.event = StrSave( appData.pgnEventHeader );
12185 gameInfo.site = StrSave(HostName());
12186 gameInfo.date = PGNDate();
12187 gameInfo.round = StrSave("-");
12188 gameInfo.white = StrSave(first.tidy);
12189 gameInfo.black = StrSave(UserName());
12190 gameInfo.timeControl = TimeControlTagValue();
12193 case MachinePlaysBlack:
12194 gameInfo.event = StrSave( appData.pgnEventHeader );
12195 gameInfo.site = StrSave(HostName());
12196 gameInfo.date = PGNDate();
12197 gameInfo.round = StrSave("-");
12198 gameInfo.white = StrSave(UserName());
12199 gameInfo.black = StrSave(first.tidy);
12200 gameInfo.timeControl = TimeControlTagValue();
12203 case TwoMachinesPlay:
12204 gameInfo.event = StrSave( appData.pgnEventHeader );
12205 gameInfo.site = StrSave(HostName());
12206 gameInfo.date = PGNDate();
12207 if (matchGame > 0) {
12209 sprintf(buf, "%d", matchGame);
12210 gameInfo.round = StrSave(buf);
12212 gameInfo.round = StrSave("-");
12214 if (first.twoMachinesColor[0] == 'w') {
12215 gameInfo.white = StrSave(first.tidy);
12216 gameInfo.black = StrSave(second.tidy);
12218 gameInfo.white = StrSave(second.tidy);
12219 gameInfo.black = StrSave(first.tidy);
12221 gameInfo.timeControl = TimeControlTagValue();
12225 gameInfo.event = StrSave("Edited game");
12226 gameInfo.site = StrSave(HostName());
12227 gameInfo.date = PGNDate();
12228 gameInfo.round = StrSave("-");
12229 gameInfo.white = StrSave("-");
12230 gameInfo.black = StrSave("-");
12234 gameInfo.event = StrSave("Edited position");
12235 gameInfo.site = StrSave(HostName());
12236 gameInfo.date = PGNDate();
12237 gameInfo.round = StrSave("-");
12238 gameInfo.white = StrSave("-");
12239 gameInfo.black = StrSave("-");
12242 case IcsPlayingWhite:
12243 case IcsPlayingBlack:
12248 case PlayFromGameFile:
12249 gameInfo.event = StrSave("Game from non-PGN file");
12250 gameInfo.site = StrSave(HostName());
12251 gameInfo.date = PGNDate();
12252 gameInfo.round = StrSave("-");
12253 gameInfo.white = StrSave("?");
12254 gameInfo.black = StrSave("?");
12263 ReplaceComment(index, text)
12269 while (*text == '\n') text++;
12270 len = strlen(text);
12271 while (len > 0 && text[len - 1] == '\n') len--;
12273 if (commentList[index] != NULL)
12274 free(commentList[index]);
12277 commentList[index] = NULL;
12280 commentList[index] = (char *) malloc(len + 2);
12281 strncpy(commentList[index], text, len);
12282 commentList[index][len] = '\n';
12283 commentList[index][len + 1] = NULLCHAR;
12296 if (ch == '\r') continue;
12298 } while (ch != '\0');
12302 AppendComment(index, text)
12309 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12312 while (*text == '\n') text++;
12313 len = strlen(text);
12314 while (len > 0 && text[len - 1] == '\n') len--;
12316 if (len == 0) return;
12318 if (commentList[index] != NULL) {
12319 old = commentList[index];
12320 oldlen = strlen(old);
12321 commentList[index] = (char *) malloc(oldlen + len + 2);
12322 strcpy(commentList[index], old);
12324 strncpy(&commentList[index][oldlen], text, len);
12325 commentList[index][oldlen + len] = '\n';
12326 commentList[index][oldlen + len + 1] = NULLCHAR;
12328 commentList[index] = (char *) malloc(len + 2);
12329 strncpy(commentList[index], text, len);
12330 commentList[index][len] = '\n';
12331 commentList[index][len + 1] = NULLCHAR;
12335 static char * FindStr( char * text, char * sub_text )
12337 char * result = strstr( text, sub_text );
12339 if( result != NULL ) {
12340 result += strlen( sub_text );
12346 /* [AS] Try to extract PV info from PGN comment */
12347 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12348 char *GetInfoFromComment( int index, char * text )
12352 if( text != NULL && index > 0 ) {
12355 int time = -1, sec = 0, deci;
12356 char * s_eval = FindStr( text, "[%eval " );
12357 char * s_emt = FindStr( text, "[%emt " );
12359 if( s_eval != NULL || s_emt != NULL ) {
12363 if( s_eval != NULL ) {
12364 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12368 if( delim != ']' ) {
12373 if( s_emt != NULL ) {
12377 /* We expect something like: [+|-]nnn.nn/dd */
12380 sep = strchr( text, '/' );
12381 if( sep == NULL || sep < (text+4) ) {
12385 time = -1; sec = -1; deci = -1;
12386 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12387 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12388 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12389 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12393 if( score_lo < 0 || score_lo >= 100 ) {
12397 if(sec >= 0) time = 600*time + 10*sec; else
12398 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12400 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12402 /* [HGM] PV time: now locate end of PV info */
12403 while( *++sep >= '0' && *sep <= '9'); // strip depth
12405 while( *++sep >= '0' && *sep <= '9'); // strip time
12407 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12409 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12410 while(*sep == ' ') sep++;
12421 pvInfoList[index-1].depth = depth;
12422 pvInfoList[index-1].score = score;
12423 pvInfoList[index-1].time = 10*time; // centi-sec
12429 SendToProgram(message, cps)
12431 ChessProgramState *cps;
12433 int count, outCount, error;
12436 if (cps->pr == NULL) return;
12439 if (appData.debugMode) {
12442 fprintf(debugFP, "%ld >%-6s: %s",
12443 SubtractTimeMarks(&now, &programStartTime),
12444 cps->which, message);
12447 count = strlen(message);
12448 outCount = OutputToProcess(cps->pr, message, count, &error);
12449 if (outCount < count && !exiting
12450 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12451 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12452 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12453 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12454 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12455 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12457 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12459 gameInfo.resultDetails = StrSave(buf);
12461 DisplayFatalError(buf, error, 1);
12466 ReceiveFromProgram(isr, closure, message, count, error)
12467 InputSourceRef isr;
12475 ChessProgramState *cps = (ChessProgramState *)closure;
12477 if (isr != cps->isr) return; /* Killed intentionally */
12481 _("Error: %s chess program (%s) exited unexpectedly"),
12482 cps->which, cps->program);
12483 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12484 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12485 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12486 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12488 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12490 gameInfo.resultDetails = StrSave(buf);
12492 RemoveInputSource(cps->isr);
12493 DisplayFatalError(buf, 0, 1);
12496 _("Error reading from %s chess program (%s)"),
12497 cps->which, cps->program);
12498 RemoveInputSource(cps->isr);
12500 /* [AS] Program is misbehaving badly... kill it */
12501 if( count == -2 ) {
12502 DestroyChildProcess( cps->pr, 9 );
12506 DisplayFatalError(buf, error, 1);
12511 if ((end_str = strchr(message, '\r')) != NULL)
12512 *end_str = NULLCHAR;
12513 if ((end_str = strchr(message, '\n')) != NULL)
12514 *end_str = NULLCHAR;
12516 if (appData.debugMode) {
12517 TimeMark now; int print = 1;
12518 char *quote = ""; char c; int i;
12520 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12521 char start = message[0];
12522 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12523 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12524 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12525 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12526 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12527 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12528 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12529 sscanf(message, "pong %c", &c)!=1 && start != '#')
12530 { quote = "# "; print = (appData.engineComments == 2); }
12531 message[0] = start; // restore original message
12535 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12536 SubtractTimeMarks(&now, &programStartTime), cps->which,
12542 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12543 if (appData.icsEngineAnalyze) {
12544 if (strstr(message, "whisper") != NULL ||
12545 strstr(message, "kibitz") != NULL ||
12546 strstr(message, "tellics") != NULL) return;
12549 HandleMachineMove(message, cps);
12554 SendTimeControl(cps, mps, tc, inc, sd, st)
12555 ChessProgramState *cps;
12556 int mps, inc, sd, st;
12562 if( timeControl_2 > 0 ) {
12563 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12564 tc = timeControl_2;
12567 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12568 inc /= cps->timeOdds;
12569 st /= cps->timeOdds;
12571 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12574 /* Set exact time per move, normally using st command */
12575 if (cps->stKludge) {
12576 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12578 if (seconds == 0) {
12579 sprintf(buf, "level 1 %d\n", st/60);
12581 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12584 sprintf(buf, "st %d\n", st);
12587 /* Set conventional or incremental time control, using level command */
12588 if (seconds == 0) {
12589 /* Note old gnuchess bug -- minutes:seconds used to not work.
12590 Fixed in later versions, but still avoid :seconds
12591 when seconds is 0. */
12592 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12594 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12595 seconds, inc/1000);
12598 SendToProgram(buf, cps);
12600 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12601 /* Orthogonally, limit search to given depth */
12603 if (cps->sdKludge) {
12604 sprintf(buf, "depth\n%d\n", sd);
12606 sprintf(buf, "sd %d\n", sd);
12608 SendToProgram(buf, cps);
12611 if(cps->nps > 0) { /* [HGM] nps */
12612 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12614 sprintf(buf, "nps %d\n", cps->nps);
12615 SendToProgram(buf, cps);
12620 ChessProgramState *WhitePlayer()
12621 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12623 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12624 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12630 SendTimeRemaining(cps, machineWhite)
12631 ChessProgramState *cps;
12632 int /*boolean*/ machineWhite;
12634 char message[MSG_SIZ];
12637 /* Note: this routine must be called when the clocks are stopped
12638 or when they have *just* been set or switched; otherwise
12639 it will be off by the time since the current tick started.
12641 if (machineWhite) {
12642 time = whiteTimeRemaining / 10;
12643 otime = blackTimeRemaining / 10;
12645 time = blackTimeRemaining / 10;
12646 otime = whiteTimeRemaining / 10;
12648 /* [HGM] translate opponent's time by time-odds factor */
12649 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12650 if (appData.debugMode) {
12651 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12654 if (time <= 0) time = 1;
12655 if (otime <= 0) otime = 1;
12657 sprintf(message, "time %ld\n", time);
12658 SendToProgram(message, cps);
12660 sprintf(message, "otim %ld\n", otime);
12661 SendToProgram(message, cps);
12665 BoolFeature(p, name, loc, cps)
12669 ChessProgramState *cps;
12672 int len = strlen(name);
12674 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12676 sscanf(*p, "%d", &val);
12678 while (**p && **p != ' ') (*p)++;
12679 sprintf(buf, "accepted %s\n", name);
12680 SendToProgram(buf, cps);
12687 IntFeature(p, name, loc, cps)
12691 ChessProgramState *cps;
12694 int len = strlen(name);
12695 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12697 sscanf(*p, "%d", loc);
12698 while (**p && **p != ' ') (*p)++;
12699 sprintf(buf, "accepted %s\n", name);
12700 SendToProgram(buf, cps);
12707 StringFeature(p, name, loc, cps)
12711 ChessProgramState *cps;
12714 int len = strlen(name);
12715 if (strncmp((*p), name, len) == 0
12716 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12718 sscanf(*p, "%[^\"]", loc);
12719 while (**p && **p != '\"') (*p)++;
12720 if (**p == '\"') (*p)++;
12721 sprintf(buf, "accepted %s\n", name);
12722 SendToProgram(buf, cps);
12729 ParseOption(Option *opt, ChessProgramState *cps)
12730 // [HGM] options: process the string that defines an engine option, and determine
12731 // name, type, default value, and allowed value range
12733 char *p, *q, buf[MSG_SIZ];
12734 int n, min = (-1)<<31, max = 1<<31, def;
12736 if(p = strstr(opt->name, " -spin ")) {
12737 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12738 if(max < min) max = min; // enforce consistency
12739 if(def < min) def = min;
12740 if(def > max) def = max;
12745 } else if((p = strstr(opt->name, " -slider "))) {
12746 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12747 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12748 if(max < min) max = min; // enforce consistency
12749 if(def < min) def = min;
12750 if(def > max) def = max;
12754 opt->type = Spin; // Slider;
12755 } else if((p = strstr(opt->name, " -string "))) {
12756 opt->textValue = p+9;
12757 opt->type = TextBox;
12758 } else if((p = strstr(opt->name, " -file "))) {
12759 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12760 opt->textValue = p+7;
12761 opt->type = TextBox; // FileName;
12762 } else if((p = strstr(opt->name, " -path "))) {
12763 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12764 opt->textValue = p+7;
12765 opt->type = TextBox; // PathName;
12766 } else if(p = strstr(opt->name, " -check ")) {
12767 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12768 opt->value = (def != 0);
12769 opt->type = CheckBox;
12770 } else if(p = strstr(opt->name, " -combo ")) {
12771 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12772 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12773 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12774 opt->value = n = 0;
12775 while(q = StrStr(q, " /// ")) {
12776 n++; *q = 0; // count choices, and null-terminate each of them
12778 if(*q == '*') { // remember default, which is marked with * prefix
12782 cps->comboList[cps->comboCnt++] = q;
12784 cps->comboList[cps->comboCnt++] = NULL;
12786 opt->type = ComboBox;
12787 } else if(p = strstr(opt->name, " -button")) {
12788 opt->type = Button;
12789 } else if(p = strstr(opt->name, " -save")) {
12790 opt->type = SaveButton;
12791 } else return FALSE;
12792 *p = 0; // terminate option name
12793 // now look if the command-line options define a setting for this engine option.
12794 if(cps->optionSettings && cps->optionSettings[0])
12795 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12796 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12797 sprintf(buf, "option %s", p);
12798 if(p = strstr(buf, ",")) *p = 0;
12800 SendToProgram(buf, cps);
12806 FeatureDone(cps, val)
12807 ChessProgramState* cps;
12810 DelayedEventCallback cb = GetDelayedEvent();
12811 if ((cb == InitBackEnd3 && cps == &first) ||
12812 (cb == TwoMachinesEventIfReady && cps == &second)) {
12813 CancelDelayedEvent();
12814 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12816 cps->initDone = val;
12819 /* Parse feature command from engine */
12821 ParseFeatures(args, cps)
12823 ChessProgramState *cps;
12831 while (*p == ' ') p++;
12832 if (*p == NULLCHAR) return;
12834 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12835 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12836 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12837 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12838 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12839 if (BoolFeature(&p, "reuse", &val, cps)) {
12840 /* Engine can disable reuse, but can't enable it if user said no */
12841 if (!val) cps->reuse = FALSE;
12844 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12845 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12846 if (gameMode == TwoMachinesPlay) {
12847 DisplayTwoMachinesTitle();
12853 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12854 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12855 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12856 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12857 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12858 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12859 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12860 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12861 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12862 if (IntFeature(&p, "done", &val, cps)) {
12863 FeatureDone(cps, val);
12866 /* Added by Tord: */
12867 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12868 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12869 /* End of additions by Tord */
12871 /* [HGM] added features: */
12872 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12873 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12874 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12875 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12876 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12877 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12878 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12879 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12880 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12881 SendToProgram(buf, cps);
12884 if(cps->nrOptions >= MAX_OPTIONS) {
12886 sprintf(buf, "%s engine has too many options\n", cps->which);
12887 DisplayError(buf, 0);
12891 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12892 /* End of additions by HGM */
12894 /* unknown feature: complain and skip */
12896 while (*q && *q != '=') q++;
12897 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12898 SendToProgram(buf, cps);
12904 while (*p && *p != '\"') p++;
12905 if (*p == '\"') p++;
12907 while (*p && *p != ' ') p++;
12915 PeriodicUpdatesEvent(newState)
12918 if (newState == appData.periodicUpdates)
12921 appData.periodicUpdates=newState;
12923 /* Display type changes, so update it now */
12924 // DisplayAnalysis();
12926 /* Get the ball rolling again... */
12928 AnalysisPeriodicEvent(1);
12929 StartAnalysisClock();
12934 PonderNextMoveEvent(newState)
12937 if (newState == appData.ponderNextMove) return;
12938 if (gameMode == EditPosition) EditPositionDone(TRUE);
12940 SendToProgram("hard\n", &first);
12941 if (gameMode == TwoMachinesPlay) {
12942 SendToProgram("hard\n", &second);
12945 SendToProgram("easy\n", &first);
12946 thinkOutput[0] = NULLCHAR;
12947 if (gameMode == TwoMachinesPlay) {
12948 SendToProgram("easy\n", &second);
12951 appData.ponderNextMove = newState;
12955 NewSettingEvent(option, command, value)
12961 if (gameMode == EditPosition) EditPositionDone(TRUE);
12962 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12963 SendToProgram(buf, &first);
12964 if (gameMode == TwoMachinesPlay) {
12965 SendToProgram(buf, &second);
12970 ShowThinkingEvent()
12971 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12973 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12974 int newState = appData.showThinking
12975 // [HGM] thinking: other features now need thinking output as well
12976 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12978 if (oldState == newState) return;
12979 oldState = newState;
12980 if (gameMode == EditPosition) EditPositionDone(TRUE);
12982 SendToProgram("post\n", &first);
12983 if (gameMode == TwoMachinesPlay) {
12984 SendToProgram("post\n", &second);
12987 SendToProgram("nopost\n", &first);
12988 thinkOutput[0] = NULLCHAR;
12989 if (gameMode == TwoMachinesPlay) {
12990 SendToProgram("nopost\n", &second);
12993 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12997 AskQuestionEvent(title, question, replyPrefix, which)
12998 char *title; char *question; char *replyPrefix; char *which;
13000 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13001 if (pr == NoProc) return;
13002 AskQuestion(title, question, replyPrefix, pr);
13006 DisplayMove(moveNumber)
13009 char message[MSG_SIZ];
13011 char cpThinkOutput[MSG_SIZ];
13013 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13015 if (moveNumber == forwardMostMove - 1 ||
13016 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13018 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13020 if (strchr(cpThinkOutput, '\n')) {
13021 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13024 *cpThinkOutput = NULLCHAR;
13027 /* [AS] Hide thinking from human user */
13028 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13029 *cpThinkOutput = NULLCHAR;
13030 if( thinkOutput[0] != NULLCHAR ) {
13033 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13034 cpThinkOutput[i] = '.';
13036 cpThinkOutput[i] = NULLCHAR;
13037 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13041 if (moveNumber == forwardMostMove - 1 &&
13042 gameInfo.resultDetails != NULL) {
13043 if (gameInfo.resultDetails[0] == NULLCHAR) {
13044 sprintf(res, " %s", PGNResult(gameInfo.result));
13046 sprintf(res, " {%s} %s",
13047 gameInfo.resultDetails, PGNResult(gameInfo.result));
13053 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13054 DisplayMessage(res, cpThinkOutput);
13056 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13057 WhiteOnMove(moveNumber) ? " " : ".. ",
13058 parseList[moveNumber], res);
13059 DisplayMessage(message, cpThinkOutput);
13064 DisplayComment(moveNumber, text)
13068 char title[MSG_SIZ];
13069 char buf[8000]; // comment can be long!
13072 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13073 strcpy(title, "Comment");
13075 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13076 WhiteOnMove(moveNumber) ? " " : ".. ",
13077 parseList[moveNumber]);
13079 // [HGM] PV info: display PV info together with (or as) comment
13080 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13081 if(text == NULL) text = "";
13082 score = pvInfoList[moveNumber].score;
13083 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13084 depth, (pvInfoList[moveNumber].time+50)/100, text);
13087 if (text != NULL && (appData.autoDisplayComment || commentUp))
13088 CommentPopUp(title, text);
13091 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13092 * might be busy thinking or pondering. It can be omitted if your
13093 * gnuchess is configured to stop thinking immediately on any user
13094 * input. However, that gnuchess feature depends on the FIONREAD
13095 * ioctl, which does not work properly on some flavors of Unix.
13099 ChessProgramState *cps;
13102 if (!cps->useSigint) return;
13103 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13104 switch (gameMode) {
13105 case MachinePlaysWhite:
13106 case MachinePlaysBlack:
13107 case TwoMachinesPlay:
13108 case IcsPlayingWhite:
13109 case IcsPlayingBlack:
13112 /* Skip if we know it isn't thinking */
13113 if (!cps->maybeThinking) return;
13114 if (appData.debugMode)
13115 fprintf(debugFP, "Interrupting %s\n", cps->which);
13116 InterruptChildProcess(cps->pr);
13117 cps->maybeThinking = FALSE;
13122 #endif /*ATTENTION*/
13128 if (whiteTimeRemaining <= 0) {
13131 if (appData.icsActive) {
13132 if (appData.autoCallFlag &&
13133 gameMode == IcsPlayingBlack && !blackFlag) {
13134 SendToICS(ics_prefix);
13135 SendToICS("flag\n");
13139 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13141 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13142 if (appData.autoCallFlag) {
13143 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13150 if (blackTimeRemaining <= 0) {
13153 if (appData.icsActive) {
13154 if (appData.autoCallFlag &&
13155 gameMode == IcsPlayingWhite && !whiteFlag) {
13156 SendToICS(ics_prefix);
13157 SendToICS("flag\n");
13161 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13163 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13164 if (appData.autoCallFlag) {
13165 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13178 if (!appData.clockMode || appData.icsActive ||
13179 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13182 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13184 if ( !WhiteOnMove(forwardMostMove) )
13185 /* White made time control */
13186 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13187 /* [HGM] time odds: correct new time quota for time odds! */
13188 / WhitePlayer()->timeOdds;
13190 /* Black made time control */
13191 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13192 / WhitePlayer()->other->timeOdds;
13196 DisplayBothClocks()
13198 int wom = gameMode == EditPosition ?
13199 !blackPlaysFirst : WhiteOnMove(currentMove);
13200 DisplayWhiteClock(whiteTimeRemaining, wom);
13201 DisplayBlackClock(blackTimeRemaining, !wom);
13205 /* Timekeeping seems to be a portability nightmare. I think everyone
13206 has ftime(), but I'm really not sure, so I'm including some ifdefs
13207 to use other calls if you don't. Clocks will be less accurate if
13208 you have neither ftime nor gettimeofday.
13211 /* VS 2008 requires the #include outside of the function */
13212 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13213 #include <sys/timeb.h>
13216 /* Get the current time as a TimeMark */
13221 #if HAVE_GETTIMEOFDAY
13223 struct timeval timeVal;
13224 struct timezone timeZone;
13226 gettimeofday(&timeVal, &timeZone);
13227 tm->sec = (long) timeVal.tv_sec;
13228 tm->ms = (int) (timeVal.tv_usec / 1000L);
13230 #else /*!HAVE_GETTIMEOFDAY*/
13233 // include <sys/timeb.h> / moved to just above start of function
13234 struct timeb timeB;
13237 tm->sec = (long) timeB.time;
13238 tm->ms = (int) timeB.millitm;
13240 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13241 tm->sec = (long) time(NULL);
13247 /* Return the difference in milliseconds between two
13248 time marks. We assume the difference will fit in a long!
13251 SubtractTimeMarks(tm2, tm1)
13252 TimeMark *tm2, *tm1;
13254 return 1000L*(tm2->sec - tm1->sec) +
13255 (long) (tm2->ms - tm1->ms);
13260 * Code to manage the game clocks.
13262 * In tournament play, black starts the clock and then white makes a move.
13263 * We give the human user a slight advantage if he is playing white---the
13264 * clocks don't run until he makes his first move, so it takes zero time.
13265 * Also, we don't account for network lag, so we could get out of sync
13266 * with GNU Chess's clock -- but then, referees are always right.
13269 static TimeMark tickStartTM;
13270 static long intendedTickLength;
13273 NextTickLength(timeRemaining)
13274 long timeRemaining;
13276 long nominalTickLength, nextTickLength;
13278 if (timeRemaining > 0L && timeRemaining <= 10000L)
13279 nominalTickLength = 100L;
13281 nominalTickLength = 1000L;
13282 nextTickLength = timeRemaining % nominalTickLength;
13283 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13285 return nextTickLength;
13288 /* Adjust clock one minute up or down */
13290 AdjustClock(Boolean which, int dir)
13292 if(which) blackTimeRemaining += 60000*dir;
13293 else whiteTimeRemaining += 60000*dir;
13294 DisplayBothClocks();
13297 /* Stop clocks and reset to a fresh time control */
13301 (void) StopClockTimer();
13302 if (appData.icsActive) {
13303 whiteTimeRemaining = blackTimeRemaining = 0;
13304 } else { /* [HGM] correct new time quote for time odds */
13305 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13306 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13308 if (whiteFlag || blackFlag) {
13310 whiteFlag = blackFlag = FALSE;
13312 DisplayBothClocks();
13315 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13317 /* Decrement running clock by amount of time that has passed */
13321 long timeRemaining;
13322 long lastTickLength, fudge;
13325 if (!appData.clockMode) return;
13326 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13330 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13332 /* Fudge if we woke up a little too soon */
13333 fudge = intendedTickLength - lastTickLength;
13334 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13336 if (WhiteOnMove(forwardMostMove)) {
13337 if(whiteNPS >= 0) lastTickLength = 0;
13338 timeRemaining = whiteTimeRemaining -= lastTickLength;
13339 DisplayWhiteClock(whiteTimeRemaining - fudge,
13340 WhiteOnMove(currentMove));
13342 if(blackNPS >= 0) lastTickLength = 0;
13343 timeRemaining = blackTimeRemaining -= lastTickLength;
13344 DisplayBlackClock(blackTimeRemaining - fudge,
13345 !WhiteOnMove(currentMove));
13348 if (CheckFlags()) return;
13351 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13352 StartClockTimer(intendedTickLength);
13354 /* if the time remaining has fallen below the alarm threshold, sound the
13355 * alarm. if the alarm has sounded and (due to a takeback or time control
13356 * with increment) the time remaining has increased to a level above the
13357 * threshold, reset the alarm so it can sound again.
13360 if (appData.icsActive && appData.icsAlarm) {
13362 /* make sure we are dealing with the user's clock */
13363 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13364 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13367 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13368 alarmSounded = FALSE;
13369 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13371 alarmSounded = TRUE;
13377 /* A player has just moved, so stop the previously running
13378 clock and (if in clock mode) start the other one.
13379 We redisplay both clocks in case we're in ICS mode, because
13380 ICS gives us an update to both clocks after every move.
13381 Note that this routine is called *after* forwardMostMove
13382 is updated, so the last fractional tick must be subtracted
13383 from the color that is *not* on move now.
13386 SwitchClocks(int newMoveNr)
13388 long lastTickLength;
13390 int flagged = FALSE;
13394 if (StopClockTimer() && appData.clockMode) {
13395 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13396 if (!WhiteOnMove(forwardMostMove)) {
13397 if(blackNPS >= 0) lastTickLength = 0;
13398 blackTimeRemaining -= lastTickLength;
13399 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13400 // if(pvInfoList[forwardMostMove-1].time == -1)
13401 pvInfoList[forwardMostMove-1].time = // use GUI time
13402 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13404 if(whiteNPS >= 0) lastTickLength = 0;
13405 whiteTimeRemaining -= lastTickLength;
13406 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13407 // if(pvInfoList[forwardMostMove-1].time == -1)
13408 pvInfoList[forwardMostMove-1].time =
13409 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13411 flagged = CheckFlags();
13413 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13414 CheckTimeControl();
13416 if (flagged || !appData.clockMode) return;
13418 switch (gameMode) {
13419 case MachinePlaysBlack:
13420 case MachinePlaysWhite:
13421 case BeginningOfGame:
13422 if (pausing) return;
13426 case PlayFromGameFile:
13435 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13436 whiteTimeRemaining : blackTimeRemaining);
13437 StartClockTimer(intendedTickLength);
13441 /* Stop both clocks */
13445 long lastTickLength;
13448 if (!StopClockTimer()) return;
13449 if (!appData.clockMode) return;
13453 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13454 if (WhiteOnMove(forwardMostMove)) {
13455 if(whiteNPS >= 0) lastTickLength = 0;
13456 whiteTimeRemaining -= lastTickLength;
13457 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13459 if(blackNPS >= 0) lastTickLength = 0;
13460 blackTimeRemaining -= lastTickLength;
13461 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13466 /* Start clock of player on move. Time may have been reset, so
13467 if clock is already running, stop and restart it. */
13471 (void) StopClockTimer(); /* in case it was running already */
13472 DisplayBothClocks();
13473 if (CheckFlags()) return;
13475 if (!appData.clockMode) return;
13476 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13478 GetTimeMark(&tickStartTM);
13479 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13480 whiteTimeRemaining : blackTimeRemaining);
13482 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13483 whiteNPS = blackNPS = -1;
13484 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13485 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13486 whiteNPS = first.nps;
13487 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13488 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13489 blackNPS = first.nps;
13490 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13491 whiteNPS = second.nps;
13492 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13493 blackNPS = second.nps;
13494 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13496 StartClockTimer(intendedTickLength);
13503 long second, minute, hour, day;
13505 static char buf[32];
13507 if (ms > 0 && ms <= 9900) {
13508 /* convert milliseconds to tenths, rounding up */
13509 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13511 sprintf(buf, " %03.1f ", tenths/10.0);
13515 /* convert milliseconds to seconds, rounding up */
13516 /* use floating point to avoid strangeness of integer division
13517 with negative dividends on many machines */
13518 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13525 day = second / (60 * 60 * 24);
13526 second = second % (60 * 60 * 24);
13527 hour = second / (60 * 60);
13528 second = second % (60 * 60);
13529 minute = second / 60;
13530 second = second % 60;
13533 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13534 sign, day, hour, minute, second);
13536 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13538 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13545 * This is necessary because some C libraries aren't ANSI C compliant yet.
13548 StrStr(string, match)
13549 char *string, *match;
13553 length = strlen(match);
13555 for (i = strlen(string) - length; i >= 0; i--, string++)
13556 if (!strncmp(match, string, length))
13563 StrCaseStr(string, match)
13564 char *string, *match;
13568 length = strlen(match);
13570 for (i = strlen(string) - length; i >= 0; i--, string++) {
13571 for (j = 0; j < length; j++) {
13572 if (ToLower(match[j]) != ToLower(string[j]))
13575 if (j == length) return string;
13589 c1 = ToLower(*s1++);
13590 c2 = ToLower(*s2++);
13591 if (c1 > c2) return 1;
13592 if (c1 < c2) return -1;
13593 if (c1 == NULLCHAR) return 0;
13602 return isupper(c) ? tolower(c) : c;
13610 return islower(c) ? toupper(c) : c;
13612 #endif /* !_amigados */
13620 if ((ret = (char *) malloc(strlen(s) + 1))) {
13627 StrSavePtr(s, savePtr)
13628 char *s, **savePtr;
13633 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13634 strcpy(*savePtr, s);
13646 clock = time((time_t *)NULL);
13647 tm = localtime(&clock);
13648 sprintf(buf, "%04d.%02d.%02d",
13649 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13650 return StrSave(buf);
13655 PositionToFEN(move, overrideCastling)
13657 char *overrideCastling;
13659 int i, j, fromX, fromY, toX, toY;
13666 whiteToPlay = (gameMode == EditPosition) ?
13667 !blackPlaysFirst : (move % 2 == 0);
13670 /* Piece placement data */
13671 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13673 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13674 if (boards[move][i][j] == EmptySquare) {
13676 } else { ChessSquare piece = boards[move][i][j];
13677 if (emptycount > 0) {
13678 if(emptycount<10) /* [HGM] can be >= 10 */
13679 *p++ = '0' + emptycount;
13680 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13683 if(PieceToChar(piece) == '+') {
13684 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13686 piece = (ChessSquare)(DEMOTED piece);
13688 *p++ = PieceToChar(piece);
13690 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13691 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13696 if (emptycount > 0) {
13697 if(emptycount<10) /* [HGM] can be >= 10 */
13698 *p++ = '0' + emptycount;
13699 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13706 /* [HGM] print Crazyhouse or Shogi holdings */
13707 if( gameInfo.holdingsWidth ) {
13708 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13710 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13711 piece = boards[move][i][BOARD_WIDTH-1];
13712 if( piece != EmptySquare )
13713 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13714 *p++ = PieceToChar(piece);
13716 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13717 piece = boards[move][BOARD_HEIGHT-i-1][0];
13718 if( piece != EmptySquare )
13719 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13720 *p++ = PieceToChar(piece);
13723 if( q == p ) *p++ = '-';
13729 *p++ = whiteToPlay ? 'w' : 'b';
13732 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13733 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13735 if(nrCastlingRights) {
13737 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13738 /* [HGM] write directly from rights */
13739 if(castlingRights[move][2] >= 0 &&
13740 castlingRights[move][0] >= 0 )
13741 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13742 if(castlingRights[move][2] >= 0 &&
13743 castlingRights[move][1] >= 0 )
13744 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13745 if(castlingRights[move][5] >= 0 &&
13746 castlingRights[move][3] >= 0 )
13747 *p++ = castlingRights[move][3] + AAA;
13748 if(castlingRights[move][5] >= 0 &&
13749 castlingRights[move][4] >= 0 )
13750 *p++ = castlingRights[move][4] + AAA;
13753 /* [HGM] write true castling rights */
13754 if( nrCastlingRights == 6 ) {
13755 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13756 castlingRights[move][2] >= 0 ) *p++ = 'K';
13757 if(castlingRights[move][1] == BOARD_LEFT &&
13758 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13759 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13760 castlingRights[move][5] >= 0 ) *p++ = 'k';
13761 if(castlingRights[move][4] == BOARD_LEFT &&
13762 castlingRights[move][5] >= 0 ) *p++ = 'q';
13765 if (q == p) *p++ = '-'; /* No castling rights */
13769 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13770 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
13771 /* En passant target square */
13772 if (move > backwardMostMove) {
13773 fromX = moveList[move - 1][0] - AAA;
13774 fromY = moveList[move - 1][1] - ONE;
13775 toX = moveList[move - 1][2] - AAA;
13776 toY = moveList[move - 1][3] - ONE;
13777 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13778 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13779 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13781 /* 2-square pawn move just happened */
13783 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13787 } else if(move == backwardMostMove) {
13788 // [HGM] perhaps we should always do it like this, and forget the above?
13789 if(epStatus[move] >= 0) {
13790 *p++ = epStatus[move] + AAA;
13791 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13802 /* [HGM] find reversible plies */
13803 { int i = 0, j=move;
13805 if (appData.debugMode) { int k;
13806 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13807 for(k=backwardMostMove; k<=forwardMostMove; k++)
13808 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13812 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13813 if( j == backwardMostMove ) i += initialRulePlies;
13814 sprintf(p, "%d ", i);
13815 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13817 /* Fullmove number */
13818 sprintf(p, "%d", (move / 2) + 1);
13820 return StrSave(buf);
13824 ParseFEN(board, blackPlaysFirst, fen)
13826 int *blackPlaysFirst;
13836 /* [HGM] by default clear Crazyhouse holdings, if present */
13837 if(gameInfo.holdingsWidth) {
13838 for(i=0; i<BOARD_HEIGHT; i++) {
13839 board[i][0] = EmptySquare; /* black holdings */
13840 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13841 board[i][1] = (ChessSquare) 0; /* black counts */
13842 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13846 /* Piece placement data */
13847 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13850 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13851 if (*p == '/') p++;
13852 emptycount = gameInfo.boardWidth - j;
13853 while (emptycount--)
13854 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13856 #if(BOARD_SIZE >= 10)
13857 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13858 p++; emptycount=10;
13859 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13860 while (emptycount--)
13861 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13863 } else if (isdigit(*p)) {
13864 emptycount = *p++ - '0';
13865 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13866 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13867 while (emptycount--)
13868 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13869 } else if (*p == '+' || isalpha(*p)) {
13870 if (j >= gameInfo.boardWidth) return FALSE;
13872 piece = CharToPiece(*++p);
13873 if(piece == EmptySquare) return FALSE; /* unknown piece */
13874 piece = (ChessSquare) (PROMOTED piece ); p++;
13875 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13876 } else piece = CharToPiece(*p++);
13878 if(piece==EmptySquare) return FALSE; /* unknown piece */
13879 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13880 piece = (ChessSquare) (PROMOTED piece);
13881 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13884 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13890 while (*p == '/' || *p == ' ') p++;
13892 /* [HGM] look for Crazyhouse holdings here */
13893 while(*p==' ') p++;
13894 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13896 if(*p == '-' ) *p++; /* empty holdings */ else {
13897 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13898 /* if we would allow FEN reading to set board size, we would */
13899 /* have to add holdings and shift the board read so far here */
13900 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13902 if((int) piece >= (int) BlackPawn ) {
13903 i = (int)piece - (int)BlackPawn;
13904 i = PieceToNumber((ChessSquare)i);
13905 if( i >= gameInfo.holdingsSize ) return FALSE;
13906 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13907 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13909 i = (int)piece - (int)WhitePawn;
13910 i = PieceToNumber((ChessSquare)i);
13911 if( i >= gameInfo.holdingsSize ) return FALSE;
13912 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13913 board[i][BOARD_WIDTH-2]++; /* black holdings */
13917 if(*p == ']') *p++;
13920 while(*p == ' ') p++;
13925 *blackPlaysFirst = FALSE;
13928 *blackPlaysFirst = TRUE;
13934 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13935 /* return the extra info in global variiables */
13937 /* set defaults in case FEN is incomplete */
13938 FENepStatus = EP_UNKNOWN;
13939 for(i=0; i<nrCastlingRights; i++ ) {
13940 FENcastlingRights[i] =
13941 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13942 } /* assume possible unless obviously impossible */
13943 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13944 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13945 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13946 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13947 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13948 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13949 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13950 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13953 while(*p==' ') p++;
13954 if(nrCastlingRights) {
13955 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13956 /* castling indicator present, so default becomes no castlings */
13957 for(i=0; i<nrCastlingRights; i++ ) {
13958 FENcastlingRights[i] = -1;
13961 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13962 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13963 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13964 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13965 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13967 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13968 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13969 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13971 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13972 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13973 if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13974 && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13975 if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13976 && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13979 for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13980 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13981 FENcastlingRights[2] = whiteKingFile;
13984 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13985 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13986 FENcastlingRights[2] = whiteKingFile;
13989 for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13990 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13991 FENcastlingRights[5] = blackKingFile;
13994 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13995 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13996 FENcastlingRights[5] = blackKingFile;
13999 default: /* FRC castlings */
14000 if(c >= 'a') { /* black rights */
14001 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14002 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14003 if(i == BOARD_RGHT) break;
14004 FENcastlingRights[5] = i;
14006 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14007 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14009 FENcastlingRights[3] = c;
14011 FENcastlingRights[4] = c;
14012 } else { /* white rights */
14013 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14014 if(board[0][i] == WhiteKing) break;
14015 if(i == BOARD_RGHT) break;
14016 FENcastlingRights[2] = i;
14017 c -= AAA - 'a' + 'A';
14018 if(board[0][c] >= WhiteKing) break;
14020 FENcastlingRights[0] = c;
14022 FENcastlingRights[1] = c;
14026 for(i=0; i<nrCastlingRights; i++)
14027 if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14028 if (appData.debugMode) {
14029 fprintf(debugFP, "FEN castling rights:");
14030 for(i=0; i<nrCastlingRights; i++)
14031 fprintf(debugFP, " %d", FENcastlingRights[i]);
14032 fprintf(debugFP, "\n");
14035 while(*p==' ') p++;
14038 /* read e.p. field in games that know e.p. capture */
14039 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14040 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14042 p++; FENepStatus = EP_NONE;
14044 char c = *p++ - AAA;
14046 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14047 if(*p >= '0' && *p <='9') *p++;
14053 if(sscanf(p, "%d", &i) == 1) {
14054 FENrulePlies = i; /* 50-move ply counter */
14055 /* (The move number is still ignored) */
14062 EditPositionPasteFEN(char *fen)
14065 Board initial_position;
14067 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14068 DisplayError(_("Bad FEN position in clipboard"), 0);
14071 int savedBlackPlaysFirst = blackPlaysFirst;
14072 EditPositionEvent();
14073 blackPlaysFirst = savedBlackPlaysFirst;
14074 CopyBoard(boards[0], initial_position);
14075 /* [HGM] copy FEN attributes as well */
14077 initialRulePlies = FENrulePlies;
14078 epStatus[0] = FENepStatus;
14079 for( i=0; i<nrCastlingRights; i++ )
14080 castlingRights[0][i] = FENcastlingRights[i];
14082 EditPositionDone(FALSE);
14083 DisplayBothClocks();
14084 DrawPosition(FALSE, boards[currentMove]);
14089 static char cseq[12] = "\\ ";
14091 Boolean set_cont_sequence(char *new_seq)
14096 // handle bad attempts to set the sequence
14098 return 0; // acceptable error - no debug
14100 len = strlen(new_seq);
14101 ret = (len > 0) && (len < sizeof(cseq));
14103 strcpy(cseq, new_seq);
14104 else if (appData.debugMode)
14105 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14110 reformat a source message so words don't cross the width boundary. internal
14111 newlines are not removed. returns the wrapped size (no null character unless
14112 included in source message). If dest is NULL, only calculate the size required
14113 for the dest buffer. lp argument indicats line position upon entry, and it's
14114 passed back upon exit.
14116 int wrap(char *dest, char *src, int count, int width, int *lp)
14118 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14120 cseq_len = strlen(cseq);
14121 old_line = line = *lp;
14122 ansi = len = clen = 0;
14124 for (i=0; i < count; i++)
14126 if (src[i] == '\033')
14129 // if we hit the width, back up
14130 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14132 // store i & len in case the word is too long
14133 old_i = i, old_len = len;
14135 // find the end of the last word
14136 while (i && src[i] != ' ' && src[i] != '\n')
14142 // word too long? restore i & len before splitting it
14143 if ((old_i-i+clen) >= width)
14150 if (i && src[i-1] == ' ')
14153 if (src[i] != ' ' && src[i] != '\n')
14160 // now append the newline and continuation sequence
14165 strncpy(dest+len, cseq, cseq_len);
14173 dest[len] = src[i];
14177 if (src[i] == '\n')
14182 if (dest && appData.debugMode)
14184 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14185 count, width, line, len, *lp);
14186 show_bytes(debugFP, src, count);
14187 fprintf(debugFP, "\ndest: ");
14188 show_bytes(debugFP, dest, len);
14189 fprintf(debugFP, "\n");
14191 *lp = dest ? line : old_line;