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) ||
2475 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2476 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2478 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2479 chattingPartner = -1;
2481 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2482 for(p=0; p<MAX_CHAT; p++) {
2483 if(channel == atoi(chatPartner[p])) {
2484 talker[0] = '['; strcat(talker, "] ");
2485 chattingPartner = p; break;
2488 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2489 for(p=0; p<MAX_CHAT; p++) {
2490 if(!strcmp("WHISPER", chatPartner[p])) {
2491 talker[0] = '['; strcat(talker, "] ");
2492 chattingPartner = p; break;
2495 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2496 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2498 chattingPartner = p; break;
2500 if(chattingPartner<0) i = oldi; else {
2501 started = STARTED_COMMENT;
2502 parse_pos = 0; parse[0] = NULLCHAR;
2503 savingComment = 3 + chattingPartner; // counts as TRUE
2504 suppressKibitz = TRUE;
2506 } // [HGM] chat: end of patch
2508 if (appData.zippyTalk || appData.zippyPlay) {
2509 /* [DM] Backup address for color zippy lines */
2513 if (loggedOn == TRUE)
2514 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2515 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2517 if (ZippyControl(buf, &i) ||
2518 ZippyConverse(buf, &i) ||
2519 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2521 if (!appData.colorize) continue;
2525 } // [DM] 'else { ' deleted
2527 /* Regular tells and says */
2528 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2529 looking_at(buf, &i, "* (your partner) tells you: ") ||
2530 looking_at(buf, &i, "* says: ") ||
2531 /* Don't color "message" or "messages" output */
2532 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2533 looking_at(buf, &i, "*. * at *:*: ") ||
2534 looking_at(buf, &i, "--* (*:*): ") ||
2535 /* Message notifications (same color as tells) */
2536 looking_at(buf, &i, "* has left a message ") ||
2537 looking_at(buf, &i, "* just sent you a message:\n") ||
2538 /* Whispers and kibitzes */
2539 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2540 looking_at(buf, &i, "* kibitzes: ") ||
2542 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2544 if (tkind == 1 && strchr(star_match[0], ':')) {
2545 /* Avoid "tells you:" spoofs in channels */
2548 if (star_match[0][0] == NULLCHAR ||
2549 strchr(star_match[0], ' ') ||
2550 (tkind == 3 && strchr(star_match[1], ' '))) {
2551 /* Reject bogus matches */
2554 if (appData.colorize) {
2555 if (oldi > next_out) {
2556 SendToPlayer(&buf[next_out], oldi - next_out);
2561 Colorize(ColorTell, FALSE);
2562 curColor = ColorTell;
2565 Colorize(ColorKibitz, FALSE);
2566 curColor = ColorKibitz;
2569 p = strrchr(star_match[1], '(');
2576 Colorize(ColorChannel1, FALSE);
2577 curColor = ColorChannel1;
2579 Colorize(ColorChannel, FALSE);
2580 curColor = ColorChannel;
2584 curColor = ColorNormal;
2588 if (started == STARTED_NONE && appData.autoComment &&
2589 (gameMode == IcsObserving ||
2590 gameMode == IcsPlayingWhite ||
2591 gameMode == IcsPlayingBlack)) {
2592 parse_pos = i - oldi;
2593 memcpy(parse, &buf[oldi], parse_pos);
2594 parse[parse_pos] = NULLCHAR;
2595 started = STARTED_COMMENT;
2596 savingComment = TRUE;
2598 started = STARTED_CHATTER;
2599 savingComment = FALSE;
2606 if (looking_at(buf, &i, "* s-shouts: ") ||
2607 looking_at(buf, &i, "* c-shouts: ")) {
2608 if (appData.colorize) {
2609 if (oldi > next_out) {
2610 SendToPlayer(&buf[next_out], oldi - next_out);
2613 Colorize(ColorSShout, FALSE);
2614 curColor = ColorSShout;
2617 started = STARTED_CHATTER;
2621 if (looking_at(buf, &i, "--->")) {
2626 if (looking_at(buf, &i, "* shouts: ") ||
2627 looking_at(buf, &i, "--> ")) {
2628 if (appData.colorize) {
2629 if (oldi > next_out) {
2630 SendToPlayer(&buf[next_out], oldi - next_out);
2633 Colorize(ColorShout, FALSE);
2634 curColor = ColorShout;
2637 started = STARTED_CHATTER;
2641 if (looking_at( buf, &i, "Challenge:")) {
2642 if (appData.colorize) {
2643 if (oldi > next_out) {
2644 SendToPlayer(&buf[next_out], oldi - next_out);
2647 Colorize(ColorChallenge, FALSE);
2648 curColor = ColorChallenge;
2654 if (looking_at(buf, &i, "* offers you") ||
2655 looking_at(buf, &i, "* offers to be") ||
2656 looking_at(buf, &i, "* would like to") ||
2657 looking_at(buf, &i, "* requests to") ||
2658 looking_at(buf, &i, "Your opponent offers") ||
2659 looking_at(buf, &i, "Your opponent requests")) {
2661 if (appData.colorize) {
2662 if (oldi > next_out) {
2663 SendToPlayer(&buf[next_out], oldi - next_out);
2666 Colorize(ColorRequest, FALSE);
2667 curColor = ColorRequest;
2672 if (looking_at(buf, &i, "* (*) seeking")) {
2673 if (appData.colorize) {
2674 if (oldi > next_out) {
2675 SendToPlayer(&buf[next_out], oldi - next_out);
2678 Colorize(ColorSeek, FALSE);
2679 curColor = ColorSeek;
2684 if (looking_at(buf, &i, "\\ ")) {
2685 if (prevColor != ColorNormal) {
2686 if (oldi > next_out) {
2687 SendToPlayer(&buf[next_out], oldi - next_out);
2690 Colorize(prevColor, TRUE);
2691 curColor = prevColor;
2693 if (savingComment) {
2694 parse_pos = i - oldi;
2695 memcpy(parse, &buf[oldi], parse_pos);
2696 parse[parse_pos] = NULLCHAR;
2697 started = STARTED_COMMENT;
2698 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2699 chattingPartner = savingComment - 3; // kludge to remember the box
2701 started = STARTED_CHATTER;
2706 if (looking_at(buf, &i, "Black Strength :") ||
2707 looking_at(buf, &i, "<<< style 10 board >>>") ||
2708 looking_at(buf, &i, "<10>") ||
2709 looking_at(buf, &i, "#@#")) {
2710 /* Wrong board style */
2712 SendToICS(ics_prefix);
2713 SendToICS("set style 12\n");
2714 SendToICS(ics_prefix);
2715 SendToICS("refresh\n");
2719 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2721 have_sent_ICS_logon = 1;
2725 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2726 (looking_at(buf, &i, "\n<12> ") ||
2727 looking_at(buf, &i, "<12> "))) {
2729 if (oldi > next_out) {
2730 SendToPlayer(&buf[next_out], oldi - next_out);
2733 started = STARTED_BOARD;
2738 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2739 looking_at(buf, &i, "<b1> ")) {
2740 if (oldi > next_out) {
2741 SendToPlayer(&buf[next_out], oldi - next_out);
2744 started = STARTED_HOLDINGS;
2749 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2751 /* Header for a move list -- first line */
2753 switch (ics_getting_history) {
2757 case BeginningOfGame:
2758 /* User typed "moves" or "oldmoves" while we
2759 were idle. Pretend we asked for these
2760 moves and soak them up so user can step
2761 through them and/or save them.
2764 gameMode = IcsObserving;
2767 ics_getting_history = H_GOT_UNREQ_HEADER;
2769 case EditGame: /*?*/
2770 case EditPosition: /*?*/
2771 /* Should above feature work in these modes too? */
2772 /* For now it doesn't */
2773 ics_getting_history = H_GOT_UNWANTED_HEADER;
2776 ics_getting_history = H_GOT_UNWANTED_HEADER;
2781 /* Is this the right one? */
2782 if (gameInfo.white && gameInfo.black &&
2783 strcmp(gameInfo.white, star_match[0]) == 0 &&
2784 strcmp(gameInfo.black, star_match[2]) == 0) {
2786 ics_getting_history = H_GOT_REQ_HEADER;
2789 case H_GOT_REQ_HEADER:
2790 case H_GOT_UNREQ_HEADER:
2791 case H_GOT_UNWANTED_HEADER:
2792 case H_GETTING_MOVES:
2793 /* Should not happen */
2794 DisplayError(_("Error gathering move list: two headers"), 0);
2795 ics_getting_history = H_FALSE;
2799 /* Save player ratings into gameInfo if needed */
2800 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2801 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2802 (gameInfo.whiteRating == -1 ||
2803 gameInfo.blackRating == -1)) {
2805 gameInfo.whiteRating = string_to_rating(star_match[1]);
2806 gameInfo.blackRating = string_to_rating(star_match[3]);
2807 if (appData.debugMode)
2808 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2809 gameInfo.whiteRating, gameInfo.blackRating);
2814 if (looking_at(buf, &i,
2815 "* * match, initial time: * minute*, increment: * second")) {
2816 /* Header for a move list -- second line */
2817 /* Initial board will follow if this is a wild game */
2818 if (gameInfo.event != NULL) free(gameInfo.event);
2819 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2820 gameInfo.event = StrSave(str);
2821 /* [HGM] we switched variant. Translate boards if needed. */
2822 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2826 if (looking_at(buf, &i, "Move ")) {
2827 /* Beginning of a move list */
2828 switch (ics_getting_history) {
2830 /* Normally should not happen */
2831 /* Maybe user hit reset while we were parsing */
2834 /* Happens if we are ignoring a move list that is not
2835 * the one we just requested. Common if the user
2836 * tries to observe two games without turning off
2839 case H_GETTING_MOVES:
2840 /* Should not happen */
2841 DisplayError(_("Error gathering move list: nested"), 0);
2842 ics_getting_history = H_FALSE;
2844 case H_GOT_REQ_HEADER:
2845 ics_getting_history = H_GETTING_MOVES;
2846 started = STARTED_MOVES;
2848 if (oldi > next_out) {
2849 SendToPlayer(&buf[next_out], oldi - next_out);
2852 case H_GOT_UNREQ_HEADER:
2853 ics_getting_history = H_GETTING_MOVES;
2854 started = STARTED_MOVES_NOHIDE;
2857 case H_GOT_UNWANTED_HEADER:
2858 ics_getting_history = H_FALSE;
2864 if (looking_at(buf, &i, "% ") ||
2865 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2866 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2867 if(suppressKibitz) next_out = i;
2868 savingComment = FALSE;
2872 case STARTED_MOVES_NOHIDE:
2873 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2874 parse[parse_pos + i - oldi] = NULLCHAR;
2875 ParseGameHistory(parse);
2877 if (appData.zippyPlay && first.initDone) {
2878 FeedMovesToProgram(&first, forwardMostMove);
2879 if (gameMode == IcsPlayingWhite) {
2880 if (WhiteOnMove(forwardMostMove)) {
2881 if (first.sendTime) {
2882 if (first.useColors) {
2883 SendToProgram("black\n", &first);
2885 SendTimeRemaining(&first, TRUE);
2887 if (first.useColors) {
2888 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2890 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2891 first.maybeThinking = TRUE;
2893 if (first.usePlayother) {
2894 if (first.sendTime) {
2895 SendTimeRemaining(&first, TRUE);
2897 SendToProgram("playother\n", &first);
2903 } else if (gameMode == IcsPlayingBlack) {
2904 if (!WhiteOnMove(forwardMostMove)) {
2905 if (first.sendTime) {
2906 if (first.useColors) {
2907 SendToProgram("white\n", &first);
2909 SendTimeRemaining(&first, FALSE);
2911 if (first.useColors) {
2912 SendToProgram("black\n", &first);
2914 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2915 first.maybeThinking = TRUE;
2917 if (first.usePlayother) {
2918 if (first.sendTime) {
2919 SendTimeRemaining(&first, FALSE);
2921 SendToProgram("playother\n", &first);
2930 if (gameMode == IcsObserving && ics_gamenum == -1) {
2931 /* Moves came from oldmoves or moves command
2932 while we weren't doing anything else.
2934 currentMove = forwardMostMove;
2935 ClearHighlights();/*!!could figure this out*/
2936 flipView = appData.flipView;
2937 DrawPosition(TRUE, boards[currentMove]);
2938 DisplayBothClocks();
2939 sprintf(str, "%s vs. %s",
2940 gameInfo.white, gameInfo.black);
2944 /* Moves were history of an active game */
2945 if (gameInfo.resultDetails != NULL) {
2946 free(gameInfo.resultDetails);
2947 gameInfo.resultDetails = NULL;
2950 HistorySet(parseList, backwardMostMove,
2951 forwardMostMove, currentMove-1);
2952 DisplayMove(currentMove - 1);
2953 if (started == STARTED_MOVES) next_out = i;
2954 started = STARTED_NONE;
2955 ics_getting_history = H_FALSE;
2958 case STARTED_OBSERVE:
2959 started = STARTED_NONE;
2960 SendToICS(ics_prefix);
2961 SendToICS("refresh\n");
2967 if(bookHit) { // [HGM] book: simulate book reply
2968 static char bookMove[MSG_SIZ]; // a bit generous?
2970 programStats.nodes = programStats.depth = programStats.time =
2971 programStats.score = programStats.got_only_move = 0;
2972 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2974 strcpy(bookMove, "move ");
2975 strcat(bookMove, bookHit);
2976 HandleMachineMove(bookMove, &first);
2981 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2982 started == STARTED_HOLDINGS ||
2983 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2984 /* Accumulate characters in move list or board */
2985 parse[parse_pos++] = buf[i];
2988 /* Start of game messages. Mostly we detect start of game
2989 when the first board image arrives. On some versions
2990 of the ICS, though, we need to do a "refresh" after starting
2991 to observe in order to get the current board right away. */
2992 if (looking_at(buf, &i, "Adding game * to observation list")) {
2993 started = STARTED_OBSERVE;
2997 /* Handle auto-observe */
2998 if (appData.autoObserve &&
2999 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3000 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3002 /* Choose the player that was highlighted, if any. */
3003 if (star_match[0][0] == '\033' ||
3004 star_match[1][0] != '\033') {
3005 player = star_match[0];
3007 player = star_match[2];
3009 sprintf(str, "%sobserve %s\n",
3010 ics_prefix, StripHighlightAndTitle(player));
3013 /* Save ratings from notify string */
3014 strcpy(player1Name, star_match[0]);
3015 player1Rating = string_to_rating(star_match[1]);
3016 strcpy(player2Name, star_match[2]);
3017 player2Rating = string_to_rating(star_match[3]);
3019 if (appData.debugMode)
3021 "Ratings from 'Game notification:' %s %d, %s %d\n",
3022 player1Name, player1Rating,
3023 player2Name, player2Rating);
3028 /* Deal with automatic examine mode after a game,
3029 and with IcsObserving -> IcsExamining transition */
3030 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3031 looking_at(buf, &i, "has made you an examiner of game *")) {
3033 int gamenum = atoi(star_match[0]);
3034 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3035 gamenum == ics_gamenum) {
3036 /* We were already playing or observing this game;
3037 no need to refetch history */
3038 gameMode = IcsExamining;
3040 pauseExamForwardMostMove = forwardMostMove;
3041 } else if (currentMove < forwardMostMove) {
3042 ForwardInner(forwardMostMove);
3045 /* I don't think this case really can happen */
3046 SendToICS(ics_prefix);
3047 SendToICS("refresh\n");
3052 /* Error messages */
3053 // if (ics_user_moved) {
3054 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3055 if (looking_at(buf, &i, "Illegal move") ||
3056 looking_at(buf, &i, "Not a legal move") ||
3057 looking_at(buf, &i, "Your king is in check") ||
3058 looking_at(buf, &i, "It isn't your turn") ||
3059 looking_at(buf, &i, "It is not your move")) {
3061 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3062 currentMove = forwardMostMove-1;
3063 DisplayMove(currentMove - 1); /* before DMError */
3064 DrawPosition(FALSE, boards[currentMove]);
3065 SwitchClocks(forwardMostMove-1); // [HGM] race
3066 DisplayBothClocks();
3068 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3074 if (looking_at(buf, &i, "still have time") ||
3075 looking_at(buf, &i, "not out of time") ||
3076 looking_at(buf, &i, "either player is out of time") ||
3077 looking_at(buf, &i, "has timeseal; checking")) {
3078 /* We must have called his flag a little too soon */
3079 whiteFlag = blackFlag = FALSE;
3083 if (looking_at(buf, &i, "added * seconds to") ||
3084 looking_at(buf, &i, "seconds were added to")) {
3085 /* Update the clocks */
3086 SendToICS(ics_prefix);
3087 SendToICS("refresh\n");
3091 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3092 ics_clock_paused = TRUE;
3097 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3098 ics_clock_paused = FALSE;
3103 /* Grab player ratings from the Creating: message.
3104 Note we have to check for the special case when
3105 the ICS inserts things like [white] or [black]. */
3106 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3107 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3109 0 player 1 name (not necessarily white)
3111 2 empty, white, or black (IGNORED)
3112 3 player 2 name (not necessarily black)
3115 The names/ratings are sorted out when the game
3116 actually starts (below).
3118 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3119 player1Rating = string_to_rating(star_match[1]);
3120 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3121 player2Rating = string_to_rating(star_match[4]);
3123 if (appData.debugMode)
3125 "Ratings from 'Creating:' %s %d, %s %d\n",
3126 player1Name, player1Rating,
3127 player2Name, player2Rating);
3132 /* Improved generic start/end-of-game messages */
3133 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3134 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3135 /* If tkind == 0: */
3136 /* star_match[0] is the game number */
3137 /* [1] is the white player's name */
3138 /* [2] is the black player's name */
3139 /* For end-of-game: */
3140 /* [3] is the reason for the game end */
3141 /* [4] is a PGN end game-token, preceded by " " */
3142 /* For start-of-game: */
3143 /* [3] begins with "Creating" or "Continuing" */
3144 /* [4] is " *" or empty (don't care). */
3145 int gamenum = atoi(star_match[0]);
3146 char *whitename, *blackname, *why, *endtoken;
3147 ChessMove endtype = (ChessMove) 0;
3150 whitename = star_match[1];
3151 blackname = star_match[2];
3152 why = star_match[3];
3153 endtoken = star_match[4];
3155 whitename = star_match[1];
3156 blackname = star_match[3];
3157 why = star_match[5];
3158 endtoken = star_match[6];
3161 /* Game start messages */
3162 if (strncmp(why, "Creating ", 9) == 0 ||
3163 strncmp(why, "Continuing ", 11) == 0) {
3164 gs_gamenum = gamenum;
3165 strcpy(gs_kind, strchr(why, ' ') + 1);
3166 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3168 if (appData.zippyPlay) {
3169 ZippyGameStart(whitename, blackname);
3175 /* Game end messages */
3176 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3177 ics_gamenum != gamenum) {
3180 while (endtoken[0] == ' ') endtoken++;
3181 switch (endtoken[0]) {
3184 endtype = GameUnfinished;
3187 endtype = BlackWins;
3190 if (endtoken[1] == '/')
3191 endtype = GameIsDrawn;
3193 endtype = WhiteWins;
3196 GameEnds(endtype, why, GE_ICS);
3198 if (appData.zippyPlay && first.initDone) {
3199 ZippyGameEnd(endtype, why);
3200 if (first.pr == NULL) {
3201 /* Start the next process early so that we'll
3202 be ready for the next challenge */
3203 StartChessProgram(&first);
3205 /* Send "new" early, in case this command takes
3206 a long time to finish, so that we'll be ready
3207 for the next challenge. */
3208 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3215 if (looking_at(buf, &i, "Removing game * from observation") ||
3216 looking_at(buf, &i, "no longer observing game *") ||
3217 looking_at(buf, &i, "Game * (*) has no examiners")) {
3218 if (gameMode == IcsObserving &&
3219 atoi(star_match[0]) == ics_gamenum)
3221 /* icsEngineAnalyze */
3222 if (appData.icsEngineAnalyze) {
3229 ics_user_moved = FALSE;
3234 if (looking_at(buf, &i, "no longer examining game *")) {
3235 if (gameMode == IcsExamining &&
3236 atoi(star_match[0]) == ics_gamenum)
3240 ics_user_moved = FALSE;
3245 /* Advance leftover_start past any newlines we find,
3246 so only partial lines can get reparsed */
3247 if (looking_at(buf, &i, "\n")) {
3248 prevColor = curColor;
3249 if (curColor != ColorNormal) {
3250 if (oldi > next_out) {
3251 SendToPlayer(&buf[next_out], oldi - next_out);
3254 Colorize(ColorNormal, FALSE);
3255 curColor = ColorNormal;
3257 if (started == STARTED_BOARD) {
3258 started = STARTED_NONE;
3259 parse[parse_pos] = NULLCHAR;
3260 ParseBoard12(parse);
3263 /* Send premove here */
3264 if (appData.premove) {
3266 if (currentMove == 0 &&
3267 gameMode == IcsPlayingWhite &&
3268 appData.premoveWhite) {
3269 sprintf(str, "%s\n", appData.premoveWhiteText);
3270 if (appData.debugMode)
3271 fprintf(debugFP, "Sending premove:\n");
3273 } else if (currentMove == 1 &&
3274 gameMode == IcsPlayingBlack &&
3275 appData.premoveBlack) {
3276 sprintf(str, "%s\n", appData.premoveBlackText);
3277 if (appData.debugMode)
3278 fprintf(debugFP, "Sending premove:\n");
3280 } else if (gotPremove) {
3282 ClearPremoveHighlights();
3283 if (appData.debugMode)
3284 fprintf(debugFP, "Sending premove:\n");
3285 UserMoveEvent(premoveFromX, premoveFromY,
3286 premoveToX, premoveToY,
3291 /* Usually suppress following prompt */
3292 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3293 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3294 if (looking_at(buf, &i, "*% ")) {
3295 savingComment = FALSE;
3300 } else if (started == STARTED_HOLDINGS) {
3302 char new_piece[MSG_SIZ];
3303 started = STARTED_NONE;
3304 parse[parse_pos] = NULLCHAR;
3305 if (appData.debugMode)
3306 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3307 parse, currentMove);
3308 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3309 gamenum == ics_gamenum) {
3310 if (gameInfo.variant == VariantNormal) {
3311 /* [HGM] We seem to switch variant during a game!
3312 * Presumably no holdings were displayed, so we have
3313 * to move the position two files to the right to
3314 * create room for them!
3316 VariantClass newVariant;
3317 switch(gameInfo.boardWidth) { // base guess on board width
3318 case 9: newVariant = VariantShogi; break;
3319 case 10: newVariant = VariantGreat; break;
3320 default: newVariant = VariantCrazyhouse; break;
3322 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3323 /* Get a move list just to see the header, which
3324 will tell us whether this is really bug or zh */
3325 if (ics_getting_history == H_FALSE) {
3326 ics_getting_history = H_REQUESTED;
3327 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3331 new_piece[0] = NULLCHAR;
3332 sscanf(parse, "game %d white [%s black [%s <- %s",
3333 &gamenum, white_holding, black_holding,
3335 white_holding[strlen(white_holding)-1] = NULLCHAR;
3336 black_holding[strlen(black_holding)-1] = NULLCHAR;
3337 /* [HGM] copy holdings to board holdings area */
3338 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3339 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3340 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3342 if (appData.zippyPlay && first.initDone) {
3343 ZippyHoldings(white_holding, black_holding,
3347 if (tinyLayout || smallLayout) {
3348 char wh[16], bh[16];
3349 PackHolding(wh, white_holding);
3350 PackHolding(bh, black_holding);
3351 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3352 gameInfo.white, gameInfo.black);
3354 sprintf(str, "%s [%s] vs. %s [%s]",
3355 gameInfo.white, white_holding,
3356 gameInfo.black, black_holding);
3359 DrawPosition(FALSE, boards[currentMove]);
3362 /* Suppress following prompt */
3363 if (looking_at(buf, &i, "*% ")) {
3364 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3365 savingComment = FALSE;
3373 i++; /* skip unparsed character and loop back */
3376 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3377 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3378 // SendToPlayer(&buf[next_out], i - next_out);
3379 started != STARTED_HOLDINGS && leftover_start > next_out) {
3380 SendToPlayer(&buf[next_out], leftover_start - next_out);
3384 leftover_len = buf_len - leftover_start;
3385 /* if buffer ends with something we couldn't parse,
3386 reparse it after appending the next read */
3388 } else if (count == 0) {
3389 RemoveInputSource(isr);
3390 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3392 DisplayFatalError(_("Error reading from ICS"), error, 1);
3397 /* Board style 12 looks like this:
3399 <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
3401 * The "<12> " is stripped before it gets to this routine. The two
3402 * trailing 0's (flip state and clock ticking) are later addition, and
3403 * some chess servers may not have them, or may have only the first.
3404 * Additional trailing fields may be added in the future.
3407 #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"
3409 #define RELATION_OBSERVING_PLAYED 0
3410 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3411 #define RELATION_PLAYING_MYMOVE 1
3412 #define RELATION_PLAYING_NOTMYMOVE -1
3413 #define RELATION_EXAMINING 2
3414 #define RELATION_ISOLATED_BOARD -3
3415 #define RELATION_STARTING_POSITION -4 /* FICS only */
3418 ParseBoard12(string)
3421 GameMode newGameMode;
3422 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3423 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3424 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3425 char to_play, board_chars[200];
3426 char move_str[500], str[500], elapsed_time[500];
3427 char black[32], white[32];
3429 int prevMove = currentMove;
3432 int fromX, fromY, toX, toY;
3434 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3435 char *bookHit = NULL; // [HGM] book
3436 Boolean weird = FALSE, reqFlag = FALSE;
3438 fromX = fromY = toX = toY = -1;
3442 if (appData.debugMode)
3443 fprintf(debugFP, _("Parsing board: %s\n"), string);
3445 move_str[0] = NULLCHAR;
3446 elapsed_time[0] = NULLCHAR;
3447 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3449 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3450 if(string[i] == ' ') { ranks++; files = 0; }
3452 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3455 for(j = 0; j <i; j++) board_chars[j] = string[j];
3456 board_chars[i] = '\0';
3459 n = sscanf(string, PATTERN, &to_play, &double_push,
3460 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3461 &gamenum, white, black, &relation, &basetime, &increment,
3462 &white_stren, &black_stren, &white_time, &black_time,
3463 &moveNum, str, elapsed_time, move_str, &ics_flip,
3467 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3468 DisplayError(str, 0);
3472 /* Convert the move number to internal form */
3473 moveNum = (moveNum - 1) * 2;
3474 if (to_play == 'B') moveNum++;
3475 if (moveNum >= MAX_MOVES) {
3476 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3482 case RELATION_OBSERVING_PLAYED:
3483 case RELATION_OBSERVING_STATIC:
3484 if (gamenum == -1) {
3485 /* Old ICC buglet */
3486 relation = RELATION_OBSERVING_STATIC;
3488 newGameMode = IcsObserving;
3490 case RELATION_PLAYING_MYMOVE:
3491 case RELATION_PLAYING_NOTMYMOVE:
3493 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3494 IcsPlayingWhite : IcsPlayingBlack;
3496 case RELATION_EXAMINING:
3497 newGameMode = IcsExamining;
3499 case RELATION_ISOLATED_BOARD:
3501 /* Just display this board. If user was doing something else,
3502 we will forget about it until the next board comes. */
3503 newGameMode = IcsIdle;
3505 case RELATION_STARTING_POSITION:
3506 newGameMode = gameMode;
3510 /* Modify behavior for initial board display on move listing
3513 switch (ics_getting_history) {
3517 case H_GOT_REQ_HEADER:
3518 case H_GOT_UNREQ_HEADER:
3519 /* This is the initial position of the current game */
3520 gamenum = ics_gamenum;
3521 moveNum = 0; /* old ICS bug workaround */
3522 if (to_play == 'B') {
3523 startedFromSetupPosition = TRUE;
3524 blackPlaysFirst = TRUE;
3526 if (forwardMostMove == 0) forwardMostMove = 1;
3527 if (backwardMostMove == 0) backwardMostMove = 1;
3528 if (currentMove == 0) currentMove = 1;
3530 newGameMode = gameMode;
3531 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3533 case H_GOT_UNWANTED_HEADER:
3534 /* This is an initial board that we don't want */
3536 case H_GETTING_MOVES:
3537 /* Should not happen */
3538 DisplayError(_("Error gathering move list: extra board"), 0);
3539 ics_getting_history = H_FALSE;
3543 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3544 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3545 /* [HGM] We seem to have switched variant unexpectedly
3546 * Try to guess new variant from board size
3548 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3549 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3550 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3551 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3552 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3553 if(!weird) newVariant = VariantNormal;
3554 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3555 /* Get a move list just to see the header, which
3556 will tell us whether this is really bug or zh */
3557 if (ics_getting_history == H_FALSE) {
3558 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3559 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3564 /* Take action if this is the first board of a new game, or of a
3565 different game than is currently being displayed. */
3566 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3567 relation == RELATION_ISOLATED_BOARD) {
3569 /* Forget the old game and get the history (if any) of the new one */
3570 if (gameMode != BeginningOfGame) {
3574 if (appData.autoRaiseBoard) BoardToTop();
3576 if (gamenum == -1) {
3577 newGameMode = IcsIdle;
3578 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3579 appData.getMoveList && !reqFlag) {
3580 /* Need to get game history */
3581 ics_getting_history = H_REQUESTED;
3582 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3586 /* Initially flip the board to have black on the bottom if playing
3587 black or if the ICS flip flag is set, but let the user change
3588 it with the Flip View button. */
3589 flipView = appData.autoFlipView ?
3590 (newGameMode == IcsPlayingBlack) || ics_flip :
3593 /* Done with values from previous mode; copy in new ones */
3594 gameMode = newGameMode;
3596 ics_gamenum = gamenum;
3597 if (gamenum == gs_gamenum) {
3598 int klen = strlen(gs_kind);
3599 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3600 sprintf(str, "ICS %s", gs_kind);
3601 gameInfo.event = StrSave(str);
3603 gameInfo.event = StrSave("ICS game");
3605 gameInfo.site = StrSave(appData.icsHost);
3606 gameInfo.date = PGNDate();
3607 gameInfo.round = StrSave("-");
3608 gameInfo.white = StrSave(white);
3609 gameInfo.black = StrSave(black);
3610 timeControl = basetime * 60 * 1000;
3612 timeIncrement = increment * 1000;
3613 movesPerSession = 0;
3614 gameInfo.timeControl = TimeControlTagValue();
3615 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3616 if (appData.debugMode) {
3617 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3618 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3619 setbuf(debugFP, NULL);
3622 gameInfo.outOfBook = NULL;
3624 /* Do we have the ratings? */
3625 if (strcmp(player1Name, white) == 0 &&
3626 strcmp(player2Name, black) == 0) {
3627 if (appData.debugMode)
3628 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3629 player1Rating, player2Rating);
3630 gameInfo.whiteRating = player1Rating;
3631 gameInfo.blackRating = player2Rating;
3632 } else if (strcmp(player2Name, white) == 0 &&
3633 strcmp(player1Name, black) == 0) {
3634 if (appData.debugMode)
3635 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3636 player2Rating, player1Rating);
3637 gameInfo.whiteRating = player2Rating;
3638 gameInfo.blackRating = player1Rating;
3640 player1Name[0] = player2Name[0] = NULLCHAR;
3642 /* Silence shouts if requested */
3643 if (appData.quietPlay &&
3644 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3645 SendToICS(ics_prefix);
3646 SendToICS("set shout 0\n");
3650 /* Deal with midgame name changes */
3652 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3653 if (gameInfo.white) free(gameInfo.white);
3654 gameInfo.white = StrSave(white);
3656 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3657 if (gameInfo.black) free(gameInfo.black);
3658 gameInfo.black = StrSave(black);
3662 /* Throw away game result if anything actually changes in examine mode */
3663 if (gameMode == IcsExamining && !newGame) {
3664 gameInfo.result = GameUnfinished;
3665 if (gameInfo.resultDetails != NULL) {
3666 free(gameInfo.resultDetails);
3667 gameInfo.resultDetails = NULL;
3671 /* In pausing && IcsExamining mode, we ignore boards coming
3672 in if they are in a different variation than we are. */
3673 if (pauseExamInvalid) return;
3674 if (pausing && gameMode == IcsExamining) {
3675 if (moveNum <= pauseExamForwardMostMove) {
3676 pauseExamInvalid = TRUE;
3677 forwardMostMove = pauseExamForwardMostMove;
3682 if (appData.debugMode) {
3683 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3685 /* Parse the board */
3686 for (k = 0; k < ranks; k++) {
3687 for (j = 0; j < files; j++)
3688 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3689 if(gameInfo.holdingsWidth > 1) {
3690 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3691 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3694 CopyBoard(boards[moveNum], board);
3695 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3697 startedFromSetupPosition =
3698 !CompareBoards(board, initialPosition);
3699 if(startedFromSetupPosition)
3700 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3703 /* [HGM] Set castling rights. Take the outermost Rooks,
3704 to make it also work for FRC opening positions. Note that board12
3705 is really defective for later FRC positions, as it has no way to
3706 indicate which Rook can castle if they are on the same side of King.
3707 For the initial position we grant rights to the outermost Rooks,
3708 and remember thos rights, and we then copy them on positions
3709 later in an FRC game. This means WB might not recognize castlings with
3710 Rooks that have moved back to their original position as illegal,
3711 but in ICS mode that is not its job anyway.
3713 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3714 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3716 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3717 if(board[0][i] == WhiteRook) j = i;
3718 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3719 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3720 if(board[0][i] == WhiteRook) j = i;
3721 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3722 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3723 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3724 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3725 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3726 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3727 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3729 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3730 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3731 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3732 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3733 if(board[BOARD_HEIGHT-1][k] == bKing)
3734 initialRights[5] = castlingRights[moveNum][5] = k;
3735 if(gameInfo.variant == VariantTwoKings) {
3736 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3737 if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3738 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3741 r = castlingRights[moveNum][0] = initialRights[0];
3742 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3743 r = castlingRights[moveNum][1] = initialRights[1];
3744 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3745 r = castlingRights[moveNum][3] = initialRights[3];
3746 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3747 r = castlingRights[moveNum][4] = initialRights[4];
3748 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3749 /* wildcastle kludge: always assume King has rights */
3750 r = castlingRights[moveNum][2] = initialRights[2];
3751 r = castlingRights[moveNum][5] = initialRights[5];
3753 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3754 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3757 if (ics_getting_history == H_GOT_REQ_HEADER ||
3758 ics_getting_history == H_GOT_UNREQ_HEADER) {
3759 /* This was an initial position from a move list, not
3760 the current position */
3764 /* Update currentMove and known move number limits */
3765 newMove = newGame || moveNum > forwardMostMove;
3768 forwardMostMove = backwardMostMove = currentMove = moveNum;
3769 if (gameMode == IcsExamining && moveNum == 0) {
3770 /* Workaround for ICS limitation: we are not told the wild
3771 type when starting to examine a game. But if we ask for
3772 the move list, the move list header will tell us */
3773 ics_getting_history = H_REQUESTED;
3774 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3777 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3778 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3780 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3781 /* [HGM] applied this also to an engine that is silently watching */
3782 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3783 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3784 gameInfo.variant == currentlyInitializedVariant) {
3785 takeback = forwardMostMove - moveNum;
3786 for (i = 0; i < takeback; i++) {
3787 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3788 SendToProgram("undo\n", &first);
3793 forwardMostMove = moveNum;
3794 if (!pausing || currentMove > forwardMostMove)
3795 currentMove = forwardMostMove;
3797 /* New part of history that is not contiguous with old part */
3798 if (pausing && gameMode == IcsExamining) {
3799 pauseExamInvalid = TRUE;
3800 forwardMostMove = pauseExamForwardMostMove;
3803 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3805 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3806 // [HGM] when we will receive the move list we now request, it will be
3807 // fed to the engine from the first move on. So if the engine is not
3808 // in the initial position now, bring it there.
3809 InitChessProgram(&first, 0);
3812 ics_getting_history = H_REQUESTED;
3813 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3816 forwardMostMove = backwardMostMove = currentMove = moveNum;
3819 /* Update the clocks */
3820 if (strchr(elapsed_time, '.')) {
3822 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3823 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3825 /* Time is in seconds */
3826 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3827 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3832 if (appData.zippyPlay && newGame &&
3833 gameMode != IcsObserving && gameMode != IcsIdle &&
3834 gameMode != IcsExamining)
3835 ZippyFirstBoard(moveNum, basetime, increment);
3838 /* Put the move on the move list, first converting
3839 to canonical algebraic form. */
3841 if (appData.debugMode) {
3842 if (appData.debugMode) { int f = forwardMostMove;
3843 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3844 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3846 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3847 fprintf(debugFP, "moveNum = %d\n", moveNum);
3848 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3849 setbuf(debugFP, NULL);
3851 if (moveNum <= backwardMostMove) {
3852 /* We don't know what the board looked like before
3854 strcpy(parseList[moveNum - 1], move_str);
3855 strcat(parseList[moveNum - 1], " ");
3856 strcat(parseList[moveNum - 1], elapsed_time);
3857 moveList[moveNum - 1][0] = NULLCHAR;
3858 } else if (strcmp(move_str, "none") == 0) {
3859 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3860 /* Again, we don't know what the board looked like;
3861 this is really the start of the game. */
3862 parseList[moveNum - 1][0] = NULLCHAR;
3863 moveList[moveNum - 1][0] = NULLCHAR;
3864 backwardMostMove = moveNum;
3865 startedFromSetupPosition = TRUE;
3866 fromX = fromY = toX = toY = -1;
3868 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3869 // So we parse the long-algebraic move string in stead of the SAN move
3870 int valid; char buf[MSG_SIZ], *prom;
3872 // str looks something like "Q/a1-a2"; kill the slash
3874 sprintf(buf, "%c%s", str[0], str+2);
3875 else strcpy(buf, str); // might be castling
3876 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3877 strcat(buf, prom); // long move lacks promo specification!
3878 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3879 if(appData.debugMode)
3880 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3881 strcpy(move_str, buf);
3883 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3884 &fromX, &fromY, &toX, &toY, &promoChar)
3885 || ParseOneMove(buf, moveNum - 1, &moveType,
3886 &fromX, &fromY, &toX, &toY, &promoChar);
3887 // end of long SAN patch
3889 (void) CoordsToAlgebraic(boards[moveNum - 1],
3890 PosFlags(moveNum - 1), EP_UNKNOWN,
3891 fromY, fromX, toY, toX, promoChar,
3892 parseList[moveNum-1]);
3893 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3894 castlingRights[moveNum]) ) {
3900 if(gameInfo.variant != VariantShogi)
3901 strcat(parseList[moveNum - 1], "+");
3904 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3905 strcat(parseList[moveNum - 1], "#");
3908 strcat(parseList[moveNum - 1], " ");
3909 strcat(parseList[moveNum - 1], elapsed_time);
3910 /* currentMoveString is set as a side-effect of ParseOneMove */
3911 strcpy(moveList[moveNum - 1], currentMoveString);
3912 strcat(moveList[moveNum - 1], "\n");
3914 /* Move from ICS was illegal!? Punt. */
3915 if (appData.debugMode) {
3916 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3917 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3919 strcpy(parseList[moveNum - 1], move_str);
3920 strcat(parseList[moveNum - 1], " ");
3921 strcat(parseList[moveNum - 1], elapsed_time);
3922 moveList[moveNum - 1][0] = NULLCHAR;
3923 fromX = fromY = toX = toY = -1;
3926 if (appData.debugMode) {
3927 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3928 setbuf(debugFP, NULL);
3932 /* Send move to chess program (BEFORE animating it). */
3933 if (appData.zippyPlay && !newGame && newMove &&
3934 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3936 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3937 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3938 if (moveList[moveNum - 1][0] == NULLCHAR) {
3939 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3941 DisplayError(str, 0);
3943 if (first.sendTime) {
3944 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3946 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3947 if (firstMove && !bookHit) {
3949 if (first.useColors) {
3950 SendToProgram(gameMode == IcsPlayingWhite ?
3952 "black\ngo\n", &first);
3954 SendToProgram("go\n", &first);
3956 first.maybeThinking = TRUE;
3959 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3960 if (moveList[moveNum - 1][0] == NULLCHAR) {
3961 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3962 DisplayError(str, 0);
3964 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3965 SendMoveToProgram(moveNum - 1, &first);
3972 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3973 /* If move comes from a remote source, animate it. If it
3974 isn't remote, it will have already been animated. */
3975 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3976 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3978 if (!pausing && appData.highlightLastMove) {
3979 SetHighlights(fromX, fromY, toX, toY);
3983 /* Start the clocks */
3984 whiteFlag = blackFlag = FALSE;
3985 appData.clockMode = !(basetime == 0 && increment == 0);
3987 ics_clock_paused = TRUE;
3989 } else if (ticking == 1) {
3990 ics_clock_paused = FALSE;
3992 if (gameMode == IcsIdle ||
3993 relation == RELATION_OBSERVING_STATIC ||
3994 relation == RELATION_EXAMINING ||
3996 DisplayBothClocks();
4000 /* Display opponents and material strengths */
4001 if (gameInfo.variant != VariantBughouse &&
4002 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4003 if (tinyLayout || smallLayout) {
4004 if(gameInfo.variant == VariantNormal)
4005 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4006 gameInfo.white, white_stren, gameInfo.black, black_stren,
4007 basetime, increment);
4009 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4010 gameInfo.white, white_stren, gameInfo.black, black_stren,
4011 basetime, increment, (int) gameInfo.variant);
4013 if(gameInfo.variant == VariantNormal)
4014 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4015 gameInfo.white, white_stren, gameInfo.black, black_stren,
4016 basetime, increment);
4018 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4019 gameInfo.white, white_stren, gameInfo.black, black_stren,
4020 basetime, increment, VariantName(gameInfo.variant));
4023 if (appData.debugMode) {
4024 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4029 /* Display the board */
4030 if (!pausing && !appData.noGUI) {
4032 if (appData.premove)
4034 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4035 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4036 ClearPremoveHighlights();
4038 DrawPosition(FALSE, boards[currentMove]);
4039 DisplayMove(moveNum - 1);
4040 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4041 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4042 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4043 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4047 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4049 if(bookHit) { // [HGM] book: simulate book reply
4050 static char bookMove[MSG_SIZ]; // a bit generous?
4052 programStats.nodes = programStats.depth = programStats.time =
4053 programStats.score = programStats.got_only_move = 0;
4054 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4056 strcpy(bookMove, "move ");
4057 strcat(bookMove, bookHit);
4058 HandleMachineMove(bookMove, &first);
4067 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4068 ics_getting_history = H_REQUESTED;
4069 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4075 AnalysisPeriodicEvent(force)
4078 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4079 && !force) || !appData.periodicUpdates)
4082 /* Send . command to Crafty to collect stats */
4083 SendToProgram(".\n", &first);
4085 /* Don't send another until we get a response (this makes
4086 us stop sending to old Crafty's which don't understand
4087 the "." command (sending illegal cmds resets node count & time,
4088 which looks bad)) */
4089 programStats.ok_to_send = 0;
4092 void ics_update_width(new_width)
4095 ics_printf("set width %d\n", new_width);
4099 SendMoveToProgram(moveNum, cps)
4101 ChessProgramState *cps;
4105 if (cps->useUsermove) {
4106 SendToProgram("usermove ", cps);
4110 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4111 int len = space - parseList[moveNum];
4112 memcpy(buf, parseList[moveNum], len);
4114 buf[len] = NULLCHAR;
4116 sprintf(buf, "%s\n", parseList[moveNum]);
4118 SendToProgram(buf, cps);
4120 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4121 AlphaRank(moveList[moveNum], 4);
4122 SendToProgram(moveList[moveNum], cps);
4123 AlphaRank(moveList[moveNum], 4); // and back
4125 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4126 * the engine. It would be nice to have a better way to identify castle
4128 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4129 && cps->useOOCastle) {
4130 int fromX = moveList[moveNum][0] - AAA;
4131 int fromY = moveList[moveNum][1] - ONE;
4132 int toX = moveList[moveNum][2] - AAA;
4133 int toY = moveList[moveNum][3] - ONE;
4134 if((boards[moveNum][fromY][fromX] == WhiteKing
4135 && boards[moveNum][toY][toX] == WhiteRook)
4136 || (boards[moveNum][fromY][fromX] == BlackKing
4137 && boards[moveNum][toY][toX] == BlackRook)) {
4138 if(toX > fromX) SendToProgram("O-O\n", cps);
4139 else SendToProgram("O-O-O\n", cps);
4141 else SendToProgram(moveList[moveNum], cps);
4143 else SendToProgram(moveList[moveNum], cps);
4144 /* End of additions by Tord */
4147 /* [HGM] setting up the opening has brought engine in force mode! */
4148 /* Send 'go' if we are in a mode where machine should play. */
4149 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4150 (gameMode == TwoMachinesPlay ||
4152 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4154 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4155 SendToProgram("go\n", cps);
4156 if (appData.debugMode) {
4157 fprintf(debugFP, "(extra)\n");
4160 setboardSpoiledMachineBlack = 0;
4164 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4166 int fromX, fromY, toX, toY;
4168 char user_move[MSG_SIZ];
4172 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4173 (int)moveType, fromX, fromY, toX, toY);
4174 DisplayError(user_move + strlen("say "), 0);
4176 case WhiteKingSideCastle:
4177 case BlackKingSideCastle:
4178 case WhiteQueenSideCastleWild:
4179 case BlackQueenSideCastleWild:
4181 case WhiteHSideCastleFR:
4182 case BlackHSideCastleFR:
4184 sprintf(user_move, "o-o\n");
4186 case WhiteQueenSideCastle:
4187 case BlackQueenSideCastle:
4188 case WhiteKingSideCastleWild:
4189 case BlackKingSideCastleWild:
4191 case WhiteASideCastleFR:
4192 case BlackASideCastleFR:
4194 sprintf(user_move, "o-o-o\n");
4196 case WhitePromotionQueen:
4197 case BlackPromotionQueen:
4198 case WhitePromotionRook:
4199 case BlackPromotionRook:
4200 case WhitePromotionBishop:
4201 case BlackPromotionBishop:
4202 case WhitePromotionKnight:
4203 case BlackPromotionKnight:
4204 case WhitePromotionKing:
4205 case BlackPromotionKing:
4206 case WhitePromotionChancellor:
4207 case BlackPromotionChancellor:
4208 case WhitePromotionArchbishop:
4209 case BlackPromotionArchbishop:
4210 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4211 sprintf(user_move, "%c%c%c%c=%c\n",
4212 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4213 PieceToChar(WhiteFerz));
4214 else if(gameInfo.variant == VariantGreat)
4215 sprintf(user_move, "%c%c%c%c=%c\n",
4216 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4217 PieceToChar(WhiteMan));
4219 sprintf(user_move, "%c%c%c%c=%c\n",
4220 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4221 PieceToChar(PromoPiece(moveType)));
4225 sprintf(user_move, "%c@%c%c\n",
4226 ToUpper(PieceToChar((ChessSquare) fromX)),
4227 AAA + toX, ONE + toY);
4230 case WhiteCapturesEnPassant:
4231 case BlackCapturesEnPassant:
4232 case IllegalMove: /* could be a variant we don't quite understand */
4233 sprintf(user_move, "%c%c%c%c\n",
4234 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4237 SendToICS(user_move);
4238 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4239 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4243 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4248 if (rf == DROP_RANK) {
4249 sprintf(move, "%c@%c%c\n",
4250 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4252 if (promoChar == 'x' || promoChar == NULLCHAR) {
4253 sprintf(move, "%c%c%c%c\n",
4254 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4256 sprintf(move, "%c%c%c%c%c\n",
4257 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4263 ProcessICSInitScript(f)
4268 while (fgets(buf, MSG_SIZ, f)) {
4269 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4276 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4278 AlphaRank(char *move, int n)
4280 // char *p = move, c; int x, y;
4282 if (appData.debugMode) {
4283 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4287 move[2]>='0' && move[2]<='9' &&
4288 move[3]>='a' && move[3]<='x' ) {
4290 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4291 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4293 if(move[0]>='0' && move[0]<='9' &&
4294 move[1]>='a' && move[1]<='x' &&
4295 move[2]>='0' && move[2]<='9' &&
4296 move[3]>='a' && move[3]<='x' ) {
4297 /* input move, Shogi -> normal */
4298 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4299 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4300 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4301 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4304 move[3]>='0' && move[3]<='9' &&
4305 move[2]>='a' && move[2]<='x' ) {
4307 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4308 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4311 move[0]>='a' && move[0]<='x' &&
4312 move[3]>='0' && move[3]<='9' &&
4313 move[2]>='a' && move[2]<='x' ) {
4314 /* output move, normal -> Shogi */
4315 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4316 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4317 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4318 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4319 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4321 if (appData.debugMode) {
4322 fprintf(debugFP, " out = '%s'\n", move);
4326 /* Parser for moves from gnuchess, ICS, or user typein box */
4328 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4331 ChessMove *moveType;
4332 int *fromX, *fromY, *toX, *toY;
4335 if (appData.debugMode) {
4336 fprintf(debugFP, "move to parse: %s\n", move);
4338 *moveType = yylexstr(moveNum, move);
4340 switch (*moveType) {
4341 case WhitePromotionChancellor:
4342 case BlackPromotionChancellor:
4343 case WhitePromotionArchbishop:
4344 case BlackPromotionArchbishop:
4345 case WhitePromotionQueen:
4346 case BlackPromotionQueen:
4347 case WhitePromotionRook:
4348 case BlackPromotionRook:
4349 case WhitePromotionBishop:
4350 case BlackPromotionBishop:
4351 case WhitePromotionKnight:
4352 case BlackPromotionKnight:
4353 case WhitePromotionKing:
4354 case BlackPromotionKing:
4356 case WhiteCapturesEnPassant:
4357 case BlackCapturesEnPassant:
4358 case WhiteKingSideCastle:
4359 case WhiteQueenSideCastle:
4360 case BlackKingSideCastle:
4361 case BlackQueenSideCastle:
4362 case WhiteKingSideCastleWild:
4363 case WhiteQueenSideCastleWild:
4364 case BlackKingSideCastleWild:
4365 case BlackQueenSideCastleWild:
4366 /* Code added by Tord: */
4367 case WhiteHSideCastleFR:
4368 case WhiteASideCastleFR:
4369 case BlackHSideCastleFR:
4370 case BlackASideCastleFR:
4371 /* End of code added by Tord */
4372 case IllegalMove: /* bug or odd chess variant */
4373 *fromX = currentMoveString[0] - AAA;
4374 *fromY = currentMoveString[1] - ONE;
4375 *toX = currentMoveString[2] - AAA;
4376 *toY = currentMoveString[3] - ONE;
4377 *promoChar = currentMoveString[4];
4378 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4379 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4380 if (appData.debugMode) {
4381 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4383 *fromX = *fromY = *toX = *toY = 0;
4386 if (appData.testLegality) {
4387 return (*moveType != IllegalMove);
4389 return !(*fromX == *toX && *fromY == *toY);
4394 *fromX = *moveType == WhiteDrop ?
4395 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4396 (int) CharToPiece(ToLower(currentMoveString[0]));
4398 *toX = currentMoveString[2] - AAA;
4399 *toY = currentMoveString[3] - ONE;
4400 *promoChar = NULLCHAR;
4404 case ImpossibleMove:
4405 case (ChessMove) 0: /* end of file */
4414 if (appData.debugMode) {
4415 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4418 *fromX = *fromY = *toX = *toY = 0;
4419 *promoChar = NULLCHAR;
4424 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4425 // All positions will have equal probability, but the current method will not provide a unique
4426 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4432 int piecesLeft[(int)BlackPawn];
4433 int seed, nrOfShuffles;
4435 void GetPositionNumber()
4436 { // sets global variable seed
4439 seed = appData.defaultFrcPosition;
4440 if(seed < 0) { // randomize based on time for negative FRC position numbers
4441 for(i=0; i<50; i++) seed += random();
4442 seed = random() ^ random() >> 8 ^ random() << 8;
4443 if(seed<0) seed = -seed;
4447 int put(Board board, int pieceType, int rank, int n, int shade)
4448 // put the piece on the (n-1)-th empty squares of the given shade
4452 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4453 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4454 board[rank][i] = (ChessSquare) pieceType;
4455 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4457 piecesLeft[pieceType]--;
4465 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4466 // calculate where the next piece goes, (any empty square), and put it there
4470 i = seed % squaresLeft[shade];
4471 nrOfShuffles *= squaresLeft[shade];
4472 seed /= squaresLeft[shade];
4473 put(board, pieceType, rank, i, shade);
4476 void AddTwoPieces(Board board, int pieceType, int rank)
4477 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4479 int i, n=squaresLeft[ANY], j=n-1, k;
4481 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4482 i = seed % k; // pick one
4485 while(i >= j) i -= j--;
4486 j = n - 1 - j; i += j;
4487 put(board, pieceType, rank, j, ANY);
4488 put(board, pieceType, rank, i, ANY);
4491 void SetUpShuffle(Board board, int number)
4495 GetPositionNumber(); nrOfShuffles = 1;
4497 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4498 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4499 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4501 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4503 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4504 p = (int) board[0][i];
4505 if(p < (int) BlackPawn) piecesLeft[p] ++;
4506 board[0][i] = EmptySquare;
4509 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4510 // shuffles restricted to allow normal castling put KRR first
4511 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4512 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4513 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4514 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4515 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4516 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4517 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4518 put(board, WhiteRook, 0, 0, ANY);
4519 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4522 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4523 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4524 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4525 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4526 while(piecesLeft[p] >= 2) {
4527 AddOnePiece(board, p, 0, LITE);
4528 AddOnePiece(board, p, 0, DARK);
4530 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4533 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4534 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4535 // but we leave King and Rooks for last, to possibly obey FRC restriction
4536 if(p == (int)WhiteRook) continue;
4537 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4538 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4541 // now everything is placed, except perhaps King (Unicorn) and Rooks
4543 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4544 // Last King gets castling rights
4545 while(piecesLeft[(int)WhiteUnicorn]) {
4546 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4547 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4550 while(piecesLeft[(int)WhiteKing]) {
4551 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4552 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4557 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4558 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4561 // Only Rooks can be left; simply place them all
4562 while(piecesLeft[(int)WhiteRook]) {
4563 i = put(board, WhiteRook, 0, 0, ANY);
4564 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4567 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4569 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4572 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4573 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4576 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4579 int SetCharTable( char *table, const char * map )
4580 /* [HGM] moved here from winboard.c because of its general usefulness */
4581 /* Basically a safe strcpy that uses the last character as King */
4583 int result = FALSE; int NrPieces;
4585 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4586 && NrPieces >= 12 && !(NrPieces&1)) {
4587 int i; /* [HGM] Accept even length from 12 to 34 */
4589 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4590 for( i=0; i<NrPieces/2-1; i++ ) {
4592 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4594 table[(int) WhiteKing] = map[NrPieces/2-1];
4595 table[(int) BlackKing] = map[NrPieces-1];
4603 void Prelude(Board board)
4604 { // [HGM] superchess: random selection of exo-pieces
4605 int i, j, k; ChessSquare p;
4606 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4608 GetPositionNumber(); // use FRC position number
4610 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4611 SetCharTable(pieceToChar, appData.pieceToCharTable);
4612 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4613 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4616 j = seed%4; seed /= 4;
4617 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4618 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4619 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4620 j = seed%3 + (seed%3 >= j); seed /= 3;
4621 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4622 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4623 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4624 j = seed%3; seed /= 3;
4625 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4626 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4627 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4628 j = seed%2 + (seed%2 >= j); seed /= 2;
4629 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4630 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4631 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4632 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4633 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4634 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4635 put(board, exoPieces[0], 0, 0, ANY);
4636 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4640 InitPosition(redraw)
4643 ChessSquare (* pieces)[BOARD_SIZE];
4644 int i, j, pawnRow, overrule,
4645 oldx = gameInfo.boardWidth,
4646 oldy = gameInfo.boardHeight,
4647 oldh = gameInfo.holdingsWidth,
4648 oldv = gameInfo.variant;
4650 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4652 /* [AS] Initialize pv info list [HGM] and game status */
4654 for( i=0; i<MAX_MOVES; i++ ) {
4655 pvInfoList[i].depth = 0;
4656 epStatus[i]=EP_NONE;
4657 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4660 initialRulePlies = 0; /* 50-move counter start */
4662 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4663 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4667 /* [HGM] logic here is completely changed. In stead of full positions */
4668 /* the initialized data only consist of the two backranks. The switch */
4669 /* selects which one we will use, which is than copied to the Board */
4670 /* initialPosition, which for the rest is initialized by Pawns and */
4671 /* empty squares. This initial position is then copied to boards[0], */
4672 /* possibly after shuffling, so that it remains available. */
4674 gameInfo.holdingsWidth = 0; /* default board sizes */
4675 gameInfo.boardWidth = 8;
4676 gameInfo.boardHeight = 8;
4677 gameInfo.holdingsSize = 0;
4678 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4679 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4680 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4682 switch (gameInfo.variant) {
4683 case VariantFischeRandom:
4684 shuffleOpenings = TRUE;
4688 case VariantShatranj:
4689 pieces = ShatranjArray;
4690 nrCastlingRights = 0;
4691 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4694 pieces = makrukArray;
4695 nrCastlingRights = 0;
4696 startedFromSetupPosition = TRUE;
4697 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4699 case VariantTwoKings:
4700 pieces = twoKingsArray;
4702 case VariantCapaRandom:
4703 shuffleOpenings = TRUE;
4704 case VariantCapablanca:
4705 pieces = CapablancaArray;
4706 gameInfo.boardWidth = 10;
4707 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4710 pieces = GothicArray;
4711 gameInfo.boardWidth = 10;
4712 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4715 pieces = JanusArray;
4716 gameInfo.boardWidth = 10;
4717 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4718 nrCastlingRights = 6;
4719 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4720 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4721 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4722 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4723 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4724 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4727 pieces = FalconArray;
4728 gameInfo.boardWidth = 10;
4729 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4731 case VariantXiangqi:
4732 pieces = XiangqiArray;
4733 gameInfo.boardWidth = 9;
4734 gameInfo.boardHeight = 10;
4735 nrCastlingRights = 0;
4736 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4739 pieces = ShogiArray;
4740 gameInfo.boardWidth = 9;
4741 gameInfo.boardHeight = 9;
4742 gameInfo.holdingsSize = 7;
4743 nrCastlingRights = 0;
4744 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4746 case VariantCourier:
4747 pieces = CourierArray;
4748 gameInfo.boardWidth = 12;
4749 nrCastlingRights = 0;
4750 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4751 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4753 case VariantKnightmate:
4754 pieces = KnightmateArray;
4755 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4758 pieces = fairyArray;
4759 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4762 pieces = GreatArray;
4763 gameInfo.boardWidth = 10;
4764 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4765 gameInfo.holdingsSize = 8;
4769 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4770 gameInfo.holdingsSize = 8;
4771 startedFromSetupPosition = TRUE;
4773 case VariantCrazyhouse:
4774 case VariantBughouse:
4776 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4777 gameInfo.holdingsSize = 5;
4779 case VariantWildCastle:
4781 /* !!?shuffle with kings guaranteed to be on d or e file */
4782 shuffleOpenings = 1;
4784 case VariantNoCastle:
4786 nrCastlingRights = 0;
4787 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4788 /* !!?unconstrained back-rank shuffle */
4789 shuffleOpenings = 1;
4794 if(appData.NrFiles >= 0) {
4795 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4796 gameInfo.boardWidth = appData.NrFiles;
4798 if(appData.NrRanks >= 0) {
4799 gameInfo.boardHeight = appData.NrRanks;
4801 if(appData.holdingsSize >= 0) {
4802 i = appData.holdingsSize;
4803 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4804 gameInfo.holdingsSize = i;
4806 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4807 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4808 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4810 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4811 if(pawnRow < 1) pawnRow = 1;
4812 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4814 /* User pieceToChar list overrules defaults */
4815 if(appData.pieceToCharTable != NULL)
4816 SetCharTable(pieceToChar, appData.pieceToCharTable);
4818 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4820 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4821 s = (ChessSquare) 0; /* account holding counts in guard band */
4822 for( i=0; i<BOARD_HEIGHT; i++ )
4823 initialPosition[i][j] = s;
4825 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4826 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4827 initialPosition[pawnRow][j] = WhitePawn;
4828 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4829 if(gameInfo.variant == VariantXiangqi) {
4831 initialPosition[pawnRow][j] =
4832 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4833 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4834 initialPosition[2][j] = WhiteCannon;
4835 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4839 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4841 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4844 initialPosition[1][j] = WhiteBishop;
4845 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4847 initialPosition[1][j] = WhiteRook;
4848 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4851 if( nrCastlingRights == -1) {
4852 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4853 /* This sets default castling rights from none to normal corners */
4854 /* Variants with other castling rights must set them themselves above */
4855 nrCastlingRights = 6;
4857 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4858 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4859 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4860 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4861 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4862 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4865 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4866 if(gameInfo.variant == VariantGreat) { // promotion commoners
4867 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4868 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4869 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4870 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4872 if (appData.debugMode) {
4873 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4875 if(shuffleOpenings) {
4876 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4877 startedFromSetupPosition = TRUE;
4879 if(startedFromPositionFile) {
4880 /* [HGM] loadPos: use PositionFile for every new game */
4881 CopyBoard(initialPosition, filePosition);
4882 for(i=0; i<nrCastlingRights; i++)
4883 castlingRights[0][i] = initialRights[i] = fileRights[i];
4884 startedFromSetupPosition = TRUE;
4887 CopyBoard(boards[0], initialPosition);
4889 if(oldx != gameInfo.boardWidth ||
4890 oldy != gameInfo.boardHeight ||
4891 oldh != gameInfo.holdingsWidth
4893 || oldv == VariantGothic || // For licensing popups
4894 gameInfo.variant == VariantGothic
4897 || oldv == VariantFalcon ||
4898 gameInfo.variant == VariantFalcon
4901 InitDrawingSizes(-2 ,0);
4904 DrawPosition(TRUE, boards[currentMove]);
4908 SendBoard(cps, moveNum)
4909 ChessProgramState *cps;
4912 char message[MSG_SIZ];
4914 if (cps->useSetboard) {
4915 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4916 sprintf(message, "setboard %s\n", fen);
4917 SendToProgram(message, cps);
4923 /* Kludge to set black to move, avoiding the troublesome and now
4924 * deprecated "black" command.
4926 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4928 SendToProgram("edit\n", cps);
4929 SendToProgram("#\n", cps);
4930 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4931 bp = &boards[moveNum][i][BOARD_LEFT];
4932 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4933 if ((int) *bp < (int) BlackPawn) {
4934 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4936 if(message[0] == '+' || message[0] == '~') {
4937 sprintf(message, "%c%c%c+\n",
4938 PieceToChar((ChessSquare)(DEMOTED *bp)),
4941 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4942 message[1] = BOARD_RGHT - 1 - j + '1';
4943 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4945 SendToProgram(message, cps);
4950 SendToProgram("c\n", cps);
4951 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4952 bp = &boards[moveNum][i][BOARD_LEFT];
4953 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4954 if (((int) *bp != (int) EmptySquare)
4955 && ((int) *bp >= (int) BlackPawn)) {
4956 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4958 if(message[0] == '+' || message[0] == '~') {
4959 sprintf(message, "%c%c%c+\n",
4960 PieceToChar((ChessSquare)(DEMOTED *bp)),
4963 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4964 message[1] = BOARD_RGHT - 1 - j + '1';
4965 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4967 SendToProgram(message, cps);
4972 SendToProgram(".\n", cps);
4974 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4978 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4980 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4981 /* [HGM] add Shogi promotions */
4982 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4987 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4988 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4990 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4991 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4994 piece = boards[currentMove][fromY][fromX];
4995 if(gameInfo.variant == VariantShogi) {
4996 promotionZoneSize = 3;
4997 highestPromotingPiece = (int)WhiteFerz;
4998 } else if(gameInfo.variant == VariantMakruk) {
4999 promotionZoneSize = 3;
5002 // next weed out all moves that do not touch the promotion zone at all
5003 if((int)piece >= BlackPawn) {
5004 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5006 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5008 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5009 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5012 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5014 // weed out mandatory Shogi promotions
5015 if(gameInfo.variant == VariantShogi) {
5016 if(piece >= BlackPawn) {
5017 if(toY == 0 && piece == BlackPawn ||
5018 toY == 0 && piece == BlackQueen ||
5019 toY <= 1 && piece == BlackKnight) {
5024 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5025 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5026 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5033 // weed out obviously illegal Pawn moves
5034 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5035 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5036 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5037 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5038 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5039 // note we are not allowed to test for valid (non-)capture, due to premove
5042 // we either have a choice what to promote to, or (in Shogi) whether to promote
5043 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5044 *promoChoice = PieceToChar(BlackFerz); // no choice
5047 if(appData.alwaysPromoteToQueen) { // predetermined
5048 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5049 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5050 else *promoChoice = PieceToChar(BlackQueen);
5054 // suppress promotion popup on illegal moves that are not premoves
5055 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5056 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5057 if(appData.testLegality && !premove) {
5058 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5059 epStatus[currentMove], castlingRights[currentMove],
5060 fromY, fromX, toY, toX, NULLCHAR);
5061 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5062 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5070 InPalace(row, column)
5072 { /* [HGM] for Xiangqi */
5073 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5074 column < (BOARD_WIDTH + 4)/2 &&
5075 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5080 PieceForSquare (x, y)
5084 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5087 return boards[currentMove][y][x];
5091 OKToStartUserMove(x, y)
5094 ChessSquare from_piece;
5097 if (matchMode) return FALSE;
5098 if (gameMode == EditPosition) return TRUE;
5100 if (x >= 0 && y >= 0)
5101 from_piece = boards[currentMove][y][x];
5103 from_piece = EmptySquare;
5105 if (from_piece == EmptySquare) return FALSE;
5107 white_piece = (int)from_piece >= (int)WhitePawn &&
5108 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5111 case PlayFromGameFile:
5113 case TwoMachinesPlay:
5121 case MachinePlaysWhite:
5122 case IcsPlayingBlack:
5123 if (appData.zippyPlay) return FALSE;
5125 DisplayMoveError(_("You are playing Black"));
5130 case MachinePlaysBlack:
5131 case IcsPlayingWhite:
5132 if (appData.zippyPlay) return FALSE;
5134 DisplayMoveError(_("You are playing White"));
5140 if (!white_piece && WhiteOnMove(currentMove)) {
5141 DisplayMoveError(_("It is White's turn"));
5144 if (white_piece && !WhiteOnMove(currentMove)) {
5145 DisplayMoveError(_("It is Black's turn"));
5148 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5149 /* Editing correspondence game history */
5150 /* Could disallow this or prompt for confirmation */
5153 if (currentMove < forwardMostMove) {
5154 /* Discarding moves */
5155 /* Could prompt for confirmation here,
5156 but I don't think that's such a good idea */
5157 forwardMostMove = currentMove;
5161 case BeginningOfGame:
5162 if (appData.icsActive) return FALSE;
5163 if (!appData.noChessProgram) {
5165 DisplayMoveError(_("You are playing White"));
5172 if (!white_piece && WhiteOnMove(currentMove)) {
5173 DisplayMoveError(_("It is White's turn"));
5176 if (white_piece && !WhiteOnMove(currentMove)) {
5177 DisplayMoveError(_("It is Black's turn"));
5186 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5187 && gameMode != AnalyzeFile && gameMode != Training) {
5188 DisplayMoveError(_("Displayed position is not current"));
5194 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5195 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5196 int lastLoadGameUseList = FALSE;
5197 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5198 ChessMove lastLoadGameStart = (ChessMove) 0;
5201 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5202 int fromX, fromY, toX, toY;
5207 ChessSquare pdown, pup;
5209 /* Check if the user is playing in turn. This is complicated because we
5210 let the user "pick up" a piece before it is his turn. So the piece he
5211 tried to pick up may have been captured by the time he puts it down!
5212 Therefore we use the color the user is supposed to be playing in this
5213 test, not the color of the piece that is currently on the starting
5214 square---except in EditGame mode, where the user is playing both
5215 sides; fortunately there the capture race can't happen. (It can
5216 now happen in IcsExamining mode, but that's just too bad. The user
5217 will get a somewhat confusing message in that case.)
5221 case PlayFromGameFile:
5223 case TwoMachinesPlay:
5227 /* We switched into a game mode where moves are not accepted,
5228 perhaps while the mouse button was down. */
5229 return ImpossibleMove;
5231 case MachinePlaysWhite:
5232 /* User is moving for Black */
5233 if (WhiteOnMove(currentMove)) {
5234 DisplayMoveError(_("It is White's turn"));
5235 return ImpossibleMove;
5239 case MachinePlaysBlack:
5240 /* User is moving for White */
5241 if (!WhiteOnMove(currentMove)) {
5242 DisplayMoveError(_("It is Black's turn"));
5243 return ImpossibleMove;
5249 case BeginningOfGame:
5252 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5253 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5254 /* User is moving for Black */
5255 if (WhiteOnMove(currentMove)) {
5256 DisplayMoveError(_("It is White's turn"));
5257 return ImpossibleMove;
5260 /* User is moving for White */
5261 if (!WhiteOnMove(currentMove)) {
5262 DisplayMoveError(_("It is Black's turn"));
5263 return ImpossibleMove;
5268 case IcsPlayingBlack:
5269 /* User is moving for Black */
5270 if (WhiteOnMove(currentMove)) {
5271 if (!appData.premove) {
5272 DisplayMoveError(_("It is White's turn"));
5273 } else if (toX >= 0 && toY >= 0) {
5276 premoveFromX = fromX;
5277 premoveFromY = fromY;
5278 premovePromoChar = promoChar;
5280 if (appData.debugMode)
5281 fprintf(debugFP, "Got premove: fromX %d,"
5282 "fromY %d, toX %d, toY %d\n",
5283 fromX, fromY, toX, toY);
5285 return ImpossibleMove;
5289 case IcsPlayingWhite:
5290 /* User is moving for White */
5291 if (!WhiteOnMove(currentMove)) {
5292 if (!appData.premove) {
5293 DisplayMoveError(_("It is Black's turn"));
5294 } else if (toX >= 0 && toY >= 0) {
5297 premoveFromX = fromX;
5298 premoveFromY = fromY;
5299 premovePromoChar = promoChar;
5301 if (appData.debugMode)
5302 fprintf(debugFP, "Got premove: fromX %d,"
5303 "fromY %d, toX %d, toY %d\n",
5304 fromX, fromY, toX, toY);
5306 return ImpossibleMove;
5314 /* EditPosition, empty square, or different color piece;
5315 click-click move is possible */
5316 if (toX == -2 || toY == -2) {
5317 boards[0][fromY][fromX] = EmptySquare;
5318 return AmbiguousMove;
5319 } else if (toX >= 0 && toY >= 0) {
5320 boards[0][toY][toX] = boards[0][fromY][fromX];
5321 boards[0][fromY][fromX] = EmptySquare;
5322 return AmbiguousMove;
5324 return ImpossibleMove;
5327 if(toX < 0 || toY < 0) return ImpossibleMove;
5328 pdown = boards[currentMove][fromY][fromX];
5329 pup = boards[currentMove][toY][toX];
5331 /* [HGM] If move started in holdings, it means a drop */
5332 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5333 if( pup != EmptySquare ) return ImpossibleMove;
5334 if(appData.testLegality) {
5335 /* it would be more logical if LegalityTest() also figured out
5336 * which drops are legal. For now we forbid pawns on back rank.
5337 * Shogi is on its own here...
5339 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5340 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5341 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5343 return WhiteDrop; /* Not needed to specify white or black yet */
5346 userOfferedDraw = FALSE;
5348 /* [HGM] always test for legality, to get promotion info */
5349 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5350 epStatus[currentMove], castlingRights[currentMove],
5351 fromY, fromX, toY, toX, promoChar);
5352 /* [HGM] but possibly ignore an IllegalMove result */
5353 if (appData.testLegality) {
5354 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5355 DisplayMoveError(_("Illegal move"));
5356 return ImpossibleMove;
5361 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5362 function is made into one that returns an OK move type if FinishMove
5363 should be called. This to give the calling driver routine the
5364 opportunity to finish the userMove input with a promotion popup,
5365 without bothering the user with this for invalid or illegal moves */
5367 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5370 /* Common tail of UserMoveEvent and DropMenuEvent */
5372 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5374 int fromX, fromY, toX, toY;
5375 /*char*/int promoChar;
5379 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5380 // [HGM] superchess: suppress promotions to non-available piece
5381 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5382 if(WhiteOnMove(currentMove)) {
5383 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5385 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5389 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5390 move type in caller when we know the move is a legal promotion */
5391 if(moveType == NormalMove && promoChar)
5392 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5394 /* [HGM] convert drag-and-drop piece drops to standard form */
5395 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5396 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5397 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5398 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5399 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5400 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5401 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5402 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5406 /* [HGM] <popupFix> The following if has been moved here from
5407 UserMoveEvent(). Because it seemed to belong here (why not allow
5408 piece drops in training games?), and because it can only be
5409 performed after it is known to what we promote. */
5410 if (gameMode == Training) {
5411 /* compare the move played on the board to the next move in the
5412 * game. If they match, display the move and the opponent's response.
5413 * If they don't match, display an error message.
5416 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5417 CopyBoard(testBoard, boards[currentMove]);
5418 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5420 if (CompareBoards(testBoard, boards[currentMove+1])) {
5421 ForwardInner(currentMove+1);
5423 /* Autoplay the opponent's response.
5424 * if appData.animate was TRUE when Training mode was entered,
5425 * the response will be animated.
5427 saveAnimate = appData.animate;
5428 appData.animate = animateTraining;
5429 ForwardInner(currentMove+1);
5430 appData.animate = saveAnimate;
5432 /* check for the end of the game */
5433 if (currentMove >= forwardMostMove) {
5434 gameMode = PlayFromGameFile;
5436 SetTrainingModeOff();
5437 DisplayInformation(_("End of game"));
5440 DisplayError(_("Incorrect move"), 0);
5445 /* Ok, now we know that the move is good, so we can kill
5446 the previous line in Analysis Mode */
5447 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5448 forwardMostMove = currentMove;
5451 /* If we need the chess program but it's dead, restart it */
5452 ResurrectChessProgram();
5454 /* A user move restarts a paused game*/
5458 thinkOutput[0] = NULLCHAR;
5460 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5462 if (gameMode == BeginningOfGame) {
5463 if (appData.noChessProgram) {
5464 gameMode = EditGame;
5468 gameMode = MachinePlaysBlack;
5471 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5473 if (first.sendName) {
5474 sprintf(buf, "name %s\n", gameInfo.white);
5475 SendToProgram(buf, &first);
5482 /* Relay move to ICS or chess engine */
5483 if (appData.icsActive) {
5484 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5485 gameMode == IcsExamining) {
5486 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5490 if (first.sendTime && (gameMode == BeginningOfGame ||
5491 gameMode == MachinePlaysWhite ||
5492 gameMode == MachinePlaysBlack)) {
5493 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5495 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5496 // [HGM] book: if program might be playing, let it use book
5497 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5498 first.maybeThinking = TRUE;
5499 } else SendMoveToProgram(forwardMostMove-1, &first);
5500 if (currentMove == cmailOldMove + 1) {
5501 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5505 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5509 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5510 EP_UNKNOWN, castlingRights[currentMove]) ) {
5516 if (WhiteOnMove(currentMove)) {
5517 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5519 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5523 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5528 case MachinePlaysBlack:
5529 case MachinePlaysWhite:
5530 /* disable certain menu options while machine is thinking */
5531 SetMachineThinkingEnables();
5538 if(bookHit) { // [HGM] book: simulate book reply
5539 static char bookMove[MSG_SIZ]; // a bit generous?
5541 programStats.nodes = programStats.depth = programStats.time =
5542 programStats.score = programStats.got_only_move = 0;
5543 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5545 strcpy(bookMove, "move ");
5546 strcat(bookMove, bookHit);
5547 HandleMachineMove(bookMove, &first);
5553 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5554 int fromX, fromY, toX, toY;
5557 /* [HGM] This routine was added to allow calling of its two logical
5558 parts from other modules in the old way. Before, UserMoveEvent()
5559 automatically called FinishMove() if the move was OK, and returned
5560 otherwise. I separated the two, in order to make it possible to
5561 slip a promotion popup in between. But that it always needs two
5562 calls, to the first part, (now called UserMoveTest() ), and to
5563 FinishMove if the first part succeeded. Calls that do not need
5564 to do anything in between, can call this routine the old way.
5566 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5567 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5568 if(moveType == AmbiguousMove)
5569 DrawPosition(FALSE, boards[currentMove]);
5570 else if(moveType != ImpossibleMove && moveType != Comment)
5571 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5574 void LeftClick(ClickType clickType, int xPix, int yPix)
5577 Boolean saveAnimate;
5578 static int second = 0, promotionChoice = 0;
5579 char promoChoice = NULLCHAR;
5581 if (clickType == Press) ErrorPopDown();
5583 x = EventToSquare(xPix, BOARD_WIDTH);
5584 y = EventToSquare(yPix, BOARD_HEIGHT);
5585 if (!flipView && y >= 0) {
5586 y = BOARD_HEIGHT - 1 - y;
5588 if (flipView && x >= 0) {
5589 x = BOARD_WIDTH - 1 - x;
5592 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5593 if(clickType == Release) return; // ignore upclick of click-click destination
5594 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5595 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5596 if(gameInfo.holdingsWidth &&
5597 (WhiteOnMove(currentMove)
5598 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5599 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5600 // click in right holdings, for determining promotion piece
5601 ChessSquare p = boards[currentMove][y][x];
5602 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5603 if(p != EmptySquare) {
5604 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5609 DrawPosition(FALSE, boards[currentMove]);
5613 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5614 if(clickType == Press
5615 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5616 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5617 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5621 if (clickType == Press) {
5623 if (OKToStartUserMove(x, y)) {
5627 DragPieceBegin(xPix, yPix);
5628 if (appData.highlightDragging) {
5629 SetHighlights(x, y, -1, -1);
5637 if (clickType == Press && gameMode != EditPosition) {
5642 // ignore off-board to clicks
5643 if(y < 0 || x < 0) return;
5645 /* Check if clicking again on the same color piece */
5646 fromP = boards[currentMove][fromY][fromX];
5647 toP = boards[currentMove][y][x];
5648 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5649 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5650 WhitePawn <= toP && toP <= WhiteKing &&
5651 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5652 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5653 (BlackPawn <= fromP && fromP <= BlackKing &&
5654 BlackPawn <= toP && toP <= BlackKing &&
5655 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5656 !(fromP == BlackKing && toP == BlackRook && frc))) {
5657 /* Clicked again on same color piece -- changed his mind */
5658 second = (x == fromX && y == fromY);
5659 if (appData.highlightDragging) {
5660 SetHighlights(x, y, -1, -1);
5664 if (OKToStartUserMove(x, y)) {
5667 DragPieceBegin(xPix, yPix);
5671 // ignore clicks on holdings
5672 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5675 if (clickType == Release && x == fromX && y == fromY) {
5676 DragPieceEnd(xPix, yPix);
5677 if (appData.animateDragging) {
5678 /* Undo animation damage if any */
5679 DrawPosition(FALSE, NULL);
5682 /* Second up/down in same square; just abort move */
5687 ClearPremoveHighlights();
5689 /* First upclick in same square; start click-click mode */
5690 SetHighlights(x, y, -1, -1);
5695 /* we now have a different from- and (possibly off-board) to-square */
5696 /* Completed move */
5699 saveAnimate = appData.animate;
5700 if (clickType == Press) {
5701 /* Finish clickclick move */
5702 if (appData.animate || appData.highlightLastMove) {
5703 SetHighlights(fromX, fromY, toX, toY);
5708 /* Finish drag move */
5709 if (appData.highlightLastMove) {
5710 SetHighlights(fromX, fromY, toX, toY);
5714 DragPieceEnd(xPix, yPix);
5715 /* Don't animate move and drag both */
5716 appData.animate = FALSE;
5719 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5720 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5723 DrawPosition(TRUE, NULL);
5727 // off-board moves should not be highlighted
5728 if(x < 0 || x < 0) ClearHighlights();
5730 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5731 SetHighlights(fromX, fromY, toX, toY);
5732 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5733 // [HGM] super: promotion to captured piece selected from holdings
5734 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5735 promotionChoice = TRUE;
5736 // kludge follows to temporarily execute move on display, without promoting yet
5737 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5738 boards[currentMove][toY][toX] = p;
5739 DrawPosition(FALSE, boards[currentMove]);
5740 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5741 boards[currentMove][toY][toX] = q;
5742 DisplayMessage("Click in holdings to choose piece", "");
5747 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5748 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5749 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5752 appData.animate = saveAnimate;
5753 if (appData.animate || appData.animateDragging) {
5754 /* Undo animation damage if needed */
5755 DrawPosition(FALSE, NULL);
5759 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5761 // char * hint = lastHint;
5762 FrontEndProgramStats stats;
5764 stats.which = cps == &first ? 0 : 1;
5765 stats.depth = cpstats->depth;
5766 stats.nodes = cpstats->nodes;
5767 stats.score = cpstats->score;
5768 stats.time = cpstats->time;
5769 stats.pv = cpstats->movelist;
5770 stats.hint = lastHint;
5771 stats.an_move_index = 0;
5772 stats.an_move_count = 0;
5774 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5775 stats.hint = cpstats->move_name;
5776 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5777 stats.an_move_count = cpstats->nr_moves;
5780 SetProgramStats( &stats );
5783 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5784 { // [HGM] book: this routine intercepts moves to simulate book replies
5785 char *bookHit = NULL;
5787 //first determine if the incoming move brings opponent into his book
5788 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5789 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5790 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5791 if(bookHit != NULL && !cps->bookSuspend) {
5792 // make sure opponent is not going to reply after receiving move to book position
5793 SendToProgram("force\n", cps);
5794 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5796 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5797 // now arrange restart after book miss
5799 // after a book hit we never send 'go', and the code after the call to this routine
5800 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5802 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5803 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5804 SendToProgram(buf, cps);
5805 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5806 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5807 SendToProgram("go\n", cps);
5808 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5809 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5810 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5811 SendToProgram("go\n", cps);
5812 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5814 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5818 ChessProgramState *savedState;
5819 void DeferredBookMove(void)
5821 if(savedState->lastPing != savedState->lastPong)
5822 ScheduleDelayedEvent(DeferredBookMove, 10);
5824 HandleMachineMove(savedMessage, savedState);
5828 HandleMachineMove(message, cps)
5830 ChessProgramState *cps;
5832 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5833 char realname[MSG_SIZ];
5834 int fromX, fromY, toX, toY;
5841 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5843 * Kludge to ignore BEL characters
5845 while (*message == '\007') message++;
5848 * [HGM] engine debug message: ignore lines starting with '#' character
5850 if(cps->debug && *message == '#') return;
5853 * Look for book output
5855 if (cps == &first && bookRequested) {
5856 if (message[0] == '\t' || message[0] == ' ') {
5857 /* Part of the book output is here; append it */
5858 strcat(bookOutput, message);
5859 strcat(bookOutput, " \n");
5861 } else if (bookOutput[0] != NULLCHAR) {
5862 /* All of book output has arrived; display it */
5863 char *p = bookOutput;
5864 while (*p != NULLCHAR) {
5865 if (*p == '\t') *p = ' ';
5868 DisplayInformation(bookOutput);
5869 bookRequested = FALSE;
5870 /* Fall through to parse the current output */
5875 * Look for machine move.
5877 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5878 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5880 /* This method is only useful on engines that support ping */
5881 if (cps->lastPing != cps->lastPong) {
5882 if (gameMode == BeginningOfGame) {
5883 /* Extra move from before last new; ignore */
5884 if (appData.debugMode) {
5885 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5888 if (appData.debugMode) {
5889 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5890 cps->which, gameMode);
5893 SendToProgram("undo\n", cps);
5899 case BeginningOfGame:
5900 /* Extra move from before last reset; ignore */
5901 if (appData.debugMode) {
5902 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5909 /* Extra move after we tried to stop. The mode test is
5910 not a reliable way of detecting this problem, but it's
5911 the best we can do on engines that don't support ping.
5913 if (appData.debugMode) {
5914 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5915 cps->which, gameMode);
5917 SendToProgram("undo\n", cps);
5920 case MachinePlaysWhite:
5921 case IcsPlayingWhite:
5922 machineWhite = TRUE;
5925 case MachinePlaysBlack:
5926 case IcsPlayingBlack:
5927 machineWhite = FALSE;
5930 case TwoMachinesPlay:
5931 machineWhite = (cps->twoMachinesColor[0] == 'w');
5934 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5935 if (appData.debugMode) {
5937 "Ignoring move out of turn by %s, gameMode %d"
5938 ", forwardMost %d\n",
5939 cps->which, gameMode, forwardMostMove);
5944 if (appData.debugMode) { int f = forwardMostMove;
5945 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5946 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5948 if(cps->alphaRank) AlphaRank(machineMove, 4);
5949 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5950 &fromX, &fromY, &toX, &toY, &promoChar)) {
5951 /* Machine move could not be parsed; ignore it. */
5952 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5953 machineMove, cps->which);
5954 DisplayError(buf1, 0);
5955 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5956 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5957 if (gameMode == TwoMachinesPlay) {
5958 GameEnds(machineWhite ? BlackWins : WhiteWins,
5964 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5965 /* So we have to redo legality test with true e.p. status here, */
5966 /* to make sure an illegal e.p. capture does not slip through, */
5967 /* to cause a forfeit on a justified illegal-move complaint */
5968 /* of the opponent. */
5969 if( gameMode==TwoMachinesPlay && appData.testLegality
5970 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5973 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5974 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5975 fromY, fromX, toY, toX, promoChar);
5976 if (appData.debugMode) {
5978 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5979 castlingRights[forwardMostMove][i], castlingRank[i]);
5980 fprintf(debugFP, "castling rights\n");
5982 if(moveType == IllegalMove) {
5983 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5984 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5985 GameEnds(machineWhite ? BlackWins : WhiteWins,
5988 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5989 /* [HGM] Kludge to handle engines that send FRC-style castling
5990 when they shouldn't (like TSCP-Gothic) */
5992 case WhiteASideCastleFR:
5993 case BlackASideCastleFR:
5995 currentMoveString[2]++;
5997 case WhiteHSideCastleFR:
5998 case BlackHSideCastleFR:
6000 currentMoveString[2]--;
6002 default: ; // nothing to do, but suppresses warning of pedantic compilers
6005 hintRequested = FALSE;
6006 lastHint[0] = NULLCHAR;
6007 bookRequested = FALSE;
6008 /* Program may be pondering now */
6009 cps->maybeThinking = TRUE;
6010 if (cps->sendTime == 2) cps->sendTime = 1;
6011 if (cps->offeredDraw) cps->offeredDraw--;
6014 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6016 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6018 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6019 char buf[3*MSG_SIZ];
6021 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6022 programStats.score / 100.,
6024 programStats.time / 100.,
6025 (unsigned int)programStats.nodes,
6026 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6027 programStats.movelist);
6029 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6033 /* currentMoveString is set as a side-effect of ParseOneMove */
6034 strcpy(machineMove, currentMoveString);
6035 strcat(machineMove, "\n");
6036 strcpy(moveList[forwardMostMove], machineMove);
6038 /* [AS] Save move info and clear stats for next move */
6039 pvInfoList[ forwardMostMove ].score = programStats.score;
6040 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6041 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6042 ClearProgramStats();
6043 thinkOutput[0] = NULLCHAR;
6044 hiddenThinkOutputState = 0;
6046 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6048 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6049 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6052 while( count < adjudicateLossPlies ) {
6053 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6056 score = -score; /* Flip score for winning side */
6059 if( score > adjudicateLossThreshold ) {
6066 if( count >= adjudicateLossPlies ) {
6067 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6070 "Xboard adjudication",
6077 if( gameMode == TwoMachinesPlay ) {
6078 // [HGM] some adjudications useful with buggy engines
6079 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6080 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6083 if( appData.testLegality )
6084 { /* [HGM] Some more adjudications for obstinate engines */
6085 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6086 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6087 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6088 static int moveCount = 6;
6090 char *reason = NULL;
6092 /* Count what is on board. */
6093 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6094 { ChessSquare p = boards[forwardMostMove][i][j];
6098 { /* count B,N,R and other of each side */
6101 NrK++; break; // [HGM] atomic: count Kings
6105 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6106 bishopsColor |= 1 << ((i^j)&1);
6111 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6112 bishopsColor |= 1 << ((i^j)&1);
6127 PawnAdvance += m; NrPawns++;
6129 NrPieces += (p != EmptySquare);
6130 NrW += ((int)p < (int)BlackPawn);
6131 if(gameInfo.variant == VariantXiangqi &&
6132 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6133 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6134 NrW -= ((int)p < (int)BlackPawn);
6138 /* Some material-based adjudications that have to be made before stalemate test */
6139 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6140 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6141 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6142 if(appData.checkMates) {
6143 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6144 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6145 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6146 "Xboard adjudication: King destroyed", GE_XBOARD );
6151 /* Bare King in Shatranj (loses) or Losers (wins) */
6152 if( NrW == 1 || NrPieces - NrW == 1) {
6153 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6154 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6155 if(appData.checkMates) {
6156 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6157 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6158 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6159 "Xboard adjudication: Bare king", GE_XBOARD );
6163 if( gameInfo.variant == VariantShatranj && --bare < 0)
6165 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6166 if(appData.checkMates) {
6167 /* but only adjudicate if adjudication enabled */
6168 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6169 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6170 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6171 "Xboard adjudication: Bare king", GE_XBOARD );
6178 // don't wait for engine to announce game end if we can judge ourselves
6179 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6180 castlingRights[forwardMostMove]) ) {
6182 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6183 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6184 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6185 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6188 reason = "Xboard adjudication: 3rd check";
6189 epStatus[forwardMostMove] = EP_CHECKMATE;
6199 reason = "Xboard adjudication: Stalemate";
6200 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6201 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6202 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6203 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6204 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6205 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6206 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6207 EP_CHECKMATE : EP_WINS);
6208 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6209 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6213 reason = "Xboard adjudication: Checkmate";
6214 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6218 switch(i = epStatus[forwardMostMove]) {
6220 result = GameIsDrawn; break;
6222 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6224 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6226 result = (ChessMove) 0;
6228 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6229 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6230 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6231 GameEnds( result, reason, GE_XBOARD );
6235 /* Next absolutely insufficient mating material. */
6236 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6237 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6238 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6239 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6240 { /* KBK, KNK, KK of KBKB with like Bishops */
6242 /* always flag draws, for judging claims */
6243 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6245 if(appData.materialDraws) {
6246 /* but only adjudicate them if adjudication enabled */
6247 SendToProgram("force\n", cps->other); // suppress reply
6248 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6249 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6250 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6255 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6257 ( NrWR == 1 && NrBR == 1 /* KRKR */
6258 || NrWQ==1 && NrBQ==1 /* KQKQ */
6259 || NrWN==2 || NrBN==2 /* KNNK */
6260 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6262 if(--moveCount < 0 && appData.trivialDraws)
6263 { /* if the first 3 moves do not show a tactical win, declare draw */
6264 SendToProgram("force\n", cps->other); // suppress reply
6265 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6266 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6267 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6270 } else moveCount = 6;
6274 /* Check for rep-draws */
6276 for(k = forwardMostMove-2;
6277 k>=backwardMostMove && k>=forwardMostMove-100 &&
6278 epStatus[k] < EP_UNKNOWN &&
6279 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6282 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6283 /* compare castling rights */
6284 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6285 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6286 rights++; /* King lost rights, while rook still had them */
6287 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6288 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6289 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6290 rights++; /* but at least one rook lost them */
6292 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6293 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6295 if( castlingRights[forwardMostMove][5] >= 0 ) {
6296 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6297 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6300 if( rights == 0 && ++count > appData.drawRepeats-2
6301 && appData.drawRepeats > 1) {
6302 /* adjudicate after user-specified nr of repeats */
6303 SendToProgram("force\n", cps->other); // suppress reply
6304 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6305 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6306 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6307 // [HGM] xiangqi: check for forbidden perpetuals
6308 int m, ourPerpetual = 1, hisPerpetual = 1;
6309 for(m=forwardMostMove; m>k; m-=2) {
6310 if(MateTest(boards[m], PosFlags(m),
6311 EP_NONE, castlingRights[m]) != MT_CHECK)
6312 ourPerpetual = 0; // the current mover did not always check
6313 if(MateTest(boards[m-1], PosFlags(m-1),
6314 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6315 hisPerpetual = 0; // the opponent did not always check
6317 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6318 ourPerpetual, hisPerpetual);
6319 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6320 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6321 "Xboard adjudication: perpetual checking", GE_XBOARD );
6324 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6325 break; // (or we would have caught him before). Abort repetition-checking loop.
6326 // Now check for perpetual chases
6327 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6328 hisPerpetual = PerpetualChase(k, forwardMostMove);
6329 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6330 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6331 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6332 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6335 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6336 break; // Abort repetition-checking loop.
6338 // if neither of us is checking or chasing all the time, or both are, it is draw
6340 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6343 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6344 epStatus[forwardMostMove] = EP_REP_DRAW;
6348 /* Now we test for 50-move draws. Determine ply count */
6349 count = forwardMostMove;
6350 /* look for last irreversble move */
6351 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6353 /* if we hit starting position, add initial plies */
6354 if( count == backwardMostMove )
6355 count -= initialRulePlies;
6356 count = forwardMostMove - count;
6358 epStatus[forwardMostMove] = EP_RULE_DRAW;
6359 /* this is used to judge if draw claims are legal */
6360 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6361 SendToProgram("force\n", cps->other); // suppress reply
6362 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6363 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6364 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6368 /* if draw offer is pending, treat it as a draw claim
6369 * when draw condition present, to allow engines a way to
6370 * claim draws before making their move to avoid a race
6371 * condition occurring after their move
6373 if( cps->other->offeredDraw || cps->offeredDraw ) {
6375 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6376 p = "Draw claim: 50-move rule";
6377 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6378 p = "Draw claim: 3-fold repetition";
6379 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6380 p = "Draw claim: insufficient mating material";
6382 SendToProgram("force\n", cps->other); // suppress reply
6383 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6384 GameEnds( GameIsDrawn, p, GE_XBOARD );
6385 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6391 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6392 SendToProgram("force\n", cps->other); // suppress reply
6393 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6394 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6396 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6403 if (gameMode == TwoMachinesPlay) {
6404 /* [HGM] relaying draw offers moved to after reception of move */
6405 /* and interpreting offer as claim if it brings draw condition */
6406 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6407 SendToProgram("draw\n", cps->other);
6409 if (cps->other->sendTime) {
6410 SendTimeRemaining(cps->other,
6411 cps->other->twoMachinesColor[0] == 'w');
6413 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6414 if (firstMove && !bookHit) {
6416 if (cps->other->useColors) {
6417 SendToProgram(cps->other->twoMachinesColor, cps->other);
6419 SendToProgram("go\n", cps->other);
6421 cps->other->maybeThinking = TRUE;
6424 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6426 if (!pausing && appData.ringBellAfterMoves) {
6431 * Reenable menu items that were disabled while
6432 * machine was thinking
6434 if (gameMode != TwoMachinesPlay)
6435 SetUserThinkingEnables();
6437 // [HGM] book: after book hit opponent has received move and is now in force mode
6438 // force the book reply into it, and then fake that it outputted this move by jumping
6439 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6441 static char bookMove[MSG_SIZ]; // a bit generous?
6443 strcpy(bookMove, "move ");
6444 strcat(bookMove, bookHit);
6447 programStats.nodes = programStats.depth = programStats.time =
6448 programStats.score = programStats.got_only_move = 0;
6449 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6451 if(cps->lastPing != cps->lastPong) {
6452 savedMessage = message; // args for deferred call
6454 ScheduleDelayedEvent(DeferredBookMove, 10);
6463 /* Set special modes for chess engines. Later something general
6464 * could be added here; for now there is just one kludge feature,
6465 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6466 * when "xboard" is given as an interactive command.
6468 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6469 cps->useSigint = FALSE;
6470 cps->useSigterm = FALSE;
6472 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6473 ParseFeatures(message+8, cps);
6474 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6477 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6478 * want this, I was asked to put it in, and obliged.
6480 if (!strncmp(message, "setboard ", 9)) {
6481 Board initial_position; int i;
6483 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6485 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6486 DisplayError(_("Bad FEN received from engine"), 0);
6490 CopyBoard(boards[0], initial_position);
6491 initialRulePlies = FENrulePlies;
6492 epStatus[0] = FENepStatus;
6493 for( i=0; i<nrCastlingRights; i++ )
6494 castlingRights[0][i] = FENcastlingRights[i];
6495 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6496 else gameMode = MachinePlaysBlack;
6497 DrawPosition(FALSE, boards[currentMove]);
6503 * Look for communication commands
6505 if (!strncmp(message, "telluser ", 9)) {
6506 DisplayNote(message + 9);
6509 if (!strncmp(message, "tellusererror ", 14)) {
6510 DisplayError(message + 14, 0);
6513 if (!strncmp(message, "tellopponent ", 13)) {
6514 if (appData.icsActive) {
6516 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6520 DisplayNote(message + 13);
6524 if (!strncmp(message, "tellothers ", 11)) {
6525 if (appData.icsActive) {
6527 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6533 if (!strncmp(message, "tellall ", 8)) {
6534 if (appData.icsActive) {
6536 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6540 DisplayNote(message + 8);
6544 if (strncmp(message, "warning", 7) == 0) {
6545 /* Undocumented feature, use tellusererror in new code */
6546 DisplayError(message, 0);
6549 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6550 strcpy(realname, cps->tidy);
6551 strcat(realname, " query");
6552 AskQuestion(realname, buf2, buf1, cps->pr);
6555 /* Commands from the engine directly to ICS. We don't allow these to be
6556 * sent until we are logged on. Crafty kibitzes have been known to
6557 * interfere with the login process.
6560 if (!strncmp(message, "tellics ", 8)) {
6561 SendToICS(message + 8);
6565 if (!strncmp(message, "tellicsnoalias ", 15)) {
6566 SendToICS(ics_prefix);
6567 SendToICS(message + 15);
6571 /* The following are for backward compatibility only */
6572 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6573 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6574 SendToICS(ics_prefix);
6580 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6584 * If the move is illegal, cancel it and redraw the board.
6585 * Also deal with other error cases. Matching is rather loose
6586 * here to accommodate engines written before the spec.
6588 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6589 strncmp(message, "Error", 5) == 0) {
6590 if (StrStr(message, "name") ||
6591 StrStr(message, "rating") || StrStr(message, "?") ||
6592 StrStr(message, "result") || StrStr(message, "board") ||
6593 StrStr(message, "bk") || StrStr(message, "computer") ||
6594 StrStr(message, "variant") || StrStr(message, "hint") ||
6595 StrStr(message, "random") || StrStr(message, "depth") ||
6596 StrStr(message, "accepted")) {
6599 if (StrStr(message, "protover")) {
6600 /* Program is responding to input, so it's apparently done
6601 initializing, and this error message indicates it is
6602 protocol version 1. So we don't need to wait any longer
6603 for it to initialize and send feature commands. */
6604 FeatureDone(cps, 1);
6605 cps->protocolVersion = 1;
6608 cps->maybeThinking = FALSE;
6610 if (StrStr(message, "draw")) {
6611 /* Program doesn't have "draw" command */
6612 cps->sendDrawOffers = 0;
6615 if (cps->sendTime != 1 &&
6616 (StrStr(message, "time") || StrStr(message, "otim"))) {
6617 /* Program apparently doesn't have "time" or "otim" command */
6621 if (StrStr(message, "analyze")) {
6622 cps->analysisSupport = FALSE;
6623 cps->analyzing = FALSE;
6625 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6626 DisplayError(buf2, 0);
6629 if (StrStr(message, "(no matching move)st")) {
6630 /* Special kludge for GNU Chess 4 only */
6631 cps->stKludge = TRUE;
6632 SendTimeControl(cps, movesPerSession, timeControl,
6633 timeIncrement, appData.searchDepth,
6637 if (StrStr(message, "(no matching move)sd")) {
6638 /* Special kludge for GNU Chess 4 only */
6639 cps->sdKludge = TRUE;
6640 SendTimeControl(cps, movesPerSession, timeControl,
6641 timeIncrement, appData.searchDepth,
6645 if (!StrStr(message, "llegal")) {
6648 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6649 gameMode == IcsIdle) return;
6650 if (forwardMostMove <= backwardMostMove) return;
6651 if (pausing) PauseEvent();
6652 if(appData.forceIllegal) {
6653 // [HGM] illegal: machine refused move; force position after move into it
6654 SendToProgram("force\n", cps);
6655 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6656 // we have a real problem now, as SendBoard will use the a2a3 kludge
6657 // when black is to move, while there might be nothing on a2 or black
6658 // might already have the move. So send the board as if white has the move.
6659 // But first we must change the stm of the engine, as it refused the last move
6660 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6661 if(WhiteOnMove(forwardMostMove)) {
6662 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6663 SendBoard(cps, forwardMostMove); // kludgeless board
6665 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6666 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6667 SendBoard(cps, forwardMostMove+1); // kludgeless board
6669 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6670 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6671 gameMode == TwoMachinesPlay)
6672 SendToProgram("go\n", cps);
6675 if (gameMode == PlayFromGameFile) {
6676 /* Stop reading this game file */
6677 gameMode = EditGame;
6680 currentMove = forwardMostMove-1;
6681 DisplayMove(currentMove-1); /* before DisplayMoveError */
6682 SwitchClocks(forwardMostMove-1); // [HGM] race
6683 DisplayBothClocks();
6684 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6685 parseList[currentMove], cps->which);
6686 DisplayMoveError(buf1);
6687 DrawPosition(FALSE, boards[currentMove]);
6689 /* [HGM] illegal-move claim should forfeit game when Xboard */
6690 /* only passes fully legal moves */
6691 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6692 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6693 "False illegal-move claim", GE_XBOARD );
6697 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6698 /* Program has a broken "time" command that
6699 outputs a string not ending in newline.
6705 * If chess program startup fails, exit with an error message.
6706 * Attempts to recover here are futile.
6708 if ((StrStr(message, "unknown host") != NULL)
6709 || (StrStr(message, "No remote directory") != NULL)
6710 || (StrStr(message, "not found") != NULL)
6711 || (StrStr(message, "No such file") != NULL)
6712 || (StrStr(message, "can't alloc") != NULL)
6713 || (StrStr(message, "Permission denied") != NULL)) {
6715 cps->maybeThinking = FALSE;
6716 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6717 cps->which, cps->program, cps->host, message);
6718 RemoveInputSource(cps->isr);
6719 DisplayFatalError(buf1, 0, 1);
6724 * Look for hint output
6726 if (sscanf(message, "Hint: %s", buf1) == 1) {
6727 if (cps == &first && hintRequested) {
6728 hintRequested = FALSE;
6729 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6730 &fromX, &fromY, &toX, &toY, &promoChar)) {
6731 (void) CoordsToAlgebraic(boards[forwardMostMove],
6732 PosFlags(forwardMostMove), EP_UNKNOWN,
6733 fromY, fromX, toY, toX, promoChar, buf1);
6734 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6735 DisplayInformation(buf2);
6737 /* Hint move could not be parsed!? */
6738 snprintf(buf2, sizeof(buf2),
6739 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6741 DisplayError(buf2, 0);
6744 strcpy(lastHint, buf1);
6750 * Ignore other messages if game is not in progress
6752 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6753 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6756 * look for win, lose, draw, or draw offer
6758 if (strncmp(message, "1-0", 3) == 0) {
6759 char *p, *q, *r = "";
6760 p = strchr(message, '{');
6768 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6770 } else if (strncmp(message, "0-1", 3) == 0) {
6771 char *p, *q, *r = "";
6772 p = strchr(message, '{');
6780 /* Kludge for Arasan 4.1 bug */
6781 if (strcmp(r, "Black resigns") == 0) {
6782 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6785 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6787 } else if (strncmp(message, "1/2", 3) == 0) {
6788 char *p, *q, *r = "";
6789 p = strchr(message, '{');
6798 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6801 } else if (strncmp(message, "White resign", 12) == 0) {
6802 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6804 } else if (strncmp(message, "Black resign", 12) == 0) {
6805 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6807 } else if (strncmp(message, "White matches", 13) == 0 ||
6808 strncmp(message, "Black matches", 13) == 0 ) {
6809 /* [HGM] ignore GNUShogi noises */
6811 } else if (strncmp(message, "White", 5) == 0 &&
6812 message[5] != '(' &&
6813 StrStr(message, "Black") == NULL) {
6814 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6816 } else if (strncmp(message, "Black", 5) == 0 &&
6817 message[5] != '(') {
6818 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6820 } else if (strcmp(message, "resign") == 0 ||
6821 strcmp(message, "computer resigns") == 0) {
6823 case MachinePlaysBlack:
6824 case IcsPlayingBlack:
6825 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6827 case MachinePlaysWhite:
6828 case IcsPlayingWhite:
6829 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6831 case TwoMachinesPlay:
6832 if (cps->twoMachinesColor[0] == 'w')
6833 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6835 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6842 } else if (strncmp(message, "opponent mates", 14) == 0) {
6844 case MachinePlaysBlack:
6845 case IcsPlayingBlack:
6846 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6848 case MachinePlaysWhite:
6849 case IcsPlayingWhite:
6850 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6852 case TwoMachinesPlay:
6853 if (cps->twoMachinesColor[0] == 'w')
6854 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6856 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6863 } else if (strncmp(message, "computer mates", 14) == 0) {
6865 case MachinePlaysBlack:
6866 case IcsPlayingBlack:
6867 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6869 case MachinePlaysWhite:
6870 case IcsPlayingWhite:
6871 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6873 case TwoMachinesPlay:
6874 if (cps->twoMachinesColor[0] == 'w')
6875 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6877 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6884 } else if (strncmp(message, "checkmate", 9) == 0) {
6885 if (WhiteOnMove(forwardMostMove)) {
6886 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6888 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6891 } else if (strstr(message, "Draw") != NULL ||
6892 strstr(message, "game is a draw") != NULL) {
6893 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6895 } else if (strstr(message, "offer") != NULL &&
6896 strstr(message, "draw") != NULL) {
6898 if (appData.zippyPlay && first.initDone) {
6899 /* Relay offer to ICS */
6900 SendToICS(ics_prefix);
6901 SendToICS("draw\n");
6904 cps->offeredDraw = 2; /* valid until this engine moves twice */
6905 if (gameMode == TwoMachinesPlay) {
6906 if (cps->other->offeredDraw) {
6907 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6908 /* [HGM] in two-machine mode we delay relaying draw offer */
6909 /* until after we also have move, to see if it is really claim */
6911 } else if (gameMode == MachinePlaysWhite ||
6912 gameMode == MachinePlaysBlack) {
6913 if (userOfferedDraw) {
6914 DisplayInformation(_("Machine accepts your draw offer"));
6915 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6917 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6924 * Look for thinking output
6926 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6927 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6929 int plylev, mvleft, mvtot, curscore, time;
6930 char mvname[MOVE_LEN];
6934 int prefixHint = FALSE;
6935 mvname[0] = NULLCHAR;
6938 case MachinePlaysBlack:
6939 case IcsPlayingBlack:
6940 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6942 case MachinePlaysWhite:
6943 case IcsPlayingWhite:
6944 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6949 case IcsObserving: /* [DM] icsEngineAnalyze */
6950 if (!appData.icsEngineAnalyze) ignore = TRUE;
6952 case TwoMachinesPlay:
6953 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6964 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6965 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6967 if (plyext != ' ' && plyext != '\t') {
6971 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6972 if( cps->scoreIsAbsolute &&
6973 ( gameMode == MachinePlaysBlack ||
6974 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6975 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6976 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6977 !WhiteOnMove(currentMove)
6980 curscore = -curscore;
6984 programStats.depth = plylev;
6985 programStats.nodes = nodes;
6986 programStats.time = time;
6987 programStats.score = curscore;
6988 programStats.got_only_move = 0;
6990 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6993 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6994 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6995 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6996 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6997 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6998 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6999 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7000 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7003 /* Buffer overflow protection */
7004 if (buf1[0] != NULLCHAR) {
7005 if (strlen(buf1) >= sizeof(programStats.movelist)
7006 && appData.debugMode) {
7008 "PV is too long; using the first %u bytes.\n",
7009 (unsigned) sizeof(programStats.movelist) - 1);
7012 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7014 sprintf(programStats.movelist, " no PV\n");
7017 if (programStats.seen_stat) {
7018 programStats.ok_to_send = 1;
7021 if (strchr(programStats.movelist, '(') != NULL) {
7022 programStats.line_is_book = 1;
7023 programStats.nr_moves = 0;
7024 programStats.moves_left = 0;
7026 programStats.line_is_book = 0;
7029 SendProgramStatsToFrontend( cps, &programStats );
7032 [AS] Protect the thinkOutput buffer from overflow... this
7033 is only useful if buf1 hasn't overflowed first!
7035 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7037 (gameMode == TwoMachinesPlay ?
7038 ToUpper(cps->twoMachinesColor[0]) : ' '),
7039 ((double) curscore) / 100.0,
7040 prefixHint ? lastHint : "",
7041 prefixHint ? " " : "" );
7043 if( buf1[0] != NULLCHAR ) {
7044 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7046 if( strlen(buf1) > max_len ) {
7047 if( appData.debugMode) {
7048 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7050 buf1[max_len+1] = '\0';
7053 strcat( thinkOutput, buf1 );
7056 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7057 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7058 DisplayMove(currentMove - 1);
7062 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7063 /* crafty (9.25+) says "(only move) <move>"
7064 * if there is only 1 legal move
7066 sscanf(p, "(only move) %s", buf1);
7067 sprintf(thinkOutput, "%s (only move)", buf1);
7068 sprintf(programStats.movelist, "%s (only move)", buf1);
7069 programStats.depth = 1;
7070 programStats.nr_moves = 1;
7071 programStats.moves_left = 1;
7072 programStats.nodes = 1;
7073 programStats.time = 1;
7074 programStats.got_only_move = 1;
7076 /* Not really, but we also use this member to
7077 mean "line isn't going to change" (Crafty
7078 isn't searching, so stats won't change) */
7079 programStats.line_is_book = 1;
7081 SendProgramStatsToFrontend( cps, &programStats );
7083 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7084 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7085 DisplayMove(currentMove - 1);
7088 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7089 &time, &nodes, &plylev, &mvleft,
7090 &mvtot, mvname) >= 5) {
7091 /* The stat01: line is from Crafty (9.29+) in response
7092 to the "." command */
7093 programStats.seen_stat = 1;
7094 cps->maybeThinking = TRUE;
7096 if (programStats.got_only_move || !appData.periodicUpdates)
7099 programStats.depth = plylev;
7100 programStats.time = time;
7101 programStats.nodes = nodes;
7102 programStats.moves_left = mvleft;
7103 programStats.nr_moves = mvtot;
7104 strcpy(programStats.move_name, mvname);
7105 programStats.ok_to_send = 1;
7106 programStats.movelist[0] = '\0';
7108 SendProgramStatsToFrontend( cps, &programStats );
7112 } else if (strncmp(message,"++",2) == 0) {
7113 /* Crafty 9.29+ outputs this */
7114 programStats.got_fail = 2;
7117 } else if (strncmp(message,"--",2) == 0) {
7118 /* Crafty 9.29+ outputs this */
7119 programStats.got_fail = 1;
7122 } else if (thinkOutput[0] != NULLCHAR &&
7123 strncmp(message, " ", 4) == 0) {
7124 unsigned message_len;
7127 while (*p && *p == ' ') p++;
7129 message_len = strlen( p );
7131 /* [AS] Avoid buffer overflow */
7132 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7133 strcat(thinkOutput, " ");
7134 strcat(thinkOutput, p);
7137 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7138 strcat(programStats.movelist, " ");
7139 strcat(programStats.movelist, p);
7142 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7143 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7144 DisplayMove(currentMove - 1);
7152 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7153 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7155 ChessProgramStats cpstats;
7157 if (plyext != ' ' && plyext != '\t') {
7161 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7162 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7163 curscore = -curscore;
7166 cpstats.depth = plylev;
7167 cpstats.nodes = nodes;
7168 cpstats.time = time;
7169 cpstats.score = curscore;
7170 cpstats.got_only_move = 0;
7171 cpstats.movelist[0] = '\0';
7173 if (buf1[0] != NULLCHAR) {
7174 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7177 cpstats.ok_to_send = 0;
7178 cpstats.line_is_book = 0;
7179 cpstats.nr_moves = 0;
7180 cpstats.moves_left = 0;
7182 SendProgramStatsToFrontend( cps, &cpstats );
7189 /* Parse a game score from the character string "game", and
7190 record it as the history of the current game. The game
7191 score is NOT assumed to start from the standard position.
7192 The display is not updated in any way.
7195 ParseGameHistory(game)
7199 int fromX, fromY, toX, toY, boardIndex;
7204 if (appData.debugMode)
7205 fprintf(debugFP, "Parsing game history: %s\n", game);
7207 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7208 gameInfo.site = StrSave(appData.icsHost);
7209 gameInfo.date = PGNDate();
7210 gameInfo.round = StrSave("-");
7212 /* Parse out names of players */
7213 while (*game == ' ') game++;
7215 while (*game != ' ') *p++ = *game++;
7217 gameInfo.white = StrSave(buf);
7218 while (*game == ' ') game++;
7220 while (*game != ' ' && *game != '\n') *p++ = *game++;
7222 gameInfo.black = StrSave(buf);
7225 boardIndex = blackPlaysFirst ? 1 : 0;
7228 yyboardindex = boardIndex;
7229 moveType = (ChessMove) yylex();
7231 case IllegalMove: /* maybe suicide chess, etc. */
7232 if (appData.debugMode) {
7233 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7234 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7235 setbuf(debugFP, NULL);
7237 case WhitePromotionChancellor:
7238 case BlackPromotionChancellor:
7239 case WhitePromotionArchbishop:
7240 case BlackPromotionArchbishop:
7241 case WhitePromotionQueen:
7242 case BlackPromotionQueen:
7243 case WhitePromotionRook:
7244 case BlackPromotionRook:
7245 case WhitePromotionBishop:
7246 case BlackPromotionBishop:
7247 case WhitePromotionKnight:
7248 case BlackPromotionKnight:
7249 case WhitePromotionKing:
7250 case BlackPromotionKing:
7252 case WhiteCapturesEnPassant:
7253 case BlackCapturesEnPassant:
7254 case WhiteKingSideCastle:
7255 case WhiteQueenSideCastle:
7256 case BlackKingSideCastle:
7257 case BlackQueenSideCastle:
7258 case WhiteKingSideCastleWild:
7259 case WhiteQueenSideCastleWild:
7260 case BlackKingSideCastleWild:
7261 case BlackQueenSideCastleWild:
7263 case WhiteHSideCastleFR:
7264 case WhiteASideCastleFR:
7265 case BlackHSideCastleFR:
7266 case BlackASideCastleFR:
7268 fromX = currentMoveString[0] - AAA;
7269 fromY = currentMoveString[1] - ONE;
7270 toX = currentMoveString[2] - AAA;
7271 toY = currentMoveString[3] - ONE;
7272 promoChar = currentMoveString[4];
7276 fromX = moveType == WhiteDrop ?
7277 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7278 (int) CharToPiece(ToLower(currentMoveString[0]));
7280 toX = currentMoveString[2] - AAA;
7281 toY = currentMoveString[3] - ONE;
7282 promoChar = NULLCHAR;
7286 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7287 if (appData.debugMode) {
7288 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7289 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7290 setbuf(debugFP, NULL);
7292 DisplayError(buf, 0);
7294 case ImpossibleMove:
7296 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7297 if (appData.debugMode) {
7298 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7299 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7300 setbuf(debugFP, NULL);
7302 DisplayError(buf, 0);
7304 case (ChessMove) 0: /* end of file */
7305 if (boardIndex < backwardMostMove) {
7306 /* Oops, gap. How did that happen? */
7307 DisplayError(_("Gap in move list"), 0);
7310 backwardMostMove = blackPlaysFirst ? 1 : 0;
7311 if (boardIndex > forwardMostMove) {
7312 forwardMostMove = boardIndex;
7316 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7317 strcat(parseList[boardIndex-1], " ");
7318 strcat(parseList[boardIndex-1], yy_text);
7330 case GameUnfinished:
7331 if (gameMode == IcsExamining) {
7332 if (boardIndex < backwardMostMove) {
7333 /* Oops, gap. How did that happen? */
7336 backwardMostMove = blackPlaysFirst ? 1 : 0;
7339 gameInfo.result = moveType;
7340 p = strchr(yy_text, '{');
7341 if (p == NULL) p = strchr(yy_text, '(');
7344 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7346 q = strchr(p, *p == '{' ? '}' : ')');
7347 if (q != NULL) *q = NULLCHAR;
7350 gameInfo.resultDetails = StrSave(p);
7353 if (boardIndex >= forwardMostMove &&
7354 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7355 backwardMostMove = blackPlaysFirst ? 1 : 0;
7358 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7359 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7360 parseList[boardIndex]);
7361 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7362 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7363 /* currentMoveString is set as a side-effect of yylex */
7364 strcpy(moveList[boardIndex], currentMoveString);
7365 strcat(moveList[boardIndex], "\n");
7367 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7368 castlingRights[boardIndex], &epStatus[boardIndex]);
7369 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7370 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7376 if(gameInfo.variant != VariantShogi)
7377 strcat(parseList[boardIndex - 1], "+");
7381 strcat(parseList[boardIndex - 1], "#");
7388 /* Apply a move to the given board */
7390 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7391 int fromX, fromY, toX, toY;
7397 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7398 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7400 /* [HGM] compute & store e.p. status and castling rights for new position */
7401 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7404 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7408 if( board[toY][toX] != EmptySquare )
7411 if( board[fromY][fromX] == WhitePawn ) {
7412 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7415 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7416 gameInfo.variant != VariantBerolina || toX < fromX)
7417 *ep = toX | berolina;
7418 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7419 gameInfo.variant != VariantBerolina || toX > fromX)
7423 if( board[fromY][fromX] == BlackPawn ) {
7424 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7426 if( toY-fromY== -2) {
7427 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7428 gameInfo.variant != VariantBerolina || toX < fromX)
7429 *ep = toX | berolina;
7430 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7431 gameInfo.variant != VariantBerolina || toX > fromX)
7436 for(i=0; i<nrCastlingRights; i++) {
7437 if(castling[i] == fromX && castlingRank[i] == fromY ||
7438 castling[i] == toX && castlingRank[i] == toY
7439 ) castling[i] = -1; // revoke for moved or captured piece
7444 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7445 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7446 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7448 if (fromX == toX && fromY == toY) return;
7450 if (fromY == DROP_RANK) {
7452 piece = board[toY][toX] = (ChessSquare) fromX;
7454 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7455 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7456 if(gameInfo.variant == VariantKnightmate)
7457 king += (int) WhiteUnicorn - (int) WhiteKing;
7459 /* Code added by Tord: */
7460 /* FRC castling assumed when king captures friendly rook. */
7461 if (board[fromY][fromX] == WhiteKing &&
7462 board[toY][toX] == WhiteRook) {
7463 board[fromY][fromX] = EmptySquare;
7464 board[toY][toX] = EmptySquare;
7466 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7468 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7470 } else if (board[fromY][fromX] == BlackKing &&
7471 board[toY][toX] == BlackRook) {
7472 board[fromY][fromX] = EmptySquare;
7473 board[toY][toX] = EmptySquare;
7475 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7477 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7479 /* End of code added by Tord */
7481 } else if (board[fromY][fromX] == king
7482 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7483 && toY == fromY && toX > fromX+1) {
7484 board[fromY][fromX] = EmptySquare;
7485 board[toY][toX] = king;
7486 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7487 board[fromY][BOARD_RGHT-1] = EmptySquare;
7488 } else if (board[fromY][fromX] == king
7489 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7490 && toY == fromY && toX < fromX-1) {
7491 board[fromY][fromX] = EmptySquare;
7492 board[toY][toX] = king;
7493 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7494 board[fromY][BOARD_LEFT] = EmptySquare;
7495 } else if (board[fromY][fromX] == WhitePawn
7496 && toY >= BOARD_HEIGHT-promoRank
7497 && gameInfo.variant != VariantXiangqi
7499 /* white pawn promotion */
7500 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7501 if (board[toY][toX] == EmptySquare) {
7502 board[toY][toX] = WhiteQueen;
7504 if(gameInfo.variant==VariantBughouse ||
7505 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7506 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7507 board[fromY][fromX] = EmptySquare;
7508 } else if ((fromY == BOARD_HEIGHT-4)
7510 && gameInfo.variant != VariantXiangqi
7511 && gameInfo.variant != VariantBerolina
7512 && (board[fromY][fromX] == WhitePawn)
7513 && (board[toY][toX] == EmptySquare)) {
7514 board[fromY][fromX] = EmptySquare;
7515 board[toY][toX] = WhitePawn;
7516 captured = board[toY - 1][toX];
7517 board[toY - 1][toX] = EmptySquare;
7518 } else if ((fromY == BOARD_HEIGHT-4)
7520 && gameInfo.variant == VariantBerolina
7521 && (board[fromY][fromX] == WhitePawn)
7522 && (board[toY][toX] == EmptySquare)) {
7523 board[fromY][fromX] = EmptySquare;
7524 board[toY][toX] = WhitePawn;
7525 if(oldEP & EP_BEROLIN_A) {
7526 captured = board[fromY][fromX-1];
7527 board[fromY][fromX-1] = EmptySquare;
7528 }else{ captured = board[fromY][fromX+1];
7529 board[fromY][fromX+1] = EmptySquare;
7531 } else if (board[fromY][fromX] == king
7532 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7533 && toY == fromY && toX > fromX+1) {
7534 board[fromY][fromX] = EmptySquare;
7535 board[toY][toX] = king;
7536 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7537 board[fromY][BOARD_RGHT-1] = EmptySquare;
7538 } else if (board[fromY][fromX] == king
7539 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7540 && toY == fromY && toX < fromX-1) {
7541 board[fromY][fromX] = EmptySquare;
7542 board[toY][toX] = king;
7543 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7544 board[fromY][BOARD_LEFT] = EmptySquare;
7545 } else if (fromY == 7 && fromX == 3
7546 && board[fromY][fromX] == BlackKing
7547 && toY == 7 && toX == 5) {
7548 board[fromY][fromX] = EmptySquare;
7549 board[toY][toX] = BlackKing;
7550 board[fromY][7] = EmptySquare;
7551 board[toY][4] = BlackRook;
7552 } else if (fromY == 7 && fromX == 3
7553 && board[fromY][fromX] == BlackKing
7554 && toY == 7 && toX == 1) {
7555 board[fromY][fromX] = EmptySquare;
7556 board[toY][toX] = BlackKing;
7557 board[fromY][0] = EmptySquare;
7558 board[toY][2] = BlackRook;
7559 } else if (board[fromY][fromX] == BlackPawn
7561 && gameInfo.variant != VariantXiangqi
7563 /* black pawn promotion */
7564 board[toY][toX] = CharToPiece(ToLower(promoChar));
7565 if (board[toY][toX] == EmptySquare) {
7566 board[toY][toX] = BlackQueen;
7568 if(gameInfo.variant==VariantBughouse ||
7569 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7570 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7571 board[fromY][fromX] = EmptySquare;
7572 } else if ((fromY == 3)
7574 && gameInfo.variant != VariantXiangqi
7575 && gameInfo.variant != VariantBerolina
7576 && (board[fromY][fromX] == BlackPawn)
7577 && (board[toY][toX] == EmptySquare)) {
7578 board[fromY][fromX] = EmptySquare;
7579 board[toY][toX] = BlackPawn;
7580 captured = board[toY + 1][toX];
7581 board[toY + 1][toX] = EmptySquare;
7582 } else if ((fromY == 3)
7584 && gameInfo.variant == VariantBerolina
7585 && (board[fromY][fromX] == BlackPawn)
7586 && (board[toY][toX] == EmptySquare)) {
7587 board[fromY][fromX] = EmptySquare;
7588 board[toY][toX] = BlackPawn;
7589 if(oldEP & EP_BEROLIN_A) {
7590 captured = board[fromY][fromX-1];
7591 board[fromY][fromX-1] = EmptySquare;
7592 }else{ captured = board[fromY][fromX+1];
7593 board[fromY][fromX+1] = EmptySquare;
7596 board[toY][toX] = board[fromY][fromX];
7597 board[fromY][fromX] = EmptySquare;
7600 /* [HGM] now we promote for Shogi, if needed */
7601 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7602 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7605 if (gameInfo.holdingsWidth != 0) {
7607 /* !!A lot more code needs to be written to support holdings */
7608 /* [HGM] OK, so I have written it. Holdings are stored in the */
7609 /* penultimate board files, so they are automaticlly stored */
7610 /* in the game history. */
7611 if (fromY == DROP_RANK) {
7612 /* Delete from holdings, by decreasing count */
7613 /* and erasing image if necessary */
7615 if(p < (int) BlackPawn) { /* white drop */
7616 p -= (int)WhitePawn;
7617 p = PieceToNumber((ChessSquare)p);
7618 if(p >= gameInfo.holdingsSize) p = 0;
7619 if(--board[p][BOARD_WIDTH-2] <= 0)
7620 board[p][BOARD_WIDTH-1] = EmptySquare;
7621 if((int)board[p][BOARD_WIDTH-2] < 0)
7622 board[p][BOARD_WIDTH-2] = 0;
7623 } else { /* black drop */
7624 p -= (int)BlackPawn;
7625 p = PieceToNumber((ChessSquare)p);
7626 if(p >= gameInfo.holdingsSize) p = 0;
7627 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7628 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7629 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7630 board[BOARD_HEIGHT-1-p][1] = 0;
7633 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7634 && gameInfo.variant != VariantBughouse ) {
7635 /* [HGM] holdings: Add to holdings, if holdings exist */
7636 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7637 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7638 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7641 if (p >= (int) BlackPawn) {
7642 p -= (int)BlackPawn;
7643 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7644 /* in Shogi restore piece to its original first */
7645 captured = (ChessSquare) (DEMOTED captured);
7648 p = PieceToNumber((ChessSquare)p);
7649 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7650 board[p][BOARD_WIDTH-2]++;
7651 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7653 p -= (int)WhitePawn;
7654 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7655 captured = (ChessSquare) (DEMOTED captured);
7658 p = PieceToNumber((ChessSquare)p);
7659 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7660 board[BOARD_HEIGHT-1-p][1]++;
7661 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7664 } else if (gameInfo.variant == VariantAtomic) {
7665 if (captured != EmptySquare) {
7667 for (y = toY-1; y <= toY+1; y++) {
7668 for (x = toX-1; x <= toX+1; x++) {
7669 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7670 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7671 board[y][x] = EmptySquare;
7675 board[toY][toX] = EmptySquare;
7678 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7679 /* [HGM] Shogi promotions */
7680 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7683 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7684 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7685 // [HGM] superchess: take promotion piece out of holdings
7686 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7687 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7688 if(!--board[k][BOARD_WIDTH-2])
7689 board[k][BOARD_WIDTH-1] = EmptySquare;
7691 if(!--board[BOARD_HEIGHT-1-k][1])
7692 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7698 /* Updates forwardMostMove */
7700 MakeMove(fromX, fromY, toX, toY, promoChar)
7701 int fromX, fromY, toX, toY;
7704 // forwardMostMove++; // [HGM] bare: moved downstream
7706 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7707 int timeLeft; static int lastLoadFlag=0; int king, piece;
7708 piece = boards[forwardMostMove][fromY][fromX];
7709 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7710 if(gameInfo.variant == VariantKnightmate)
7711 king += (int) WhiteUnicorn - (int) WhiteKing;
7712 if(forwardMostMove == 0) {
7714 fprintf(serverMoves, "%s;", second.tidy);
7715 fprintf(serverMoves, "%s;", first.tidy);
7716 if(!blackPlaysFirst)
7717 fprintf(serverMoves, "%s;", second.tidy);
7718 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7719 lastLoadFlag = loadFlag;
7721 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7722 // print castling suffix
7723 if( toY == fromY && piece == king ) {
7725 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7727 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7730 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7731 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7732 boards[forwardMostMove][toY][toX] == EmptySquare
7734 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7736 if(promoChar != NULLCHAR)
7737 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7739 fprintf(serverMoves, "/%d/%d",
7740 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7741 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7742 else timeLeft = blackTimeRemaining/1000;
7743 fprintf(serverMoves, "/%d", timeLeft);
7745 fflush(serverMoves);
7748 if (forwardMostMove+1 >= MAX_MOVES) {
7749 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7753 if (commentList[forwardMostMove+1] != NULL) {
7754 free(commentList[forwardMostMove+1]);
7755 commentList[forwardMostMove+1] = NULL;
7757 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7758 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7759 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7760 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7761 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7762 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
7763 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7764 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7765 gameInfo.result = GameUnfinished;
7766 if (gameInfo.resultDetails != NULL) {
7767 free(gameInfo.resultDetails);
7768 gameInfo.resultDetails = NULL;
7770 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7771 moveList[forwardMostMove - 1]);
7772 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7773 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7774 fromY, fromX, toY, toX, promoChar,
7775 parseList[forwardMostMove - 1]);
7776 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7777 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7778 castlingRights[forwardMostMove]) ) {
7784 if(gameInfo.variant != VariantShogi)
7785 strcat(parseList[forwardMostMove - 1], "+");
7789 strcat(parseList[forwardMostMove - 1], "#");
7792 if (appData.debugMode) {
7793 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7798 /* Updates currentMove if not pausing */
7800 ShowMove(fromX, fromY, toX, toY)
7802 int instant = (gameMode == PlayFromGameFile) ?
7803 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7804 if(appData.noGUI) return;
7805 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7807 if (forwardMostMove == currentMove + 1) {
7808 AnimateMove(boards[forwardMostMove - 1],
7809 fromX, fromY, toX, toY);
7811 if (appData.highlightLastMove) {
7812 SetHighlights(fromX, fromY, toX, toY);
7815 currentMove = forwardMostMove;
7818 if (instant) return;
7820 DisplayMove(currentMove - 1);
7821 DrawPosition(FALSE, boards[currentMove]);
7822 DisplayBothClocks();
7823 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7826 void SendEgtPath(ChessProgramState *cps)
7827 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7828 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7830 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7833 char c, *q = name+1, *r, *s;
7835 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7836 while(*p && *p != ',') *q++ = *p++;
7838 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7839 strcmp(name, ",nalimov:") == 0 ) {
7840 // take nalimov path from the menu-changeable option first, if it is defined
7841 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7842 SendToProgram(buf,cps); // send egtbpath command for nalimov
7844 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7845 (s = StrStr(appData.egtFormats, name)) != NULL) {
7846 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7847 s = r = StrStr(s, ":") + 1; // beginning of path info
7848 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7849 c = *r; *r = 0; // temporarily null-terminate path info
7850 *--q = 0; // strip of trailig ':' from name
7851 sprintf(buf, "egtpath %s %s\n", name+1, s);
7853 SendToProgram(buf,cps); // send egtbpath command for this format
7855 if(*p == ',') p++; // read away comma to position for next format name
7860 InitChessProgram(cps, setup)
7861 ChessProgramState *cps;
7862 int setup; /* [HGM] needed to setup FRC opening position */
7864 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7865 if (appData.noChessProgram) return;
7866 hintRequested = FALSE;
7867 bookRequested = FALSE;
7869 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7870 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7871 if(cps->memSize) { /* [HGM] memory */
7872 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7873 SendToProgram(buf, cps);
7875 SendEgtPath(cps); /* [HGM] EGT */
7876 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7877 sprintf(buf, "cores %d\n", appData.smpCores);
7878 SendToProgram(buf, cps);
7881 SendToProgram(cps->initString, cps);
7882 if (gameInfo.variant != VariantNormal &&
7883 gameInfo.variant != VariantLoadable
7884 /* [HGM] also send variant if board size non-standard */
7885 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7887 char *v = VariantName(gameInfo.variant);
7888 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7889 /* [HGM] in protocol 1 we have to assume all variants valid */
7890 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7891 DisplayFatalError(buf, 0, 1);
7895 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7896 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7897 if( gameInfo.variant == VariantXiangqi )
7898 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7899 if( gameInfo.variant == VariantShogi )
7900 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7901 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7902 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7903 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7904 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7905 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7906 if( gameInfo.variant == VariantCourier )
7907 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7908 if( gameInfo.variant == VariantSuper )
7909 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7910 if( gameInfo.variant == VariantGreat )
7911 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7914 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7915 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7916 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7917 if(StrStr(cps->variants, b) == NULL) {
7918 // specific sized variant not known, check if general sizing allowed
7919 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7920 if(StrStr(cps->variants, "boardsize") == NULL) {
7921 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7922 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7923 DisplayFatalError(buf, 0, 1);
7926 /* [HGM] here we really should compare with the maximum supported board size */
7929 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7930 sprintf(buf, "variant %s\n", b);
7931 SendToProgram(buf, cps);
7933 currentlyInitializedVariant = gameInfo.variant;
7935 /* [HGM] send opening position in FRC to first engine */
7937 SendToProgram("force\n", cps);
7939 /* engine is now in force mode! Set flag to wake it up after first move. */
7940 setboardSpoiledMachineBlack = 1;
7944 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7945 SendToProgram(buf, cps);
7947 cps->maybeThinking = FALSE;
7948 cps->offeredDraw = 0;
7949 if (!appData.icsActive) {
7950 SendTimeControl(cps, movesPerSession, timeControl,
7951 timeIncrement, appData.searchDepth,
7954 if (appData.showThinking
7955 // [HGM] thinking: four options require thinking output to be sent
7956 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7958 SendToProgram("post\n", cps);
7960 SendToProgram("hard\n", cps);
7961 if (!appData.ponderNextMove) {
7962 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7963 it without being sure what state we are in first. "hard"
7964 is not a toggle, so that one is OK.
7966 SendToProgram("easy\n", cps);
7969 sprintf(buf, "ping %d\n", ++cps->lastPing);
7970 SendToProgram(buf, cps);
7972 cps->initDone = TRUE;
7977 StartChessProgram(cps)
7978 ChessProgramState *cps;
7983 if (appData.noChessProgram) return;
7984 cps->initDone = FALSE;
7986 if (strcmp(cps->host, "localhost") == 0) {
7987 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7988 } else if (*appData.remoteShell == NULLCHAR) {
7989 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7991 if (*appData.remoteUser == NULLCHAR) {
7992 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7995 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7996 cps->host, appData.remoteUser, cps->program);
7998 err = StartChildProcess(buf, "", &cps->pr);
8002 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8003 DisplayFatalError(buf, err, 1);
8009 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8010 if (cps->protocolVersion > 1) {
8011 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8012 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8013 cps->comboCnt = 0; // and values of combo boxes
8014 SendToProgram(buf, cps);
8016 SendToProgram("xboard\n", cps);
8022 TwoMachinesEventIfReady P((void))
8024 if (first.lastPing != first.lastPong) {
8025 DisplayMessage("", _("Waiting for first chess program"));
8026 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8029 if (second.lastPing != second.lastPong) {
8030 DisplayMessage("", _("Waiting for second chess program"));
8031 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8039 NextMatchGame P((void))
8041 int index; /* [HGM] autoinc: step load index during match */
8043 if (*appData.loadGameFile != NULLCHAR) {
8044 index = appData.loadGameIndex;
8045 if(index < 0) { // [HGM] autoinc
8046 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8047 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8049 LoadGameFromFile(appData.loadGameFile,
8051 appData.loadGameFile, FALSE);
8052 } else if (*appData.loadPositionFile != NULLCHAR) {
8053 index = appData.loadPositionIndex;
8054 if(index < 0) { // [HGM] autoinc
8055 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8056 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8058 LoadPositionFromFile(appData.loadPositionFile,
8060 appData.loadPositionFile);
8062 TwoMachinesEventIfReady();
8065 void UserAdjudicationEvent( int result )
8067 ChessMove gameResult = GameIsDrawn;
8070 gameResult = WhiteWins;
8072 else if( result < 0 ) {
8073 gameResult = BlackWins;
8076 if( gameMode == TwoMachinesPlay ) {
8077 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8082 // [HGM] save: calculate checksum of game to make games easily identifiable
8083 int StringCheckSum(char *s)
8086 if(s==NULL) return 0;
8087 while(*s) i = i*259 + *s++;
8094 for(i=backwardMostMove; i<forwardMostMove; i++) {
8095 sum += pvInfoList[i].depth;
8096 sum += StringCheckSum(parseList[i]);
8097 sum += StringCheckSum(commentList[i]);
8100 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8101 return sum + StringCheckSum(commentList[i]);
8102 } // end of save patch
8105 GameEnds(result, resultDetails, whosays)
8107 char *resultDetails;
8110 GameMode nextGameMode;
8114 if(endingGame) return; /* [HGM] crash: forbid recursion */
8117 if (appData.debugMode) {
8118 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8119 result, resultDetails ? resultDetails : "(null)", whosays);
8122 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8124 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8125 /* If we are playing on ICS, the server decides when the
8126 game is over, but the engine can offer to draw, claim
8130 if (appData.zippyPlay && first.initDone) {
8131 if (result == GameIsDrawn) {
8132 /* In case draw still needs to be claimed */
8133 SendToICS(ics_prefix);
8134 SendToICS("draw\n");
8135 } else if (StrCaseStr(resultDetails, "resign")) {
8136 SendToICS(ics_prefix);
8137 SendToICS("resign\n");
8141 endingGame = 0; /* [HGM] crash */
8145 /* If we're loading the game from a file, stop */
8146 if (whosays == GE_FILE) {
8147 (void) StopLoadGameTimer();
8151 /* Cancel draw offers */
8152 first.offeredDraw = second.offeredDraw = 0;
8154 /* If this is an ICS game, only ICS can really say it's done;
8155 if not, anyone can. */
8156 isIcsGame = (gameMode == IcsPlayingWhite ||
8157 gameMode == IcsPlayingBlack ||
8158 gameMode == IcsObserving ||
8159 gameMode == IcsExamining);
8161 if (!isIcsGame || whosays == GE_ICS) {
8162 /* OK -- not an ICS game, or ICS said it was done */
8164 if (!isIcsGame && !appData.noChessProgram)
8165 SetUserThinkingEnables();
8167 /* [HGM] if a machine claims the game end we verify this claim */
8168 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8169 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8171 ChessMove trueResult = (ChessMove) -1;
8173 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8174 first.twoMachinesColor[0] :
8175 second.twoMachinesColor[0] ;
8177 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8178 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8179 /* [HGM] verify: engine mate claims accepted if they were flagged */
8180 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8182 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8183 /* [HGM] verify: engine mate claims accepted if they were flagged */
8184 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8186 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8187 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8190 // now verify win claims, but not in drop games, as we don't understand those yet
8191 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8192 || gameInfo.variant == VariantGreat) &&
8193 (result == WhiteWins && claimer == 'w' ||
8194 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8195 if (appData.debugMode) {
8196 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8197 result, epStatus[forwardMostMove], forwardMostMove);
8199 if(result != trueResult) {
8200 sprintf(buf, "False win claim: '%s'", resultDetails);
8201 result = claimer == 'w' ? BlackWins : WhiteWins;
8202 resultDetails = buf;
8205 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8206 && (forwardMostMove <= backwardMostMove ||
8207 epStatus[forwardMostMove-1] > EP_DRAWS ||
8208 (claimer=='b')==(forwardMostMove&1))
8210 /* [HGM] verify: draws that were not flagged are false claims */
8211 sprintf(buf, "False draw claim: '%s'", resultDetails);
8212 result = claimer == 'w' ? BlackWins : WhiteWins;
8213 resultDetails = buf;
8215 /* (Claiming a loss is accepted no questions asked!) */
8217 /* [HGM] bare: don't allow bare King to win */
8218 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8219 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8220 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8221 && result != GameIsDrawn)
8222 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8223 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8224 int p = (int)boards[forwardMostMove][i][j] - color;
8225 if(p >= 0 && p <= (int)WhiteKing) k++;
8227 if (appData.debugMode) {
8228 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8229 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8232 result = GameIsDrawn;
8233 sprintf(buf, "%s but bare king", resultDetails);
8234 resultDetails = buf;
8240 if(serverMoves != NULL && !loadFlag) { char c = '=';
8241 if(result==WhiteWins) c = '+';
8242 if(result==BlackWins) c = '-';
8243 if(resultDetails != NULL)
8244 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8246 if (resultDetails != NULL) {
8247 gameInfo.result = result;
8248 gameInfo.resultDetails = StrSave(resultDetails);
8250 /* display last move only if game was not loaded from file */
8251 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8252 DisplayMove(currentMove - 1);
8254 if (forwardMostMove != 0) {
8255 if (gameMode != PlayFromGameFile && gameMode != EditGame
8256 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8258 if (*appData.saveGameFile != NULLCHAR) {
8259 SaveGameToFile(appData.saveGameFile, TRUE);
8260 } else if (appData.autoSaveGames) {
8263 if (*appData.savePositionFile != NULLCHAR) {
8264 SavePositionToFile(appData.savePositionFile);
8269 /* Tell program how game ended in case it is learning */
8270 /* [HGM] Moved this to after saving the PGN, just in case */
8271 /* engine died and we got here through time loss. In that */
8272 /* case we will get a fatal error writing the pipe, which */
8273 /* would otherwise lose us the PGN. */
8274 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8275 /* output during GameEnds should never be fatal anymore */
8276 if (gameMode == MachinePlaysWhite ||
8277 gameMode == MachinePlaysBlack ||
8278 gameMode == TwoMachinesPlay ||
8279 gameMode == IcsPlayingWhite ||
8280 gameMode == IcsPlayingBlack ||
8281 gameMode == BeginningOfGame) {
8283 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8285 if (first.pr != NoProc) {
8286 SendToProgram(buf, &first);
8288 if (second.pr != NoProc &&
8289 gameMode == TwoMachinesPlay) {
8290 SendToProgram(buf, &second);
8295 if (appData.icsActive) {
8296 if (appData.quietPlay &&
8297 (gameMode == IcsPlayingWhite ||
8298 gameMode == IcsPlayingBlack)) {
8299 SendToICS(ics_prefix);
8300 SendToICS("set shout 1\n");
8302 nextGameMode = IcsIdle;
8303 ics_user_moved = FALSE;
8304 /* clean up premove. It's ugly when the game has ended and the
8305 * premove highlights are still on the board.
8309 ClearPremoveHighlights();
8310 DrawPosition(FALSE, boards[currentMove]);
8312 if (whosays == GE_ICS) {
8315 if (gameMode == IcsPlayingWhite)
8317 else if(gameMode == IcsPlayingBlack)
8321 if (gameMode == IcsPlayingBlack)
8323 else if(gameMode == IcsPlayingWhite)
8330 PlayIcsUnfinishedSound();
8333 } else if (gameMode == EditGame ||
8334 gameMode == PlayFromGameFile ||
8335 gameMode == AnalyzeMode ||
8336 gameMode == AnalyzeFile) {
8337 nextGameMode = gameMode;
8339 nextGameMode = EndOfGame;
8344 nextGameMode = gameMode;
8347 if (appData.noChessProgram) {
8348 gameMode = nextGameMode;
8350 endingGame = 0; /* [HGM] crash */
8355 /* Put first chess program into idle state */
8356 if (first.pr != NoProc &&
8357 (gameMode == MachinePlaysWhite ||
8358 gameMode == MachinePlaysBlack ||
8359 gameMode == TwoMachinesPlay ||
8360 gameMode == IcsPlayingWhite ||
8361 gameMode == IcsPlayingBlack ||
8362 gameMode == BeginningOfGame)) {
8363 SendToProgram("force\n", &first);
8364 if (first.usePing) {
8366 sprintf(buf, "ping %d\n", ++first.lastPing);
8367 SendToProgram(buf, &first);
8370 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8371 /* Kill off first chess program */
8372 if (first.isr != NULL)
8373 RemoveInputSource(first.isr);
8376 if (first.pr != NoProc) {
8378 DoSleep( appData.delayBeforeQuit );
8379 SendToProgram("quit\n", &first);
8380 DoSleep( appData.delayAfterQuit );
8381 DestroyChildProcess(first.pr, first.useSigterm);
8386 /* Put second chess program into idle state */
8387 if (second.pr != NoProc &&
8388 gameMode == TwoMachinesPlay) {
8389 SendToProgram("force\n", &second);
8390 if (second.usePing) {
8392 sprintf(buf, "ping %d\n", ++second.lastPing);
8393 SendToProgram(buf, &second);
8396 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8397 /* Kill off second chess program */
8398 if (second.isr != NULL)
8399 RemoveInputSource(second.isr);
8402 if (second.pr != NoProc) {
8403 DoSleep( appData.delayBeforeQuit );
8404 SendToProgram("quit\n", &second);
8405 DoSleep( appData.delayAfterQuit );
8406 DestroyChildProcess(second.pr, second.useSigterm);
8411 if (matchMode && gameMode == TwoMachinesPlay) {
8414 if (first.twoMachinesColor[0] == 'w') {
8421 if (first.twoMachinesColor[0] == 'b') {
8430 if (matchGame < appData.matchGames) {
8432 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8433 tmp = first.twoMachinesColor;
8434 first.twoMachinesColor = second.twoMachinesColor;
8435 second.twoMachinesColor = tmp;
8437 gameMode = nextGameMode;
8439 if(appData.matchPause>10000 || appData.matchPause<10)
8440 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8441 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8442 endingGame = 0; /* [HGM] crash */
8446 gameMode = nextGameMode;
8447 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8448 first.tidy, second.tidy,
8449 first.matchWins, second.matchWins,
8450 appData.matchGames - (first.matchWins + second.matchWins));
8451 DisplayFatalError(buf, 0, 0);
8454 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8455 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8457 gameMode = nextGameMode;
8459 endingGame = 0; /* [HGM] crash */
8462 /* Assumes program was just initialized (initString sent).
8463 Leaves program in force mode. */
8465 FeedMovesToProgram(cps, upto)
8466 ChessProgramState *cps;
8471 if (appData.debugMode)
8472 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8473 startedFromSetupPosition ? "position and " : "",
8474 backwardMostMove, upto, cps->which);
8475 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8476 // [HGM] variantswitch: make engine aware of new variant
8477 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8478 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8479 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8480 SendToProgram(buf, cps);
8481 currentlyInitializedVariant = gameInfo.variant;
8483 SendToProgram("force\n", cps);
8484 if (startedFromSetupPosition) {
8485 SendBoard(cps, backwardMostMove);
8486 if (appData.debugMode) {
8487 fprintf(debugFP, "feedMoves\n");
8490 for (i = backwardMostMove; i < upto; i++) {
8491 SendMoveToProgram(i, cps);
8497 ResurrectChessProgram()
8499 /* The chess program may have exited.
8500 If so, restart it and feed it all the moves made so far. */
8502 if (appData.noChessProgram || first.pr != NoProc) return;
8504 StartChessProgram(&first);
8505 InitChessProgram(&first, FALSE);
8506 FeedMovesToProgram(&first, currentMove);
8508 if (!first.sendTime) {
8509 /* can't tell gnuchess what its clock should read,
8510 so we bow to its notion. */
8512 timeRemaining[0][currentMove] = whiteTimeRemaining;
8513 timeRemaining[1][currentMove] = blackTimeRemaining;
8516 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8517 appData.icsEngineAnalyze) && first.analysisSupport) {
8518 SendToProgram("analyze\n", &first);
8519 first.analyzing = TRUE;
8532 if (appData.debugMode) {
8533 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8534 redraw, init, gameMode);
8536 pausing = pauseExamInvalid = FALSE;
8537 startedFromSetupPosition = blackPlaysFirst = FALSE;
8539 whiteFlag = blackFlag = FALSE;
8540 userOfferedDraw = FALSE;
8541 hintRequested = bookRequested = FALSE;
8542 first.maybeThinking = FALSE;
8543 second.maybeThinking = FALSE;
8544 first.bookSuspend = FALSE; // [HGM] book
8545 second.bookSuspend = FALSE;
8546 thinkOutput[0] = NULLCHAR;
8547 lastHint[0] = NULLCHAR;
8548 ClearGameInfo(&gameInfo);
8549 gameInfo.variant = StringToVariant(appData.variant);
8550 ics_user_moved = ics_clock_paused = FALSE;
8551 ics_getting_history = H_FALSE;
8553 white_holding[0] = black_holding[0] = NULLCHAR;
8554 ClearProgramStats();
8555 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8559 flipView = appData.flipView;
8560 ClearPremoveHighlights();
8562 alarmSounded = FALSE;
8564 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8565 if(appData.serverMovesName != NULL) {
8566 /* [HGM] prepare to make moves file for broadcasting */
8567 clock_t t = clock();
8568 if(serverMoves != NULL) fclose(serverMoves);
8569 serverMoves = fopen(appData.serverMovesName, "r");
8570 if(serverMoves != NULL) {
8571 fclose(serverMoves);
8572 /* delay 15 sec before overwriting, so all clients can see end */
8573 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8575 serverMoves = fopen(appData.serverMovesName, "w");
8579 gameMode = BeginningOfGame;
8581 if(appData.icsActive) gameInfo.variant = VariantNormal;
8582 currentMove = forwardMostMove = backwardMostMove = 0;
8583 InitPosition(redraw);
8584 for (i = 0; i < MAX_MOVES; i++) {
8585 if (commentList[i] != NULL) {
8586 free(commentList[i]);
8587 commentList[i] = NULL;
8591 timeRemaining[0][0] = whiteTimeRemaining;
8592 timeRemaining[1][0] = blackTimeRemaining;
8593 if (first.pr == NULL) {
8594 StartChessProgram(&first);
8597 InitChessProgram(&first, startedFromSetupPosition);
8600 DisplayMessage("", "");
8601 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8602 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8609 if (!AutoPlayOneMove())
8611 if (matchMode || appData.timeDelay == 0)
8613 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8615 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8624 int fromX, fromY, toX, toY;
8626 if (appData.debugMode) {
8627 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8630 if (gameMode != PlayFromGameFile)
8633 if (currentMove >= forwardMostMove) {
8634 gameMode = EditGame;
8637 /* [AS] Clear current move marker at the end of a game */
8638 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8643 toX = moveList[currentMove][2] - AAA;
8644 toY = moveList[currentMove][3] - ONE;
8646 if (moveList[currentMove][1] == '@') {
8647 if (appData.highlightLastMove) {
8648 SetHighlights(-1, -1, toX, toY);
8651 fromX = moveList[currentMove][0] - AAA;
8652 fromY = moveList[currentMove][1] - ONE;
8654 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8656 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8658 if (appData.highlightLastMove) {
8659 SetHighlights(fromX, fromY, toX, toY);
8662 DisplayMove(currentMove);
8663 SendMoveToProgram(currentMove++, &first);
8664 DisplayBothClocks();
8665 DrawPosition(FALSE, boards[currentMove]);
8666 // [HGM] PV info: always display, routine tests if empty
8667 DisplayComment(currentMove - 1, commentList[currentMove]);
8673 LoadGameOneMove(readAhead)
8674 ChessMove readAhead;
8676 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8677 char promoChar = NULLCHAR;
8682 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8683 gameMode != AnalyzeMode && gameMode != Training) {
8688 yyboardindex = forwardMostMove;
8689 if (readAhead != (ChessMove)0) {
8690 moveType = readAhead;
8692 if (gameFileFP == NULL)
8694 moveType = (ChessMove) yylex();
8700 if (appData.debugMode)
8701 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8703 if (*p == '{' || *p == '[' || *p == '(') {
8704 p[strlen(p) - 1] = NULLCHAR;
8708 /* append the comment but don't display it */
8709 while (*p == '\n') p++;
8710 AppendComment(currentMove, p);
8713 case WhiteCapturesEnPassant:
8714 case BlackCapturesEnPassant:
8715 case WhitePromotionChancellor:
8716 case BlackPromotionChancellor:
8717 case WhitePromotionArchbishop:
8718 case BlackPromotionArchbishop:
8719 case WhitePromotionCentaur:
8720 case BlackPromotionCentaur:
8721 case WhitePromotionQueen:
8722 case BlackPromotionQueen:
8723 case WhitePromotionRook:
8724 case BlackPromotionRook:
8725 case WhitePromotionBishop:
8726 case BlackPromotionBishop:
8727 case WhitePromotionKnight:
8728 case BlackPromotionKnight:
8729 case WhitePromotionKing:
8730 case BlackPromotionKing:
8732 case WhiteKingSideCastle:
8733 case WhiteQueenSideCastle:
8734 case BlackKingSideCastle:
8735 case BlackQueenSideCastle:
8736 case WhiteKingSideCastleWild:
8737 case WhiteQueenSideCastleWild:
8738 case BlackKingSideCastleWild:
8739 case BlackQueenSideCastleWild:
8741 case WhiteHSideCastleFR:
8742 case WhiteASideCastleFR:
8743 case BlackHSideCastleFR:
8744 case BlackASideCastleFR:
8746 if (appData.debugMode)
8747 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8748 fromX = currentMoveString[0] - AAA;
8749 fromY = currentMoveString[1] - ONE;
8750 toX = currentMoveString[2] - AAA;
8751 toY = currentMoveString[3] - ONE;
8752 promoChar = currentMoveString[4];
8757 if (appData.debugMode)
8758 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8759 fromX = moveType == WhiteDrop ?
8760 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8761 (int) CharToPiece(ToLower(currentMoveString[0]));
8763 toX = currentMoveString[2] - AAA;
8764 toY = currentMoveString[3] - ONE;
8770 case GameUnfinished:
8771 if (appData.debugMode)
8772 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8773 p = strchr(yy_text, '{');
8774 if (p == NULL) p = strchr(yy_text, '(');
8777 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8779 q = strchr(p, *p == '{' ? '}' : ')');
8780 if (q != NULL) *q = NULLCHAR;
8783 GameEnds(moveType, p, GE_FILE);
8785 if (cmailMsgLoaded) {
8787 flipView = WhiteOnMove(currentMove);
8788 if (moveType == GameUnfinished) flipView = !flipView;
8789 if (appData.debugMode)
8790 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8794 case (ChessMove) 0: /* end of file */
8795 if (appData.debugMode)
8796 fprintf(debugFP, "Parser hit end of file\n");
8797 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8798 EP_UNKNOWN, castlingRights[currentMove]) ) {
8804 if (WhiteOnMove(currentMove)) {
8805 GameEnds(BlackWins, "Black mates", GE_FILE);
8807 GameEnds(WhiteWins, "White mates", GE_FILE);
8811 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8818 if (lastLoadGameStart == GNUChessGame) {
8819 /* GNUChessGames have numbers, but they aren't move numbers */
8820 if (appData.debugMode)
8821 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8822 yy_text, (int) moveType);
8823 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8825 /* else fall thru */
8830 /* Reached start of next game in file */
8831 if (appData.debugMode)
8832 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8833 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8834 EP_UNKNOWN, castlingRights[currentMove]) ) {
8840 if (WhiteOnMove(currentMove)) {
8841 GameEnds(BlackWins, "Black mates", GE_FILE);
8843 GameEnds(WhiteWins, "White mates", GE_FILE);
8847 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8853 case PositionDiagram: /* should not happen; ignore */
8854 case ElapsedTime: /* ignore */
8855 case NAG: /* ignore */
8856 if (appData.debugMode)
8857 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8858 yy_text, (int) moveType);
8859 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8862 if (appData.testLegality) {
8863 if (appData.debugMode)
8864 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8865 sprintf(move, _("Illegal move: %d.%s%s"),
8866 (forwardMostMove / 2) + 1,
8867 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8868 DisplayError(move, 0);
8871 if (appData.debugMode)
8872 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8873 yy_text, currentMoveString);
8874 fromX = currentMoveString[0] - AAA;
8875 fromY = currentMoveString[1] - ONE;
8876 toX = currentMoveString[2] - AAA;
8877 toY = currentMoveString[3] - ONE;
8878 promoChar = currentMoveString[4];
8883 if (appData.debugMode)
8884 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8885 sprintf(move, _("Ambiguous move: %d.%s%s"),
8886 (forwardMostMove / 2) + 1,
8887 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8888 DisplayError(move, 0);
8893 case ImpossibleMove:
8894 if (appData.debugMode)
8895 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8896 sprintf(move, _("Illegal move: %d.%s%s"),
8897 (forwardMostMove / 2) + 1,
8898 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8899 DisplayError(move, 0);
8905 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8906 DrawPosition(FALSE, boards[currentMove]);
8907 DisplayBothClocks();
8908 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8909 DisplayComment(currentMove - 1, commentList[currentMove]);
8911 (void) StopLoadGameTimer();
8913 cmailOldMove = forwardMostMove;
8916 /* currentMoveString is set as a side-effect of yylex */
8917 strcat(currentMoveString, "\n");
8918 strcpy(moveList[forwardMostMove], currentMoveString);
8920 thinkOutput[0] = NULLCHAR;
8921 MakeMove(fromX, fromY, toX, toY, promoChar);
8922 currentMove = forwardMostMove;
8927 /* Load the nth game from the given file */
8929 LoadGameFromFile(filename, n, title, useList)
8933 /*Boolean*/ int useList;
8938 if (strcmp(filename, "-") == 0) {
8942 f = fopen(filename, "rb");
8944 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8945 DisplayError(buf, errno);
8949 if (fseek(f, 0, 0) == -1) {
8950 /* f is not seekable; probably a pipe */
8953 if (useList && n == 0) {
8954 int error = GameListBuild(f);
8956 DisplayError(_("Cannot build game list"), error);
8957 } else if (!ListEmpty(&gameList) &&
8958 ((ListGame *) gameList.tailPred)->number > 1) {
8959 GameListPopUp(f, title);
8966 return LoadGame(f, n, title, FALSE);
8971 MakeRegisteredMove()
8973 int fromX, fromY, toX, toY;
8975 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8976 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8979 if (appData.debugMode)
8980 fprintf(debugFP, "Restoring %s for game %d\n",
8981 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8983 thinkOutput[0] = NULLCHAR;
8984 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8985 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8986 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8987 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8988 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8989 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8990 MakeMove(fromX, fromY, toX, toY, promoChar);
8991 ShowMove(fromX, fromY, toX, toY);
8993 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8994 EP_UNKNOWN, castlingRights[currentMove]) ) {
9001 if (WhiteOnMove(currentMove)) {
9002 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9004 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9009 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9016 if (WhiteOnMove(currentMove)) {
9017 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9019 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9024 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9035 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9037 CmailLoadGame(f, gameNumber, title, useList)
9045 if (gameNumber > nCmailGames) {
9046 DisplayError(_("No more games in this message"), 0);
9049 if (f == lastLoadGameFP) {
9050 int offset = gameNumber - lastLoadGameNumber;
9052 cmailMsg[0] = NULLCHAR;
9053 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9054 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9055 nCmailMovesRegistered--;
9057 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9058 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9059 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9062 if (! RegisterMove()) return FALSE;
9066 retVal = LoadGame(f, gameNumber, title, useList);
9068 /* Make move registered during previous look at this game, if any */
9069 MakeRegisteredMove();
9071 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9072 commentList[currentMove]
9073 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9074 DisplayComment(currentMove - 1, commentList[currentMove]);
9080 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9085 int gameNumber = lastLoadGameNumber + offset;
9086 if (lastLoadGameFP == NULL) {
9087 DisplayError(_("No game has been loaded yet"), 0);
9090 if (gameNumber <= 0) {
9091 DisplayError(_("Can't back up any further"), 0);
9094 if (cmailMsgLoaded) {
9095 return CmailLoadGame(lastLoadGameFP, gameNumber,
9096 lastLoadGameTitle, lastLoadGameUseList);
9098 return LoadGame(lastLoadGameFP, gameNumber,
9099 lastLoadGameTitle, lastLoadGameUseList);
9105 /* Load the nth game from open file f */
9107 LoadGame(f, gameNumber, title, useList)
9115 int gn = gameNumber;
9116 ListGame *lg = NULL;
9119 GameMode oldGameMode;
9120 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9122 if (appData.debugMode)
9123 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9125 if (gameMode == Training )
9126 SetTrainingModeOff();
9128 oldGameMode = gameMode;
9129 if (gameMode != BeginningOfGame) {
9134 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9135 fclose(lastLoadGameFP);
9139 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9142 fseek(f, lg->offset, 0);
9143 GameListHighlight(gameNumber);
9147 DisplayError(_("Game number out of range"), 0);
9152 if (fseek(f, 0, 0) == -1) {
9153 if (f == lastLoadGameFP ?
9154 gameNumber == lastLoadGameNumber + 1 :
9158 DisplayError(_("Can't seek on game file"), 0);
9164 lastLoadGameNumber = gameNumber;
9165 strcpy(lastLoadGameTitle, title);
9166 lastLoadGameUseList = useList;
9170 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9171 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9172 lg->gameInfo.black);
9174 } else if (*title != NULLCHAR) {
9175 if (gameNumber > 1) {
9176 sprintf(buf, "%s %d", title, gameNumber);
9179 DisplayTitle(title);
9183 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9184 gameMode = PlayFromGameFile;
9188 currentMove = forwardMostMove = backwardMostMove = 0;
9189 CopyBoard(boards[0], initialPosition);
9193 * Skip the first gn-1 games in the file.
9194 * Also skip over anything that precedes an identifiable
9195 * start of game marker, to avoid being confused by
9196 * garbage at the start of the file. Currently
9197 * recognized start of game markers are the move number "1",
9198 * the pattern "gnuchess .* game", the pattern
9199 * "^[#;%] [^ ]* game file", and a PGN tag block.
9200 * A game that starts with one of the latter two patterns
9201 * will also have a move number 1, possibly
9202 * following a position diagram.
9203 * 5-4-02: Let's try being more lenient and allowing a game to
9204 * start with an unnumbered move. Does that break anything?
9206 cm = lastLoadGameStart = (ChessMove) 0;
9208 yyboardindex = forwardMostMove;
9209 cm = (ChessMove) yylex();
9212 if (cmailMsgLoaded) {
9213 nCmailGames = CMAIL_MAX_GAMES - gn;
9216 DisplayError(_("Game not found in file"), 0);
9223 lastLoadGameStart = cm;
9227 switch (lastLoadGameStart) {
9234 gn--; /* count this game */
9235 lastLoadGameStart = cm;
9244 switch (lastLoadGameStart) {
9249 gn--; /* count this game */
9250 lastLoadGameStart = cm;
9253 lastLoadGameStart = cm; /* game counted already */
9261 yyboardindex = forwardMostMove;
9262 cm = (ChessMove) yylex();
9263 } while (cm == PGNTag || cm == Comment);
9270 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9271 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9272 != CMAIL_OLD_RESULT) {
9274 cmailResult[ CMAIL_MAX_GAMES
9275 - gn - 1] = CMAIL_OLD_RESULT;
9281 /* Only a NormalMove can be at the start of a game
9282 * without a position diagram. */
9283 if (lastLoadGameStart == (ChessMove) 0) {
9285 lastLoadGameStart = MoveNumberOne;
9294 if (appData.debugMode)
9295 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9297 if (cm == XBoardGame) {
9298 /* Skip any header junk before position diagram and/or move 1 */
9300 yyboardindex = forwardMostMove;
9301 cm = (ChessMove) yylex();
9303 if (cm == (ChessMove) 0 ||
9304 cm == GNUChessGame || cm == XBoardGame) {
9305 /* Empty game; pretend end-of-file and handle later */
9310 if (cm == MoveNumberOne || cm == PositionDiagram ||
9311 cm == PGNTag || cm == Comment)
9314 } else if (cm == GNUChessGame) {
9315 if (gameInfo.event != NULL) {
9316 free(gameInfo.event);
9318 gameInfo.event = StrSave(yy_text);
9321 startedFromSetupPosition = FALSE;
9322 while (cm == PGNTag) {
9323 if (appData.debugMode)
9324 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9325 err = ParsePGNTag(yy_text, &gameInfo);
9326 if (!err) numPGNTags++;
9328 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9329 if(gameInfo.variant != oldVariant) {
9330 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9332 oldVariant = gameInfo.variant;
9333 if (appData.debugMode)
9334 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9338 if (gameInfo.fen != NULL) {
9339 Board initial_position;
9340 startedFromSetupPosition = TRUE;
9341 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9343 DisplayError(_("Bad FEN position in file"), 0);
9346 CopyBoard(boards[0], initial_position);
9347 if (blackPlaysFirst) {
9348 currentMove = forwardMostMove = backwardMostMove = 1;
9349 CopyBoard(boards[1], initial_position);
9350 strcpy(moveList[0], "");
9351 strcpy(parseList[0], "");
9352 timeRemaining[0][1] = whiteTimeRemaining;
9353 timeRemaining[1][1] = blackTimeRemaining;
9354 if (commentList[0] != NULL) {
9355 commentList[1] = commentList[0];
9356 commentList[0] = NULL;
9359 currentMove = forwardMostMove = backwardMostMove = 0;
9361 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9363 initialRulePlies = FENrulePlies;
9364 epStatus[forwardMostMove] = FENepStatus;
9365 for( i=0; i< nrCastlingRights; i++ )
9366 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9368 yyboardindex = forwardMostMove;
9370 gameInfo.fen = NULL;
9373 yyboardindex = forwardMostMove;
9374 cm = (ChessMove) yylex();
9376 /* Handle comments interspersed among the tags */
9377 while (cm == Comment) {
9379 if (appData.debugMode)
9380 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9382 if (*p == '{' || *p == '[' || *p == '(') {
9383 p[strlen(p) - 1] = NULLCHAR;
9386 while (*p == '\n') p++;
9387 AppendComment(currentMove, p);
9388 yyboardindex = forwardMostMove;
9389 cm = (ChessMove) yylex();
9393 /* don't rely on existence of Event tag since if game was
9394 * pasted from clipboard the Event tag may not exist
9396 if (numPGNTags > 0){
9398 if (gameInfo.variant == VariantNormal) {
9399 gameInfo.variant = StringToVariant(gameInfo.event);
9402 if( appData.autoDisplayTags ) {
9403 tags = PGNTags(&gameInfo);
9404 TagsPopUp(tags, CmailMsg());
9409 /* Make something up, but don't display it now */
9414 if (cm == PositionDiagram) {
9417 Board initial_position;
9419 if (appData.debugMode)
9420 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9422 if (!startedFromSetupPosition) {
9424 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9425 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9435 initial_position[i][j++] = CharToPiece(*p);
9438 while (*p == ' ' || *p == '\t' ||
9439 *p == '\n' || *p == '\r') p++;
9441 if (strncmp(p, "black", strlen("black"))==0)
9442 blackPlaysFirst = TRUE;
9444 blackPlaysFirst = FALSE;
9445 startedFromSetupPosition = TRUE;
9447 CopyBoard(boards[0], initial_position);
9448 if (blackPlaysFirst) {
9449 currentMove = forwardMostMove = backwardMostMove = 1;
9450 CopyBoard(boards[1], initial_position);
9451 strcpy(moveList[0], "");
9452 strcpy(parseList[0], "");
9453 timeRemaining[0][1] = whiteTimeRemaining;
9454 timeRemaining[1][1] = blackTimeRemaining;
9455 if (commentList[0] != NULL) {
9456 commentList[1] = commentList[0];
9457 commentList[0] = NULL;
9460 currentMove = forwardMostMove = backwardMostMove = 0;
9463 yyboardindex = forwardMostMove;
9464 cm = (ChessMove) yylex();
9467 if (first.pr == NoProc) {
9468 StartChessProgram(&first);
9470 InitChessProgram(&first, FALSE);
9471 SendToProgram("force\n", &first);
9472 if (startedFromSetupPosition) {
9473 SendBoard(&first, forwardMostMove);
9474 if (appData.debugMode) {
9475 fprintf(debugFP, "Load Game\n");
9477 DisplayBothClocks();
9480 /* [HGM] server: flag to write setup moves in broadcast file as one */
9481 loadFlag = appData.suppressLoadMoves;
9483 while (cm == Comment) {
9485 if (appData.debugMode)
9486 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9488 if (*p == '{' || *p == '[' || *p == '(') {
9489 p[strlen(p) - 1] = NULLCHAR;
9492 while (*p == '\n') p++;
9493 AppendComment(currentMove, p);
9494 yyboardindex = forwardMostMove;
9495 cm = (ChessMove) yylex();
9498 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9499 cm == WhiteWins || cm == BlackWins ||
9500 cm == GameIsDrawn || cm == GameUnfinished) {
9501 DisplayMessage("", _("No moves in game"));
9502 if (cmailMsgLoaded) {
9503 if (appData.debugMode)
9504 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9508 DrawPosition(FALSE, boards[currentMove]);
9509 DisplayBothClocks();
9510 gameMode = EditGame;
9517 // [HGM] PV info: routine tests if comment empty
9518 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9519 DisplayComment(currentMove - 1, commentList[currentMove]);
9521 if (!matchMode && appData.timeDelay != 0)
9522 DrawPosition(FALSE, boards[currentMove]);
9524 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9525 programStats.ok_to_send = 1;
9528 /* if the first token after the PGN tags is a move
9529 * and not move number 1, retrieve it from the parser
9531 if (cm != MoveNumberOne)
9532 LoadGameOneMove(cm);
9534 /* load the remaining moves from the file */
9535 while (LoadGameOneMove((ChessMove)0)) {
9536 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9537 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9540 /* rewind to the start of the game */
9541 currentMove = backwardMostMove;
9543 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9545 if (oldGameMode == AnalyzeFile ||
9546 oldGameMode == AnalyzeMode) {
9550 if (matchMode || appData.timeDelay == 0) {
9552 gameMode = EditGame;
9554 } else if (appData.timeDelay > 0) {
9558 if (appData.debugMode)
9559 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9561 loadFlag = 0; /* [HGM] true game starts */
9565 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9567 ReloadPosition(offset)
9570 int positionNumber = lastLoadPositionNumber + offset;
9571 if (lastLoadPositionFP == NULL) {
9572 DisplayError(_("No position has been loaded yet"), 0);
9575 if (positionNumber <= 0) {
9576 DisplayError(_("Can't back up any further"), 0);
9579 return LoadPosition(lastLoadPositionFP, positionNumber,
9580 lastLoadPositionTitle);
9583 /* Load the nth position from the given file */
9585 LoadPositionFromFile(filename, n, title)
9593 if (strcmp(filename, "-") == 0) {
9594 return LoadPosition(stdin, n, "stdin");
9596 f = fopen(filename, "rb");
9598 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9599 DisplayError(buf, errno);
9602 return LoadPosition(f, n, title);
9607 /* Load the nth position from the given open file, and close it */
9609 LoadPosition(f, positionNumber, title)
9614 char *p, line[MSG_SIZ];
9615 Board initial_position;
9616 int i, j, fenMode, pn;
9618 if (gameMode == Training )
9619 SetTrainingModeOff();
9621 if (gameMode != BeginningOfGame) {
9624 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9625 fclose(lastLoadPositionFP);
9627 if (positionNumber == 0) positionNumber = 1;
9628 lastLoadPositionFP = f;
9629 lastLoadPositionNumber = positionNumber;
9630 strcpy(lastLoadPositionTitle, title);
9631 if (first.pr == NoProc) {
9632 StartChessProgram(&first);
9633 InitChessProgram(&first, FALSE);
9635 pn = positionNumber;
9636 if (positionNumber < 0) {
9637 /* Negative position number means to seek to that byte offset */
9638 if (fseek(f, -positionNumber, 0) == -1) {
9639 DisplayError(_("Can't seek on position file"), 0);
9644 if (fseek(f, 0, 0) == -1) {
9645 if (f == lastLoadPositionFP ?
9646 positionNumber == lastLoadPositionNumber + 1 :
9647 positionNumber == 1) {
9650 DisplayError(_("Can't seek on position file"), 0);
9655 /* See if this file is FEN or old-style xboard */
9656 if (fgets(line, MSG_SIZ, f) == NULL) {
9657 DisplayError(_("Position not found in file"), 0);
9660 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9661 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9664 if (fenMode || line[0] == '#') pn--;
9666 /* skip positions before number pn */
9667 if (fgets(line, MSG_SIZ, f) == NULL) {
9669 DisplayError(_("Position not found in file"), 0);
9672 if (fenMode || line[0] == '#') pn--;
9677 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9678 DisplayError(_("Bad FEN position in file"), 0);
9682 (void) fgets(line, MSG_SIZ, f);
9683 (void) fgets(line, MSG_SIZ, f);
9685 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9686 (void) fgets(line, MSG_SIZ, f);
9687 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9690 initial_position[i][j++] = CharToPiece(*p);
9694 blackPlaysFirst = FALSE;
9696 (void) fgets(line, MSG_SIZ, f);
9697 if (strncmp(line, "black", strlen("black"))==0)
9698 blackPlaysFirst = TRUE;
9701 startedFromSetupPosition = TRUE;
9703 SendToProgram("force\n", &first);
9704 CopyBoard(boards[0], initial_position);
9705 if (blackPlaysFirst) {
9706 currentMove = forwardMostMove = backwardMostMove = 1;
9707 strcpy(moveList[0], "");
9708 strcpy(parseList[0], "");
9709 CopyBoard(boards[1], initial_position);
9710 DisplayMessage("", _("Black to play"));
9712 currentMove = forwardMostMove = backwardMostMove = 0;
9713 DisplayMessage("", _("White to play"));
9715 /* [HGM] copy FEN attributes as well */
9717 initialRulePlies = FENrulePlies;
9718 epStatus[forwardMostMove] = FENepStatus;
9719 for( i=0; i< nrCastlingRights; i++ )
9720 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9722 SendBoard(&first, forwardMostMove);
9723 if (appData.debugMode) {
9725 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9726 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9727 fprintf(debugFP, "Load Position\n");
9730 if (positionNumber > 1) {
9731 sprintf(line, "%s %d", title, positionNumber);
9734 DisplayTitle(title);
9736 gameMode = EditGame;
9739 timeRemaining[0][1] = whiteTimeRemaining;
9740 timeRemaining[1][1] = blackTimeRemaining;
9741 DrawPosition(FALSE, boards[currentMove]);
9748 CopyPlayerNameIntoFileName(dest, src)
9751 while (*src != NULLCHAR && *src != ',') {
9756 *(*dest)++ = *src++;
9761 char *DefaultFileName(ext)
9764 static char def[MSG_SIZ];
9767 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9769 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9771 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9780 /* Save the current game to the given file */
9782 SaveGameToFile(filename, append)
9789 if (strcmp(filename, "-") == 0) {
9790 return SaveGame(stdout, 0, NULL);
9792 f = fopen(filename, append ? "a" : "w");
9794 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9795 DisplayError(buf, errno);
9798 return SaveGame(f, 0, NULL);
9807 static char buf[MSG_SIZ];
9810 p = strchr(str, ' ');
9811 if (p == NULL) return str;
9812 strncpy(buf, str, p - str);
9813 buf[p - str] = NULLCHAR;
9817 #define PGN_MAX_LINE 75
9819 #define PGN_SIDE_WHITE 0
9820 #define PGN_SIDE_BLACK 1
9823 static int FindFirstMoveOutOfBook( int side )
9827 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9828 int index = backwardMostMove;
9829 int has_book_hit = 0;
9831 if( (index % 2) != side ) {
9835 while( index < forwardMostMove ) {
9836 /* Check to see if engine is in book */
9837 int depth = pvInfoList[index].depth;
9838 int score = pvInfoList[index].score;
9844 else if( score == 0 && depth == 63 ) {
9845 in_book = 1; /* Zappa */
9847 else if( score == 2 && depth == 99 ) {
9848 in_book = 1; /* Abrok */
9851 has_book_hit += in_book;
9867 void GetOutOfBookInfo( char * buf )
9871 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9873 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9874 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9878 if( oob[0] >= 0 || oob[1] >= 0 ) {
9879 for( i=0; i<2; i++ ) {
9883 if( i > 0 && oob[0] >= 0 ) {
9887 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9888 sprintf( buf+strlen(buf), "%s%.2f",
9889 pvInfoList[idx].score >= 0 ? "+" : "",
9890 pvInfoList[idx].score / 100.0 );
9896 /* Save game in PGN style and close the file */
9901 int i, offset, linelen, newblock;
9905 int movelen, numlen, blank;
9906 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9908 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9910 tm = time((time_t *) NULL);
9912 PrintPGNTags(f, &gameInfo);
9914 if (backwardMostMove > 0 || startedFromSetupPosition) {
9915 char *fen = PositionToFEN(backwardMostMove, NULL);
9916 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9917 fprintf(f, "\n{--------------\n");
9918 PrintPosition(f, backwardMostMove);
9919 fprintf(f, "--------------}\n");
9923 /* [AS] Out of book annotation */
9924 if( appData.saveOutOfBookInfo ) {
9927 GetOutOfBookInfo( buf );
9929 if( buf[0] != '\0' ) {
9930 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9937 i = backwardMostMove;
9941 while (i < forwardMostMove) {
9942 /* Print comments preceding this move */
9943 if (commentList[i] != NULL) {
9944 if (linelen > 0) fprintf(f, "\n");
9945 fprintf(f, "{\n%s}\n", commentList[i]);
9950 /* Format move number */
9952 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9955 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9957 numtext[0] = NULLCHAR;
9960 numlen = strlen(numtext);
9963 /* Print move number */
9964 blank = linelen > 0 && numlen > 0;
9965 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9974 fprintf(f, "%s", numtext);
9978 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9979 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9982 blank = linelen > 0 && movelen > 0;
9983 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9992 fprintf(f, "%s", move_buffer);
9995 /* [AS] Add PV info if present */
9996 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9997 /* [HGM] add time */
9998 char buf[MSG_SIZ]; int seconds;
10000 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10002 if( seconds <= 0) buf[0] = 0; else
10003 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10004 seconds = (seconds + 4)/10; // round to full seconds
10005 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10006 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10009 sprintf( move_buffer, "{%s%.2f/%d%s}",
10010 pvInfoList[i].score >= 0 ? "+" : "",
10011 pvInfoList[i].score / 100.0,
10012 pvInfoList[i].depth,
10015 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10017 /* Print score/depth */
10018 blank = linelen > 0 && movelen > 0;
10019 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10028 fprintf(f, "%s", move_buffer);
10029 linelen += movelen;
10035 /* Start a new line */
10036 if (linelen > 0) fprintf(f, "\n");
10038 /* Print comments after last move */
10039 if (commentList[i] != NULL) {
10040 fprintf(f, "{\n%s}\n", commentList[i]);
10044 if (gameInfo.resultDetails != NULL &&
10045 gameInfo.resultDetails[0] != NULLCHAR) {
10046 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10047 PGNResult(gameInfo.result));
10049 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10053 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10057 /* Save game in old style and close the file */
10059 SaveGameOldStyle(f)
10065 tm = time((time_t *) NULL);
10067 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10070 if (backwardMostMove > 0 || startedFromSetupPosition) {
10071 fprintf(f, "\n[--------------\n");
10072 PrintPosition(f, backwardMostMove);
10073 fprintf(f, "--------------]\n");
10078 i = backwardMostMove;
10079 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10081 while (i < forwardMostMove) {
10082 if (commentList[i] != NULL) {
10083 fprintf(f, "[%s]\n", commentList[i]);
10086 if ((i % 2) == 1) {
10087 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10090 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10092 if (commentList[i] != NULL) {
10096 if (i >= forwardMostMove) {
10100 fprintf(f, "%s\n", parseList[i]);
10105 if (commentList[i] != NULL) {
10106 fprintf(f, "[%s]\n", commentList[i]);
10109 /* This isn't really the old style, but it's close enough */
10110 if (gameInfo.resultDetails != NULL &&
10111 gameInfo.resultDetails[0] != NULLCHAR) {
10112 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10113 gameInfo.resultDetails);
10115 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10122 /* Save the current game to open file f and close the file */
10124 SaveGame(f, dummy, dummy2)
10129 if (gameMode == EditPosition) EditPositionDone(TRUE);
10130 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10131 if (appData.oldSaveStyle)
10132 return SaveGameOldStyle(f);
10134 return SaveGamePGN(f);
10137 /* Save the current position to the given file */
10139 SavePositionToFile(filename)
10145 if (strcmp(filename, "-") == 0) {
10146 return SavePosition(stdout, 0, NULL);
10148 f = fopen(filename, "a");
10150 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10151 DisplayError(buf, errno);
10154 SavePosition(f, 0, NULL);
10160 /* Save the current position to the given open file and close the file */
10162 SavePosition(f, dummy, dummy2)
10170 if (gameMode == EditPosition) EditPositionDone(TRUE);
10171 if (appData.oldSaveStyle) {
10172 tm = time((time_t *) NULL);
10174 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10176 fprintf(f, "[--------------\n");
10177 PrintPosition(f, currentMove);
10178 fprintf(f, "--------------]\n");
10180 fen = PositionToFEN(currentMove, NULL);
10181 fprintf(f, "%s\n", fen);
10189 ReloadCmailMsgEvent(unregister)
10193 static char *inFilename = NULL;
10194 static char *outFilename;
10196 struct stat inbuf, outbuf;
10199 /* Any registered moves are unregistered if unregister is set, */
10200 /* i.e. invoked by the signal handler */
10202 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10203 cmailMoveRegistered[i] = FALSE;
10204 if (cmailCommentList[i] != NULL) {
10205 free(cmailCommentList[i]);
10206 cmailCommentList[i] = NULL;
10209 nCmailMovesRegistered = 0;
10212 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10213 cmailResult[i] = CMAIL_NOT_RESULT;
10217 if (inFilename == NULL) {
10218 /* Because the filenames are static they only get malloced once */
10219 /* and they never get freed */
10220 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10221 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10223 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10224 sprintf(outFilename, "%s.out", appData.cmailGameName);
10227 status = stat(outFilename, &outbuf);
10229 cmailMailedMove = FALSE;
10231 status = stat(inFilename, &inbuf);
10232 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10235 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10236 counts the games, notes how each one terminated, etc.
10238 It would be nice to remove this kludge and instead gather all
10239 the information while building the game list. (And to keep it
10240 in the game list nodes instead of having a bunch of fixed-size
10241 parallel arrays.) Note this will require getting each game's
10242 termination from the PGN tags, as the game list builder does
10243 not process the game moves. --mann
10245 cmailMsgLoaded = TRUE;
10246 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10248 /* Load first game in the file or popup game menu */
10249 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10251 #endif /* !WIN32 */
10259 char string[MSG_SIZ];
10261 if ( cmailMailedMove
10262 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10263 return TRUE; /* Allow free viewing */
10266 /* Unregister move to ensure that we don't leave RegisterMove */
10267 /* with the move registered when the conditions for registering no */
10269 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10270 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10271 nCmailMovesRegistered --;
10273 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10275 free(cmailCommentList[lastLoadGameNumber - 1]);
10276 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10280 if (cmailOldMove == -1) {
10281 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10285 if (currentMove > cmailOldMove + 1) {
10286 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10290 if (currentMove < cmailOldMove) {
10291 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10295 if (forwardMostMove > currentMove) {
10296 /* Silently truncate extra moves */
10300 if ( (currentMove == cmailOldMove + 1)
10301 || ( (currentMove == cmailOldMove)
10302 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10303 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10304 if (gameInfo.result != GameUnfinished) {
10305 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10308 if (commentList[currentMove] != NULL) {
10309 cmailCommentList[lastLoadGameNumber - 1]
10310 = StrSave(commentList[currentMove]);
10312 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10314 if (appData.debugMode)
10315 fprintf(debugFP, "Saving %s for game %d\n",
10316 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10319 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10321 f = fopen(string, "w");
10322 if (appData.oldSaveStyle) {
10323 SaveGameOldStyle(f); /* also closes the file */
10325 sprintf(string, "%s.pos.out", appData.cmailGameName);
10326 f = fopen(string, "w");
10327 SavePosition(f, 0, NULL); /* also closes the file */
10329 fprintf(f, "{--------------\n");
10330 PrintPosition(f, currentMove);
10331 fprintf(f, "--------------}\n\n");
10333 SaveGame(f, 0, NULL); /* also closes the file*/
10336 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10337 nCmailMovesRegistered ++;
10338 } else if (nCmailGames == 1) {
10339 DisplayError(_("You have not made a move yet"), 0);
10350 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10351 FILE *commandOutput;
10352 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10353 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10359 if (! cmailMsgLoaded) {
10360 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10364 if (nCmailGames == nCmailResults) {
10365 DisplayError(_("No unfinished games"), 0);
10369 #if CMAIL_PROHIBIT_REMAIL
10370 if (cmailMailedMove) {
10371 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);
10372 DisplayError(msg, 0);
10377 if (! (cmailMailedMove || RegisterMove())) return;
10379 if ( cmailMailedMove
10380 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10381 sprintf(string, partCommandString,
10382 appData.debugMode ? " -v" : "", appData.cmailGameName);
10383 commandOutput = popen(string, "r");
10385 if (commandOutput == NULL) {
10386 DisplayError(_("Failed to invoke cmail"), 0);
10388 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10389 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10391 if (nBuffers > 1) {
10392 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10393 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10394 nBytes = MSG_SIZ - 1;
10396 (void) memcpy(msg, buffer, nBytes);
10398 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10400 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10401 cmailMailedMove = TRUE; /* Prevent >1 moves */
10404 for (i = 0; i < nCmailGames; i ++) {
10405 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10410 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10412 sprintf(buffer, "%s/%s.%s.archive",
10414 appData.cmailGameName,
10416 LoadGameFromFile(buffer, 1, buffer, FALSE);
10417 cmailMsgLoaded = FALSE;
10421 DisplayInformation(msg);
10422 pclose(commandOutput);
10425 if ((*cmailMsg) != '\0') {
10426 DisplayInformation(cmailMsg);
10431 #endif /* !WIN32 */
10440 int prependComma = 0;
10442 char string[MSG_SIZ]; /* Space for game-list */
10445 if (!cmailMsgLoaded) return "";
10447 if (cmailMailedMove) {
10448 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10450 /* Create a list of games left */
10451 sprintf(string, "[");
10452 for (i = 0; i < nCmailGames; i ++) {
10453 if (! ( cmailMoveRegistered[i]
10454 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10455 if (prependComma) {
10456 sprintf(number, ",%d", i + 1);
10458 sprintf(number, "%d", i + 1);
10462 strcat(string, number);
10465 strcat(string, "]");
10467 if (nCmailMovesRegistered + nCmailResults == 0) {
10468 switch (nCmailGames) {
10471 _("Still need to make move for game\n"));
10476 _("Still need to make moves for both games\n"));
10481 _("Still need to make moves for all %d games\n"),
10486 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10489 _("Still need to make a move for game %s\n"),
10494 if (nCmailResults == nCmailGames) {
10495 sprintf(cmailMsg, _("No unfinished games\n"));
10497 sprintf(cmailMsg, _("Ready to send mail\n"));
10503 _("Still need to make moves for games %s\n"),
10515 if (gameMode == Training)
10516 SetTrainingModeOff();
10519 cmailMsgLoaded = FALSE;
10520 if (appData.icsActive) {
10521 SendToICS(ics_prefix);
10522 SendToICS("refresh\n");
10532 /* Give up on clean exit */
10536 /* Keep trying for clean exit */
10540 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10542 if (telnetISR != NULL) {
10543 RemoveInputSource(telnetISR);
10545 if (icsPR != NoProc) {
10546 DestroyChildProcess(icsPR, TRUE);
10549 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10550 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10552 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10553 /* make sure this other one finishes before killing it! */
10554 if(endingGame) { int count = 0;
10555 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10556 while(endingGame && count++ < 10) DoSleep(1);
10557 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10560 /* Kill off chess programs */
10561 if (first.pr != NoProc) {
10564 DoSleep( appData.delayBeforeQuit );
10565 SendToProgram("quit\n", &first);
10566 DoSleep( appData.delayAfterQuit );
10567 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10569 if (second.pr != NoProc) {
10570 DoSleep( appData.delayBeforeQuit );
10571 SendToProgram("quit\n", &second);
10572 DoSleep( appData.delayAfterQuit );
10573 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10575 if (first.isr != NULL) {
10576 RemoveInputSource(first.isr);
10578 if (second.isr != NULL) {
10579 RemoveInputSource(second.isr);
10582 ShutDownFrontEnd();
10589 if (appData.debugMode)
10590 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10594 if (gameMode == MachinePlaysWhite ||
10595 gameMode == MachinePlaysBlack) {
10598 DisplayBothClocks();
10600 if (gameMode == PlayFromGameFile) {
10601 if (appData.timeDelay >= 0)
10602 AutoPlayGameLoop();
10603 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10604 Reset(FALSE, TRUE);
10605 SendToICS(ics_prefix);
10606 SendToICS("refresh\n");
10607 } else if (currentMove < forwardMostMove) {
10608 ForwardInner(forwardMostMove);
10610 pauseExamInvalid = FALSE;
10612 switch (gameMode) {
10616 pauseExamForwardMostMove = forwardMostMove;
10617 pauseExamInvalid = FALSE;
10620 case IcsPlayingWhite:
10621 case IcsPlayingBlack:
10625 case PlayFromGameFile:
10626 (void) StopLoadGameTimer();
10630 case BeginningOfGame:
10631 if (appData.icsActive) return;
10632 /* else fall through */
10633 case MachinePlaysWhite:
10634 case MachinePlaysBlack:
10635 case TwoMachinesPlay:
10636 if (forwardMostMove == 0)
10637 return; /* don't pause if no one has moved */
10638 if ((gameMode == MachinePlaysWhite &&
10639 !WhiteOnMove(forwardMostMove)) ||
10640 (gameMode == MachinePlaysBlack &&
10641 WhiteOnMove(forwardMostMove))) {
10654 char title[MSG_SIZ];
10656 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10657 strcpy(title, _("Edit comment"));
10659 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10660 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10661 parseList[currentMove - 1]);
10664 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10671 char *tags = PGNTags(&gameInfo);
10672 EditTagsPopUp(tags);
10679 if (appData.noChessProgram || gameMode == AnalyzeMode)
10682 if (gameMode != AnalyzeFile) {
10683 if (!appData.icsEngineAnalyze) {
10685 if (gameMode != EditGame) return;
10687 ResurrectChessProgram();
10688 SendToProgram("analyze\n", &first);
10689 first.analyzing = TRUE;
10690 /*first.maybeThinking = TRUE;*/
10691 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10692 EngineOutputPopUp();
10694 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10699 StartAnalysisClock();
10700 GetTimeMark(&lastNodeCountTime);
10707 if (appData.noChessProgram || gameMode == AnalyzeFile)
10710 if (gameMode != AnalyzeMode) {
10712 if (gameMode != EditGame) return;
10713 ResurrectChessProgram();
10714 SendToProgram("analyze\n", &first);
10715 first.analyzing = TRUE;
10716 /*first.maybeThinking = TRUE;*/
10717 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10718 EngineOutputPopUp();
10720 gameMode = AnalyzeFile;
10725 StartAnalysisClock();
10726 GetTimeMark(&lastNodeCountTime);
10731 MachineWhiteEvent()
10734 char *bookHit = NULL;
10736 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10740 if (gameMode == PlayFromGameFile ||
10741 gameMode == TwoMachinesPlay ||
10742 gameMode == Training ||
10743 gameMode == AnalyzeMode ||
10744 gameMode == EndOfGame)
10747 if (gameMode == EditPosition)
10748 EditPositionDone(TRUE);
10750 if (!WhiteOnMove(currentMove)) {
10751 DisplayError(_("It is not White's turn"), 0);
10755 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10758 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10759 gameMode == AnalyzeFile)
10762 ResurrectChessProgram(); /* in case it isn't running */
10763 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10764 gameMode = MachinePlaysWhite;
10767 gameMode = MachinePlaysWhite;
10771 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10773 if (first.sendName) {
10774 sprintf(buf, "name %s\n", gameInfo.black);
10775 SendToProgram(buf, &first);
10777 if (first.sendTime) {
10778 if (first.useColors) {
10779 SendToProgram("black\n", &first); /*gnu kludge*/
10781 SendTimeRemaining(&first, TRUE);
10783 if (first.useColors) {
10784 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10786 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10787 SetMachineThinkingEnables();
10788 first.maybeThinking = TRUE;
10792 if (appData.autoFlipView && !flipView) {
10793 flipView = !flipView;
10794 DrawPosition(FALSE, NULL);
10795 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10798 if(bookHit) { // [HGM] book: simulate book reply
10799 static char bookMove[MSG_SIZ]; // a bit generous?
10801 programStats.nodes = programStats.depth = programStats.time =
10802 programStats.score = programStats.got_only_move = 0;
10803 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10805 strcpy(bookMove, "move ");
10806 strcat(bookMove, bookHit);
10807 HandleMachineMove(bookMove, &first);
10812 MachineBlackEvent()
10815 char *bookHit = NULL;
10817 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10821 if (gameMode == PlayFromGameFile ||
10822 gameMode == TwoMachinesPlay ||
10823 gameMode == Training ||
10824 gameMode == AnalyzeMode ||
10825 gameMode == EndOfGame)
10828 if (gameMode == EditPosition)
10829 EditPositionDone(TRUE);
10831 if (WhiteOnMove(currentMove)) {
10832 DisplayError(_("It is not Black's turn"), 0);
10836 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10839 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10840 gameMode == AnalyzeFile)
10843 ResurrectChessProgram(); /* in case it isn't running */
10844 gameMode = MachinePlaysBlack;
10848 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10850 if (first.sendName) {
10851 sprintf(buf, "name %s\n", gameInfo.white);
10852 SendToProgram(buf, &first);
10854 if (first.sendTime) {
10855 if (first.useColors) {
10856 SendToProgram("white\n", &first); /*gnu kludge*/
10858 SendTimeRemaining(&first, FALSE);
10860 if (first.useColors) {
10861 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10863 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10864 SetMachineThinkingEnables();
10865 first.maybeThinking = TRUE;
10868 if (appData.autoFlipView && flipView) {
10869 flipView = !flipView;
10870 DrawPosition(FALSE, NULL);
10871 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10873 if(bookHit) { // [HGM] book: simulate book reply
10874 static char bookMove[MSG_SIZ]; // a bit generous?
10876 programStats.nodes = programStats.depth = programStats.time =
10877 programStats.score = programStats.got_only_move = 0;
10878 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10880 strcpy(bookMove, "move ");
10881 strcat(bookMove, bookHit);
10882 HandleMachineMove(bookMove, &first);
10888 DisplayTwoMachinesTitle()
10891 if (appData.matchGames > 0) {
10892 if (first.twoMachinesColor[0] == 'w') {
10893 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10894 gameInfo.white, gameInfo.black,
10895 first.matchWins, second.matchWins,
10896 matchGame - 1 - (first.matchWins + second.matchWins));
10898 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10899 gameInfo.white, gameInfo.black,
10900 second.matchWins, first.matchWins,
10901 matchGame - 1 - (first.matchWins + second.matchWins));
10904 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10910 TwoMachinesEvent P((void))
10914 ChessProgramState *onmove;
10915 char *bookHit = NULL;
10917 if (appData.noChessProgram) return;
10919 switch (gameMode) {
10920 case TwoMachinesPlay:
10922 case MachinePlaysWhite:
10923 case MachinePlaysBlack:
10924 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10925 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10929 case BeginningOfGame:
10930 case PlayFromGameFile:
10933 if (gameMode != EditGame) return;
10936 EditPositionDone(TRUE);
10947 forwardMostMove = currentMove;
10948 ResurrectChessProgram(); /* in case first program isn't running */
10950 if (second.pr == NULL) {
10951 StartChessProgram(&second);
10952 if (second.protocolVersion == 1) {
10953 TwoMachinesEventIfReady();
10955 /* kludge: allow timeout for initial "feature" command */
10957 DisplayMessage("", _("Starting second chess program"));
10958 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10962 DisplayMessage("", "");
10963 InitChessProgram(&second, FALSE);
10964 SendToProgram("force\n", &second);
10965 if (startedFromSetupPosition) {
10966 SendBoard(&second, backwardMostMove);
10967 if (appData.debugMode) {
10968 fprintf(debugFP, "Two Machines\n");
10971 for (i = backwardMostMove; i < forwardMostMove; i++) {
10972 SendMoveToProgram(i, &second);
10975 gameMode = TwoMachinesPlay;
10979 DisplayTwoMachinesTitle();
10981 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10987 SendToProgram(first.computerString, &first);
10988 if (first.sendName) {
10989 sprintf(buf, "name %s\n", second.tidy);
10990 SendToProgram(buf, &first);
10992 SendToProgram(second.computerString, &second);
10993 if (second.sendName) {
10994 sprintf(buf, "name %s\n", first.tidy);
10995 SendToProgram(buf, &second);
10999 if (!first.sendTime || !second.sendTime) {
11000 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11001 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11003 if (onmove->sendTime) {
11004 if (onmove->useColors) {
11005 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11007 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11009 if (onmove->useColors) {
11010 SendToProgram(onmove->twoMachinesColor, onmove);
11012 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11013 // SendToProgram("go\n", onmove);
11014 onmove->maybeThinking = TRUE;
11015 SetMachineThinkingEnables();
11019 if(bookHit) { // [HGM] book: simulate book reply
11020 static char bookMove[MSG_SIZ]; // a bit generous?
11022 programStats.nodes = programStats.depth = programStats.time =
11023 programStats.score = programStats.got_only_move = 0;
11024 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11026 strcpy(bookMove, "move ");
11027 strcat(bookMove, bookHit);
11028 savedMessage = bookMove; // args for deferred call
11029 savedState = onmove;
11030 ScheduleDelayedEvent(DeferredBookMove, 1);
11037 if (gameMode == Training) {
11038 SetTrainingModeOff();
11039 gameMode = PlayFromGameFile;
11040 DisplayMessage("", _("Training mode off"));
11042 gameMode = Training;
11043 animateTraining = appData.animate;
11045 /* make sure we are not already at the end of the game */
11046 if (currentMove < forwardMostMove) {
11047 SetTrainingModeOn();
11048 DisplayMessage("", _("Training mode on"));
11050 gameMode = PlayFromGameFile;
11051 DisplayError(_("Already at end of game"), 0);
11060 if (!appData.icsActive) return;
11061 switch (gameMode) {
11062 case IcsPlayingWhite:
11063 case IcsPlayingBlack:
11066 case BeginningOfGame:
11074 EditPositionDone(TRUE);
11087 gameMode = IcsIdle;
11098 switch (gameMode) {
11100 SetTrainingModeOff();
11102 case MachinePlaysWhite:
11103 case MachinePlaysBlack:
11104 case BeginningOfGame:
11105 SendToProgram("force\n", &first);
11106 SetUserThinkingEnables();
11108 case PlayFromGameFile:
11109 (void) StopLoadGameTimer();
11110 if (gameFileFP != NULL) {
11115 EditPositionDone(TRUE);
11120 SendToProgram("force\n", &first);
11122 case TwoMachinesPlay:
11123 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11124 ResurrectChessProgram();
11125 SetUserThinkingEnables();
11128 ResurrectChessProgram();
11130 case IcsPlayingBlack:
11131 case IcsPlayingWhite:
11132 DisplayError(_("Warning: You are still playing a game"), 0);
11135 DisplayError(_("Warning: You are still observing a game"), 0);
11138 DisplayError(_("Warning: You are still examining a game"), 0);
11149 first.offeredDraw = second.offeredDraw = 0;
11151 if (gameMode == PlayFromGameFile) {
11152 whiteTimeRemaining = timeRemaining[0][currentMove];
11153 blackTimeRemaining = timeRemaining[1][currentMove];
11157 if (gameMode == MachinePlaysWhite ||
11158 gameMode == MachinePlaysBlack ||
11159 gameMode == TwoMachinesPlay ||
11160 gameMode == EndOfGame) {
11161 i = forwardMostMove;
11162 while (i > currentMove) {
11163 SendToProgram("undo\n", &first);
11166 whiteTimeRemaining = timeRemaining[0][currentMove];
11167 blackTimeRemaining = timeRemaining[1][currentMove];
11168 DisplayBothClocks();
11169 if (whiteFlag || blackFlag) {
11170 whiteFlag = blackFlag = 0;
11175 gameMode = EditGame;
11182 EditPositionEvent()
11184 if (gameMode == EditPosition) {
11190 if (gameMode != EditGame) return;
11192 gameMode = EditPosition;
11195 if (currentMove > 0)
11196 CopyBoard(boards[0], boards[currentMove]);
11198 blackPlaysFirst = !WhiteOnMove(currentMove);
11200 currentMove = forwardMostMove = backwardMostMove = 0;
11201 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11208 /* [DM] icsEngineAnalyze - possible call from other functions */
11209 if (appData.icsEngineAnalyze) {
11210 appData.icsEngineAnalyze = FALSE;
11212 DisplayMessage("",_("Close ICS engine analyze..."));
11214 if (first.analysisSupport && first.analyzing) {
11215 SendToProgram("exit\n", &first);
11216 first.analyzing = FALSE;
11218 thinkOutput[0] = NULLCHAR;
11222 EditPositionDone(Boolean fakeRights)
11224 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11226 startedFromSetupPosition = TRUE;
11227 InitChessProgram(&first, FALSE);
11229 { /* don't do this if we just pasted FEN */
11230 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11231 if(boards[0][0][BOARD_WIDTH>>1] == king)
11233 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11234 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11237 castlingRights[0][2] = -1;
11238 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11240 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11241 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11244 castlingRights[0][5] = -1;
11246 SendToProgram("force\n", &first);
11247 if (blackPlaysFirst) {
11248 strcpy(moveList[0], "");
11249 strcpy(parseList[0], "");
11250 currentMove = forwardMostMove = backwardMostMove = 1;
11251 CopyBoard(boards[1], boards[0]);
11252 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11254 epStatus[1] = epStatus[0];
11255 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11258 currentMove = forwardMostMove = backwardMostMove = 0;
11260 SendBoard(&first, forwardMostMove);
11261 if (appData.debugMode) {
11262 fprintf(debugFP, "EditPosDone\n");
11265 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11266 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11267 gameMode = EditGame;
11269 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11270 ClearHighlights(); /* [AS] */
11273 /* Pause for `ms' milliseconds */
11274 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11284 } while (SubtractTimeMarks(&m2, &m1) < ms);
11287 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11289 SendMultiLineToICS(buf)
11292 char temp[MSG_SIZ+1], *p;
11299 strncpy(temp, buf, len);
11304 if (*p == '\n' || *p == '\r')
11309 strcat(temp, "\n");
11311 SendToPlayer(temp, strlen(temp));
11315 SetWhiteToPlayEvent()
11317 if (gameMode == EditPosition) {
11318 blackPlaysFirst = FALSE;
11319 DisplayBothClocks(); /* works because currentMove is 0 */
11320 } else if (gameMode == IcsExamining) {
11321 SendToICS(ics_prefix);
11322 SendToICS("tomove white\n");
11327 SetBlackToPlayEvent()
11329 if (gameMode == EditPosition) {
11330 blackPlaysFirst = TRUE;
11331 currentMove = 1; /* kludge */
11332 DisplayBothClocks();
11334 } else if (gameMode == IcsExamining) {
11335 SendToICS(ics_prefix);
11336 SendToICS("tomove black\n");
11341 EditPositionMenuEvent(selection, x, y)
11342 ChessSquare selection;
11346 ChessSquare piece = boards[0][y][x];
11348 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11350 switch (selection) {
11352 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11353 SendToICS(ics_prefix);
11354 SendToICS("bsetup clear\n");
11355 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11356 SendToICS(ics_prefix);
11357 SendToICS("clearboard\n");
11359 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11360 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11361 for (y = 0; y < BOARD_HEIGHT; y++) {
11362 if (gameMode == IcsExamining) {
11363 if (boards[currentMove][y][x] != EmptySquare) {
11364 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11369 boards[0][y][x] = p;
11374 if (gameMode == EditPosition) {
11375 DrawPosition(FALSE, boards[0]);
11380 SetWhiteToPlayEvent();
11384 SetBlackToPlayEvent();
11388 if (gameMode == IcsExamining) {
11389 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11392 boards[0][y][x] = EmptySquare;
11393 DrawPosition(FALSE, boards[0]);
11398 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11399 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11400 selection = (ChessSquare) (PROMOTED piece);
11401 } else if(piece == EmptySquare) selection = WhiteSilver;
11402 else selection = (ChessSquare)((int)piece - 1);
11406 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11407 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11408 selection = (ChessSquare) (DEMOTED piece);
11409 } else if(piece == EmptySquare) selection = BlackSilver;
11410 else selection = (ChessSquare)((int)piece + 1);
11415 if(gameInfo.variant == VariantShatranj ||
11416 gameInfo.variant == VariantXiangqi ||
11417 gameInfo.variant == VariantCourier ||
11418 gameInfo.variant == VariantMakruk )
11419 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11424 if(gameInfo.variant == VariantXiangqi)
11425 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11426 if(gameInfo.variant == VariantKnightmate)
11427 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11430 if (gameMode == IcsExamining) {
11431 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11432 PieceToChar(selection), AAA + x, ONE + y);
11435 boards[0][y][x] = selection;
11436 DrawPosition(FALSE, boards[0]);
11444 DropMenuEvent(selection, x, y)
11445 ChessSquare selection;
11448 ChessMove moveType;
11450 switch (gameMode) {
11451 case IcsPlayingWhite:
11452 case MachinePlaysBlack:
11453 if (!WhiteOnMove(currentMove)) {
11454 DisplayMoveError(_("It is Black's turn"));
11457 moveType = WhiteDrop;
11459 case IcsPlayingBlack:
11460 case MachinePlaysWhite:
11461 if (WhiteOnMove(currentMove)) {
11462 DisplayMoveError(_("It is White's turn"));
11465 moveType = BlackDrop;
11468 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11474 if (moveType == BlackDrop && selection < BlackPawn) {
11475 selection = (ChessSquare) ((int) selection
11476 + (int) BlackPawn - (int) WhitePawn);
11478 if (boards[currentMove][y][x] != EmptySquare) {
11479 DisplayMoveError(_("That square is occupied"));
11483 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11489 /* Accept a pending offer of any kind from opponent */
11491 if (appData.icsActive) {
11492 SendToICS(ics_prefix);
11493 SendToICS("accept\n");
11494 } else if (cmailMsgLoaded) {
11495 if (currentMove == cmailOldMove &&
11496 commentList[cmailOldMove] != NULL &&
11497 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11498 "Black offers a draw" : "White offers a draw")) {
11500 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11501 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11503 DisplayError(_("There is no pending offer on this move"), 0);
11504 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11507 /* Not used for offers from chess program */
11514 /* Decline a pending offer of any kind from opponent */
11516 if (appData.icsActive) {
11517 SendToICS(ics_prefix);
11518 SendToICS("decline\n");
11519 } else if (cmailMsgLoaded) {
11520 if (currentMove == cmailOldMove &&
11521 commentList[cmailOldMove] != NULL &&
11522 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11523 "Black offers a draw" : "White offers a draw")) {
11525 AppendComment(cmailOldMove, "Draw declined");
11526 DisplayComment(cmailOldMove - 1, "Draw declined");
11529 DisplayError(_("There is no pending offer on this move"), 0);
11532 /* Not used for offers from chess program */
11539 /* Issue ICS rematch command */
11540 if (appData.icsActive) {
11541 SendToICS(ics_prefix);
11542 SendToICS("rematch\n");
11549 /* Call your opponent's flag (claim a win on time) */
11550 if (appData.icsActive) {
11551 SendToICS(ics_prefix);
11552 SendToICS("flag\n");
11554 switch (gameMode) {
11557 case MachinePlaysWhite:
11560 GameEnds(GameIsDrawn, "Both players ran out of time",
11563 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11565 DisplayError(_("Your opponent is not out of time"), 0);
11568 case MachinePlaysBlack:
11571 GameEnds(GameIsDrawn, "Both players ran out of time",
11574 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11576 DisplayError(_("Your opponent is not out of time"), 0);
11586 /* Offer draw or accept pending draw offer from opponent */
11588 if (appData.icsActive) {
11589 /* Note: tournament rules require draw offers to be
11590 made after you make your move but before you punch
11591 your clock. Currently ICS doesn't let you do that;
11592 instead, you immediately punch your clock after making
11593 a move, but you can offer a draw at any time. */
11595 SendToICS(ics_prefix);
11596 SendToICS("draw\n");
11597 } else if (cmailMsgLoaded) {
11598 if (currentMove == cmailOldMove &&
11599 commentList[cmailOldMove] != NULL &&
11600 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11601 "Black offers a draw" : "White offers a draw")) {
11602 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11603 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11604 } else if (currentMove == cmailOldMove + 1) {
11605 char *offer = WhiteOnMove(cmailOldMove) ?
11606 "White offers a draw" : "Black offers a draw";
11607 AppendComment(currentMove, offer);
11608 DisplayComment(currentMove - 1, offer);
11609 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11611 DisplayError(_("You must make your move before offering a draw"), 0);
11612 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11614 } else if (first.offeredDraw) {
11615 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11617 if (first.sendDrawOffers) {
11618 SendToProgram("draw\n", &first);
11619 userOfferedDraw = TRUE;
11627 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11629 if (appData.icsActive) {
11630 SendToICS(ics_prefix);
11631 SendToICS("adjourn\n");
11633 /* Currently GNU Chess doesn't offer or accept Adjourns */
11641 /* Offer Abort or accept pending Abort offer from opponent */
11643 if (appData.icsActive) {
11644 SendToICS(ics_prefix);
11645 SendToICS("abort\n");
11647 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11654 /* Resign. You can do this even if it's not your turn. */
11656 if (appData.icsActive) {
11657 SendToICS(ics_prefix);
11658 SendToICS("resign\n");
11660 switch (gameMode) {
11661 case MachinePlaysWhite:
11662 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11664 case MachinePlaysBlack:
11665 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11668 if (cmailMsgLoaded) {
11670 if (WhiteOnMove(cmailOldMove)) {
11671 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11673 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11675 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11686 StopObservingEvent()
11688 /* Stop observing current games */
11689 SendToICS(ics_prefix);
11690 SendToICS("unobserve\n");
11694 StopExaminingEvent()
11696 /* Stop observing current game */
11697 SendToICS(ics_prefix);
11698 SendToICS("unexamine\n");
11702 ForwardInner(target)
11707 if (appData.debugMode)
11708 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11709 target, currentMove, forwardMostMove);
11711 if (gameMode == EditPosition)
11714 if (gameMode == PlayFromGameFile && !pausing)
11717 if (gameMode == IcsExamining && pausing)
11718 limit = pauseExamForwardMostMove;
11720 limit = forwardMostMove;
11722 if (target > limit) target = limit;
11724 if (target > 0 && moveList[target - 1][0]) {
11725 int fromX, fromY, toX, toY;
11726 toX = moveList[target - 1][2] - AAA;
11727 toY = moveList[target - 1][3] - ONE;
11728 if (moveList[target - 1][1] == '@') {
11729 if (appData.highlightLastMove) {
11730 SetHighlights(-1, -1, toX, toY);
11733 fromX = moveList[target - 1][0] - AAA;
11734 fromY = moveList[target - 1][1] - ONE;
11735 if (target == currentMove + 1) {
11736 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11738 if (appData.highlightLastMove) {
11739 SetHighlights(fromX, fromY, toX, toY);
11743 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11744 gameMode == Training || gameMode == PlayFromGameFile ||
11745 gameMode == AnalyzeFile) {
11746 while (currentMove < target) {
11747 SendMoveToProgram(currentMove++, &first);
11750 currentMove = target;
11753 if (gameMode == EditGame || gameMode == EndOfGame) {
11754 whiteTimeRemaining = timeRemaining[0][currentMove];
11755 blackTimeRemaining = timeRemaining[1][currentMove];
11757 DisplayBothClocks();
11758 DisplayMove(currentMove - 1);
11759 DrawPosition(FALSE, boards[currentMove]);
11760 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11761 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11762 DisplayComment(currentMove - 1, commentList[currentMove]);
11770 if (gameMode == IcsExamining && !pausing) {
11771 SendToICS(ics_prefix);
11772 SendToICS("forward\n");
11774 ForwardInner(currentMove + 1);
11781 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11782 /* to optimze, we temporarily turn off analysis mode while we feed
11783 * the remaining moves to the engine. Otherwise we get analysis output
11786 if (first.analysisSupport) {
11787 SendToProgram("exit\nforce\n", &first);
11788 first.analyzing = FALSE;
11792 if (gameMode == IcsExamining && !pausing) {
11793 SendToICS(ics_prefix);
11794 SendToICS("forward 999999\n");
11796 ForwardInner(forwardMostMove);
11799 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11800 /* we have fed all the moves, so reactivate analysis mode */
11801 SendToProgram("analyze\n", &first);
11802 first.analyzing = TRUE;
11803 /*first.maybeThinking = TRUE;*/
11804 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11809 BackwardInner(target)
11812 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11814 if (appData.debugMode)
11815 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11816 target, currentMove, forwardMostMove);
11818 if (gameMode == EditPosition) return;
11819 if (currentMove <= backwardMostMove) {
11821 DrawPosition(full_redraw, boards[currentMove]);
11824 if (gameMode == PlayFromGameFile && !pausing)
11827 if (moveList[target][0]) {
11828 int fromX, fromY, toX, toY;
11829 toX = moveList[target][2] - AAA;
11830 toY = moveList[target][3] - ONE;
11831 if (moveList[target][1] == '@') {
11832 if (appData.highlightLastMove) {
11833 SetHighlights(-1, -1, toX, toY);
11836 fromX = moveList[target][0] - AAA;
11837 fromY = moveList[target][1] - ONE;
11838 if (target == currentMove - 1) {
11839 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11841 if (appData.highlightLastMove) {
11842 SetHighlights(fromX, fromY, toX, toY);
11846 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11847 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11848 while (currentMove > target) {
11849 SendToProgram("undo\n", &first);
11853 currentMove = target;
11856 if (gameMode == EditGame || gameMode == EndOfGame) {
11857 whiteTimeRemaining = timeRemaining[0][currentMove];
11858 blackTimeRemaining = timeRemaining[1][currentMove];
11860 DisplayBothClocks();
11861 DisplayMove(currentMove - 1);
11862 DrawPosition(full_redraw, boards[currentMove]);
11863 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11864 // [HGM] PV info: routine tests if comment empty
11865 DisplayComment(currentMove - 1, commentList[currentMove]);
11871 if (gameMode == IcsExamining && !pausing) {
11872 SendToICS(ics_prefix);
11873 SendToICS("backward\n");
11875 BackwardInner(currentMove - 1);
11882 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11883 /* to optimize, we temporarily turn off analysis mode while we undo
11884 * all the moves. Otherwise we get analysis output after each undo.
11886 if (first.analysisSupport) {
11887 SendToProgram("exit\nforce\n", &first);
11888 first.analyzing = FALSE;
11892 if (gameMode == IcsExamining && !pausing) {
11893 SendToICS(ics_prefix);
11894 SendToICS("backward 999999\n");
11896 BackwardInner(backwardMostMove);
11899 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11900 /* we have fed all the moves, so reactivate analysis mode */
11901 SendToProgram("analyze\n", &first);
11902 first.analyzing = TRUE;
11903 /*first.maybeThinking = TRUE;*/
11904 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11911 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11912 if (to >= forwardMostMove) to = forwardMostMove;
11913 if (to <= backwardMostMove) to = backwardMostMove;
11914 if (to < currentMove) {
11924 if (gameMode != IcsExamining) {
11925 DisplayError(_("You are not examining a game"), 0);
11929 DisplayError(_("You can't revert while pausing"), 0);
11932 SendToICS(ics_prefix);
11933 SendToICS("revert\n");
11939 switch (gameMode) {
11940 case MachinePlaysWhite:
11941 case MachinePlaysBlack:
11942 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11943 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11946 if (forwardMostMove < 2) return;
11947 currentMove = forwardMostMove = forwardMostMove - 2;
11948 whiteTimeRemaining = timeRemaining[0][currentMove];
11949 blackTimeRemaining = timeRemaining[1][currentMove];
11950 DisplayBothClocks();
11951 DisplayMove(currentMove - 1);
11952 ClearHighlights();/*!! could figure this out*/
11953 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11954 SendToProgram("remove\n", &first);
11955 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11958 case BeginningOfGame:
11962 case IcsPlayingWhite:
11963 case IcsPlayingBlack:
11964 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11965 SendToICS(ics_prefix);
11966 SendToICS("takeback 2\n");
11968 SendToICS(ics_prefix);
11969 SendToICS("takeback 1\n");
11978 ChessProgramState *cps;
11980 switch (gameMode) {
11981 case MachinePlaysWhite:
11982 if (!WhiteOnMove(forwardMostMove)) {
11983 DisplayError(_("It is your turn"), 0);
11988 case MachinePlaysBlack:
11989 if (WhiteOnMove(forwardMostMove)) {
11990 DisplayError(_("It is your turn"), 0);
11995 case TwoMachinesPlay:
11996 if (WhiteOnMove(forwardMostMove) ==
11997 (first.twoMachinesColor[0] == 'w')) {
12003 case BeginningOfGame:
12007 SendToProgram("?\n", cps);
12011 TruncateGameEvent()
12014 if (gameMode != EditGame) return;
12021 if (forwardMostMove > currentMove) {
12022 if (gameInfo.resultDetails != NULL) {
12023 free(gameInfo.resultDetails);
12024 gameInfo.resultDetails = NULL;
12025 gameInfo.result = GameUnfinished;
12027 forwardMostMove = currentMove;
12028 HistorySet(parseList, backwardMostMove, forwardMostMove,
12036 if (appData.noChessProgram) return;
12037 switch (gameMode) {
12038 case MachinePlaysWhite:
12039 if (WhiteOnMove(forwardMostMove)) {
12040 DisplayError(_("Wait until your turn"), 0);
12044 case BeginningOfGame:
12045 case MachinePlaysBlack:
12046 if (!WhiteOnMove(forwardMostMove)) {
12047 DisplayError(_("Wait until your turn"), 0);
12052 DisplayError(_("No hint available"), 0);
12055 SendToProgram("hint\n", &first);
12056 hintRequested = TRUE;
12062 if (appData.noChessProgram) return;
12063 switch (gameMode) {
12064 case MachinePlaysWhite:
12065 if (WhiteOnMove(forwardMostMove)) {
12066 DisplayError(_("Wait until your turn"), 0);
12070 case BeginningOfGame:
12071 case MachinePlaysBlack:
12072 if (!WhiteOnMove(forwardMostMove)) {
12073 DisplayError(_("Wait until your turn"), 0);
12078 EditPositionDone(TRUE);
12080 case TwoMachinesPlay:
12085 SendToProgram("bk\n", &first);
12086 bookOutput[0] = NULLCHAR;
12087 bookRequested = TRUE;
12093 char *tags = PGNTags(&gameInfo);
12094 TagsPopUp(tags, CmailMsg());
12098 /* end button procedures */
12101 PrintPosition(fp, move)
12107 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12108 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12109 char c = PieceToChar(boards[move][i][j]);
12110 fputc(c == 'x' ? '.' : c, fp);
12111 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12114 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12115 fprintf(fp, "white to play\n");
12117 fprintf(fp, "black to play\n");
12124 if (gameInfo.white != NULL) {
12125 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12131 /* Find last component of program's own name, using some heuristics */
12133 TidyProgramName(prog, host, buf)
12134 char *prog, *host, buf[MSG_SIZ];
12137 int local = (strcmp(host, "localhost") == 0);
12138 while (!local && (p = strchr(prog, ';')) != NULL) {
12140 while (*p == ' ') p++;
12143 if (*prog == '"' || *prog == '\'') {
12144 q = strchr(prog + 1, *prog);
12146 q = strchr(prog, ' ');
12148 if (q == NULL) q = prog + strlen(prog);
12150 while (p >= prog && *p != '/' && *p != '\\') p--;
12152 if(p == prog && *p == '"') p++;
12153 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12154 memcpy(buf, p, q - p);
12155 buf[q - p] = NULLCHAR;
12163 TimeControlTagValue()
12166 if (!appData.clockMode) {
12168 } else if (movesPerSession > 0) {
12169 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12170 } else if (timeIncrement == 0) {
12171 sprintf(buf, "%ld", timeControl/1000);
12173 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12175 return StrSave(buf);
12181 /* This routine is used only for certain modes */
12182 VariantClass v = gameInfo.variant;
12183 ClearGameInfo(&gameInfo);
12184 gameInfo.variant = v;
12186 switch (gameMode) {
12187 case MachinePlaysWhite:
12188 gameInfo.event = StrSave( appData.pgnEventHeader );
12189 gameInfo.site = StrSave(HostName());
12190 gameInfo.date = PGNDate();
12191 gameInfo.round = StrSave("-");
12192 gameInfo.white = StrSave(first.tidy);
12193 gameInfo.black = StrSave(UserName());
12194 gameInfo.timeControl = TimeControlTagValue();
12197 case MachinePlaysBlack:
12198 gameInfo.event = StrSave( appData.pgnEventHeader );
12199 gameInfo.site = StrSave(HostName());
12200 gameInfo.date = PGNDate();
12201 gameInfo.round = StrSave("-");
12202 gameInfo.white = StrSave(UserName());
12203 gameInfo.black = StrSave(first.tidy);
12204 gameInfo.timeControl = TimeControlTagValue();
12207 case TwoMachinesPlay:
12208 gameInfo.event = StrSave( appData.pgnEventHeader );
12209 gameInfo.site = StrSave(HostName());
12210 gameInfo.date = PGNDate();
12211 if (matchGame > 0) {
12213 sprintf(buf, "%d", matchGame);
12214 gameInfo.round = StrSave(buf);
12216 gameInfo.round = StrSave("-");
12218 if (first.twoMachinesColor[0] == 'w') {
12219 gameInfo.white = StrSave(first.tidy);
12220 gameInfo.black = StrSave(second.tidy);
12222 gameInfo.white = StrSave(second.tidy);
12223 gameInfo.black = StrSave(first.tidy);
12225 gameInfo.timeControl = TimeControlTagValue();
12229 gameInfo.event = StrSave("Edited game");
12230 gameInfo.site = StrSave(HostName());
12231 gameInfo.date = PGNDate();
12232 gameInfo.round = StrSave("-");
12233 gameInfo.white = StrSave("-");
12234 gameInfo.black = StrSave("-");
12238 gameInfo.event = StrSave("Edited position");
12239 gameInfo.site = StrSave(HostName());
12240 gameInfo.date = PGNDate();
12241 gameInfo.round = StrSave("-");
12242 gameInfo.white = StrSave("-");
12243 gameInfo.black = StrSave("-");
12246 case IcsPlayingWhite:
12247 case IcsPlayingBlack:
12252 case PlayFromGameFile:
12253 gameInfo.event = StrSave("Game from non-PGN file");
12254 gameInfo.site = StrSave(HostName());
12255 gameInfo.date = PGNDate();
12256 gameInfo.round = StrSave("-");
12257 gameInfo.white = StrSave("?");
12258 gameInfo.black = StrSave("?");
12267 ReplaceComment(index, text)
12273 while (*text == '\n') text++;
12274 len = strlen(text);
12275 while (len > 0 && text[len - 1] == '\n') len--;
12277 if (commentList[index] != NULL)
12278 free(commentList[index]);
12281 commentList[index] = NULL;
12284 commentList[index] = (char *) malloc(len + 2);
12285 strncpy(commentList[index], text, len);
12286 commentList[index][len] = '\n';
12287 commentList[index][len + 1] = NULLCHAR;
12300 if (ch == '\r') continue;
12302 } while (ch != '\0');
12306 AppendComment(index, text)
12313 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12316 while (*text == '\n') text++;
12317 len = strlen(text);
12318 while (len > 0 && text[len - 1] == '\n') len--;
12320 if (len == 0) return;
12322 if (commentList[index] != NULL) {
12323 old = commentList[index];
12324 oldlen = strlen(old);
12325 commentList[index] = (char *) malloc(oldlen + len + 2);
12326 strcpy(commentList[index], old);
12328 strncpy(&commentList[index][oldlen], text, len);
12329 commentList[index][oldlen + len] = '\n';
12330 commentList[index][oldlen + len + 1] = NULLCHAR;
12332 commentList[index] = (char *) malloc(len + 2);
12333 strncpy(commentList[index], text, len);
12334 commentList[index][len] = '\n';
12335 commentList[index][len + 1] = NULLCHAR;
12339 static char * FindStr( char * text, char * sub_text )
12341 char * result = strstr( text, sub_text );
12343 if( result != NULL ) {
12344 result += strlen( sub_text );
12350 /* [AS] Try to extract PV info from PGN comment */
12351 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12352 char *GetInfoFromComment( int index, char * text )
12356 if( text != NULL && index > 0 ) {
12359 int time = -1, sec = 0, deci;
12360 char * s_eval = FindStr( text, "[%eval " );
12361 char * s_emt = FindStr( text, "[%emt " );
12363 if( s_eval != NULL || s_emt != NULL ) {
12367 if( s_eval != NULL ) {
12368 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12372 if( delim != ']' ) {
12377 if( s_emt != NULL ) {
12381 /* We expect something like: [+|-]nnn.nn/dd */
12384 sep = strchr( text, '/' );
12385 if( sep == NULL || sep < (text+4) ) {
12389 time = -1; sec = -1; deci = -1;
12390 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12391 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12392 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12393 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12397 if( score_lo < 0 || score_lo >= 100 ) {
12401 if(sec >= 0) time = 600*time + 10*sec; else
12402 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12404 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12406 /* [HGM] PV time: now locate end of PV info */
12407 while( *++sep >= '0' && *sep <= '9'); // strip depth
12409 while( *++sep >= '0' && *sep <= '9'); // strip time
12411 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12413 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12414 while(*sep == ' ') sep++;
12425 pvInfoList[index-1].depth = depth;
12426 pvInfoList[index-1].score = score;
12427 pvInfoList[index-1].time = 10*time; // centi-sec
12433 SendToProgram(message, cps)
12435 ChessProgramState *cps;
12437 int count, outCount, error;
12440 if (cps->pr == NULL) return;
12443 if (appData.debugMode) {
12446 fprintf(debugFP, "%ld >%-6s: %s",
12447 SubtractTimeMarks(&now, &programStartTime),
12448 cps->which, message);
12451 count = strlen(message);
12452 outCount = OutputToProcess(cps->pr, message, count, &error);
12453 if (outCount < count && !exiting
12454 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12455 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12456 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12457 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12458 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12459 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12461 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12463 gameInfo.resultDetails = StrSave(buf);
12465 DisplayFatalError(buf, error, 1);
12470 ReceiveFromProgram(isr, closure, message, count, error)
12471 InputSourceRef isr;
12479 ChessProgramState *cps = (ChessProgramState *)closure;
12481 if (isr != cps->isr) return; /* Killed intentionally */
12485 _("Error: %s chess program (%s) exited unexpectedly"),
12486 cps->which, cps->program);
12487 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12488 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12489 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12490 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12492 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12494 gameInfo.resultDetails = StrSave(buf);
12496 RemoveInputSource(cps->isr);
12497 DisplayFatalError(buf, 0, 1);
12500 _("Error reading from %s chess program (%s)"),
12501 cps->which, cps->program);
12502 RemoveInputSource(cps->isr);
12504 /* [AS] Program is misbehaving badly... kill it */
12505 if( count == -2 ) {
12506 DestroyChildProcess( cps->pr, 9 );
12510 DisplayFatalError(buf, error, 1);
12515 if ((end_str = strchr(message, '\r')) != NULL)
12516 *end_str = NULLCHAR;
12517 if ((end_str = strchr(message, '\n')) != NULL)
12518 *end_str = NULLCHAR;
12520 if (appData.debugMode) {
12521 TimeMark now; int print = 1;
12522 char *quote = ""; char c; int i;
12524 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12525 char start = message[0];
12526 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12527 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12528 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12529 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12530 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12531 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12532 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12533 sscanf(message, "pong %c", &c)!=1 && start != '#')
12534 { quote = "# "; print = (appData.engineComments == 2); }
12535 message[0] = start; // restore original message
12539 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12540 SubtractTimeMarks(&now, &programStartTime), cps->which,
12546 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12547 if (appData.icsEngineAnalyze) {
12548 if (strstr(message, "whisper") != NULL ||
12549 strstr(message, "kibitz") != NULL ||
12550 strstr(message, "tellics") != NULL) return;
12553 HandleMachineMove(message, cps);
12558 SendTimeControl(cps, mps, tc, inc, sd, st)
12559 ChessProgramState *cps;
12560 int mps, inc, sd, st;
12566 if( timeControl_2 > 0 ) {
12567 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12568 tc = timeControl_2;
12571 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12572 inc /= cps->timeOdds;
12573 st /= cps->timeOdds;
12575 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12578 /* Set exact time per move, normally using st command */
12579 if (cps->stKludge) {
12580 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12582 if (seconds == 0) {
12583 sprintf(buf, "level 1 %d\n", st/60);
12585 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12588 sprintf(buf, "st %d\n", st);
12591 /* Set conventional or incremental time control, using level command */
12592 if (seconds == 0) {
12593 /* Note old gnuchess bug -- minutes:seconds used to not work.
12594 Fixed in later versions, but still avoid :seconds
12595 when seconds is 0. */
12596 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12598 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12599 seconds, inc/1000);
12602 SendToProgram(buf, cps);
12604 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12605 /* Orthogonally, limit search to given depth */
12607 if (cps->sdKludge) {
12608 sprintf(buf, "depth\n%d\n", sd);
12610 sprintf(buf, "sd %d\n", sd);
12612 SendToProgram(buf, cps);
12615 if(cps->nps > 0) { /* [HGM] nps */
12616 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12618 sprintf(buf, "nps %d\n", cps->nps);
12619 SendToProgram(buf, cps);
12624 ChessProgramState *WhitePlayer()
12625 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12627 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12628 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12634 SendTimeRemaining(cps, machineWhite)
12635 ChessProgramState *cps;
12636 int /*boolean*/ machineWhite;
12638 char message[MSG_SIZ];
12641 /* Note: this routine must be called when the clocks are stopped
12642 or when they have *just* been set or switched; otherwise
12643 it will be off by the time since the current tick started.
12645 if (machineWhite) {
12646 time = whiteTimeRemaining / 10;
12647 otime = blackTimeRemaining / 10;
12649 time = blackTimeRemaining / 10;
12650 otime = whiteTimeRemaining / 10;
12652 /* [HGM] translate opponent's time by time-odds factor */
12653 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12654 if (appData.debugMode) {
12655 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12658 if (time <= 0) time = 1;
12659 if (otime <= 0) otime = 1;
12661 sprintf(message, "time %ld\n", time);
12662 SendToProgram(message, cps);
12664 sprintf(message, "otim %ld\n", otime);
12665 SendToProgram(message, cps);
12669 BoolFeature(p, name, loc, cps)
12673 ChessProgramState *cps;
12676 int len = strlen(name);
12678 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12680 sscanf(*p, "%d", &val);
12682 while (**p && **p != ' ') (*p)++;
12683 sprintf(buf, "accepted %s\n", name);
12684 SendToProgram(buf, cps);
12691 IntFeature(p, name, loc, cps)
12695 ChessProgramState *cps;
12698 int len = strlen(name);
12699 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12701 sscanf(*p, "%d", loc);
12702 while (**p && **p != ' ') (*p)++;
12703 sprintf(buf, "accepted %s\n", name);
12704 SendToProgram(buf, cps);
12711 StringFeature(p, name, loc, cps)
12715 ChessProgramState *cps;
12718 int len = strlen(name);
12719 if (strncmp((*p), name, len) == 0
12720 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12722 sscanf(*p, "%[^\"]", loc);
12723 while (**p && **p != '\"') (*p)++;
12724 if (**p == '\"') (*p)++;
12725 sprintf(buf, "accepted %s\n", name);
12726 SendToProgram(buf, cps);
12733 ParseOption(Option *opt, ChessProgramState *cps)
12734 // [HGM] options: process the string that defines an engine option, and determine
12735 // name, type, default value, and allowed value range
12737 char *p, *q, buf[MSG_SIZ];
12738 int n, min = (-1)<<31, max = 1<<31, def;
12740 if(p = strstr(opt->name, " -spin ")) {
12741 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12742 if(max < min) max = min; // enforce consistency
12743 if(def < min) def = min;
12744 if(def > max) def = max;
12749 } else if((p = strstr(opt->name, " -slider "))) {
12750 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12751 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12752 if(max < min) max = min; // enforce consistency
12753 if(def < min) def = min;
12754 if(def > max) def = max;
12758 opt->type = Spin; // Slider;
12759 } else if((p = strstr(opt->name, " -string "))) {
12760 opt->textValue = p+9;
12761 opt->type = TextBox;
12762 } else if((p = strstr(opt->name, " -file "))) {
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; // FileName;
12766 } else if((p = strstr(opt->name, " -path "))) {
12767 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12768 opt->textValue = p+7;
12769 opt->type = TextBox; // PathName;
12770 } else if(p = strstr(opt->name, " -check ")) {
12771 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12772 opt->value = (def != 0);
12773 opt->type = CheckBox;
12774 } else if(p = strstr(opt->name, " -combo ")) {
12775 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12776 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12777 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12778 opt->value = n = 0;
12779 while(q = StrStr(q, " /// ")) {
12780 n++; *q = 0; // count choices, and null-terminate each of them
12782 if(*q == '*') { // remember default, which is marked with * prefix
12786 cps->comboList[cps->comboCnt++] = q;
12788 cps->comboList[cps->comboCnt++] = NULL;
12790 opt->type = ComboBox;
12791 } else if(p = strstr(opt->name, " -button")) {
12792 opt->type = Button;
12793 } else if(p = strstr(opt->name, " -save")) {
12794 opt->type = SaveButton;
12795 } else return FALSE;
12796 *p = 0; // terminate option name
12797 // now look if the command-line options define a setting for this engine option.
12798 if(cps->optionSettings && cps->optionSettings[0])
12799 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12800 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12801 sprintf(buf, "option %s", p);
12802 if(p = strstr(buf, ",")) *p = 0;
12804 SendToProgram(buf, cps);
12810 FeatureDone(cps, val)
12811 ChessProgramState* cps;
12814 DelayedEventCallback cb = GetDelayedEvent();
12815 if ((cb == InitBackEnd3 && cps == &first) ||
12816 (cb == TwoMachinesEventIfReady && cps == &second)) {
12817 CancelDelayedEvent();
12818 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12820 cps->initDone = val;
12823 /* Parse feature command from engine */
12825 ParseFeatures(args, cps)
12827 ChessProgramState *cps;
12835 while (*p == ' ') p++;
12836 if (*p == NULLCHAR) return;
12838 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12839 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12840 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12841 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12842 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12843 if (BoolFeature(&p, "reuse", &val, cps)) {
12844 /* Engine can disable reuse, but can't enable it if user said no */
12845 if (!val) cps->reuse = FALSE;
12848 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12849 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12850 if (gameMode == TwoMachinesPlay) {
12851 DisplayTwoMachinesTitle();
12857 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12858 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12859 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12860 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12861 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12862 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12863 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12864 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12865 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12866 if (IntFeature(&p, "done", &val, cps)) {
12867 FeatureDone(cps, val);
12870 /* Added by Tord: */
12871 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12872 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12873 /* End of additions by Tord */
12875 /* [HGM] added features: */
12876 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12877 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12878 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12879 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12880 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12881 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12882 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12883 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12884 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12885 SendToProgram(buf, cps);
12888 if(cps->nrOptions >= MAX_OPTIONS) {
12890 sprintf(buf, "%s engine has too many options\n", cps->which);
12891 DisplayError(buf, 0);
12895 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12896 /* End of additions by HGM */
12898 /* unknown feature: complain and skip */
12900 while (*q && *q != '=') q++;
12901 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12902 SendToProgram(buf, cps);
12908 while (*p && *p != '\"') p++;
12909 if (*p == '\"') p++;
12911 while (*p && *p != ' ') p++;
12919 PeriodicUpdatesEvent(newState)
12922 if (newState == appData.periodicUpdates)
12925 appData.periodicUpdates=newState;
12927 /* Display type changes, so update it now */
12928 // DisplayAnalysis();
12930 /* Get the ball rolling again... */
12932 AnalysisPeriodicEvent(1);
12933 StartAnalysisClock();
12938 PonderNextMoveEvent(newState)
12941 if (newState == appData.ponderNextMove) return;
12942 if (gameMode == EditPosition) EditPositionDone(TRUE);
12944 SendToProgram("hard\n", &first);
12945 if (gameMode == TwoMachinesPlay) {
12946 SendToProgram("hard\n", &second);
12949 SendToProgram("easy\n", &first);
12950 thinkOutput[0] = NULLCHAR;
12951 if (gameMode == TwoMachinesPlay) {
12952 SendToProgram("easy\n", &second);
12955 appData.ponderNextMove = newState;
12959 NewSettingEvent(option, command, value)
12965 if (gameMode == EditPosition) EditPositionDone(TRUE);
12966 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12967 SendToProgram(buf, &first);
12968 if (gameMode == TwoMachinesPlay) {
12969 SendToProgram(buf, &second);
12974 ShowThinkingEvent()
12975 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12977 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12978 int newState = appData.showThinking
12979 // [HGM] thinking: other features now need thinking output as well
12980 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12982 if (oldState == newState) return;
12983 oldState = newState;
12984 if (gameMode == EditPosition) EditPositionDone(TRUE);
12986 SendToProgram("post\n", &first);
12987 if (gameMode == TwoMachinesPlay) {
12988 SendToProgram("post\n", &second);
12991 SendToProgram("nopost\n", &first);
12992 thinkOutput[0] = NULLCHAR;
12993 if (gameMode == TwoMachinesPlay) {
12994 SendToProgram("nopost\n", &second);
12997 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13001 AskQuestionEvent(title, question, replyPrefix, which)
13002 char *title; char *question; char *replyPrefix; char *which;
13004 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13005 if (pr == NoProc) return;
13006 AskQuestion(title, question, replyPrefix, pr);
13010 DisplayMove(moveNumber)
13013 char message[MSG_SIZ];
13015 char cpThinkOutput[MSG_SIZ];
13017 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13019 if (moveNumber == forwardMostMove - 1 ||
13020 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13022 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13024 if (strchr(cpThinkOutput, '\n')) {
13025 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13028 *cpThinkOutput = NULLCHAR;
13031 /* [AS] Hide thinking from human user */
13032 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13033 *cpThinkOutput = NULLCHAR;
13034 if( thinkOutput[0] != NULLCHAR ) {
13037 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13038 cpThinkOutput[i] = '.';
13040 cpThinkOutput[i] = NULLCHAR;
13041 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13045 if (moveNumber == forwardMostMove - 1 &&
13046 gameInfo.resultDetails != NULL) {
13047 if (gameInfo.resultDetails[0] == NULLCHAR) {
13048 sprintf(res, " %s", PGNResult(gameInfo.result));
13050 sprintf(res, " {%s} %s",
13051 gameInfo.resultDetails, PGNResult(gameInfo.result));
13057 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13058 DisplayMessage(res, cpThinkOutput);
13060 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13061 WhiteOnMove(moveNumber) ? " " : ".. ",
13062 parseList[moveNumber], res);
13063 DisplayMessage(message, cpThinkOutput);
13068 DisplayComment(moveNumber, text)
13072 char title[MSG_SIZ];
13073 char buf[8000]; // comment can be long!
13076 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13077 strcpy(title, "Comment");
13079 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13080 WhiteOnMove(moveNumber) ? " " : ".. ",
13081 parseList[moveNumber]);
13083 // [HGM] PV info: display PV info together with (or as) comment
13084 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13085 if(text == NULL) text = "";
13086 score = pvInfoList[moveNumber].score;
13087 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13088 depth, (pvInfoList[moveNumber].time+50)/100, text);
13091 if (text != NULL && (appData.autoDisplayComment || commentUp))
13092 CommentPopUp(title, text);
13095 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13096 * might be busy thinking or pondering. It can be omitted if your
13097 * gnuchess is configured to stop thinking immediately on any user
13098 * input. However, that gnuchess feature depends on the FIONREAD
13099 * ioctl, which does not work properly on some flavors of Unix.
13103 ChessProgramState *cps;
13106 if (!cps->useSigint) return;
13107 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13108 switch (gameMode) {
13109 case MachinePlaysWhite:
13110 case MachinePlaysBlack:
13111 case TwoMachinesPlay:
13112 case IcsPlayingWhite:
13113 case IcsPlayingBlack:
13116 /* Skip if we know it isn't thinking */
13117 if (!cps->maybeThinking) return;
13118 if (appData.debugMode)
13119 fprintf(debugFP, "Interrupting %s\n", cps->which);
13120 InterruptChildProcess(cps->pr);
13121 cps->maybeThinking = FALSE;
13126 #endif /*ATTENTION*/
13132 if (whiteTimeRemaining <= 0) {
13135 if (appData.icsActive) {
13136 if (appData.autoCallFlag &&
13137 gameMode == IcsPlayingBlack && !blackFlag) {
13138 SendToICS(ics_prefix);
13139 SendToICS("flag\n");
13143 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13145 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13146 if (appData.autoCallFlag) {
13147 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13154 if (blackTimeRemaining <= 0) {
13157 if (appData.icsActive) {
13158 if (appData.autoCallFlag &&
13159 gameMode == IcsPlayingWhite && !whiteFlag) {
13160 SendToICS(ics_prefix);
13161 SendToICS("flag\n");
13165 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13167 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13168 if (appData.autoCallFlag) {
13169 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13182 if (!appData.clockMode || appData.icsActive ||
13183 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13186 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13188 if ( !WhiteOnMove(forwardMostMove) )
13189 /* White made time control */
13190 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13191 /* [HGM] time odds: correct new time quota for time odds! */
13192 / WhitePlayer()->timeOdds;
13194 /* Black made time control */
13195 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13196 / WhitePlayer()->other->timeOdds;
13200 DisplayBothClocks()
13202 int wom = gameMode == EditPosition ?
13203 !blackPlaysFirst : WhiteOnMove(currentMove);
13204 DisplayWhiteClock(whiteTimeRemaining, wom);
13205 DisplayBlackClock(blackTimeRemaining, !wom);
13209 /* Timekeeping seems to be a portability nightmare. I think everyone
13210 has ftime(), but I'm really not sure, so I'm including some ifdefs
13211 to use other calls if you don't. Clocks will be less accurate if
13212 you have neither ftime nor gettimeofday.
13215 /* VS 2008 requires the #include outside of the function */
13216 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13217 #include <sys/timeb.h>
13220 /* Get the current time as a TimeMark */
13225 #if HAVE_GETTIMEOFDAY
13227 struct timeval timeVal;
13228 struct timezone timeZone;
13230 gettimeofday(&timeVal, &timeZone);
13231 tm->sec = (long) timeVal.tv_sec;
13232 tm->ms = (int) (timeVal.tv_usec / 1000L);
13234 #else /*!HAVE_GETTIMEOFDAY*/
13237 // include <sys/timeb.h> / moved to just above start of function
13238 struct timeb timeB;
13241 tm->sec = (long) timeB.time;
13242 tm->ms = (int) timeB.millitm;
13244 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13245 tm->sec = (long) time(NULL);
13251 /* Return the difference in milliseconds between two
13252 time marks. We assume the difference will fit in a long!
13255 SubtractTimeMarks(tm2, tm1)
13256 TimeMark *tm2, *tm1;
13258 return 1000L*(tm2->sec - tm1->sec) +
13259 (long) (tm2->ms - tm1->ms);
13264 * Code to manage the game clocks.
13266 * In tournament play, black starts the clock and then white makes a move.
13267 * We give the human user a slight advantage if he is playing white---the
13268 * clocks don't run until he makes his first move, so it takes zero time.
13269 * Also, we don't account for network lag, so we could get out of sync
13270 * with GNU Chess's clock -- but then, referees are always right.
13273 static TimeMark tickStartTM;
13274 static long intendedTickLength;
13277 NextTickLength(timeRemaining)
13278 long timeRemaining;
13280 long nominalTickLength, nextTickLength;
13282 if (timeRemaining > 0L && timeRemaining <= 10000L)
13283 nominalTickLength = 100L;
13285 nominalTickLength = 1000L;
13286 nextTickLength = timeRemaining % nominalTickLength;
13287 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13289 return nextTickLength;
13292 /* Adjust clock one minute up or down */
13294 AdjustClock(Boolean which, int dir)
13296 if(which) blackTimeRemaining += 60000*dir;
13297 else whiteTimeRemaining += 60000*dir;
13298 DisplayBothClocks();
13301 /* Stop clocks and reset to a fresh time control */
13305 (void) StopClockTimer();
13306 if (appData.icsActive) {
13307 whiteTimeRemaining = blackTimeRemaining = 0;
13308 } else { /* [HGM] correct new time quote for time odds */
13309 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13310 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13312 if (whiteFlag || blackFlag) {
13314 whiteFlag = blackFlag = FALSE;
13316 DisplayBothClocks();
13319 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13321 /* Decrement running clock by amount of time that has passed */
13325 long timeRemaining;
13326 long lastTickLength, fudge;
13329 if (!appData.clockMode) return;
13330 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13334 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13336 /* Fudge if we woke up a little too soon */
13337 fudge = intendedTickLength - lastTickLength;
13338 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13340 if (WhiteOnMove(forwardMostMove)) {
13341 if(whiteNPS >= 0) lastTickLength = 0;
13342 timeRemaining = whiteTimeRemaining -= lastTickLength;
13343 DisplayWhiteClock(whiteTimeRemaining - fudge,
13344 WhiteOnMove(currentMove));
13346 if(blackNPS >= 0) lastTickLength = 0;
13347 timeRemaining = blackTimeRemaining -= lastTickLength;
13348 DisplayBlackClock(blackTimeRemaining - fudge,
13349 !WhiteOnMove(currentMove));
13352 if (CheckFlags()) return;
13355 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13356 StartClockTimer(intendedTickLength);
13358 /* if the time remaining has fallen below the alarm threshold, sound the
13359 * alarm. if the alarm has sounded and (due to a takeback or time control
13360 * with increment) the time remaining has increased to a level above the
13361 * threshold, reset the alarm so it can sound again.
13364 if (appData.icsActive && appData.icsAlarm) {
13366 /* make sure we are dealing with the user's clock */
13367 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13368 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13371 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13372 alarmSounded = FALSE;
13373 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13375 alarmSounded = TRUE;
13381 /* A player has just moved, so stop the previously running
13382 clock and (if in clock mode) start the other one.
13383 We redisplay both clocks in case we're in ICS mode, because
13384 ICS gives us an update to both clocks after every move.
13385 Note that this routine is called *after* forwardMostMove
13386 is updated, so the last fractional tick must be subtracted
13387 from the color that is *not* on move now.
13390 SwitchClocks(int newMoveNr)
13392 long lastTickLength;
13394 int flagged = FALSE;
13398 if (StopClockTimer() && appData.clockMode) {
13399 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13400 if (!WhiteOnMove(forwardMostMove)) {
13401 if(blackNPS >= 0) lastTickLength = 0;
13402 blackTimeRemaining -= lastTickLength;
13403 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13404 // if(pvInfoList[forwardMostMove-1].time == -1)
13405 pvInfoList[forwardMostMove-1].time = // use GUI time
13406 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13408 if(whiteNPS >= 0) lastTickLength = 0;
13409 whiteTimeRemaining -= lastTickLength;
13410 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13411 // if(pvInfoList[forwardMostMove-1].time == -1)
13412 pvInfoList[forwardMostMove-1].time =
13413 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13415 flagged = CheckFlags();
13417 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13418 CheckTimeControl();
13420 if (flagged || !appData.clockMode) return;
13422 switch (gameMode) {
13423 case MachinePlaysBlack:
13424 case MachinePlaysWhite:
13425 case BeginningOfGame:
13426 if (pausing) return;
13430 case PlayFromGameFile:
13439 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13440 whiteTimeRemaining : blackTimeRemaining);
13441 StartClockTimer(intendedTickLength);
13445 /* Stop both clocks */
13449 long lastTickLength;
13452 if (!StopClockTimer()) return;
13453 if (!appData.clockMode) return;
13457 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13458 if (WhiteOnMove(forwardMostMove)) {
13459 if(whiteNPS >= 0) lastTickLength = 0;
13460 whiteTimeRemaining -= lastTickLength;
13461 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13463 if(blackNPS >= 0) lastTickLength = 0;
13464 blackTimeRemaining -= lastTickLength;
13465 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13470 /* Start clock of player on move. Time may have been reset, so
13471 if clock is already running, stop and restart it. */
13475 (void) StopClockTimer(); /* in case it was running already */
13476 DisplayBothClocks();
13477 if (CheckFlags()) return;
13479 if (!appData.clockMode) return;
13480 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13482 GetTimeMark(&tickStartTM);
13483 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13484 whiteTimeRemaining : blackTimeRemaining);
13486 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13487 whiteNPS = blackNPS = -1;
13488 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13489 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13490 whiteNPS = first.nps;
13491 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13492 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13493 blackNPS = first.nps;
13494 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13495 whiteNPS = second.nps;
13496 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13497 blackNPS = second.nps;
13498 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13500 StartClockTimer(intendedTickLength);
13507 long second, minute, hour, day;
13509 static char buf[32];
13511 if (ms > 0 && ms <= 9900) {
13512 /* convert milliseconds to tenths, rounding up */
13513 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13515 sprintf(buf, " %03.1f ", tenths/10.0);
13519 /* convert milliseconds to seconds, rounding up */
13520 /* use floating point to avoid strangeness of integer division
13521 with negative dividends on many machines */
13522 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13529 day = second / (60 * 60 * 24);
13530 second = second % (60 * 60 * 24);
13531 hour = second / (60 * 60);
13532 second = second % (60 * 60);
13533 minute = second / 60;
13534 second = second % 60;
13537 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13538 sign, day, hour, minute, second);
13540 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13542 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13549 * This is necessary because some C libraries aren't ANSI C compliant yet.
13552 StrStr(string, match)
13553 char *string, *match;
13557 length = strlen(match);
13559 for (i = strlen(string) - length; i >= 0; i--, string++)
13560 if (!strncmp(match, string, length))
13567 StrCaseStr(string, match)
13568 char *string, *match;
13572 length = strlen(match);
13574 for (i = strlen(string) - length; i >= 0; i--, string++) {
13575 for (j = 0; j < length; j++) {
13576 if (ToLower(match[j]) != ToLower(string[j]))
13579 if (j == length) return string;
13593 c1 = ToLower(*s1++);
13594 c2 = ToLower(*s2++);
13595 if (c1 > c2) return 1;
13596 if (c1 < c2) return -1;
13597 if (c1 == NULLCHAR) return 0;
13606 return isupper(c) ? tolower(c) : c;
13614 return islower(c) ? toupper(c) : c;
13616 #endif /* !_amigados */
13624 if ((ret = (char *) malloc(strlen(s) + 1))) {
13631 StrSavePtr(s, savePtr)
13632 char *s, **savePtr;
13637 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13638 strcpy(*savePtr, s);
13650 clock = time((time_t *)NULL);
13651 tm = localtime(&clock);
13652 sprintf(buf, "%04d.%02d.%02d",
13653 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13654 return StrSave(buf);
13659 PositionToFEN(move, overrideCastling)
13661 char *overrideCastling;
13663 int i, j, fromX, fromY, toX, toY;
13670 whiteToPlay = (gameMode == EditPosition) ?
13671 !blackPlaysFirst : (move % 2 == 0);
13674 /* Piece placement data */
13675 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13677 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13678 if (boards[move][i][j] == EmptySquare) {
13680 } else { ChessSquare piece = boards[move][i][j];
13681 if (emptycount > 0) {
13682 if(emptycount<10) /* [HGM] can be >= 10 */
13683 *p++ = '0' + emptycount;
13684 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13687 if(PieceToChar(piece) == '+') {
13688 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13690 piece = (ChessSquare)(DEMOTED piece);
13692 *p++ = PieceToChar(piece);
13694 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13695 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13700 if (emptycount > 0) {
13701 if(emptycount<10) /* [HGM] can be >= 10 */
13702 *p++ = '0' + emptycount;
13703 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13710 /* [HGM] print Crazyhouse or Shogi holdings */
13711 if( gameInfo.holdingsWidth ) {
13712 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13714 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13715 piece = boards[move][i][BOARD_WIDTH-1];
13716 if( piece != EmptySquare )
13717 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13718 *p++ = PieceToChar(piece);
13720 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13721 piece = boards[move][BOARD_HEIGHT-i-1][0];
13722 if( piece != EmptySquare )
13723 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13724 *p++ = PieceToChar(piece);
13727 if( q == p ) *p++ = '-';
13733 *p++ = whiteToPlay ? 'w' : 'b';
13736 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13737 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13739 if(nrCastlingRights) {
13741 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13742 /* [HGM] write directly from rights */
13743 if(castlingRights[move][2] >= 0 &&
13744 castlingRights[move][0] >= 0 )
13745 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13746 if(castlingRights[move][2] >= 0 &&
13747 castlingRights[move][1] >= 0 )
13748 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13749 if(castlingRights[move][5] >= 0 &&
13750 castlingRights[move][3] >= 0 )
13751 *p++ = castlingRights[move][3] + AAA;
13752 if(castlingRights[move][5] >= 0 &&
13753 castlingRights[move][4] >= 0 )
13754 *p++ = castlingRights[move][4] + AAA;
13757 /* [HGM] write true castling rights */
13758 if( nrCastlingRights == 6 ) {
13759 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13760 castlingRights[move][2] >= 0 ) *p++ = 'K';
13761 if(castlingRights[move][1] == BOARD_LEFT &&
13762 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13763 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13764 castlingRights[move][5] >= 0 ) *p++ = 'k';
13765 if(castlingRights[move][4] == BOARD_LEFT &&
13766 castlingRights[move][5] >= 0 ) *p++ = 'q';
13769 if (q == p) *p++ = '-'; /* No castling rights */
13773 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13774 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
13775 /* En passant target square */
13776 if (move > backwardMostMove) {
13777 fromX = moveList[move - 1][0] - AAA;
13778 fromY = moveList[move - 1][1] - ONE;
13779 toX = moveList[move - 1][2] - AAA;
13780 toY = moveList[move - 1][3] - ONE;
13781 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13782 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13783 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13785 /* 2-square pawn move just happened */
13787 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13791 } else if(move == backwardMostMove) {
13792 // [HGM] perhaps we should always do it like this, and forget the above?
13793 if(epStatus[move] >= 0) {
13794 *p++ = epStatus[move] + AAA;
13795 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13806 /* [HGM] find reversible plies */
13807 { int i = 0, j=move;
13809 if (appData.debugMode) { int k;
13810 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13811 for(k=backwardMostMove; k<=forwardMostMove; k++)
13812 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13816 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13817 if( j == backwardMostMove ) i += initialRulePlies;
13818 sprintf(p, "%d ", i);
13819 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13821 /* Fullmove number */
13822 sprintf(p, "%d", (move / 2) + 1);
13824 return StrSave(buf);
13828 ParseFEN(board, blackPlaysFirst, fen)
13830 int *blackPlaysFirst;
13840 /* [HGM] by default clear Crazyhouse holdings, if present */
13841 if(gameInfo.holdingsWidth) {
13842 for(i=0; i<BOARD_HEIGHT; i++) {
13843 board[i][0] = EmptySquare; /* black holdings */
13844 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13845 board[i][1] = (ChessSquare) 0; /* black counts */
13846 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13850 /* Piece placement data */
13851 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13854 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13855 if (*p == '/') p++;
13856 emptycount = gameInfo.boardWidth - j;
13857 while (emptycount--)
13858 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13860 #if(BOARD_SIZE >= 10)
13861 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13862 p++; emptycount=10;
13863 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13864 while (emptycount--)
13865 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13867 } else if (isdigit(*p)) {
13868 emptycount = *p++ - '0';
13869 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13870 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13871 while (emptycount--)
13872 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13873 } else if (*p == '+' || isalpha(*p)) {
13874 if (j >= gameInfo.boardWidth) return FALSE;
13876 piece = CharToPiece(*++p);
13877 if(piece == EmptySquare) return FALSE; /* unknown piece */
13878 piece = (ChessSquare) (PROMOTED piece ); p++;
13879 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13880 } else piece = CharToPiece(*p++);
13882 if(piece==EmptySquare) return FALSE; /* unknown piece */
13883 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13884 piece = (ChessSquare) (PROMOTED piece);
13885 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13888 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13894 while (*p == '/' || *p == ' ') p++;
13896 /* [HGM] look for Crazyhouse holdings here */
13897 while(*p==' ') p++;
13898 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13900 if(*p == '-' ) *p++; /* empty holdings */ else {
13901 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13902 /* if we would allow FEN reading to set board size, we would */
13903 /* have to add holdings and shift the board read so far here */
13904 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13906 if((int) piece >= (int) BlackPawn ) {
13907 i = (int)piece - (int)BlackPawn;
13908 i = PieceToNumber((ChessSquare)i);
13909 if( i >= gameInfo.holdingsSize ) return FALSE;
13910 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13911 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13913 i = (int)piece - (int)WhitePawn;
13914 i = PieceToNumber((ChessSquare)i);
13915 if( i >= gameInfo.holdingsSize ) return FALSE;
13916 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13917 board[i][BOARD_WIDTH-2]++; /* black holdings */
13921 if(*p == ']') *p++;
13924 while(*p == ' ') p++;
13929 *blackPlaysFirst = FALSE;
13932 *blackPlaysFirst = TRUE;
13938 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13939 /* return the extra info in global variiables */
13941 /* set defaults in case FEN is incomplete */
13942 FENepStatus = EP_UNKNOWN;
13943 for(i=0; i<nrCastlingRights; i++ ) {
13944 FENcastlingRights[i] =
13945 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13946 } /* assume possible unless obviously impossible */
13947 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13948 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13949 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13950 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13951 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13952 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13953 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13954 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13957 while(*p==' ') p++;
13958 if(nrCastlingRights) {
13959 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13960 /* castling indicator present, so default becomes no castlings */
13961 for(i=0; i<nrCastlingRights; i++ ) {
13962 FENcastlingRights[i] = -1;
13965 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13966 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13967 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13968 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13969 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13971 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13972 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13973 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13975 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13976 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13977 if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13978 && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13979 if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13980 && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13983 for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13984 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13985 FENcastlingRights[2] = whiteKingFile;
13988 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13989 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13990 FENcastlingRights[2] = whiteKingFile;
13993 for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13994 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13995 FENcastlingRights[5] = blackKingFile;
13998 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13999 FENcastlingRights[4] = i != blackKingFile ? i : -1;
14000 FENcastlingRights[5] = blackKingFile;
14003 default: /* FRC castlings */
14004 if(c >= 'a') { /* black rights */
14005 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14006 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14007 if(i == BOARD_RGHT) break;
14008 FENcastlingRights[5] = i;
14010 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14011 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14013 FENcastlingRights[3] = c;
14015 FENcastlingRights[4] = c;
14016 } else { /* white rights */
14017 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14018 if(board[0][i] == WhiteKing) break;
14019 if(i == BOARD_RGHT) break;
14020 FENcastlingRights[2] = i;
14021 c -= AAA - 'a' + 'A';
14022 if(board[0][c] >= WhiteKing) break;
14024 FENcastlingRights[0] = c;
14026 FENcastlingRights[1] = c;
14030 for(i=0; i<nrCastlingRights; i++)
14031 if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14032 if (appData.debugMode) {
14033 fprintf(debugFP, "FEN castling rights:");
14034 for(i=0; i<nrCastlingRights; i++)
14035 fprintf(debugFP, " %d", FENcastlingRights[i]);
14036 fprintf(debugFP, "\n");
14039 while(*p==' ') p++;
14042 /* read e.p. field in games that know e.p. capture */
14043 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14044 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14046 p++; FENepStatus = EP_NONE;
14048 char c = *p++ - AAA;
14050 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14051 if(*p >= '0' && *p <='9') *p++;
14057 if(sscanf(p, "%d", &i) == 1) {
14058 FENrulePlies = i; /* 50-move ply counter */
14059 /* (The move number is still ignored) */
14066 EditPositionPasteFEN(char *fen)
14069 Board initial_position;
14071 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14072 DisplayError(_("Bad FEN position in clipboard"), 0);
14075 int savedBlackPlaysFirst = blackPlaysFirst;
14076 EditPositionEvent();
14077 blackPlaysFirst = savedBlackPlaysFirst;
14078 CopyBoard(boards[0], initial_position);
14079 /* [HGM] copy FEN attributes as well */
14081 initialRulePlies = FENrulePlies;
14082 epStatus[0] = FENepStatus;
14083 for( i=0; i<nrCastlingRights; i++ )
14084 castlingRights[0][i] = FENcastlingRights[i];
14086 EditPositionDone(FALSE);
14087 DisplayBothClocks();
14088 DrawPosition(FALSE, boards[currentMove]);
14093 static char cseq[12] = "\\ ";
14095 Boolean set_cont_sequence(char *new_seq)
14100 // handle bad attempts to set the sequence
14102 return 0; // acceptable error - no debug
14104 len = strlen(new_seq);
14105 ret = (len > 0) && (len < sizeof(cseq));
14107 strcpy(cseq, new_seq);
14108 else if (appData.debugMode)
14109 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14114 reformat a source message so words don't cross the width boundary. internal
14115 newlines are not removed. returns the wrapped size (no null character unless
14116 included in source message). If dest is NULL, only calculate the size required
14117 for the dest buffer. lp argument indicats line position upon entry, and it's
14118 passed back upon exit.
14120 int wrap(char *dest, char *src, int count, int width, int *lp)
14122 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14124 cseq_len = strlen(cseq);
14125 old_line = line = *lp;
14126 ansi = len = clen = 0;
14128 for (i=0; i < count; i++)
14130 if (src[i] == '\033')
14133 // if we hit the width, back up
14134 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14136 // store i & len in case the word is too long
14137 old_i = i, old_len = len;
14139 // find the end of the last word
14140 while (i && src[i] != ' ' && src[i] != '\n')
14146 // word too long? restore i & len before splitting it
14147 if ((old_i-i+clen) >= width)
14154 if (i && src[i-1] == ' ')
14157 if (src[i] != ' ' && src[i] != '\n')
14164 // now append the newline and continuation sequence
14169 strncpy(dest+len, cseq, cseq_len);
14177 dest[len] = src[i];
14181 if (src[i] == '\n')
14186 if (dest && appData.debugMode)
14188 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14189 count, width, line, len, *lp);
14190 show_bytes(debugFP, src, count);
14191 fprintf(debugFP, "\ndest: ");
14192 show_bytes(debugFP, dest, len);
14193 fprintf(debugFP, "\n");
14195 *lp = dest ? line : old_line;