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 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8123 /* If we are playing on ICS, the server decides when the
8124 game is over, but the engine can offer to draw, claim
8128 if (appData.zippyPlay && first.initDone) {
8129 if (result == GameIsDrawn) {
8130 /* In case draw still needs to be claimed */
8131 SendToICS(ics_prefix);
8132 SendToICS("draw\n");
8133 } else if (StrCaseStr(resultDetails, "resign")) {
8134 SendToICS(ics_prefix);
8135 SendToICS("resign\n");
8139 endingGame = 0; /* [HGM] crash */
8143 /* If we're loading the game from a file, stop */
8144 if (whosays == GE_FILE) {
8145 (void) StopLoadGameTimer();
8149 /* Cancel draw offers */
8150 first.offeredDraw = second.offeredDraw = 0;
8152 /* If this is an ICS game, only ICS can really say it's done;
8153 if not, anyone can. */
8154 isIcsGame = (gameMode == IcsPlayingWhite ||
8155 gameMode == IcsPlayingBlack ||
8156 gameMode == IcsObserving ||
8157 gameMode == IcsExamining);
8159 if (!isIcsGame || whosays == GE_ICS) {
8160 /* OK -- not an ICS game, or ICS said it was done */
8162 if (!isIcsGame && !appData.noChessProgram)
8163 SetUserThinkingEnables();
8165 /* [HGM] if a machine claims the game end we verify this claim */
8166 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8167 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8169 ChessMove trueResult = (ChessMove) -1;
8171 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8172 first.twoMachinesColor[0] :
8173 second.twoMachinesColor[0] ;
8175 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8176 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8177 /* [HGM] verify: engine mate claims accepted if they were flagged */
8178 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8180 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8181 /* [HGM] verify: engine mate claims accepted if they were flagged */
8182 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8184 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8185 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8188 // now verify win claims, but not in drop games, as we don't understand those yet
8189 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8190 || gameInfo.variant == VariantGreat) &&
8191 (result == WhiteWins && claimer == 'w' ||
8192 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8193 if (appData.debugMode) {
8194 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8195 result, epStatus[forwardMostMove], forwardMostMove);
8197 if(result != trueResult) {
8198 sprintf(buf, "False win claim: '%s'", resultDetails);
8199 result = claimer == 'w' ? BlackWins : WhiteWins;
8200 resultDetails = buf;
8203 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8204 && (forwardMostMove <= backwardMostMove ||
8205 epStatus[forwardMostMove-1] > EP_DRAWS ||
8206 (claimer=='b')==(forwardMostMove&1))
8208 /* [HGM] verify: draws that were not flagged are false claims */
8209 sprintf(buf, "False draw claim: '%s'", resultDetails);
8210 result = claimer == 'w' ? BlackWins : WhiteWins;
8211 resultDetails = buf;
8213 /* (Claiming a loss is accepted no questions asked!) */
8215 /* [HGM] bare: don't allow bare King to win */
8216 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8217 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8218 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8219 && result != GameIsDrawn)
8220 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8221 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8222 int p = (int)boards[forwardMostMove][i][j] - color;
8223 if(p >= 0 && p <= (int)WhiteKing) k++;
8225 if (appData.debugMode) {
8226 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8227 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8230 result = GameIsDrawn;
8231 sprintf(buf, "%s but bare king", resultDetails);
8232 resultDetails = buf;
8238 if(serverMoves != NULL && !loadFlag) { char c = '=';
8239 if(result==WhiteWins) c = '+';
8240 if(result==BlackWins) c = '-';
8241 if(resultDetails != NULL)
8242 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8244 if (resultDetails != NULL) {
8245 gameInfo.result = result;
8246 gameInfo.resultDetails = StrSave(resultDetails);
8248 /* display last move only if game was not loaded from file */
8249 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8250 DisplayMove(currentMove - 1);
8252 if (forwardMostMove != 0) {
8253 if (gameMode != PlayFromGameFile && gameMode != EditGame
8254 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8256 if (*appData.saveGameFile != NULLCHAR) {
8257 SaveGameToFile(appData.saveGameFile, TRUE);
8258 } else if (appData.autoSaveGames) {
8261 if (*appData.savePositionFile != NULLCHAR) {
8262 SavePositionToFile(appData.savePositionFile);
8267 /* Tell program how game ended in case it is learning */
8268 /* [HGM] Moved this to after saving the PGN, just in case */
8269 /* engine died and we got here through time loss. In that */
8270 /* case we will get a fatal error writing the pipe, which */
8271 /* would otherwise lose us the PGN. */
8272 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8273 /* output during GameEnds should never be fatal anymore */
8274 if (gameMode == MachinePlaysWhite ||
8275 gameMode == MachinePlaysBlack ||
8276 gameMode == TwoMachinesPlay ||
8277 gameMode == IcsPlayingWhite ||
8278 gameMode == IcsPlayingBlack ||
8279 gameMode == BeginningOfGame) {
8281 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8283 if (first.pr != NoProc) {
8284 SendToProgram(buf, &first);
8286 if (second.pr != NoProc &&
8287 gameMode == TwoMachinesPlay) {
8288 SendToProgram(buf, &second);
8293 if (appData.icsActive) {
8294 if (appData.quietPlay &&
8295 (gameMode == IcsPlayingWhite ||
8296 gameMode == IcsPlayingBlack)) {
8297 SendToICS(ics_prefix);
8298 SendToICS("set shout 1\n");
8300 nextGameMode = IcsIdle;
8301 ics_user_moved = FALSE;
8302 /* clean up premove. It's ugly when the game has ended and the
8303 * premove highlights are still on the board.
8307 ClearPremoveHighlights();
8308 DrawPosition(FALSE, boards[currentMove]);
8310 if (whosays == GE_ICS) {
8313 if (gameMode == IcsPlayingWhite)
8315 else if(gameMode == IcsPlayingBlack)
8319 if (gameMode == IcsPlayingBlack)
8321 else if(gameMode == IcsPlayingWhite)
8328 PlayIcsUnfinishedSound();
8331 } else if (gameMode == EditGame ||
8332 gameMode == PlayFromGameFile ||
8333 gameMode == AnalyzeMode ||
8334 gameMode == AnalyzeFile) {
8335 nextGameMode = gameMode;
8337 nextGameMode = EndOfGame;
8342 nextGameMode = gameMode;
8345 if (appData.noChessProgram) {
8346 gameMode = nextGameMode;
8348 endingGame = 0; /* [HGM] crash */
8353 /* Put first chess program into idle state */
8354 if (first.pr != NoProc &&
8355 (gameMode == MachinePlaysWhite ||
8356 gameMode == MachinePlaysBlack ||
8357 gameMode == TwoMachinesPlay ||
8358 gameMode == IcsPlayingWhite ||
8359 gameMode == IcsPlayingBlack ||
8360 gameMode == BeginningOfGame)) {
8361 SendToProgram("force\n", &first);
8362 if (first.usePing) {
8364 sprintf(buf, "ping %d\n", ++first.lastPing);
8365 SendToProgram(buf, &first);
8368 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8369 /* Kill off first chess program */
8370 if (first.isr != NULL)
8371 RemoveInputSource(first.isr);
8374 if (first.pr != NoProc) {
8376 DoSleep( appData.delayBeforeQuit );
8377 SendToProgram("quit\n", &first);
8378 DoSleep( appData.delayAfterQuit );
8379 DestroyChildProcess(first.pr, first.useSigterm);
8384 /* Put second chess program into idle state */
8385 if (second.pr != NoProc &&
8386 gameMode == TwoMachinesPlay) {
8387 SendToProgram("force\n", &second);
8388 if (second.usePing) {
8390 sprintf(buf, "ping %d\n", ++second.lastPing);
8391 SendToProgram(buf, &second);
8394 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8395 /* Kill off second chess program */
8396 if (second.isr != NULL)
8397 RemoveInputSource(second.isr);
8400 if (second.pr != NoProc) {
8401 DoSleep( appData.delayBeforeQuit );
8402 SendToProgram("quit\n", &second);
8403 DoSleep( appData.delayAfterQuit );
8404 DestroyChildProcess(second.pr, second.useSigterm);
8409 if (matchMode && gameMode == TwoMachinesPlay) {
8412 if (first.twoMachinesColor[0] == 'w') {
8419 if (first.twoMachinesColor[0] == 'b') {
8428 if (matchGame < appData.matchGames) {
8430 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8431 tmp = first.twoMachinesColor;
8432 first.twoMachinesColor = second.twoMachinesColor;
8433 second.twoMachinesColor = tmp;
8435 gameMode = nextGameMode;
8437 if(appData.matchPause>10000 || appData.matchPause<10)
8438 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8439 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8440 endingGame = 0; /* [HGM] crash */
8444 gameMode = nextGameMode;
8445 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8446 first.tidy, second.tidy,
8447 first.matchWins, second.matchWins,
8448 appData.matchGames - (first.matchWins + second.matchWins));
8449 DisplayFatalError(buf, 0, 0);
8452 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8453 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8455 gameMode = nextGameMode;
8457 endingGame = 0; /* [HGM] crash */
8460 /* Assumes program was just initialized (initString sent).
8461 Leaves program in force mode. */
8463 FeedMovesToProgram(cps, upto)
8464 ChessProgramState *cps;
8469 if (appData.debugMode)
8470 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8471 startedFromSetupPosition ? "position and " : "",
8472 backwardMostMove, upto, cps->which);
8473 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8474 // [HGM] variantswitch: make engine aware of new variant
8475 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8476 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8477 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8478 SendToProgram(buf, cps);
8479 currentlyInitializedVariant = gameInfo.variant;
8481 SendToProgram("force\n", cps);
8482 if (startedFromSetupPosition) {
8483 SendBoard(cps, backwardMostMove);
8484 if (appData.debugMode) {
8485 fprintf(debugFP, "feedMoves\n");
8488 for (i = backwardMostMove; i < upto; i++) {
8489 SendMoveToProgram(i, cps);
8495 ResurrectChessProgram()
8497 /* The chess program may have exited.
8498 If so, restart it and feed it all the moves made so far. */
8500 if (appData.noChessProgram || first.pr != NoProc) return;
8502 StartChessProgram(&first);
8503 InitChessProgram(&first, FALSE);
8504 FeedMovesToProgram(&first, currentMove);
8506 if (!first.sendTime) {
8507 /* can't tell gnuchess what its clock should read,
8508 so we bow to its notion. */
8510 timeRemaining[0][currentMove] = whiteTimeRemaining;
8511 timeRemaining[1][currentMove] = blackTimeRemaining;
8514 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8515 appData.icsEngineAnalyze) && first.analysisSupport) {
8516 SendToProgram("analyze\n", &first);
8517 first.analyzing = TRUE;
8530 if (appData.debugMode) {
8531 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8532 redraw, init, gameMode);
8534 pausing = pauseExamInvalid = FALSE;
8535 startedFromSetupPosition = blackPlaysFirst = FALSE;
8537 whiteFlag = blackFlag = FALSE;
8538 userOfferedDraw = FALSE;
8539 hintRequested = bookRequested = FALSE;
8540 first.maybeThinking = FALSE;
8541 second.maybeThinking = FALSE;
8542 first.bookSuspend = FALSE; // [HGM] book
8543 second.bookSuspend = FALSE;
8544 thinkOutput[0] = NULLCHAR;
8545 lastHint[0] = NULLCHAR;
8546 ClearGameInfo(&gameInfo);
8547 gameInfo.variant = StringToVariant(appData.variant);
8548 ics_user_moved = ics_clock_paused = FALSE;
8549 ics_getting_history = H_FALSE;
8551 white_holding[0] = black_holding[0] = NULLCHAR;
8552 ClearProgramStats();
8553 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8557 flipView = appData.flipView;
8558 ClearPremoveHighlights();
8560 alarmSounded = FALSE;
8562 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8563 if(appData.serverMovesName != NULL) {
8564 /* [HGM] prepare to make moves file for broadcasting */
8565 clock_t t = clock();
8566 if(serverMoves != NULL) fclose(serverMoves);
8567 serverMoves = fopen(appData.serverMovesName, "r");
8568 if(serverMoves != NULL) {
8569 fclose(serverMoves);
8570 /* delay 15 sec before overwriting, so all clients can see end */
8571 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8573 serverMoves = fopen(appData.serverMovesName, "w");
8577 gameMode = BeginningOfGame;
8579 if(appData.icsActive) gameInfo.variant = VariantNormal;
8580 currentMove = forwardMostMove = backwardMostMove = 0;
8581 InitPosition(redraw);
8582 for (i = 0; i < MAX_MOVES; i++) {
8583 if (commentList[i] != NULL) {
8584 free(commentList[i]);
8585 commentList[i] = NULL;
8589 timeRemaining[0][0] = whiteTimeRemaining;
8590 timeRemaining[1][0] = blackTimeRemaining;
8591 if (first.pr == NULL) {
8592 StartChessProgram(&first);
8595 InitChessProgram(&first, startedFromSetupPosition);
8598 DisplayMessage("", "");
8599 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8600 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8607 if (!AutoPlayOneMove())
8609 if (matchMode || appData.timeDelay == 0)
8611 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8613 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8622 int fromX, fromY, toX, toY;
8624 if (appData.debugMode) {
8625 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8628 if (gameMode != PlayFromGameFile)
8631 if (currentMove >= forwardMostMove) {
8632 gameMode = EditGame;
8635 /* [AS] Clear current move marker at the end of a game */
8636 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8641 toX = moveList[currentMove][2] - AAA;
8642 toY = moveList[currentMove][3] - ONE;
8644 if (moveList[currentMove][1] == '@') {
8645 if (appData.highlightLastMove) {
8646 SetHighlights(-1, -1, toX, toY);
8649 fromX = moveList[currentMove][0] - AAA;
8650 fromY = moveList[currentMove][1] - ONE;
8652 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8654 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8656 if (appData.highlightLastMove) {
8657 SetHighlights(fromX, fromY, toX, toY);
8660 DisplayMove(currentMove);
8661 SendMoveToProgram(currentMove++, &first);
8662 DisplayBothClocks();
8663 DrawPosition(FALSE, boards[currentMove]);
8664 // [HGM] PV info: always display, routine tests if empty
8665 DisplayComment(currentMove - 1, commentList[currentMove]);
8671 LoadGameOneMove(readAhead)
8672 ChessMove readAhead;
8674 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8675 char promoChar = NULLCHAR;
8680 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8681 gameMode != AnalyzeMode && gameMode != Training) {
8686 yyboardindex = forwardMostMove;
8687 if (readAhead != (ChessMove)0) {
8688 moveType = readAhead;
8690 if (gameFileFP == NULL)
8692 moveType = (ChessMove) yylex();
8698 if (appData.debugMode)
8699 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8701 if (*p == '{' || *p == '[' || *p == '(') {
8702 p[strlen(p) - 1] = NULLCHAR;
8706 /* append the comment but don't display it */
8707 while (*p == '\n') p++;
8708 AppendComment(currentMove, p);
8711 case WhiteCapturesEnPassant:
8712 case BlackCapturesEnPassant:
8713 case WhitePromotionChancellor:
8714 case BlackPromotionChancellor:
8715 case WhitePromotionArchbishop:
8716 case BlackPromotionArchbishop:
8717 case WhitePromotionCentaur:
8718 case BlackPromotionCentaur:
8719 case WhitePromotionQueen:
8720 case BlackPromotionQueen:
8721 case WhitePromotionRook:
8722 case BlackPromotionRook:
8723 case WhitePromotionBishop:
8724 case BlackPromotionBishop:
8725 case WhitePromotionKnight:
8726 case BlackPromotionKnight:
8727 case WhitePromotionKing:
8728 case BlackPromotionKing:
8730 case WhiteKingSideCastle:
8731 case WhiteQueenSideCastle:
8732 case BlackKingSideCastle:
8733 case BlackQueenSideCastle:
8734 case WhiteKingSideCastleWild:
8735 case WhiteQueenSideCastleWild:
8736 case BlackKingSideCastleWild:
8737 case BlackQueenSideCastleWild:
8739 case WhiteHSideCastleFR:
8740 case WhiteASideCastleFR:
8741 case BlackHSideCastleFR:
8742 case BlackASideCastleFR:
8744 if (appData.debugMode)
8745 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8746 fromX = currentMoveString[0] - AAA;
8747 fromY = currentMoveString[1] - ONE;
8748 toX = currentMoveString[2] - AAA;
8749 toY = currentMoveString[3] - ONE;
8750 promoChar = currentMoveString[4];
8755 if (appData.debugMode)
8756 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8757 fromX = moveType == WhiteDrop ?
8758 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8759 (int) CharToPiece(ToLower(currentMoveString[0]));
8761 toX = currentMoveString[2] - AAA;
8762 toY = currentMoveString[3] - ONE;
8768 case GameUnfinished:
8769 if (appData.debugMode)
8770 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8771 p = strchr(yy_text, '{');
8772 if (p == NULL) p = strchr(yy_text, '(');
8775 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8777 q = strchr(p, *p == '{' ? '}' : ')');
8778 if (q != NULL) *q = NULLCHAR;
8781 GameEnds(moveType, p, GE_FILE);
8783 if (cmailMsgLoaded) {
8785 flipView = WhiteOnMove(currentMove);
8786 if (moveType == GameUnfinished) flipView = !flipView;
8787 if (appData.debugMode)
8788 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8792 case (ChessMove) 0: /* end of file */
8793 if (appData.debugMode)
8794 fprintf(debugFP, "Parser hit end of file\n");
8795 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8796 EP_UNKNOWN, castlingRights[currentMove]) ) {
8802 if (WhiteOnMove(currentMove)) {
8803 GameEnds(BlackWins, "Black mates", GE_FILE);
8805 GameEnds(WhiteWins, "White mates", GE_FILE);
8809 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8816 if (lastLoadGameStart == GNUChessGame) {
8817 /* GNUChessGames have numbers, but they aren't move numbers */
8818 if (appData.debugMode)
8819 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8820 yy_text, (int) moveType);
8821 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8823 /* else fall thru */
8828 /* Reached start of next game in file */
8829 if (appData.debugMode)
8830 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8831 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8832 EP_UNKNOWN, castlingRights[currentMove]) ) {
8838 if (WhiteOnMove(currentMove)) {
8839 GameEnds(BlackWins, "Black mates", GE_FILE);
8841 GameEnds(WhiteWins, "White mates", GE_FILE);
8845 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8851 case PositionDiagram: /* should not happen; ignore */
8852 case ElapsedTime: /* ignore */
8853 case NAG: /* ignore */
8854 if (appData.debugMode)
8855 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8856 yy_text, (int) moveType);
8857 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8860 if (appData.testLegality) {
8861 if (appData.debugMode)
8862 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8863 sprintf(move, _("Illegal move: %d.%s%s"),
8864 (forwardMostMove / 2) + 1,
8865 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8866 DisplayError(move, 0);
8869 if (appData.debugMode)
8870 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8871 yy_text, currentMoveString);
8872 fromX = currentMoveString[0] - AAA;
8873 fromY = currentMoveString[1] - ONE;
8874 toX = currentMoveString[2] - AAA;
8875 toY = currentMoveString[3] - ONE;
8876 promoChar = currentMoveString[4];
8881 if (appData.debugMode)
8882 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8883 sprintf(move, _("Ambiguous move: %d.%s%s"),
8884 (forwardMostMove / 2) + 1,
8885 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8886 DisplayError(move, 0);
8891 case ImpossibleMove:
8892 if (appData.debugMode)
8893 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8894 sprintf(move, _("Illegal move: %d.%s%s"),
8895 (forwardMostMove / 2) + 1,
8896 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8897 DisplayError(move, 0);
8903 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8904 DrawPosition(FALSE, boards[currentMove]);
8905 DisplayBothClocks();
8906 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8907 DisplayComment(currentMove - 1, commentList[currentMove]);
8909 (void) StopLoadGameTimer();
8911 cmailOldMove = forwardMostMove;
8914 /* currentMoveString is set as a side-effect of yylex */
8915 strcat(currentMoveString, "\n");
8916 strcpy(moveList[forwardMostMove], currentMoveString);
8918 thinkOutput[0] = NULLCHAR;
8919 MakeMove(fromX, fromY, toX, toY, promoChar);
8920 currentMove = forwardMostMove;
8925 /* Load the nth game from the given file */
8927 LoadGameFromFile(filename, n, title, useList)
8931 /*Boolean*/ int useList;
8936 if (strcmp(filename, "-") == 0) {
8940 f = fopen(filename, "rb");
8942 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8943 DisplayError(buf, errno);
8947 if (fseek(f, 0, 0) == -1) {
8948 /* f is not seekable; probably a pipe */
8951 if (useList && n == 0) {
8952 int error = GameListBuild(f);
8954 DisplayError(_("Cannot build game list"), error);
8955 } else if (!ListEmpty(&gameList) &&
8956 ((ListGame *) gameList.tailPred)->number > 1) {
8957 GameListPopUp(f, title);
8964 return LoadGame(f, n, title, FALSE);
8969 MakeRegisteredMove()
8971 int fromX, fromY, toX, toY;
8973 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8974 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8977 if (appData.debugMode)
8978 fprintf(debugFP, "Restoring %s for game %d\n",
8979 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8981 thinkOutput[0] = NULLCHAR;
8982 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8983 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8984 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8985 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8986 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8987 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8988 MakeMove(fromX, fromY, toX, toY, promoChar);
8989 ShowMove(fromX, fromY, toX, toY);
8991 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8992 EP_UNKNOWN, castlingRights[currentMove]) ) {
8999 if (WhiteOnMove(currentMove)) {
9000 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9002 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9007 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9014 if (WhiteOnMove(currentMove)) {
9015 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9017 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9022 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9033 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9035 CmailLoadGame(f, gameNumber, title, useList)
9043 if (gameNumber > nCmailGames) {
9044 DisplayError(_("No more games in this message"), 0);
9047 if (f == lastLoadGameFP) {
9048 int offset = gameNumber - lastLoadGameNumber;
9050 cmailMsg[0] = NULLCHAR;
9051 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9052 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9053 nCmailMovesRegistered--;
9055 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9056 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9057 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9060 if (! RegisterMove()) return FALSE;
9064 retVal = LoadGame(f, gameNumber, title, useList);
9066 /* Make move registered during previous look at this game, if any */
9067 MakeRegisteredMove();
9069 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9070 commentList[currentMove]
9071 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9072 DisplayComment(currentMove - 1, commentList[currentMove]);
9078 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9083 int gameNumber = lastLoadGameNumber + offset;
9084 if (lastLoadGameFP == NULL) {
9085 DisplayError(_("No game has been loaded yet"), 0);
9088 if (gameNumber <= 0) {
9089 DisplayError(_("Can't back up any further"), 0);
9092 if (cmailMsgLoaded) {
9093 return CmailLoadGame(lastLoadGameFP, gameNumber,
9094 lastLoadGameTitle, lastLoadGameUseList);
9096 return LoadGame(lastLoadGameFP, gameNumber,
9097 lastLoadGameTitle, lastLoadGameUseList);
9103 /* Load the nth game from open file f */
9105 LoadGame(f, gameNumber, title, useList)
9113 int gn = gameNumber;
9114 ListGame *lg = NULL;
9117 GameMode oldGameMode;
9118 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9120 if (appData.debugMode)
9121 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9123 if (gameMode == Training )
9124 SetTrainingModeOff();
9126 oldGameMode = gameMode;
9127 if (gameMode != BeginningOfGame) {
9132 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9133 fclose(lastLoadGameFP);
9137 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9140 fseek(f, lg->offset, 0);
9141 GameListHighlight(gameNumber);
9145 DisplayError(_("Game number out of range"), 0);
9150 if (fseek(f, 0, 0) == -1) {
9151 if (f == lastLoadGameFP ?
9152 gameNumber == lastLoadGameNumber + 1 :
9156 DisplayError(_("Can't seek on game file"), 0);
9162 lastLoadGameNumber = gameNumber;
9163 strcpy(lastLoadGameTitle, title);
9164 lastLoadGameUseList = useList;
9168 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9169 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9170 lg->gameInfo.black);
9172 } else if (*title != NULLCHAR) {
9173 if (gameNumber > 1) {
9174 sprintf(buf, "%s %d", title, gameNumber);
9177 DisplayTitle(title);
9181 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9182 gameMode = PlayFromGameFile;
9186 currentMove = forwardMostMove = backwardMostMove = 0;
9187 CopyBoard(boards[0], initialPosition);
9191 * Skip the first gn-1 games in the file.
9192 * Also skip over anything that precedes an identifiable
9193 * start of game marker, to avoid being confused by
9194 * garbage at the start of the file. Currently
9195 * recognized start of game markers are the move number "1",
9196 * the pattern "gnuchess .* game", the pattern
9197 * "^[#;%] [^ ]* game file", and a PGN tag block.
9198 * A game that starts with one of the latter two patterns
9199 * will also have a move number 1, possibly
9200 * following a position diagram.
9201 * 5-4-02: Let's try being more lenient and allowing a game to
9202 * start with an unnumbered move. Does that break anything?
9204 cm = lastLoadGameStart = (ChessMove) 0;
9206 yyboardindex = forwardMostMove;
9207 cm = (ChessMove) yylex();
9210 if (cmailMsgLoaded) {
9211 nCmailGames = CMAIL_MAX_GAMES - gn;
9214 DisplayError(_("Game not found in file"), 0);
9221 lastLoadGameStart = cm;
9225 switch (lastLoadGameStart) {
9232 gn--; /* count this game */
9233 lastLoadGameStart = cm;
9242 switch (lastLoadGameStart) {
9247 gn--; /* count this game */
9248 lastLoadGameStart = cm;
9251 lastLoadGameStart = cm; /* game counted already */
9259 yyboardindex = forwardMostMove;
9260 cm = (ChessMove) yylex();
9261 } while (cm == PGNTag || cm == Comment);
9268 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9269 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9270 != CMAIL_OLD_RESULT) {
9272 cmailResult[ CMAIL_MAX_GAMES
9273 - gn - 1] = CMAIL_OLD_RESULT;
9279 /* Only a NormalMove can be at the start of a game
9280 * without a position diagram. */
9281 if (lastLoadGameStart == (ChessMove) 0) {
9283 lastLoadGameStart = MoveNumberOne;
9292 if (appData.debugMode)
9293 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9295 if (cm == XBoardGame) {
9296 /* Skip any header junk before position diagram and/or move 1 */
9298 yyboardindex = forwardMostMove;
9299 cm = (ChessMove) yylex();
9301 if (cm == (ChessMove) 0 ||
9302 cm == GNUChessGame || cm == XBoardGame) {
9303 /* Empty game; pretend end-of-file and handle later */
9308 if (cm == MoveNumberOne || cm == PositionDiagram ||
9309 cm == PGNTag || cm == Comment)
9312 } else if (cm == GNUChessGame) {
9313 if (gameInfo.event != NULL) {
9314 free(gameInfo.event);
9316 gameInfo.event = StrSave(yy_text);
9319 startedFromSetupPosition = FALSE;
9320 while (cm == PGNTag) {
9321 if (appData.debugMode)
9322 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9323 err = ParsePGNTag(yy_text, &gameInfo);
9324 if (!err) numPGNTags++;
9326 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9327 if(gameInfo.variant != oldVariant) {
9328 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9330 oldVariant = gameInfo.variant;
9331 if (appData.debugMode)
9332 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9336 if (gameInfo.fen != NULL) {
9337 Board initial_position;
9338 startedFromSetupPosition = TRUE;
9339 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9341 DisplayError(_("Bad FEN position in file"), 0);
9344 CopyBoard(boards[0], initial_position);
9345 if (blackPlaysFirst) {
9346 currentMove = forwardMostMove = backwardMostMove = 1;
9347 CopyBoard(boards[1], initial_position);
9348 strcpy(moveList[0], "");
9349 strcpy(parseList[0], "");
9350 timeRemaining[0][1] = whiteTimeRemaining;
9351 timeRemaining[1][1] = blackTimeRemaining;
9352 if (commentList[0] != NULL) {
9353 commentList[1] = commentList[0];
9354 commentList[0] = NULL;
9357 currentMove = forwardMostMove = backwardMostMove = 0;
9359 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9361 initialRulePlies = FENrulePlies;
9362 epStatus[forwardMostMove] = FENepStatus;
9363 for( i=0; i< nrCastlingRights; i++ )
9364 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9366 yyboardindex = forwardMostMove;
9368 gameInfo.fen = NULL;
9371 yyboardindex = forwardMostMove;
9372 cm = (ChessMove) yylex();
9374 /* Handle comments interspersed among the tags */
9375 while (cm == Comment) {
9377 if (appData.debugMode)
9378 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9380 if (*p == '{' || *p == '[' || *p == '(') {
9381 p[strlen(p) - 1] = NULLCHAR;
9384 while (*p == '\n') p++;
9385 AppendComment(currentMove, p);
9386 yyboardindex = forwardMostMove;
9387 cm = (ChessMove) yylex();
9391 /* don't rely on existence of Event tag since if game was
9392 * pasted from clipboard the Event tag may not exist
9394 if (numPGNTags > 0){
9396 if (gameInfo.variant == VariantNormal) {
9397 gameInfo.variant = StringToVariant(gameInfo.event);
9400 if( appData.autoDisplayTags ) {
9401 tags = PGNTags(&gameInfo);
9402 TagsPopUp(tags, CmailMsg());
9407 /* Make something up, but don't display it now */
9412 if (cm == PositionDiagram) {
9415 Board initial_position;
9417 if (appData.debugMode)
9418 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9420 if (!startedFromSetupPosition) {
9422 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9423 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9433 initial_position[i][j++] = CharToPiece(*p);
9436 while (*p == ' ' || *p == '\t' ||
9437 *p == '\n' || *p == '\r') p++;
9439 if (strncmp(p, "black", strlen("black"))==0)
9440 blackPlaysFirst = TRUE;
9442 blackPlaysFirst = FALSE;
9443 startedFromSetupPosition = TRUE;
9445 CopyBoard(boards[0], initial_position);
9446 if (blackPlaysFirst) {
9447 currentMove = forwardMostMove = backwardMostMove = 1;
9448 CopyBoard(boards[1], initial_position);
9449 strcpy(moveList[0], "");
9450 strcpy(parseList[0], "");
9451 timeRemaining[0][1] = whiteTimeRemaining;
9452 timeRemaining[1][1] = blackTimeRemaining;
9453 if (commentList[0] != NULL) {
9454 commentList[1] = commentList[0];
9455 commentList[0] = NULL;
9458 currentMove = forwardMostMove = backwardMostMove = 0;
9461 yyboardindex = forwardMostMove;
9462 cm = (ChessMove) yylex();
9465 if (first.pr == NoProc) {
9466 StartChessProgram(&first);
9468 InitChessProgram(&first, FALSE);
9469 SendToProgram("force\n", &first);
9470 if (startedFromSetupPosition) {
9471 SendBoard(&first, forwardMostMove);
9472 if (appData.debugMode) {
9473 fprintf(debugFP, "Load Game\n");
9475 DisplayBothClocks();
9478 /* [HGM] server: flag to write setup moves in broadcast file as one */
9479 loadFlag = appData.suppressLoadMoves;
9481 while (cm == Comment) {
9483 if (appData.debugMode)
9484 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9486 if (*p == '{' || *p == '[' || *p == '(') {
9487 p[strlen(p) - 1] = NULLCHAR;
9490 while (*p == '\n') p++;
9491 AppendComment(currentMove, p);
9492 yyboardindex = forwardMostMove;
9493 cm = (ChessMove) yylex();
9496 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9497 cm == WhiteWins || cm == BlackWins ||
9498 cm == GameIsDrawn || cm == GameUnfinished) {
9499 DisplayMessage("", _("No moves in game"));
9500 if (cmailMsgLoaded) {
9501 if (appData.debugMode)
9502 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9506 DrawPosition(FALSE, boards[currentMove]);
9507 DisplayBothClocks();
9508 gameMode = EditGame;
9515 // [HGM] PV info: routine tests if comment empty
9516 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9517 DisplayComment(currentMove - 1, commentList[currentMove]);
9519 if (!matchMode && appData.timeDelay != 0)
9520 DrawPosition(FALSE, boards[currentMove]);
9522 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9523 programStats.ok_to_send = 1;
9526 /* if the first token after the PGN tags is a move
9527 * and not move number 1, retrieve it from the parser
9529 if (cm != MoveNumberOne)
9530 LoadGameOneMove(cm);
9532 /* load the remaining moves from the file */
9533 while (LoadGameOneMove((ChessMove)0)) {
9534 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9535 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9538 /* rewind to the start of the game */
9539 currentMove = backwardMostMove;
9541 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9543 if (oldGameMode == AnalyzeFile ||
9544 oldGameMode == AnalyzeMode) {
9548 if (matchMode || appData.timeDelay == 0) {
9550 gameMode = EditGame;
9552 } else if (appData.timeDelay > 0) {
9556 if (appData.debugMode)
9557 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9559 loadFlag = 0; /* [HGM] true game starts */
9563 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9565 ReloadPosition(offset)
9568 int positionNumber = lastLoadPositionNumber + offset;
9569 if (lastLoadPositionFP == NULL) {
9570 DisplayError(_("No position has been loaded yet"), 0);
9573 if (positionNumber <= 0) {
9574 DisplayError(_("Can't back up any further"), 0);
9577 return LoadPosition(lastLoadPositionFP, positionNumber,
9578 lastLoadPositionTitle);
9581 /* Load the nth position from the given file */
9583 LoadPositionFromFile(filename, n, title)
9591 if (strcmp(filename, "-") == 0) {
9592 return LoadPosition(stdin, n, "stdin");
9594 f = fopen(filename, "rb");
9596 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9597 DisplayError(buf, errno);
9600 return LoadPosition(f, n, title);
9605 /* Load the nth position from the given open file, and close it */
9607 LoadPosition(f, positionNumber, title)
9612 char *p, line[MSG_SIZ];
9613 Board initial_position;
9614 int i, j, fenMode, pn;
9616 if (gameMode == Training )
9617 SetTrainingModeOff();
9619 if (gameMode != BeginningOfGame) {
9622 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9623 fclose(lastLoadPositionFP);
9625 if (positionNumber == 0) positionNumber = 1;
9626 lastLoadPositionFP = f;
9627 lastLoadPositionNumber = positionNumber;
9628 strcpy(lastLoadPositionTitle, title);
9629 if (first.pr == NoProc) {
9630 StartChessProgram(&first);
9631 InitChessProgram(&first, FALSE);
9633 pn = positionNumber;
9634 if (positionNumber < 0) {
9635 /* Negative position number means to seek to that byte offset */
9636 if (fseek(f, -positionNumber, 0) == -1) {
9637 DisplayError(_("Can't seek on position file"), 0);
9642 if (fseek(f, 0, 0) == -1) {
9643 if (f == lastLoadPositionFP ?
9644 positionNumber == lastLoadPositionNumber + 1 :
9645 positionNumber == 1) {
9648 DisplayError(_("Can't seek on position file"), 0);
9653 /* See if this file is FEN or old-style xboard */
9654 if (fgets(line, MSG_SIZ, f) == NULL) {
9655 DisplayError(_("Position not found in file"), 0);
9658 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9659 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9662 if (fenMode || line[0] == '#') pn--;
9664 /* skip positions before number pn */
9665 if (fgets(line, MSG_SIZ, f) == NULL) {
9667 DisplayError(_("Position not found in file"), 0);
9670 if (fenMode || line[0] == '#') pn--;
9675 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9676 DisplayError(_("Bad FEN position in file"), 0);
9680 (void) fgets(line, MSG_SIZ, f);
9681 (void) fgets(line, MSG_SIZ, f);
9683 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9684 (void) fgets(line, MSG_SIZ, f);
9685 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9688 initial_position[i][j++] = CharToPiece(*p);
9692 blackPlaysFirst = FALSE;
9694 (void) fgets(line, MSG_SIZ, f);
9695 if (strncmp(line, "black", strlen("black"))==0)
9696 blackPlaysFirst = TRUE;
9699 startedFromSetupPosition = TRUE;
9701 SendToProgram("force\n", &first);
9702 CopyBoard(boards[0], initial_position);
9703 if (blackPlaysFirst) {
9704 currentMove = forwardMostMove = backwardMostMove = 1;
9705 strcpy(moveList[0], "");
9706 strcpy(parseList[0], "");
9707 CopyBoard(boards[1], initial_position);
9708 DisplayMessage("", _("Black to play"));
9710 currentMove = forwardMostMove = backwardMostMove = 0;
9711 DisplayMessage("", _("White to play"));
9713 /* [HGM] copy FEN attributes as well */
9715 initialRulePlies = FENrulePlies;
9716 epStatus[forwardMostMove] = FENepStatus;
9717 for( i=0; i< nrCastlingRights; i++ )
9718 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9720 SendBoard(&first, forwardMostMove);
9721 if (appData.debugMode) {
9723 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9724 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9725 fprintf(debugFP, "Load Position\n");
9728 if (positionNumber > 1) {
9729 sprintf(line, "%s %d", title, positionNumber);
9732 DisplayTitle(title);
9734 gameMode = EditGame;
9737 timeRemaining[0][1] = whiteTimeRemaining;
9738 timeRemaining[1][1] = blackTimeRemaining;
9739 DrawPosition(FALSE, boards[currentMove]);
9746 CopyPlayerNameIntoFileName(dest, src)
9749 while (*src != NULLCHAR && *src != ',') {
9754 *(*dest)++ = *src++;
9759 char *DefaultFileName(ext)
9762 static char def[MSG_SIZ];
9765 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9767 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9769 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9778 /* Save the current game to the given file */
9780 SaveGameToFile(filename, append)
9787 if (strcmp(filename, "-") == 0) {
9788 return SaveGame(stdout, 0, NULL);
9790 f = fopen(filename, append ? "a" : "w");
9792 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9793 DisplayError(buf, errno);
9796 return SaveGame(f, 0, NULL);
9805 static char buf[MSG_SIZ];
9808 p = strchr(str, ' ');
9809 if (p == NULL) return str;
9810 strncpy(buf, str, p - str);
9811 buf[p - str] = NULLCHAR;
9815 #define PGN_MAX_LINE 75
9817 #define PGN_SIDE_WHITE 0
9818 #define PGN_SIDE_BLACK 1
9821 static int FindFirstMoveOutOfBook( int side )
9825 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9826 int index = backwardMostMove;
9827 int has_book_hit = 0;
9829 if( (index % 2) != side ) {
9833 while( index < forwardMostMove ) {
9834 /* Check to see if engine is in book */
9835 int depth = pvInfoList[index].depth;
9836 int score = pvInfoList[index].score;
9842 else if( score == 0 && depth == 63 ) {
9843 in_book = 1; /* Zappa */
9845 else if( score == 2 && depth == 99 ) {
9846 in_book = 1; /* Abrok */
9849 has_book_hit += in_book;
9865 void GetOutOfBookInfo( char * buf )
9869 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9871 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9872 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9876 if( oob[0] >= 0 || oob[1] >= 0 ) {
9877 for( i=0; i<2; i++ ) {
9881 if( i > 0 && oob[0] >= 0 ) {
9885 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9886 sprintf( buf+strlen(buf), "%s%.2f",
9887 pvInfoList[idx].score >= 0 ? "+" : "",
9888 pvInfoList[idx].score / 100.0 );
9894 /* Save game in PGN style and close the file */
9899 int i, offset, linelen, newblock;
9903 int movelen, numlen, blank;
9904 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9906 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9908 tm = time((time_t *) NULL);
9910 PrintPGNTags(f, &gameInfo);
9912 if (backwardMostMove > 0 || startedFromSetupPosition) {
9913 char *fen = PositionToFEN(backwardMostMove, NULL);
9914 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9915 fprintf(f, "\n{--------------\n");
9916 PrintPosition(f, backwardMostMove);
9917 fprintf(f, "--------------}\n");
9921 /* [AS] Out of book annotation */
9922 if( appData.saveOutOfBookInfo ) {
9925 GetOutOfBookInfo( buf );
9927 if( buf[0] != '\0' ) {
9928 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9935 i = backwardMostMove;
9939 while (i < forwardMostMove) {
9940 /* Print comments preceding this move */
9941 if (commentList[i] != NULL) {
9942 if (linelen > 0) fprintf(f, "\n");
9943 fprintf(f, "{\n%s}\n", commentList[i]);
9948 /* Format move number */
9950 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9953 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9955 numtext[0] = NULLCHAR;
9958 numlen = strlen(numtext);
9961 /* Print move number */
9962 blank = linelen > 0 && numlen > 0;
9963 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9972 fprintf(f, "%s", numtext);
9976 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9977 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9980 blank = linelen > 0 && movelen > 0;
9981 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9990 fprintf(f, "%s", move_buffer);
9993 /* [AS] Add PV info if present */
9994 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9995 /* [HGM] add time */
9996 char buf[MSG_SIZ]; int seconds;
9998 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10000 if( seconds <= 0) buf[0] = 0; else
10001 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10002 seconds = (seconds + 4)/10; // round to full seconds
10003 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10004 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10007 sprintf( move_buffer, "{%s%.2f/%d%s}",
10008 pvInfoList[i].score >= 0 ? "+" : "",
10009 pvInfoList[i].score / 100.0,
10010 pvInfoList[i].depth,
10013 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10015 /* Print score/depth */
10016 blank = linelen > 0 && movelen > 0;
10017 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10026 fprintf(f, "%s", move_buffer);
10027 linelen += movelen;
10033 /* Start a new line */
10034 if (linelen > 0) fprintf(f, "\n");
10036 /* Print comments after last move */
10037 if (commentList[i] != NULL) {
10038 fprintf(f, "{\n%s}\n", commentList[i]);
10042 if (gameInfo.resultDetails != NULL &&
10043 gameInfo.resultDetails[0] != NULLCHAR) {
10044 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10045 PGNResult(gameInfo.result));
10047 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10051 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10055 /* Save game in old style and close the file */
10057 SaveGameOldStyle(f)
10063 tm = time((time_t *) NULL);
10065 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10068 if (backwardMostMove > 0 || startedFromSetupPosition) {
10069 fprintf(f, "\n[--------------\n");
10070 PrintPosition(f, backwardMostMove);
10071 fprintf(f, "--------------]\n");
10076 i = backwardMostMove;
10077 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10079 while (i < forwardMostMove) {
10080 if (commentList[i] != NULL) {
10081 fprintf(f, "[%s]\n", commentList[i]);
10084 if ((i % 2) == 1) {
10085 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10088 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10090 if (commentList[i] != NULL) {
10094 if (i >= forwardMostMove) {
10098 fprintf(f, "%s\n", parseList[i]);
10103 if (commentList[i] != NULL) {
10104 fprintf(f, "[%s]\n", commentList[i]);
10107 /* This isn't really the old style, but it's close enough */
10108 if (gameInfo.resultDetails != NULL &&
10109 gameInfo.resultDetails[0] != NULLCHAR) {
10110 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10111 gameInfo.resultDetails);
10113 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10120 /* Save the current game to open file f and close the file */
10122 SaveGame(f, dummy, dummy2)
10127 if (gameMode == EditPosition) EditPositionDone(TRUE);
10128 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10129 if (appData.oldSaveStyle)
10130 return SaveGameOldStyle(f);
10132 return SaveGamePGN(f);
10135 /* Save the current position to the given file */
10137 SavePositionToFile(filename)
10143 if (strcmp(filename, "-") == 0) {
10144 return SavePosition(stdout, 0, NULL);
10146 f = fopen(filename, "a");
10148 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10149 DisplayError(buf, errno);
10152 SavePosition(f, 0, NULL);
10158 /* Save the current position to the given open file and close the file */
10160 SavePosition(f, dummy, dummy2)
10168 if (gameMode == EditPosition) EditPositionDone(TRUE);
10169 if (appData.oldSaveStyle) {
10170 tm = time((time_t *) NULL);
10172 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10174 fprintf(f, "[--------------\n");
10175 PrintPosition(f, currentMove);
10176 fprintf(f, "--------------]\n");
10178 fen = PositionToFEN(currentMove, NULL);
10179 fprintf(f, "%s\n", fen);
10187 ReloadCmailMsgEvent(unregister)
10191 static char *inFilename = NULL;
10192 static char *outFilename;
10194 struct stat inbuf, outbuf;
10197 /* Any registered moves are unregistered if unregister is set, */
10198 /* i.e. invoked by the signal handler */
10200 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10201 cmailMoveRegistered[i] = FALSE;
10202 if (cmailCommentList[i] != NULL) {
10203 free(cmailCommentList[i]);
10204 cmailCommentList[i] = NULL;
10207 nCmailMovesRegistered = 0;
10210 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10211 cmailResult[i] = CMAIL_NOT_RESULT;
10215 if (inFilename == NULL) {
10216 /* Because the filenames are static they only get malloced once */
10217 /* and they never get freed */
10218 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10219 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10221 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10222 sprintf(outFilename, "%s.out", appData.cmailGameName);
10225 status = stat(outFilename, &outbuf);
10227 cmailMailedMove = FALSE;
10229 status = stat(inFilename, &inbuf);
10230 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10233 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10234 counts the games, notes how each one terminated, etc.
10236 It would be nice to remove this kludge and instead gather all
10237 the information while building the game list. (And to keep it
10238 in the game list nodes instead of having a bunch of fixed-size
10239 parallel arrays.) Note this will require getting each game's
10240 termination from the PGN tags, as the game list builder does
10241 not process the game moves. --mann
10243 cmailMsgLoaded = TRUE;
10244 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10246 /* Load first game in the file or popup game menu */
10247 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10249 #endif /* !WIN32 */
10257 char string[MSG_SIZ];
10259 if ( cmailMailedMove
10260 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10261 return TRUE; /* Allow free viewing */
10264 /* Unregister move to ensure that we don't leave RegisterMove */
10265 /* with the move registered when the conditions for registering no */
10267 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10268 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10269 nCmailMovesRegistered --;
10271 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10273 free(cmailCommentList[lastLoadGameNumber - 1]);
10274 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10278 if (cmailOldMove == -1) {
10279 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10283 if (currentMove > cmailOldMove + 1) {
10284 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10288 if (currentMove < cmailOldMove) {
10289 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10293 if (forwardMostMove > currentMove) {
10294 /* Silently truncate extra moves */
10298 if ( (currentMove == cmailOldMove + 1)
10299 || ( (currentMove == cmailOldMove)
10300 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10301 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10302 if (gameInfo.result != GameUnfinished) {
10303 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10306 if (commentList[currentMove] != NULL) {
10307 cmailCommentList[lastLoadGameNumber - 1]
10308 = StrSave(commentList[currentMove]);
10310 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10312 if (appData.debugMode)
10313 fprintf(debugFP, "Saving %s for game %d\n",
10314 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10317 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10319 f = fopen(string, "w");
10320 if (appData.oldSaveStyle) {
10321 SaveGameOldStyle(f); /* also closes the file */
10323 sprintf(string, "%s.pos.out", appData.cmailGameName);
10324 f = fopen(string, "w");
10325 SavePosition(f, 0, NULL); /* also closes the file */
10327 fprintf(f, "{--------------\n");
10328 PrintPosition(f, currentMove);
10329 fprintf(f, "--------------}\n\n");
10331 SaveGame(f, 0, NULL); /* also closes the file*/
10334 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10335 nCmailMovesRegistered ++;
10336 } else if (nCmailGames == 1) {
10337 DisplayError(_("You have not made a move yet"), 0);
10348 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10349 FILE *commandOutput;
10350 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10351 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10357 if (! cmailMsgLoaded) {
10358 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10362 if (nCmailGames == nCmailResults) {
10363 DisplayError(_("No unfinished games"), 0);
10367 #if CMAIL_PROHIBIT_REMAIL
10368 if (cmailMailedMove) {
10369 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);
10370 DisplayError(msg, 0);
10375 if (! (cmailMailedMove || RegisterMove())) return;
10377 if ( cmailMailedMove
10378 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10379 sprintf(string, partCommandString,
10380 appData.debugMode ? " -v" : "", appData.cmailGameName);
10381 commandOutput = popen(string, "r");
10383 if (commandOutput == NULL) {
10384 DisplayError(_("Failed to invoke cmail"), 0);
10386 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10387 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10389 if (nBuffers > 1) {
10390 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10391 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10392 nBytes = MSG_SIZ - 1;
10394 (void) memcpy(msg, buffer, nBytes);
10396 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10398 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10399 cmailMailedMove = TRUE; /* Prevent >1 moves */
10402 for (i = 0; i < nCmailGames; i ++) {
10403 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10408 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10410 sprintf(buffer, "%s/%s.%s.archive",
10412 appData.cmailGameName,
10414 LoadGameFromFile(buffer, 1, buffer, FALSE);
10415 cmailMsgLoaded = FALSE;
10419 DisplayInformation(msg);
10420 pclose(commandOutput);
10423 if ((*cmailMsg) != '\0') {
10424 DisplayInformation(cmailMsg);
10429 #endif /* !WIN32 */
10438 int prependComma = 0;
10440 char string[MSG_SIZ]; /* Space for game-list */
10443 if (!cmailMsgLoaded) return "";
10445 if (cmailMailedMove) {
10446 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10448 /* Create a list of games left */
10449 sprintf(string, "[");
10450 for (i = 0; i < nCmailGames; i ++) {
10451 if (! ( cmailMoveRegistered[i]
10452 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10453 if (prependComma) {
10454 sprintf(number, ",%d", i + 1);
10456 sprintf(number, "%d", i + 1);
10460 strcat(string, number);
10463 strcat(string, "]");
10465 if (nCmailMovesRegistered + nCmailResults == 0) {
10466 switch (nCmailGames) {
10469 _("Still need to make move for game\n"));
10474 _("Still need to make moves for both games\n"));
10479 _("Still need to make moves for all %d games\n"),
10484 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10487 _("Still need to make a move for game %s\n"),
10492 if (nCmailResults == nCmailGames) {
10493 sprintf(cmailMsg, _("No unfinished games\n"));
10495 sprintf(cmailMsg, _("Ready to send mail\n"));
10501 _("Still need to make moves for games %s\n"),
10513 if (gameMode == Training)
10514 SetTrainingModeOff();
10517 cmailMsgLoaded = FALSE;
10518 if (appData.icsActive) {
10519 SendToICS(ics_prefix);
10520 SendToICS("refresh\n");
10530 /* Give up on clean exit */
10534 /* Keep trying for clean exit */
10538 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10540 if (telnetISR != NULL) {
10541 RemoveInputSource(telnetISR);
10543 if (icsPR != NoProc) {
10544 DestroyChildProcess(icsPR, TRUE);
10547 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10548 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10550 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10551 /* make sure this other one finishes before killing it! */
10552 if(endingGame) { int count = 0;
10553 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10554 while(endingGame && count++ < 10) DoSleep(1);
10555 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10558 /* Kill off chess programs */
10559 if (first.pr != NoProc) {
10562 DoSleep( appData.delayBeforeQuit );
10563 SendToProgram("quit\n", &first);
10564 DoSleep( appData.delayAfterQuit );
10565 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10567 if (second.pr != NoProc) {
10568 DoSleep( appData.delayBeforeQuit );
10569 SendToProgram("quit\n", &second);
10570 DoSleep( appData.delayAfterQuit );
10571 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10573 if (first.isr != NULL) {
10574 RemoveInputSource(first.isr);
10576 if (second.isr != NULL) {
10577 RemoveInputSource(second.isr);
10580 ShutDownFrontEnd();
10587 if (appData.debugMode)
10588 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10592 if (gameMode == MachinePlaysWhite ||
10593 gameMode == MachinePlaysBlack) {
10596 DisplayBothClocks();
10598 if (gameMode == PlayFromGameFile) {
10599 if (appData.timeDelay >= 0)
10600 AutoPlayGameLoop();
10601 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10602 Reset(FALSE, TRUE);
10603 SendToICS(ics_prefix);
10604 SendToICS("refresh\n");
10605 } else if (currentMove < forwardMostMove) {
10606 ForwardInner(forwardMostMove);
10608 pauseExamInvalid = FALSE;
10610 switch (gameMode) {
10614 pauseExamForwardMostMove = forwardMostMove;
10615 pauseExamInvalid = FALSE;
10618 case IcsPlayingWhite:
10619 case IcsPlayingBlack:
10623 case PlayFromGameFile:
10624 (void) StopLoadGameTimer();
10628 case BeginningOfGame:
10629 if (appData.icsActive) return;
10630 /* else fall through */
10631 case MachinePlaysWhite:
10632 case MachinePlaysBlack:
10633 case TwoMachinesPlay:
10634 if (forwardMostMove == 0)
10635 return; /* don't pause if no one has moved */
10636 if ((gameMode == MachinePlaysWhite &&
10637 !WhiteOnMove(forwardMostMove)) ||
10638 (gameMode == MachinePlaysBlack &&
10639 WhiteOnMove(forwardMostMove))) {
10652 char title[MSG_SIZ];
10654 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10655 strcpy(title, _("Edit comment"));
10657 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10658 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10659 parseList[currentMove - 1]);
10662 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10669 char *tags = PGNTags(&gameInfo);
10670 EditTagsPopUp(tags);
10677 if (appData.noChessProgram || gameMode == AnalyzeMode)
10680 if (gameMode != AnalyzeFile) {
10681 if (!appData.icsEngineAnalyze) {
10683 if (gameMode != EditGame) return;
10685 ResurrectChessProgram();
10686 SendToProgram("analyze\n", &first);
10687 first.analyzing = TRUE;
10688 /*first.maybeThinking = TRUE;*/
10689 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10690 EngineOutputPopUp();
10692 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10697 StartAnalysisClock();
10698 GetTimeMark(&lastNodeCountTime);
10705 if (appData.noChessProgram || gameMode == AnalyzeFile)
10708 if (gameMode != AnalyzeMode) {
10710 if (gameMode != EditGame) return;
10711 ResurrectChessProgram();
10712 SendToProgram("analyze\n", &first);
10713 first.analyzing = TRUE;
10714 /*first.maybeThinking = TRUE;*/
10715 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10716 EngineOutputPopUp();
10718 gameMode = AnalyzeFile;
10723 StartAnalysisClock();
10724 GetTimeMark(&lastNodeCountTime);
10729 MachineWhiteEvent()
10732 char *bookHit = NULL;
10734 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10738 if (gameMode == PlayFromGameFile ||
10739 gameMode == TwoMachinesPlay ||
10740 gameMode == Training ||
10741 gameMode == AnalyzeMode ||
10742 gameMode == EndOfGame)
10745 if (gameMode == EditPosition)
10746 EditPositionDone(TRUE);
10748 if (!WhiteOnMove(currentMove)) {
10749 DisplayError(_("It is not White's turn"), 0);
10753 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10756 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10757 gameMode == AnalyzeFile)
10760 ResurrectChessProgram(); /* in case it isn't running */
10761 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10762 gameMode = MachinePlaysWhite;
10765 gameMode = MachinePlaysWhite;
10769 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10771 if (first.sendName) {
10772 sprintf(buf, "name %s\n", gameInfo.black);
10773 SendToProgram(buf, &first);
10775 if (first.sendTime) {
10776 if (first.useColors) {
10777 SendToProgram("black\n", &first); /*gnu kludge*/
10779 SendTimeRemaining(&first, TRUE);
10781 if (first.useColors) {
10782 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10784 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10785 SetMachineThinkingEnables();
10786 first.maybeThinking = TRUE;
10790 if (appData.autoFlipView && !flipView) {
10791 flipView = !flipView;
10792 DrawPosition(FALSE, NULL);
10793 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10796 if(bookHit) { // [HGM] book: simulate book reply
10797 static char bookMove[MSG_SIZ]; // a bit generous?
10799 programStats.nodes = programStats.depth = programStats.time =
10800 programStats.score = programStats.got_only_move = 0;
10801 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10803 strcpy(bookMove, "move ");
10804 strcat(bookMove, bookHit);
10805 HandleMachineMove(bookMove, &first);
10810 MachineBlackEvent()
10813 char *bookHit = NULL;
10815 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10819 if (gameMode == PlayFromGameFile ||
10820 gameMode == TwoMachinesPlay ||
10821 gameMode == Training ||
10822 gameMode == AnalyzeMode ||
10823 gameMode == EndOfGame)
10826 if (gameMode == EditPosition)
10827 EditPositionDone(TRUE);
10829 if (WhiteOnMove(currentMove)) {
10830 DisplayError(_("It is not Black's turn"), 0);
10834 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10837 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10838 gameMode == AnalyzeFile)
10841 ResurrectChessProgram(); /* in case it isn't running */
10842 gameMode = MachinePlaysBlack;
10846 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10848 if (first.sendName) {
10849 sprintf(buf, "name %s\n", gameInfo.white);
10850 SendToProgram(buf, &first);
10852 if (first.sendTime) {
10853 if (first.useColors) {
10854 SendToProgram("white\n", &first); /*gnu kludge*/
10856 SendTimeRemaining(&first, FALSE);
10858 if (first.useColors) {
10859 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10861 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10862 SetMachineThinkingEnables();
10863 first.maybeThinking = TRUE;
10866 if (appData.autoFlipView && flipView) {
10867 flipView = !flipView;
10868 DrawPosition(FALSE, NULL);
10869 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10871 if(bookHit) { // [HGM] book: simulate book reply
10872 static char bookMove[MSG_SIZ]; // a bit generous?
10874 programStats.nodes = programStats.depth = programStats.time =
10875 programStats.score = programStats.got_only_move = 0;
10876 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10878 strcpy(bookMove, "move ");
10879 strcat(bookMove, bookHit);
10880 HandleMachineMove(bookMove, &first);
10886 DisplayTwoMachinesTitle()
10889 if (appData.matchGames > 0) {
10890 if (first.twoMachinesColor[0] == 'w') {
10891 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10892 gameInfo.white, gameInfo.black,
10893 first.matchWins, second.matchWins,
10894 matchGame - 1 - (first.matchWins + second.matchWins));
10896 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10897 gameInfo.white, gameInfo.black,
10898 second.matchWins, first.matchWins,
10899 matchGame - 1 - (first.matchWins + second.matchWins));
10902 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10908 TwoMachinesEvent P((void))
10912 ChessProgramState *onmove;
10913 char *bookHit = NULL;
10915 if (appData.noChessProgram) return;
10917 switch (gameMode) {
10918 case TwoMachinesPlay:
10920 case MachinePlaysWhite:
10921 case MachinePlaysBlack:
10922 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10923 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10927 case BeginningOfGame:
10928 case PlayFromGameFile:
10931 if (gameMode != EditGame) return;
10934 EditPositionDone(TRUE);
10945 forwardMostMove = currentMove;
10946 ResurrectChessProgram(); /* in case first program isn't running */
10948 if (second.pr == NULL) {
10949 StartChessProgram(&second);
10950 if (second.protocolVersion == 1) {
10951 TwoMachinesEventIfReady();
10953 /* kludge: allow timeout for initial "feature" command */
10955 DisplayMessage("", _("Starting second chess program"));
10956 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10960 DisplayMessage("", "");
10961 InitChessProgram(&second, FALSE);
10962 SendToProgram("force\n", &second);
10963 if (startedFromSetupPosition) {
10964 SendBoard(&second, backwardMostMove);
10965 if (appData.debugMode) {
10966 fprintf(debugFP, "Two Machines\n");
10969 for (i = backwardMostMove; i < forwardMostMove; i++) {
10970 SendMoveToProgram(i, &second);
10973 gameMode = TwoMachinesPlay;
10977 DisplayTwoMachinesTitle();
10979 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10985 SendToProgram(first.computerString, &first);
10986 if (first.sendName) {
10987 sprintf(buf, "name %s\n", second.tidy);
10988 SendToProgram(buf, &first);
10990 SendToProgram(second.computerString, &second);
10991 if (second.sendName) {
10992 sprintf(buf, "name %s\n", first.tidy);
10993 SendToProgram(buf, &second);
10997 if (!first.sendTime || !second.sendTime) {
10998 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10999 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11001 if (onmove->sendTime) {
11002 if (onmove->useColors) {
11003 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11005 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11007 if (onmove->useColors) {
11008 SendToProgram(onmove->twoMachinesColor, onmove);
11010 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11011 // SendToProgram("go\n", onmove);
11012 onmove->maybeThinking = TRUE;
11013 SetMachineThinkingEnables();
11017 if(bookHit) { // [HGM] book: simulate book reply
11018 static char bookMove[MSG_SIZ]; // a bit generous?
11020 programStats.nodes = programStats.depth = programStats.time =
11021 programStats.score = programStats.got_only_move = 0;
11022 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11024 strcpy(bookMove, "move ");
11025 strcat(bookMove, bookHit);
11026 savedMessage = bookMove; // args for deferred call
11027 savedState = onmove;
11028 ScheduleDelayedEvent(DeferredBookMove, 1);
11035 if (gameMode == Training) {
11036 SetTrainingModeOff();
11037 gameMode = PlayFromGameFile;
11038 DisplayMessage("", _("Training mode off"));
11040 gameMode = Training;
11041 animateTraining = appData.animate;
11043 /* make sure we are not already at the end of the game */
11044 if (currentMove < forwardMostMove) {
11045 SetTrainingModeOn();
11046 DisplayMessage("", _("Training mode on"));
11048 gameMode = PlayFromGameFile;
11049 DisplayError(_("Already at end of game"), 0);
11058 if (!appData.icsActive) return;
11059 switch (gameMode) {
11060 case IcsPlayingWhite:
11061 case IcsPlayingBlack:
11064 case BeginningOfGame:
11072 EditPositionDone(TRUE);
11085 gameMode = IcsIdle;
11096 switch (gameMode) {
11098 SetTrainingModeOff();
11100 case MachinePlaysWhite:
11101 case MachinePlaysBlack:
11102 case BeginningOfGame:
11103 SendToProgram("force\n", &first);
11104 SetUserThinkingEnables();
11106 case PlayFromGameFile:
11107 (void) StopLoadGameTimer();
11108 if (gameFileFP != NULL) {
11113 EditPositionDone(TRUE);
11118 SendToProgram("force\n", &first);
11120 case TwoMachinesPlay:
11121 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11122 ResurrectChessProgram();
11123 SetUserThinkingEnables();
11126 ResurrectChessProgram();
11128 case IcsPlayingBlack:
11129 case IcsPlayingWhite:
11130 DisplayError(_("Warning: You are still playing a game"), 0);
11133 DisplayError(_("Warning: You are still observing a game"), 0);
11136 DisplayError(_("Warning: You are still examining a game"), 0);
11147 first.offeredDraw = second.offeredDraw = 0;
11149 if (gameMode == PlayFromGameFile) {
11150 whiteTimeRemaining = timeRemaining[0][currentMove];
11151 blackTimeRemaining = timeRemaining[1][currentMove];
11155 if (gameMode == MachinePlaysWhite ||
11156 gameMode == MachinePlaysBlack ||
11157 gameMode == TwoMachinesPlay ||
11158 gameMode == EndOfGame) {
11159 i = forwardMostMove;
11160 while (i > currentMove) {
11161 SendToProgram("undo\n", &first);
11164 whiteTimeRemaining = timeRemaining[0][currentMove];
11165 blackTimeRemaining = timeRemaining[1][currentMove];
11166 DisplayBothClocks();
11167 if (whiteFlag || blackFlag) {
11168 whiteFlag = blackFlag = 0;
11173 gameMode = EditGame;
11180 EditPositionEvent()
11182 if (gameMode == EditPosition) {
11188 if (gameMode != EditGame) return;
11190 gameMode = EditPosition;
11193 if (currentMove > 0)
11194 CopyBoard(boards[0], boards[currentMove]);
11196 blackPlaysFirst = !WhiteOnMove(currentMove);
11198 currentMove = forwardMostMove = backwardMostMove = 0;
11199 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11206 /* [DM] icsEngineAnalyze - possible call from other functions */
11207 if (appData.icsEngineAnalyze) {
11208 appData.icsEngineAnalyze = FALSE;
11210 DisplayMessage("",_("Close ICS engine analyze..."));
11212 if (first.analysisSupport && first.analyzing) {
11213 SendToProgram("exit\n", &first);
11214 first.analyzing = FALSE;
11216 thinkOutput[0] = NULLCHAR;
11220 EditPositionDone(Boolean fakeRights)
11222 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11224 startedFromSetupPosition = TRUE;
11225 InitChessProgram(&first, FALSE);
11227 { /* don't do this if we just pasted FEN */
11228 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11229 if(boards[0][0][BOARD_WIDTH>>1] == king)
11231 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11232 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11235 castlingRights[0][2] = -1;
11236 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11238 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11239 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11242 castlingRights[0][5] = -1;
11244 SendToProgram("force\n", &first);
11245 if (blackPlaysFirst) {
11246 strcpy(moveList[0], "");
11247 strcpy(parseList[0], "");
11248 currentMove = forwardMostMove = backwardMostMove = 1;
11249 CopyBoard(boards[1], boards[0]);
11250 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11252 epStatus[1] = epStatus[0];
11253 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11256 currentMove = forwardMostMove = backwardMostMove = 0;
11258 SendBoard(&first, forwardMostMove);
11259 if (appData.debugMode) {
11260 fprintf(debugFP, "EditPosDone\n");
11263 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11264 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11265 gameMode = EditGame;
11267 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11268 ClearHighlights(); /* [AS] */
11271 /* Pause for `ms' milliseconds */
11272 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11282 } while (SubtractTimeMarks(&m2, &m1) < ms);
11285 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11287 SendMultiLineToICS(buf)
11290 char temp[MSG_SIZ+1], *p;
11297 strncpy(temp, buf, len);
11302 if (*p == '\n' || *p == '\r')
11307 strcat(temp, "\n");
11309 SendToPlayer(temp, strlen(temp));
11313 SetWhiteToPlayEvent()
11315 if (gameMode == EditPosition) {
11316 blackPlaysFirst = FALSE;
11317 DisplayBothClocks(); /* works because currentMove is 0 */
11318 } else if (gameMode == IcsExamining) {
11319 SendToICS(ics_prefix);
11320 SendToICS("tomove white\n");
11325 SetBlackToPlayEvent()
11327 if (gameMode == EditPosition) {
11328 blackPlaysFirst = TRUE;
11329 currentMove = 1; /* kludge */
11330 DisplayBothClocks();
11332 } else if (gameMode == IcsExamining) {
11333 SendToICS(ics_prefix);
11334 SendToICS("tomove black\n");
11339 EditPositionMenuEvent(selection, x, y)
11340 ChessSquare selection;
11344 ChessSquare piece = boards[0][y][x];
11346 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11348 switch (selection) {
11350 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11351 SendToICS(ics_prefix);
11352 SendToICS("bsetup clear\n");
11353 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11354 SendToICS(ics_prefix);
11355 SendToICS("clearboard\n");
11357 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11358 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11359 for (y = 0; y < BOARD_HEIGHT; y++) {
11360 if (gameMode == IcsExamining) {
11361 if (boards[currentMove][y][x] != EmptySquare) {
11362 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11367 boards[0][y][x] = p;
11372 if (gameMode == EditPosition) {
11373 DrawPosition(FALSE, boards[0]);
11378 SetWhiteToPlayEvent();
11382 SetBlackToPlayEvent();
11386 if (gameMode == IcsExamining) {
11387 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11390 boards[0][y][x] = EmptySquare;
11391 DrawPosition(FALSE, boards[0]);
11396 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11397 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11398 selection = (ChessSquare) (PROMOTED piece);
11399 } else if(piece == EmptySquare) selection = WhiteSilver;
11400 else selection = (ChessSquare)((int)piece - 1);
11404 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11405 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11406 selection = (ChessSquare) (DEMOTED piece);
11407 } else if(piece == EmptySquare) selection = BlackSilver;
11408 else selection = (ChessSquare)((int)piece + 1);
11413 if(gameInfo.variant == VariantShatranj ||
11414 gameInfo.variant == VariantXiangqi ||
11415 gameInfo.variant == VariantCourier ||
11416 gameInfo.variant == VariantMakruk )
11417 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11422 if(gameInfo.variant == VariantXiangqi)
11423 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11424 if(gameInfo.variant == VariantKnightmate)
11425 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11428 if (gameMode == IcsExamining) {
11429 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11430 PieceToChar(selection), AAA + x, ONE + y);
11433 boards[0][y][x] = selection;
11434 DrawPosition(FALSE, boards[0]);
11442 DropMenuEvent(selection, x, y)
11443 ChessSquare selection;
11446 ChessMove moveType;
11448 switch (gameMode) {
11449 case IcsPlayingWhite:
11450 case MachinePlaysBlack:
11451 if (!WhiteOnMove(currentMove)) {
11452 DisplayMoveError(_("It is Black's turn"));
11455 moveType = WhiteDrop;
11457 case IcsPlayingBlack:
11458 case MachinePlaysWhite:
11459 if (WhiteOnMove(currentMove)) {
11460 DisplayMoveError(_("It is White's turn"));
11463 moveType = BlackDrop;
11466 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11472 if (moveType == BlackDrop && selection < BlackPawn) {
11473 selection = (ChessSquare) ((int) selection
11474 + (int) BlackPawn - (int) WhitePawn);
11476 if (boards[currentMove][y][x] != EmptySquare) {
11477 DisplayMoveError(_("That square is occupied"));
11481 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11487 /* Accept a pending offer of any kind from opponent */
11489 if (appData.icsActive) {
11490 SendToICS(ics_prefix);
11491 SendToICS("accept\n");
11492 } else if (cmailMsgLoaded) {
11493 if (currentMove == cmailOldMove &&
11494 commentList[cmailOldMove] != NULL &&
11495 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11496 "Black offers a draw" : "White offers a draw")) {
11498 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11499 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11501 DisplayError(_("There is no pending offer on this move"), 0);
11502 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11505 /* Not used for offers from chess program */
11512 /* Decline a pending offer of any kind from opponent */
11514 if (appData.icsActive) {
11515 SendToICS(ics_prefix);
11516 SendToICS("decline\n");
11517 } else if (cmailMsgLoaded) {
11518 if (currentMove == cmailOldMove &&
11519 commentList[cmailOldMove] != NULL &&
11520 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11521 "Black offers a draw" : "White offers a draw")) {
11523 AppendComment(cmailOldMove, "Draw declined");
11524 DisplayComment(cmailOldMove - 1, "Draw declined");
11527 DisplayError(_("There is no pending offer on this move"), 0);
11530 /* Not used for offers from chess program */
11537 /* Issue ICS rematch command */
11538 if (appData.icsActive) {
11539 SendToICS(ics_prefix);
11540 SendToICS("rematch\n");
11547 /* Call your opponent's flag (claim a win on time) */
11548 if (appData.icsActive) {
11549 SendToICS(ics_prefix);
11550 SendToICS("flag\n");
11552 switch (gameMode) {
11555 case MachinePlaysWhite:
11558 GameEnds(GameIsDrawn, "Both players ran out of time",
11561 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11563 DisplayError(_("Your opponent is not out of time"), 0);
11566 case MachinePlaysBlack:
11569 GameEnds(GameIsDrawn, "Both players ran out of time",
11572 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11574 DisplayError(_("Your opponent is not out of time"), 0);
11584 /* Offer draw or accept pending draw offer from opponent */
11586 if (appData.icsActive) {
11587 /* Note: tournament rules require draw offers to be
11588 made after you make your move but before you punch
11589 your clock. Currently ICS doesn't let you do that;
11590 instead, you immediately punch your clock after making
11591 a move, but you can offer a draw at any time. */
11593 SendToICS(ics_prefix);
11594 SendToICS("draw\n");
11595 } else if (cmailMsgLoaded) {
11596 if (currentMove == cmailOldMove &&
11597 commentList[cmailOldMove] != NULL &&
11598 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11599 "Black offers a draw" : "White offers a draw")) {
11600 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11601 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11602 } else if (currentMove == cmailOldMove + 1) {
11603 char *offer = WhiteOnMove(cmailOldMove) ?
11604 "White offers a draw" : "Black offers a draw";
11605 AppendComment(currentMove, offer);
11606 DisplayComment(currentMove - 1, offer);
11607 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11609 DisplayError(_("You must make your move before offering a draw"), 0);
11610 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11612 } else if (first.offeredDraw) {
11613 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11615 if (first.sendDrawOffers) {
11616 SendToProgram("draw\n", &first);
11617 userOfferedDraw = TRUE;
11625 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11627 if (appData.icsActive) {
11628 SendToICS(ics_prefix);
11629 SendToICS("adjourn\n");
11631 /* Currently GNU Chess doesn't offer or accept Adjourns */
11639 /* Offer Abort or accept pending Abort offer from opponent */
11641 if (appData.icsActive) {
11642 SendToICS(ics_prefix);
11643 SendToICS("abort\n");
11645 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11652 /* Resign. You can do this even if it's not your turn. */
11654 if (appData.icsActive) {
11655 SendToICS(ics_prefix);
11656 SendToICS("resign\n");
11658 switch (gameMode) {
11659 case MachinePlaysWhite:
11660 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11662 case MachinePlaysBlack:
11663 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11666 if (cmailMsgLoaded) {
11668 if (WhiteOnMove(cmailOldMove)) {
11669 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11671 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11673 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11684 StopObservingEvent()
11686 /* Stop observing current games */
11687 SendToICS(ics_prefix);
11688 SendToICS("unobserve\n");
11692 StopExaminingEvent()
11694 /* Stop observing current game */
11695 SendToICS(ics_prefix);
11696 SendToICS("unexamine\n");
11700 ForwardInner(target)
11705 if (appData.debugMode)
11706 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11707 target, currentMove, forwardMostMove);
11709 if (gameMode == EditPosition)
11712 if (gameMode == PlayFromGameFile && !pausing)
11715 if (gameMode == IcsExamining && pausing)
11716 limit = pauseExamForwardMostMove;
11718 limit = forwardMostMove;
11720 if (target > limit) target = limit;
11722 if (target > 0 && moveList[target - 1][0]) {
11723 int fromX, fromY, toX, toY;
11724 toX = moveList[target - 1][2] - AAA;
11725 toY = moveList[target - 1][3] - ONE;
11726 if (moveList[target - 1][1] == '@') {
11727 if (appData.highlightLastMove) {
11728 SetHighlights(-1, -1, toX, toY);
11731 fromX = moveList[target - 1][0] - AAA;
11732 fromY = moveList[target - 1][1] - ONE;
11733 if (target == currentMove + 1) {
11734 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11736 if (appData.highlightLastMove) {
11737 SetHighlights(fromX, fromY, toX, toY);
11741 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11742 gameMode == Training || gameMode == PlayFromGameFile ||
11743 gameMode == AnalyzeFile) {
11744 while (currentMove < target) {
11745 SendMoveToProgram(currentMove++, &first);
11748 currentMove = target;
11751 if (gameMode == EditGame || gameMode == EndOfGame) {
11752 whiteTimeRemaining = timeRemaining[0][currentMove];
11753 blackTimeRemaining = timeRemaining[1][currentMove];
11755 DisplayBothClocks();
11756 DisplayMove(currentMove - 1);
11757 DrawPosition(FALSE, boards[currentMove]);
11758 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11759 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11760 DisplayComment(currentMove - 1, commentList[currentMove]);
11768 if (gameMode == IcsExamining && !pausing) {
11769 SendToICS(ics_prefix);
11770 SendToICS("forward\n");
11772 ForwardInner(currentMove + 1);
11779 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11780 /* to optimze, we temporarily turn off analysis mode while we feed
11781 * the remaining moves to the engine. Otherwise we get analysis output
11784 if (first.analysisSupport) {
11785 SendToProgram("exit\nforce\n", &first);
11786 first.analyzing = FALSE;
11790 if (gameMode == IcsExamining && !pausing) {
11791 SendToICS(ics_prefix);
11792 SendToICS("forward 999999\n");
11794 ForwardInner(forwardMostMove);
11797 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11798 /* we have fed all the moves, so reactivate analysis mode */
11799 SendToProgram("analyze\n", &first);
11800 first.analyzing = TRUE;
11801 /*first.maybeThinking = TRUE;*/
11802 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11807 BackwardInner(target)
11810 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11812 if (appData.debugMode)
11813 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11814 target, currentMove, forwardMostMove);
11816 if (gameMode == EditPosition) return;
11817 if (currentMove <= backwardMostMove) {
11819 DrawPosition(full_redraw, boards[currentMove]);
11822 if (gameMode == PlayFromGameFile && !pausing)
11825 if (moveList[target][0]) {
11826 int fromX, fromY, toX, toY;
11827 toX = moveList[target][2] - AAA;
11828 toY = moveList[target][3] - ONE;
11829 if (moveList[target][1] == '@') {
11830 if (appData.highlightLastMove) {
11831 SetHighlights(-1, -1, toX, toY);
11834 fromX = moveList[target][0] - AAA;
11835 fromY = moveList[target][1] - ONE;
11836 if (target == currentMove - 1) {
11837 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11839 if (appData.highlightLastMove) {
11840 SetHighlights(fromX, fromY, toX, toY);
11844 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11845 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11846 while (currentMove > target) {
11847 SendToProgram("undo\n", &first);
11851 currentMove = target;
11854 if (gameMode == EditGame || gameMode == EndOfGame) {
11855 whiteTimeRemaining = timeRemaining[0][currentMove];
11856 blackTimeRemaining = timeRemaining[1][currentMove];
11858 DisplayBothClocks();
11859 DisplayMove(currentMove - 1);
11860 DrawPosition(full_redraw, boards[currentMove]);
11861 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11862 // [HGM] PV info: routine tests if comment empty
11863 DisplayComment(currentMove - 1, commentList[currentMove]);
11869 if (gameMode == IcsExamining && !pausing) {
11870 SendToICS(ics_prefix);
11871 SendToICS("backward\n");
11873 BackwardInner(currentMove - 1);
11880 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11881 /* to optimize, we temporarily turn off analysis mode while we undo
11882 * all the moves. Otherwise we get analysis output after each undo.
11884 if (first.analysisSupport) {
11885 SendToProgram("exit\nforce\n", &first);
11886 first.analyzing = FALSE;
11890 if (gameMode == IcsExamining && !pausing) {
11891 SendToICS(ics_prefix);
11892 SendToICS("backward 999999\n");
11894 BackwardInner(backwardMostMove);
11897 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11898 /* we have fed all the moves, so reactivate analysis mode */
11899 SendToProgram("analyze\n", &first);
11900 first.analyzing = TRUE;
11901 /*first.maybeThinking = TRUE;*/
11902 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11909 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11910 if (to >= forwardMostMove) to = forwardMostMove;
11911 if (to <= backwardMostMove) to = backwardMostMove;
11912 if (to < currentMove) {
11922 if (gameMode != IcsExamining) {
11923 DisplayError(_("You are not examining a game"), 0);
11927 DisplayError(_("You can't revert while pausing"), 0);
11930 SendToICS(ics_prefix);
11931 SendToICS("revert\n");
11937 switch (gameMode) {
11938 case MachinePlaysWhite:
11939 case MachinePlaysBlack:
11940 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11941 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11944 if (forwardMostMove < 2) return;
11945 currentMove = forwardMostMove = forwardMostMove - 2;
11946 whiteTimeRemaining = timeRemaining[0][currentMove];
11947 blackTimeRemaining = timeRemaining[1][currentMove];
11948 DisplayBothClocks();
11949 DisplayMove(currentMove - 1);
11950 ClearHighlights();/*!! could figure this out*/
11951 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11952 SendToProgram("remove\n", &first);
11953 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11956 case BeginningOfGame:
11960 case IcsPlayingWhite:
11961 case IcsPlayingBlack:
11962 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11963 SendToICS(ics_prefix);
11964 SendToICS("takeback 2\n");
11966 SendToICS(ics_prefix);
11967 SendToICS("takeback 1\n");
11976 ChessProgramState *cps;
11978 switch (gameMode) {
11979 case MachinePlaysWhite:
11980 if (!WhiteOnMove(forwardMostMove)) {
11981 DisplayError(_("It is your turn"), 0);
11986 case MachinePlaysBlack:
11987 if (WhiteOnMove(forwardMostMove)) {
11988 DisplayError(_("It is your turn"), 0);
11993 case TwoMachinesPlay:
11994 if (WhiteOnMove(forwardMostMove) ==
11995 (first.twoMachinesColor[0] == 'w')) {
12001 case BeginningOfGame:
12005 SendToProgram("?\n", cps);
12009 TruncateGameEvent()
12012 if (gameMode != EditGame) return;
12019 if (forwardMostMove > currentMove) {
12020 if (gameInfo.resultDetails != NULL) {
12021 free(gameInfo.resultDetails);
12022 gameInfo.resultDetails = NULL;
12023 gameInfo.result = GameUnfinished;
12025 forwardMostMove = currentMove;
12026 HistorySet(parseList, backwardMostMove, forwardMostMove,
12034 if (appData.noChessProgram) return;
12035 switch (gameMode) {
12036 case MachinePlaysWhite:
12037 if (WhiteOnMove(forwardMostMove)) {
12038 DisplayError(_("Wait until your turn"), 0);
12042 case BeginningOfGame:
12043 case MachinePlaysBlack:
12044 if (!WhiteOnMove(forwardMostMove)) {
12045 DisplayError(_("Wait until your turn"), 0);
12050 DisplayError(_("No hint available"), 0);
12053 SendToProgram("hint\n", &first);
12054 hintRequested = TRUE;
12060 if (appData.noChessProgram) return;
12061 switch (gameMode) {
12062 case MachinePlaysWhite:
12063 if (WhiteOnMove(forwardMostMove)) {
12064 DisplayError(_("Wait until your turn"), 0);
12068 case BeginningOfGame:
12069 case MachinePlaysBlack:
12070 if (!WhiteOnMove(forwardMostMove)) {
12071 DisplayError(_("Wait until your turn"), 0);
12076 EditPositionDone(TRUE);
12078 case TwoMachinesPlay:
12083 SendToProgram("bk\n", &first);
12084 bookOutput[0] = NULLCHAR;
12085 bookRequested = TRUE;
12091 char *tags = PGNTags(&gameInfo);
12092 TagsPopUp(tags, CmailMsg());
12096 /* end button procedures */
12099 PrintPosition(fp, move)
12105 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12106 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12107 char c = PieceToChar(boards[move][i][j]);
12108 fputc(c == 'x' ? '.' : c, fp);
12109 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12112 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12113 fprintf(fp, "white to play\n");
12115 fprintf(fp, "black to play\n");
12122 if (gameInfo.white != NULL) {
12123 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12129 /* Find last component of program's own name, using some heuristics */
12131 TidyProgramName(prog, host, buf)
12132 char *prog, *host, buf[MSG_SIZ];
12135 int local = (strcmp(host, "localhost") == 0);
12136 while (!local && (p = strchr(prog, ';')) != NULL) {
12138 while (*p == ' ') p++;
12141 if (*prog == '"' || *prog == '\'') {
12142 q = strchr(prog + 1, *prog);
12144 q = strchr(prog, ' ');
12146 if (q == NULL) q = prog + strlen(prog);
12148 while (p >= prog && *p != '/' && *p != '\\') p--;
12150 if(p == prog && *p == '"') p++;
12151 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12152 memcpy(buf, p, q - p);
12153 buf[q - p] = NULLCHAR;
12161 TimeControlTagValue()
12164 if (!appData.clockMode) {
12166 } else if (movesPerSession > 0) {
12167 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12168 } else if (timeIncrement == 0) {
12169 sprintf(buf, "%ld", timeControl/1000);
12171 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12173 return StrSave(buf);
12179 /* This routine is used only for certain modes */
12180 VariantClass v = gameInfo.variant;
12181 ClearGameInfo(&gameInfo);
12182 gameInfo.variant = v;
12184 switch (gameMode) {
12185 case MachinePlaysWhite:
12186 gameInfo.event = StrSave( appData.pgnEventHeader );
12187 gameInfo.site = StrSave(HostName());
12188 gameInfo.date = PGNDate();
12189 gameInfo.round = StrSave("-");
12190 gameInfo.white = StrSave(first.tidy);
12191 gameInfo.black = StrSave(UserName());
12192 gameInfo.timeControl = TimeControlTagValue();
12195 case MachinePlaysBlack:
12196 gameInfo.event = StrSave( appData.pgnEventHeader );
12197 gameInfo.site = StrSave(HostName());
12198 gameInfo.date = PGNDate();
12199 gameInfo.round = StrSave("-");
12200 gameInfo.white = StrSave(UserName());
12201 gameInfo.black = StrSave(first.tidy);
12202 gameInfo.timeControl = TimeControlTagValue();
12205 case TwoMachinesPlay:
12206 gameInfo.event = StrSave( appData.pgnEventHeader );
12207 gameInfo.site = StrSave(HostName());
12208 gameInfo.date = PGNDate();
12209 if (matchGame > 0) {
12211 sprintf(buf, "%d", matchGame);
12212 gameInfo.round = StrSave(buf);
12214 gameInfo.round = StrSave("-");
12216 if (first.twoMachinesColor[0] == 'w') {
12217 gameInfo.white = StrSave(first.tidy);
12218 gameInfo.black = StrSave(second.tidy);
12220 gameInfo.white = StrSave(second.tidy);
12221 gameInfo.black = StrSave(first.tidy);
12223 gameInfo.timeControl = TimeControlTagValue();
12227 gameInfo.event = StrSave("Edited game");
12228 gameInfo.site = StrSave(HostName());
12229 gameInfo.date = PGNDate();
12230 gameInfo.round = StrSave("-");
12231 gameInfo.white = StrSave("-");
12232 gameInfo.black = StrSave("-");
12236 gameInfo.event = StrSave("Edited position");
12237 gameInfo.site = StrSave(HostName());
12238 gameInfo.date = PGNDate();
12239 gameInfo.round = StrSave("-");
12240 gameInfo.white = StrSave("-");
12241 gameInfo.black = StrSave("-");
12244 case IcsPlayingWhite:
12245 case IcsPlayingBlack:
12250 case PlayFromGameFile:
12251 gameInfo.event = StrSave("Game from non-PGN file");
12252 gameInfo.site = StrSave(HostName());
12253 gameInfo.date = PGNDate();
12254 gameInfo.round = StrSave("-");
12255 gameInfo.white = StrSave("?");
12256 gameInfo.black = StrSave("?");
12265 ReplaceComment(index, text)
12271 while (*text == '\n') text++;
12272 len = strlen(text);
12273 while (len > 0 && text[len - 1] == '\n') len--;
12275 if (commentList[index] != NULL)
12276 free(commentList[index]);
12279 commentList[index] = NULL;
12282 commentList[index] = (char *) malloc(len + 2);
12283 strncpy(commentList[index], text, len);
12284 commentList[index][len] = '\n';
12285 commentList[index][len + 1] = NULLCHAR;
12298 if (ch == '\r') continue;
12300 } while (ch != '\0');
12304 AppendComment(index, text)
12311 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12314 while (*text == '\n') text++;
12315 len = strlen(text);
12316 while (len > 0 && text[len - 1] == '\n') len--;
12318 if (len == 0) return;
12320 if (commentList[index] != NULL) {
12321 old = commentList[index];
12322 oldlen = strlen(old);
12323 commentList[index] = (char *) malloc(oldlen + len + 2);
12324 strcpy(commentList[index], old);
12326 strncpy(&commentList[index][oldlen], text, len);
12327 commentList[index][oldlen + len] = '\n';
12328 commentList[index][oldlen + len + 1] = NULLCHAR;
12330 commentList[index] = (char *) malloc(len + 2);
12331 strncpy(commentList[index], text, len);
12332 commentList[index][len] = '\n';
12333 commentList[index][len + 1] = NULLCHAR;
12337 static char * FindStr( char * text, char * sub_text )
12339 char * result = strstr( text, sub_text );
12341 if( result != NULL ) {
12342 result += strlen( sub_text );
12348 /* [AS] Try to extract PV info from PGN comment */
12349 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12350 char *GetInfoFromComment( int index, char * text )
12354 if( text != NULL && index > 0 ) {
12357 int time = -1, sec = 0, deci;
12358 char * s_eval = FindStr( text, "[%eval " );
12359 char * s_emt = FindStr( text, "[%emt " );
12361 if( s_eval != NULL || s_emt != NULL ) {
12365 if( s_eval != NULL ) {
12366 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12370 if( delim != ']' ) {
12375 if( s_emt != NULL ) {
12379 /* We expect something like: [+|-]nnn.nn/dd */
12382 sep = strchr( text, '/' );
12383 if( sep == NULL || sep < (text+4) ) {
12387 time = -1; sec = -1; deci = -1;
12388 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12389 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12390 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12391 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12395 if( score_lo < 0 || score_lo >= 100 ) {
12399 if(sec >= 0) time = 600*time + 10*sec; else
12400 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12402 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12404 /* [HGM] PV time: now locate end of PV info */
12405 while( *++sep >= '0' && *sep <= '9'); // strip depth
12407 while( *++sep >= '0' && *sep <= '9'); // strip time
12409 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12411 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12412 while(*sep == ' ') sep++;
12423 pvInfoList[index-1].depth = depth;
12424 pvInfoList[index-1].score = score;
12425 pvInfoList[index-1].time = 10*time; // centi-sec
12431 SendToProgram(message, cps)
12433 ChessProgramState *cps;
12435 int count, outCount, error;
12438 if (cps->pr == NULL) return;
12441 if (appData.debugMode) {
12444 fprintf(debugFP, "%ld >%-6s: %s",
12445 SubtractTimeMarks(&now, &programStartTime),
12446 cps->which, message);
12449 count = strlen(message);
12450 outCount = OutputToProcess(cps->pr, message, count, &error);
12451 if (outCount < count && !exiting
12452 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12453 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12454 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12455 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12456 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12457 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12459 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12461 gameInfo.resultDetails = StrSave(buf);
12463 DisplayFatalError(buf, error, 1);
12468 ReceiveFromProgram(isr, closure, message, count, error)
12469 InputSourceRef isr;
12477 ChessProgramState *cps = (ChessProgramState *)closure;
12479 if (isr != cps->isr) return; /* Killed intentionally */
12483 _("Error: %s chess program (%s) exited unexpectedly"),
12484 cps->which, cps->program);
12485 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12486 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12487 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12488 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12490 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12492 gameInfo.resultDetails = StrSave(buf);
12494 RemoveInputSource(cps->isr);
12495 DisplayFatalError(buf, 0, 1);
12498 _("Error reading from %s chess program (%s)"),
12499 cps->which, cps->program);
12500 RemoveInputSource(cps->isr);
12502 /* [AS] Program is misbehaving badly... kill it */
12503 if( count == -2 ) {
12504 DestroyChildProcess( cps->pr, 9 );
12508 DisplayFatalError(buf, error, 1);
12513 if ((end_str = strchr(message, '\r')) != NULL)
12514 *end_str = NULLCHAR;
12515 if ((end_str = strchr(message, '\n')) != NULL)
12516 *end_str = NULLCHAR;
12518 if (appData.debugMode) {
12519 TimeMark now; int print = 1;
12520 char *quote = ""; char c; int i;
12522 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12523 char start = message[0];
12524 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12525 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12526 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12527 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12528 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12529 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12530 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12531 sscanf(message, "pong %c", &c)!=1 && start != '#')
12532 { quote = "# "; print = (appData.engineComments == 2); }
12533 message[0] = start; // restore original message
12537 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12538 SubtractTimeMarks(&now, &programStartTime), cps->which,
12544 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12545 if (appData.icsEngineAnalyze) {
12546 if (strstr(message, "whisper") != NULL ||
12547 strstr(message, "kibitz") != NULL ||
12548 strstr(message, "tellics") != NULL) return;
12551 HandleMachineMove(message, cps);
12556 SendTimeControl(cps, mps, tc, inc, sd, st)
12557 ChessProgramState *cps;
12558 int mps, inc, sd, st;
12564 if( timeControl_2 > 0 ) {
12565 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12566 tc = timeControl_2;
12569 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12570 inc /= cps->timeOdds;
12571 st /= cps->timeOdds;
12573 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12576 /* Set exact time per move, normally using st command */
12577 if (cps->stKludge) {
12578 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12580 if (seconds == 0) {
12581 sprintf(buf, "level 1 %d\n", st/60);
12583 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12586 sprintf(buf, "st %d\n", st);
12589 /* Set conventional or incremental time control, using level command */
12590 if (seconds == 0) {
12591 /* Note old gnuchess bug -- minutes:seconds used to not work.
12592 Fixed in later versions, but still avoid :seconds
12593 when seconds is 0. */
12594 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12596 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12597 seconds, inc/1000);
12600 SendToProgram(buf, cps);
12602 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12603 /* Orthogonally, limit search to given depth */
12605 if (cps->sdKludge) {
12606 sprintf(buf, "depth\n%d\n", sd);
12608 sprintf(buf, "sd %d\n", sd);
12610 SendToProgram(buf, cps);
12613 if(cps->nps > 0) { /* [HGM] nps */
12614 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12616 sprintf(buf, "nps %d\n", cps->nps);
12617 SendToProgram(buf, cps);
12622 ChessProgramState *WhitePlayer()
12623 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12625 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12626 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12632 SendTimeRemaining(cps, machineWhite)
12633 ChessProgramState *cps;
12634 int /*boolean*/ machineWhite;
12636 char message[MSG_SIZ];
12639 /* Note: this routine must be called when the clocks are stopped
12640 or when they have *just* been set or switched; otherwise
12641 it will be off by the time since the current tick started.
12643 if (machineWhite) {
12644 time = whiteTimeRemaining / 10;
12645 otime = blackTimeRemaining / 10;
12647 time = blackTimeRemaining / 10;
12648 otime = whiteTimeRemaining / 10;
12650 /* [HGM] translate opponent's time by time-odds factor */
12651 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12652 if (appData.debugMode) {
12653 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12656 if (time <= 0) time = 1;
12657 if (otime <= 0) otime = 1;
12659 sprintf(message, "time %ld\n", time);
12660 SendToProgram(message, cps);
12662 sprintf(message, "otim %ld\n", otime);
12663 SendToProgram(message, cps);
12667 BoolFeature(p, name, loc, cps)
12671 ChessProgramState *cps;
12674 int len = strlen(name);
12676 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12678 sscanf(*p, "%d", &val);
12680 while (**p && **p != ' ') (*p)++;
12681 sprintf(buf, "accepted %s\n", name);
12682 SendToProgram(buf, cps);
12689 IntFeature(p, name, loc, cps)
12693 ChessProgramState *cps;
12696 int len = strlen(name);
12697 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12699 sscanf(*p, "%d", loc);
12700 while (**p && **p != ' ') (*p)++;
12701 sprintf(buf, "accepted %s\n", name);
12702 SendToProgram(buf, cps);
12709 StringFeature(p, name, loc, cps)
12713 ChessProgramState *cps;
12716 int len = strlen(name);
12717 if (strncmp((*p), name, len) == 0
12718 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12720 sscanf(*p, "%[^\"]", loc);
12721 while (**p && **p != '\"') (*p)++;
12722 if (**p == '\"') (*p)++;
12723 sprintf(buf, "accepted %s\n", name);
12724 SendToProgram(buf, cps);
12731 ParseOption(Option *opt, ChessProgramState *cps)
12732 // [HGM] options: process the string that defines an engine option, and determine
12733 // name, type, default value, and allowed value range
12735 char *p, *q, buf[MSG_SIZ];
12736 int n, min = (-1)<<31, max = 1<<31, def;
12738 if(p = strstr(opt->name, " -spin ")) {
12739 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12740 if(max < min) max = min; // enforce consistency
12741 if(def < min) def = min;
12742 if(def > max) def = max;
12747 } else if((p = strstr(opt->name, " -slider "))) {
12748 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12749 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12750 if(max < min) max = min; // enforce consistency
12751 if(def < min) def = min;
12752 if(def > max) def = max;
12756 opt->type = Spin; // Slider;
12757 } else if((p = strstr(opt->name, " -string "))) {
12758 opt->textValue = p+9;
12759 opt->type = TextBox;
12760 } else if((p = strstr(opt->name, " -file "))) {
12761 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12762 opt->textValue = p+7;
12763 opt->type = TextBox; // FileName;
12764 } else if((p = strstr(opt->name, " -path "))) {
12765 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12766 opt->textValue = p+7;
12767 opt->type = TextBox; // PathName;
12768 } else if(p = strstr(opt->name, " -check ")) {
12769 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12770 opt->value = (def != 0);
12771 opt->type = CheckBox;
12772 } else if(p = strstr(opt->name, " -combo ")) {
12773 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12774 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12775 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12776 opt->value = n = 0;
12777 while(q = StrStr(q, " /// ")) {
12778 n++; *q = 0; // count choices, and null-terminate each of them
12780 if(*q == '*') { // remember default, which is marked with * prefix
12784 cps->comboList[cps->comboCnt++] = q;
12786 cps->comboList[cps->comboCnt++] = NULL;
12788 opt->type = ComboBox;
12789 } else if(p = strstr(opt->name, " -button")) {
12790 opt->type = Button;
12791 } else if(p = strstr(opt->name, " -save")) {
12792 opt->type = SaveButton;
12793 } else return FALSE;
12794 *p = 0; // terminate option name
12795 // now look if the command-line options define a setting for this engine option.
12796 if(cps->optionSettings && cps->optionSettings[0])
12797 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12798 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12799 sprintf(buf, "option %s", p);
12800 if(p = strstr(buf, ",")) *p = 0;
12802 SendToProgram(buf, cps);
12808 FeatureDone(cps, val)
12809 ChessProgramState* cps;
12812 DelayedEventCallback cb = GetDelayedEvent();
12813 if ((cb == InitBackEnd3 && cps == &first) ||
12814 (cb == TwoMachinesEventIfReady && cps == &second)) {
12815 CancelDelayedEvent();
12816 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12818 cps->initDone = val;
12821 /* Parse feature command from engine */
12823 ParseFeatures(args, cps)
12825 ChessProgramState *cps;
12833 while (*p == ' ') p++;
12834 if (*p == NULLCHAR) return;
12836 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12837 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12838 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12839 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12840 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12841 if (BoolFeature(&p, "reuse", &val, cps)) {
12842 /* Engine can disable reuse, but can't enable it if user said no */
12843 if (!val) cps->reuse = FALSE;
12846 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12847 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12848 if (gameMode == TwoMachinesPlay) {
12849 DisplayTwoMachinesTitle();
12855 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12856 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12857 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12858 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12859 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12860 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12861 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12862 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12863 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12864 if (IntFeature(&p, "done", &val, cps)) {
12865 FeatureDone(cps, val);
12868 /* Added by Tord: */
12869 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12870 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12871 /* End of additions by Tord */
12873 /* [HGM] added features: */
12874 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12875 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12876 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12877 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12878 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12879 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12880 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12881 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12882 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12883 SendToProgram(buf, cps);
12886 if(cps->nrOptions >= MAX_OPTIONS) {
12888 sprintf(buf, "%s engine has too many options\n", cps->which);
12889 DisplayError(buf, 0);
12893 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12894 /* End of additions by HGM */
12896 /* unknown feature: complain and skip */
12898 while (*q && *q != '=') q++;
12899 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12900 SendToProgram(buf, cps);
12906 while (*p && *p != '\"') p++;
12907 if (*p == '\"') p++;
12909 while (*p && *p != ' ') p++;
12917 PeriodicUpdatesEvent(newState)
12920 if (newState == appData.periodicUpdates)
12923 appData.periodicUpdates=newState;
12925 /* Display type changes, so update it now */
12926 // DisplayAnalysis();
12928 /* Get the ball rolling again... */
12930 AnalysisPeriodicEvent(1);
12931 StartAnalysisClock();
12936 PonderNextMoveEvent(newState)
12939 if (newState == appData.ponderNextMove) return;
12940 if (gameMode == EditPosition) EditPositionDone(TRUE);
12942 SendToProgram("hard\n", &first);
12943 if (gameMode == TwoMachinesPlay) {
12944 SendToProgram("hard\n", &second);
12947 SendToProgram("easy\n", &first);
12948 thinkOutput[0] = NULLCHAR;
12949 if (gameMode == TwoMachinesPlay) {
12950 SendToProgram("easy\n", &second);
12953 appData.ponderNextMove = newState;
12957 NewSettingEvent(option, command, value)
12963 if (gameMode == EditPosition) EditPositionDone(TRUE);
12964 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12965 SendToProgram(buf, &first);
12966 if (gameMode == TwoMachinesPlay) {
12967 SendToProgram(buf, &second);
12972 ShowThinkingEvent()
12973 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12975 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12976 int newState = appData.showThinking
12977 // [HGM] thinking: other features now need thinking output as well
12978 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12980 if (oldState == newState) return;
12981 oldState = newState;
12982 if (gameMode == EditPosition) EditPositionDone(TRUE);
12984 SendToProgram("post\n", &first);
12985 if (gameMode == TwoMachinesPlay) {
12986 SendToProgram("post\n", &second);
12989 SendToProgram("nopost\n", &first);
12990 thinkOutput[0] = NULLCHAR;
12991 if (gameMode == TwoMachinesPlay) {
12992 SendToProgram("nopost\n", &second);
12995 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12999 AskQuestionEvent(title, question, replyPrefix, which)
13000 char *title; char *question; char *replyPrefix; char *which;
13002 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13003 if (pr == NoProc) return;
13004 AskQuestion(title, question, replyPrefix, pr);
13008 DisplayMove(moveNumber)
13011 char message[MSG_SIZ];
13013 char cpThinkOutput[MSG_SIZ];
13015 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13017 if (moveNumber == forwardMostMove - 1 ||
13018 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13020 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13022 if (strchr(cpThinkOutput, '\n')) {
13023 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13026 *cpThinkOutput = NULLCHAR;
13029 /* [AS] Hide thinking from human user */
13030 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13031 *cpThinkOutput = NULLCHAR;
13032 if( thinkOutput[0] != NULLCHAR ) {
13035 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13036 cpThinkOutput[i] = '.';
13038 cpThinkOutput[i] = NULLCHAR;
13039 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13043 if (moveNumber == forwardMostMove - 1 &&
13044 gameInfo.resultDetails != NULL) {
13045 if (gameInfo.resultDetails[0] == NULLCHAR) {
13046 sprintf(res, " %s", PGNResult(gameInfo.result));
13048 sprintf(res, " {%s} %s",
13049 gameInfo.resultDetails, PGNResult(gameInfo.result));
13055 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13056 DisplayMessage(res, cpThinkOutput);
13058 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13059 WhiteOnMove(moveNumber) ? " " : ".. ",
13060 parseList[moveNumber], res);
13061 DisplayMessage(message, cpThinkOutput);
13066 DisplayComment(moveNumber, text)
13070 char title[MSG_SIZ];
13071 char buf[8000]; // comment can be long!
13074 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13075 strcpy(title, "Comment");
13077 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13078 WhiteOnMove(moveNumber) ? " " : ".. ",
13079 parseList[moveNumber]);
13081 // [HGM] PV info: display PV info together with (or as) comment
13082 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13083 if(text == NULL) text = "";
13084 score = pvInfoList[moveNumber].score;
13085 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13086 depth, (pvInfoList[moveNumber].time+50)/100, text);
13089 if (text != NULL && (appData.autoDisplayComment || commentUp))
13090 CommentPopUp(title, text);
13093 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13094 * might be busy thinking or pondering. It can be omitted if your
13095 * gnuchess is configured to stop thinking immediately on any user
13096 * input. However, that gnuchess feature depends on the FIONREAD
13097 * ioctl, which does not work properly on some flavors of Unix.
13101 ChessProgramState *cps;
13104 if (!cps->useSigint) return;
13105 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13106 switch (gameMode) {
13107 case MachinePlaysWhite:
13108 case MachinePlaysBlack:
13109 case TwoMachinesPlay:
13110 case IcsPlayingWhite:
13111 case IcsPlayingBlack:
13114 /* Skip if we know it isn't thinking */
13115 if (!cps->maybeThinking) return;
13116 if (appData.debugMode)
13117 fprintf(debugFP, "Interrupting %s\n", cps->which);
13118 InterruptChildProcess(cps->pr);
13119 cps->maybeThinking = FALSE;
13124 #endif /*ATTENTION*/
13130 if (whiteTimeRemaining <= 0) {
13133 if (appData.icsActive) {
13134 if (appData.autoCallFlag &&
13135 gameMode == IcsPlayingBlack && !blackFlag) {
13136 SendToICS(ics_prefix);
13137 SendToICS("flag\n");
13141 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13143 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13144 if (appData.autoCallFlag) {
13145 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13152 if (blackTimeRemaining <= 0) {
13155 if (appData.icsActive) {
13156 if (appData.autoCallFlag &&
13157 gameMode == IcsPlayingWhite && !whiteFlag) {
13158 SendToICS(ics_prefix);
13159 SendToICS("flag\n");
13163 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13165 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13166 if (appData.autoCallFlag) {
13167 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13180 if (!appData.clockMode || appData.icsActive ||
13181 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13184 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13186 if ( !WhiteOnMove(forwardMostMove) )
13187 /* White made time control */
13188 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13189 /* [HGM] time odds: correct new time quota for time odds! */
13190 / WhitePlayer()->timeOdds;
13192 /* Black made time control */
13193 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13194 / WhitePlayer()->other->timeOdds;
13198 DisplayBothClocks()
13200 int wom = gameMode == EditPosition ?
13201 !blackPlaysFirst : WhiteOnMove(currentMove);
13202 DisplayWhiteClock(whiteTimeRemaining, wom);
13203 DisplayBlackClock(blackTimeRemaining, !wom);
13207 /* Timekeeping seems to be a portability nightmare. I think everyone
13208 has ftime(), but I'm really not sure, so I'm including some ifdefs
13209 to use other calls if you don't. Clocks will be less accurate if
13210 you have neither ftime nor gettimeofday.
13213 /* VS 2008 requires the #include outside of the function */
13214 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13215 #include <sys/timeb.h>
13218 /* Get the current time as a TimeMark */
13223 #if HAVE_GETTIMEOFDAY
13225 struct timeval timeVal;
13226 struct timezone timeZone;
13228 gettimeofday(&timeVal, &timeZone);
13229 tm->sec = (long) timeVal.tv_sec;
13230 tm->ms = (int) (timeVal.tv_usec / 1000L);
13232 #else /*!HAVE_GETTIMEOFDAY*/
13235 // include <sys/timeb.h> / moved to just above start of function
13236 struct timeb timeB;
13239 tm->sec = (long) timeB.time;
13240 tm->ms = (int) timeB.millitm;
13242 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13243 tm->sec = (long) time(NULL);
13249 /* Return the difference in milliseconds between two
13250 time marks. We assume the difference will fit in a long!
13253 SubtractTimeMarks(tm2, tm1)
13254 TimeMark *tm2, *tm1;
13256 return 1000L*(tm2->sec - tm1->sec) +
13257 (long) (tm2->ms - tm1->ms);
13262 * Code to manage the game clocks.
13264 * In tournament play, black starts the clock and then white makes a move.
13265 * We give the human user a slight advantage if he is playing white---the
13266 * clocks don't run until he makes his first move, so it takes zero time.
13267 * Also, we don't account for network lag, so we could get out of sync
13268 * with GNU Chess's clock -- but then, referees are always right.
13271 static TimeMark tickStartTM;
13272 static long intendedTickLength;
13275 NextTickLength(timeRemaining)
13276 long timeRemaining;
13278 long nominalTickLength, nextTickLength;
13280 if (timeRemaining > 0L && timeRemaining <= 10000L)
13281 nominalTickLength = 100L;
13283 nominalTickLength = 1000L;
13284 nextTickLength = timeRemaining % nominalTickLength;
13285 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13287 return nextTickLength;
13290 /* Adjust clock one minute up or down */
13292 AdjustClock(Boolean which, int dir)
13294 if(which) blackTimeRemaining += 60000*dir;
13295 else whiteTimeRemaining += 60000*dir;
13296 DisplayBothClocks();
13299 /* Stop clocks and reset to a fresh time control */
13303 (void) StopClockTimer();
13304 if (appData.icsActive) {
13305 whiteTimeRemaining = blackTimeRemaining = 0;
13306 } else { /* [HGM] correct new time quote for time odds */
13307 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13308 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13310 if (whiteFlag || blackFlag) {
13312 whiteFlag = blackFlag = FALSE;
13314 DisplayBothClocks();
13317 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13319 /* Decrement running clock by amount of time that has passed */
13323 long timeRemaining;
13324 long lastTickLength, fudge;
13327 if (!appData.clockMode) return;
13328 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13332 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13334 /* Fudge if we woke up a little too soon */
13335 fudge = intendedTickLength - lastTickLength;
13336 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13338 if (WhiteOnMove(forwardMostMove)) {
13339 if(whiteNPS >= 0) lastTickLength = 0;
13340 timeRemaining = whiteTimeRemaining -= lastTickLength;
13341 DisplayWhiteClock(whiteTimeRemaining - fudge,
13342 WhiteOnMove(currentMove));
13344 if(blackNPS >= 0) lastTickLength = 0;
13345 timeRemaining = blackTimeRemaining -= lastTickLength;
13346 DisplayBlackClock(blackTimeRemaining - fudge,
13347 !WhiteOnMove(currentMove));
13350 if (CheckFlags()) return;
13353 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13354 StartClockTimer(intendedTickLength);
13356 /* if the time remaining has fallen below the alarm threshold, sound the
13357 * alarm. if the alarm has sounded and (due to a takeback or time control
13358 * with increment) the time remaining has increased to a level above the
13359 * threshold, reset the alarm so it can sound again.
13362 if (appData.icsActive && appData.icsAlarm) {
13364 /* make sure we are dealing with the user's clock */
13365 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13366 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13369 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13370 alarmSounded = FALSE;
13371 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13373 alarmSounded = TRUE;
13379 /* A player has just moved, so stop the previously running
13380 clock and (if in clock mode) start the other one.
13381 We redisplay both clocks in case we're in ICS mode, because
13382 ICS gives us an update to both clocks after every move.
13383 Note that this routine is called *after* forwardMostMove
13384 is updated, so the last fractional tick must be subtracted
13385 from the color that is *not* on move now.
13388 SwitchClocks(int newMoveNr)
13390 long lastTickLength;
13392 int flagged = FALSE;
13396 if (StopClockTimer() && appData.clockMode) {
13397 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13398 if (!WhiteOnMove(forwardMostMove)) {
13399 if(blackNPS >= 0) lastTickLength = 0;
13400 blackTimeRemaining -= lastTickLength;
13401 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13402 // if(pvInfoList[forwardMostMove-1].time == -1)
13403 pvInfoList[forwardMostMove-1].time = // use GUI time
13404 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13406 if(whiteNPS >= 0) lastTickLength = 0;
13407 whiteTimeRemaining -= lastTickLength;
13408 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13409 // if(pvInfoList[forwardMostMove-1].time == -1)
13410 pvInfoList[forwardMostMove-1].time =
13411 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13413 flagged = CheckFlags();
13415 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13416 CheckTimeControl();
13418 if (flagged || !appData.clockMode) return;
13420 switch (gameMode) {
13421 case MachinePlaysBlack:
13422 case MachinePlaysWhite:
13423 case BeginningOfGame:
13424 if (pausing) return;
13428 case PlayFromGameFile:
13437 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13438 whiteTimeRemaining : blackTimeRemaining);
13439 StartClockTimer(intendedTickLength);
13443 /* Stop both clocks */
13447 long lastTickLength;
13450 if (!StopClockTimer()) return;
13451 if (!appData.clockMode) return;
13455 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13456 if (WhiteOnMove(forwardMostMove)) {
13457 if(whiteNPS >= 0) lastTickLength = 0;
13458 whiteTimeRemaining -= lastTickLength;
13459 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13461 if(blackNPS >= 0) lastTickLength = 0;
13462 blackTimeRemaining -= lastTickLength;
13463 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13468 /* Start clock of player on move. Time may have been reset, so
13469 if clock is already running, stop and restart it. */
13473 (void) StopClockTimer(); /* in case it was running already */
13474 DisplayBothClocks();
13475 if (CheckFlags()) return;
13477 if (!appData.clockMode) return;
13478 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13480 GetTimeMark(&tickStartTM);
13481 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13482 whiteTimeRemaining : blackTimeRemaining);
13484 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13485 whiteNPS = blackNPS = -1;
13486 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13487 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13488 whiteNPS = first.nps;
13489 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13490 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13491 blackNPS = first.nps;
13492 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13493 whiteNPS = second.nps;
13494 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13495 blackNPS = second.nps;
13496 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13498 StartClockTimer(intendedTickLength);
13505 long second, minute, hour, day;
13507 static char buf[32];
13509 if (ms > 0 && ms <= 9900) {
13510 /* convert milliseconds to tenths, rounding up */
13511 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13513 sprintf(buf, " %03.1f ", tenths/10.0);
13517 /* convert milliseconds to seconds, rounding up */
13518 /* use floating point to avoid strangeness of integer division
13519 with negative dividends on many machines */
13520 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13527 day = second / (60 * 60 * 24);
13528 second = second % (60 * 60 * 24);
13529 hour = second / (60 * 60);
13530 second = second % (60 * 60);
13531 minute = second / 60;
13532 second = second % 60;
13535 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13536 sign, day, hour, minute, second);
13538 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13540 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13547 * This is necessary because some C libraries aren't ANSI C compliant yet.
13550 StrStr(string, match)
13551 char *string, *match;
13555 length = strlen(match);
13557 for (i = strlen(string) - length; i >= 0; i--, string++)
13558 if (!strncmp(match, string, length))
13565 StrCaseStr(string, match)
13566 char *string, *match;
13570 length = strlen(match);
13572 for (i = strlen(string) - length; i >= 0; i--, string++) {
13573 for (j = 0; j < length; j++) {
13574 if (ToLower(match[j]) != ToLower(string[j]))
13577 if (j == length) return string;
13591 c1 = ToLower(*s1++);
13592 c2 = ToLower(*s2++);
13593 if (c1 > c2) return 1;
13594 if (c1 < c2) return -1;
13595 if (c1 == NULLCHAR) return 0;
13604 return isupper(c) ? tolower(c) : c;
13612 return islower(c) ? toupper(c) : c;
13614 #endif /* !_amigados */
13622 if ((ret = (char *) malloc(strlen(s) + 1))) {
13629 StrSavePtr(s, savePtr)
13630 char *s, **savePtr;
13635 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13636 strcpy(*savePtr, s);
13648 clock = time((time_t *)NULL);
13649 tm = localtime(&clock);
13650 sprintf(buf, "%04d.%02d.%02d",
13651 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13652 return StrSave(buf);
13657 PositionToFEN(move, overrideCastling)
13659 char *overrideCastling;
13661 int i, j, fromX, fromY, toX, toY;
13668 whiteToPlay = (gameMode == EditPosition) ?
13669 !blackPlaysFirst : (move % 2 == 0);
13672 /* Piece placement data */
13673 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13675 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13676 if (boards[move][i][j] == EmptySquare) {
13678 } else { ChessSquare piece = boards[move][i][j];
13679 if (emptycount > 0) {
13680 if(emptycount<10) /* [HGM] can be >= 10 */
13681 *p++ = '0' + emptycount;
13682 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13685 if(PieceToChar(piece) == '+') {
13686 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13688 piece = (ChessSquare)(DEMOTED piece);
13690 *p++ = PieceToChar(piece);
13692 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13693 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13698 if (emptycount > 0) {
13699 if(emptycount<10) /* [HGM] can be >= 10 */
13700 *p++ = '0' + emptycount;
13701 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13708 /* [HGM] print Crazyhouse or Shogi holdings */
13709 if( gameInfo.holdingsWidth ) {
13710 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13712 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13713 piece = boards[move][i][BOARD_WIDTH-1];
13714 if( piece != EmptySquare )
13715 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13716 *p++ = PieceToChar(piece);
13718 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13719 piece = boards[move][BOARD_HEIGHT-i-1][0];
13720 if( piece != EmptySquare )
13721 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13722 *p++ = PieceToChar(piece);
13725 if( q == p ) *p++ = '-';
13731 *p++ = whiteToPlay ? 'w' : 'b';
13734 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13735 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13737 if(nrCastlingRights) {
13739 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13740 /* [HGM] write directly from rights */
13741 if(castlingRights[move][2] >= 0 &&
13742 castlingRights[move][0] >= 0 )
13743 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13744 if(castlingRights[move][2] >= 0 &&
13745 castlingRights[move][1] >= 0 )
13746 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13747 if(castlingRights[move][5] >= 0 &&
13748 castlingRights[move][3] >= 0 )
13749 *p++ = castlingRights[move][3] + AAA;
13750 if(castlingRights[move][5] >= 0 &&
13751 castlingRights[move][4] >= 0 )
13752 *p++ = castlingRights[move][4] + AAA;
13755 /* [HGM] write true castling rights */
13756 if( nrCastlingRights == 6 ) {
13757 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13758 castlingRights[move][2] >= 0 ) *p++ = 'K';
13759 if(castlingRights[move][1] == BOARD_LEFT &&
13760 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13761 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13762 castlingRights[move][5] >= 0 ) *p++ = 'k';
13763 if(castlingRights[move][4] == BOARD_LEFT &&
13764 castlingRights[move][5] >= 0 ) *p++ = 'q';
13767 if (q == p) *p++ = '-'; /* No castling rights */
13771 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13772 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
13773 /* En passant target square */
13774 if (move > backwardMostMove) {
13775 fromX = moveList[move - 1][0] - AAA;
13776 fromY = moveList[move - 1][1] - ONE;
13777 toX = moveList[move - 1][2] - AAA;
13778 toY = moveList[move - 1][3] - ONE;
13779 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13780 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13781 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13783 /* 2-square pawn move just happened */
13785 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13789 } else if(move == backwardMostMove) {
13790 // [HGM] perhaps we should always do it like this, and forget the above?
13791 if(epStatus[move] >= 0) {
13792 *p++ = epStatus[move] + AAA;
13793 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13804 /* [HGM] find reversible plies */
13805 { int i = 0, j=move;
13807 if (appData.debugMode) { int k;
13808 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13809 for(k=backwardMostMove; k<=forwardMostMove; k++)
13810 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13814 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13815 if( j == backwardMostMove ) i += initialRulePlies;
13816 sprintf(p, "%d ", i);
13817 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13819 /* Fullmove number */
13820 sprintf(p, "%d", (move / 2) + 1);
13822 return StrSave(buf);
13826 ParseFEN(board, blackPlaysFirst, fen)
13828 int *blackPlaysFirst;
13838 /* [HGM] by default clear Crazyhouse holdings, if present */
13839 if(gameInfo.holdingsWidth) {
13840 for(i=0; i<BOARD_HEIGHT; i++) {
13841 board[i][0] = EmptySquare; /* black holdings */
13842 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13843 board[i][1] = (ChessSquare) 0; /* black counts */
13844 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13848 /* Piece placement data */
13849 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13852 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13853 if (*p == '/') p++;
13854 emptycount = gameInfo.boardWidth - j;
13855 while (emptycount--)
13856 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13858 #if(BOARD_SIZE >= 10)
13859 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13860 p++; emptycount=10;
13861 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13862 while (emptycount--)
13863 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13865 } else if (isdigit(*p)) {
13866 emptycount = *p++ - '0';
13867 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13868 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13869 while (emptycount--)
13870 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13871 } else if (*p == '+' || isalpha(*p)) {
13872 if (j >= gameInfo.boardWidth) return FALSE;
13874 piece = CharToPiece(*++p);
13875 if(piece == EmptySquare) return FALSE; /* unknown piece */
13876 piece = (ChessSquare) (PROMOTED piece ); p++;
13877 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13878 } else piece = CharToPiece(*p++);
13880 if(piece==EmptySquare) return FALSE; /* unknown piece */
13881 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13882 piece = (ChessSquare) (PROMOTED piece);
13883 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13886 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13892 while (*p == '/' || *p == ' ') p++;
13894 /* [HGM] look for Crazyhouse holdings here */
13895 while(*p==' ') p++;
13896 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13898 if(*p == '-' ) *p++; /* empty holdings */ else {
13899 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13900 /* if we would allow FEN reading to set board size, we would */
13901 /* have to add holdings and shift the board read so far here */
13902 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13904 if((int) piece >= (int) BlackPawn ) {
13905 i = (int)piece - (int)BlackPawn;
13906 i = PieceToNumber((ChessSquare)i);
13907 if( i >= gameInfo.holdingsSize ) return FALSE;
13908 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13909 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13911 i = (int)piece - (int)WhitePawn;
13912 i = PieceToNumber((ChessSquare)i);
13913 if( i >= gameInfo.holdingsSize ) return FALSE;
13914 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13915 board[i][BOARD_WIDTH-2]++; /* black holdings */
13919 if(*p == ']') *p++;
13922 while(*p == ' ') p++;
13927 *blackPlaysFirst = FALSE;
13930 *blackPlaysFirst = TRUE;
13936 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13937 /* return the extra info in global variiables */
13939 /* set defaults in case FEN is incomplete */
13940 FENepStatus = EP_UNKNOWN;
13941 for(i=0; i<nrCastlingRights; i++ ) {
13942 FENcastlingRights[i] =
13943 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13944 } /* assume possible unless obviously impossible */
13945 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13946 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13947 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13948 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13949 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13950 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13951 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13952 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13955 while(*p==' ') p++;
13956 if(nrCastlingRights) {
13957 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13958 /* castling indicator present, so default becomes no castlings */
13959 for(i=0; i<nrCastlingRights; i++ ) {
13960 FENcastlingRights[i] = -1;
13963 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13964 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13965 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13966 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13967 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13969 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13970 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13971 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13973 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13974 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13975 if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13976 && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13977 if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13978 && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13981 for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13982 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13983 FENcastlingRights[2] = whiteKingFile;
13986 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13987 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13988 FENcastlingRights[2] = whiteKingFile;
13991 for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13992 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13993 FENcastlingRights[5] = blackKingFile;
13996 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13997 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13998 FENcastlingRights[5] = blackKingFile;
14001 default: /* FRC castlings */
14002 if(c >= 'a') { /* black rights */
14003 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14004 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14005 if(i == BOARD_RGHT) break;
14006 FENcastlingRights[5] = i;
14008 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14009 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14011 FENcastlingRights[3] = c;
14013 FENcastlingRights[4] = c;
14014 } else { /* white rights */
14015 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14016 if(board[0][i] == WhiteKing) break;
14017 if(i == BOARD_RGHT) break;
14018 FENcastlingRights[2] = i;
14019 c -= AAA - 'a' + 'A';
14020 if(board[0][c] >= WhiteKing) break;
14022 FENcastlingRights[0] = c;
14024 FENcastlingRights[1] = c;
14028 for(i=0; i<nrCastlingRights; i++)
14029 if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14030 if (appData.debugMode) {
14031 fprintf(debugFP, "FEN castling rights:");
14032 for(i=0; i<nrCastlingRights; i++)
14033 fprintf(debugFP, " %d", FENcastlingRights[i]);
14034 fprintf(debugFP, "\n");
14037 while(*p==' ') p++;
14040 /* read e.p. field in games that know e.p. capture */
14041 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14042 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14044 p++; FENepStatus = EP_NONE;
14046 char c = *p++ - AAA;
14048 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14049 if(*p >= '0' && *p <='9') *p++;
14055 if(sscanf(p, "%d", &i) == 1) {
14056 FENrulePlies = i; /* 50-move ply counter */
14057 /* (The move number is still ignored) */
14064 EditPositionPasteFEN(char *fen)
14067 Board initial_position;
14069 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14070 DisplayError(_("Bad FEN position in clipboard"), 0);
14073 int savedBlackPlaysFirst = blackPlaysFirst;
14074 EditPositionEvent();
14075 blackPlaysFirst = savedBlackPlaysFirst;
14076 CopyBoard(boards[0], initial_position);
14077 /* [HGM] copy FEN attributes as well */
14079 initialRulePlies = FENrulePlies;
14080 epStatus[0] = FENepStatus;
14081 for( i=0; i<nrCastlingRights; i++ )
14082 castlingRights[0][i] = FENcastlingRights[i];
14084 EditPositionDone(FALSE);
14085 DisplayBothClocks();
14086 DrawPosition(FALSE, boards[currentMove]);
14091 static char cseq[12] = "\\ ";
14093 Boolean set_cont_sequence(char *new_seq)
14098 // handle bad attempts to set the sequence
14100 return 0; // acceptable error - no debug
14102 len = strlen(new_seq);
14103 ret = (len > 0) && (len < sizeof(cseq));
14105 strcpy(cseq, new_seq);
14106 else if (appData.debugMode)
14107 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14112 reformat a source message so words don't cross the width boundary. internal
14113 newlines are not removed. returns the wrapped size (no null character unless
14114 included in source message). If dest is NULL, only calculate the size required
14115 for the dest buffer. lp argument indicats line position upon entry, and it's
14116 passed back upon exit.
14118 int wrap(char *dest, char *src, int count, int width, int *lp)
14120 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14122 cseq_len = strlen(cseq);
14123 old_line = line = *lp;
14124 ansi = len = clen = 0;
14126 for (i=0; i < count; i++)
14128 if (src[i] == '\033')
14131 // if we hit the width, back up
14132 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14134 // store i & len in case the word is too long
14135 old_i = i, old_len = len;
14137 // find the end of the last word
14138 while (i && src[i] != ' ' && src[i] != '\n')
14144 // word too long? restore i & len before splitting it
14145 if ((old_i-i+clen) >= width)
14152 if (i && src[i-1] == ' ')
14155 if (src[i] != ' ' && src[i] != '\n')
14162 // now append the newline and continuation sequence
14167 strncpy(dest+len, cseq, cseq_len);
14175 dest[len] = src[i];
14179 if (src[i] == '\n')
14184 if (dest && appData.debugMode)
14186 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14187 count, width, line, len, *lp);
14188 show_bytes(debugFP, src, count);
14189 fprintf(debugFP, "\ndest: ");
14190 show_bytes(debugFP, dest, len);
14191 fprintf(debugFP, "\n");
14193 *lp = dest ? line : old_line;