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;
2325 next_out = i+1; // [HGM] suppress printing in ICS window
2327 if(!suppressKibitz) // [HGM] kibitz
2328 AppendComment(forwardMostMove, StripHighlight(parse));
2329 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2330 int nrDigit = 0, nrAlph = 0, j;
2331 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2332 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2333 parse[parse_pos] = NULLCHAR;
2334 // try to be smart: if it does not look like search info, it should go to
2335 // ICS interaction window after all, not to engine-output window.
2336 for(j=0; j<parse_pos; j++) { // count letters and digits
2337 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2338 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2339 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2341 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2342 int depth=0; float score;
2343 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2344 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2345 pvInfoList[forwardMostMove-1].depth = depth;
2346 pvInfoList[forwardMostMove-1].score = 100*score;
2348 OutputKibitz(suppressKibitz, parse);
2351 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2352 SendToPlayer(tmp, strlen(tmp));
2354 next_out = i+1; // [HGM] suppress printing in ICS window
2356 started = STARTED_NONE;
2358 /* Don't match patterns against characters in comment */
2363 if (started == STARTED_CHATTER) {
2364 if (buf[i] != '\n') {
2365 /* Don't match patterns against characters in chatter */
2369 started = STARTED_NONE;
2370 if(suppressKibitz) next_out = i+1;
2373 /* Kludge to deal with rcmd protocol */
2374 if (firstTime && looking_at(buf, &i, "\001*")) {
2375 DisplayFatalError(&buf[1], 0, 1);
2381 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2384 if (appData.debugMode)
2385 fprintf(debugFP, "ics_type %d\n", ics_type);
2388 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2389 ics_type = ICS_FICS;
2391 if (appData.debugMode)
2392 fprintf(debugFP, "ics_type %d\n", ics_type);
2395 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2396 ics_type = ICS_CHESSNET;
2398 if (appData.debugMode)
2399 fprintf(debugFP, "ics_type %d\n", ics_type);
2404 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2405 looking_at(buf, &i, "Logging you in as \"*\"") ||
2406 looking_at(buf, &i, "will be \"*\""))) {
2407 strcpy(ics_handle, star_match[0]);
2411 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2413 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2414 DisplayIcsInteractionTitle(buf);
2415 have_set_title = TRUE;
2418 /* skip finger notes */
2419 if (started == STARTED_NONE &&
2420 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2421 (buf[i] == '1' && buf[i+1] == '0')) &&
2422 buf[i+2] == ':' && buf[i+3] == ' ') {
2423 started = STARTED_CHATTER;
2428 /* skip formula vars */
2429 if (started == STARTED_NONE &&
2430 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2431 started = STARTED_CHATTER;
2437 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2438 if (appData.autoKibitz && started == STARTED_NONE &&
2439 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2440 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2441 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "* kibitzes: ")) &&
2442 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2443 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2444 suppressKibitz = TRUE;
2445 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2447 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2448 && (gameMode == IcsPlayingWhite)) ||
2449 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2450 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2451 started = STARTED_CHATTER; // own kibitz we simply discard
2453 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2454 parse_pos = 0; parse[0] = NULLCHAR;
2455 savingComment = TRUE;
2456 suppressKibitz = gameMode != IcsObserving ? 2 :
2457 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2461 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2462 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2463 && atoi(star_match[0])) {
2464 // suppress the acknowledgements of our own autoKibitz
2466 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2467 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2468 SendToPlayer(star_match[0], strlen(star_match[0]));
2469 if(looking_at(buf, &i, "*% ")) // eat prompt
2470 suppressKibitz = FALSE;
2474 } // [HGM] kibitz: end of patch
2476 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2478 // [HGM] chat: intercept tells by users for which we have an open chat window
2480 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2481 looking_at(buf, &i, "* whispers:") ||
2482 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2483 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2484 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2485 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2487 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2488 chattingPartner = -1;
2490 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2491 for(p=0; p<MAX_CHAT; p++) {
2492 if(channel == atoi(chatPartner[p])) {
2493 talker[0] = '['; strcat(talker, "] ");
2494 chattingPartner = p; break;
2497 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2498 for(p=0; p<MAX_CHAT; p++) {
2499 if(!strcmp("WHISPER", chatPartner[p])) {
2500 talker[0] = '['; strcat(talker, "] ");
2501 chattingPartner = p; break;
2504 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2505 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2507 chattingPartner = p; break;
2509 if(chattingPartner<0) i = oldi; else {
2510 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2511 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2512 started = STARTED_COMMENT;
2513 parse_pos = 0; parse[0] = NULLCHAR;
2514 savingComment = 3 + chattingPartner; // counts as TRUE
2515 suppressKibitz = TRUE;
2517 } // [HGM] chat: end of patch
2519 if (appData.zippyTalk || appData.zippyPlay) {
2520 /* [DM] Backup address for color zippy lines */
2524 if (loggedOn == TRUE)
2525 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2526 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2528 if (ZippyControl(buf, &i) ||
2529 ZippyConverse(buf, &i) ||
2530 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2532 if (!appData.colorize) continue;
2536 } // [DM] 'else { ' deleted
2538 /* Regular tells and says */
2539 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2540 looking_at(buf, &i, "* (your partner) tells you: ") ||
2541 looking_at(buf, &i, "* says: ") ||
2542 /* Don't color "message" or "messages" output */
2543 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2544 looking_at(buf, &i, "*. * at *:*: ") ||
2545 looking_at(buf, &i, "--* (*:*): ") ||
2546 /* Message notifications (same color as tells) */
2547 looking_at(buf, &i, "* has left a message ") ||
2548 looking_at(buf, &i, "* just sent you a message:\n") ||
2549 /* Whispers and kibitzes */
2550 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2551 looking_at(buf, &i, "* kibitzes: ") ||
2553 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2555 if (tkind == 1 && strchr(star_match[0], ':')) {
2556 /* Avoid "tells you:" spoofs in channels */
2559 if (star_match[0][0] == NULLCHAR ||
2560 strchr(star_match[0], ' ') ||
2561 (tkind == 3 && strchr(star_match[1], ' '))) {
2562 /* Reject bogus matches */
2565 if (appData.colorize) {
2566 if (oldi > next_out) {
2567 SendToPlayer(&buf[next_out], oldi - next_out);
2572 Colorize(ColorTell, FALSE);
2573 curColor = ColorTell;
2576 Colorize(ColorKibitz, FALSE);
2577 curColor = ColorKibitz;
2580 p = strrchr(star_match[1], '(');
2587 Colorize(ColorChannel1, FALSE);
2588 curColor = ColorChannel1;
2590 Colorize(ColorChannel, FALSE);
2591 curColor = ColorChannel;
2595 curColor = ColorNormal;
2599 if (started == STARTED_NONE && appData.autoComment &&
2600 (gameMode == IcsObserving ||
2601 gameMode == IcsPlayingWhite ||
2602 gameMode == IcsPlayingBlack)) {
2603 parse_pos = i - oldi;
2604 memcpy(parse, &buf[oldi], parse_pos);
2605 parse[parse_pos] = NULLCHAR;
2606 started = STARTED_COMMENT;
2607 savingComment = TRUE;
2609 started = STARTED_CHATTER;
2610 savingComment = FALSE;
2617 if (looking_at(buf, &i, "* s-shouts: ") ||
2618 looking_at(buf, &i, "* c-shouts: ")) {
2619 if (appData.colorize) {
2620 if (oldi > next_out) {
2621 SendToPlayer(&buf[next_out], oldi - next_out);
2624 Colorize(ColorSShout, FALSE);
2625 curColor = ColorSShout;
2628 started = STARTED_CHATTER;
2632 if (looking_at(buf, &i, "--->")) {
2637 if (looking_at(buf, &i, "* shouts: ") ||
2638 looking_at(buf, &i, "--> ")) {
2639 if (appData.colorize) {
2640 if (oldi > next_out) {
2641 SendToPlayer(&buf[next_out], oldi - next_out);
2644 Colorize(ColorShout, FALSE);
2645 curColor = ColorShout;
2648 started = STARTED_CHATTER;
2652 if (looking_at( buf, &i, "Challenge:")) {
2653 if (appData.colorize) {
2654 if (oldi > next_out) {
2655 SendToPlayer(&buf[next_out], oldi - next_out);
2658 Colorize(ColorChallenge, FALSE);
2659 curColor = ColorChallenge;
2665 if (looking_at(buf, &i, "* offers you") ||
2666 looking_at(buf, &i, "* offers to be") ||
2667 looking_at(buf, &i, "* would like to") ||
2668 looking_at(buf, &i, "* requests to") ||
2669 looking_at(buf, &i, "Your opponent offers") ||
2670 looking_at(buf, &i, "Your opponent requests")) {
2672 if (appData.colorize) {
2673 if (oldi > next_out) {
2674 SendToPlayer(&buf[next_out], oldi - next_out);
2677 Colorize(ColorRequest, FALSE);
2678 curColor = ColorRequest;
2683 if (looking_at(buf, &i, "* (*) seeking")) {
2684 if (appData.colorize) {
2685 if (oldi > next_out) {
2686 SendToPlayer(&buf[next_out], oldi - next_out);
2689 Colorize(ColorSeek, FALSE);
2690 curColor = ColorSeek;
2695 if (looking_at(buf, &i, "\\ ")) {
2696 if (prevColor != ColorNormal) {
2697 if (oldi > next_out) {
2698 SendToPlayer(&buf[next_out], oldi - next_out);
2701 Colorize(prevColor, TRUE);
2702 curColor = prevColor;
2704 if (savingComment) {
2705 parse_pos = i - oldi;
2706 memcpy(parse, &buf[oldi], parse_pos);
2707 parse[parse_pos] = NULLCHAR;
2708 started = STARTED_COMMENT;
2709 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2710 chattingPartner = savingComment - 3; // kludge to remember the box
2712 started = STARTED_CHATTER;
2717 if (looking_at(buf, &i, "Black Strength :") ||
2718 looking_at(buf, &i, "<<< style 10 board >>>") ||
2719 looking_at(buf, &i, "<10>") ||
2720 looking_at(buf, &i, "#@#")) {
2721 /* Wrong board style */
2723 SendToICS(ics_prefix);
2724 SendToICS("set style 12\n");
2725 SendToICS(ics_prefix);
2726 SendToICS("refresh\n");
2730 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2732 have_sent_ICS_logon = 1;
2736 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2737 (looking_at(buf, &i, "\n<12> ") ||
2738 looking_at(buf, &i, "<12> "))) {
2740 if (oldi > next_out) {
2741 SendToPlayer(&buf[next_out], oldi - next_out);
2744 started = STARTED_BOARD;
2749 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2750 looking_at(buf, &i, "<b1> ")) {
2751 if (oldi > next_out) {
2752 SendToPlayer(&buf[next_out], oldi - next_out);
2755 started = STARTED_HOLDINGS;
2760 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2762 /* Header for a move list -- first line */
2764 switch (ics_getting_history) {
2768 case BeginningOfGame:
2769 /* User typed "moves" or "oldmoves" while we
2770 were idle. Pretend we asked for these
2771 moves and soak them up so user can step
2772 through them and/or save them.
2775 gameMode = IcsObserving;
2778 ics_getting_history = H_GOT_UNREQ_HEADER;
2780 case EditGame: /*?*/
2781 case EditPosition: /*?*/
2782 /* Should above feature work in these modes too? */
2783 /* For now it doesn't */
2784 ics_getting_history = H_GOT_UNWANTED_HEADER;
2787 ics_getting_history = H_GOT_UNWANTED_HEADER;
2792 /* Is this the right one? */
2793 if (gameInfo.white && gameInfo.black &&
2794 strcmp(gameInfo.white, star_match[0]) == 0 &&
2795 strcmp(gameInfo.black, star_match[2]) == 0) {
2797 ics_getting_history = H_GOT_REQ_HEADER;
2800 case H_GOT_REQ_HEADER:
2801 case H_GOT_UNREQ_HEADER:
2802 case H_GOT_UNWANTED_HEADER:
2803 case H_GETTING_MOVES:
2804 /* Should not happen */
2805 DisplayError(_("Error gathering move list: two headers"), 0);
2806 ics_getting_history = H_FALSE;
2810 /* Save player ratings into gameInfo if needed */
2811 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2812 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2813 (gameInfo.whiteRating == -1 ||
2814 gameInfo.blackRating == -1)) {
2816 gameInfo.whiteRating = string_to_rating(star_match[1]);
2817 gameInfo.blackRating = string_to_rating(star_match[3]);
2818 if (appData.debugMode)
2819 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2820 gameInfo.whiteRating, gameInfo.blackRating);
2825 if (looking_at(buf, &i,
2826 "* * match, initial time: * minute*, increment: * second")) {
2827 /* Header for a move list -- second line */
2828 /* Initial board will follow if this is a wild game */
2829 if (gameInfo.event != NULL) free(gameInfo.event);
2830 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2831 gameInfo.event = StrSave(str);
2832 /* [HGM] we switched variant. Translate boards if needed. */
2833 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2837 if (looking_at(buf, &i, "Move ")) {
2838 /* Beginning of a move list */
2839 switch (ics_getting_history) {
2841 /* Normally should not happen */
2842 /* Maybe user hit reset while we were parsing */
2845 /* Happens if we are ignoring a move list that is not
2846 * the one we just requested. Common if the user
2847 * tries to observe two games without turning off
2850 case H_GETTING_MOVES:
2851 /* Should not happen */
2852 DisplayError(_("Error gathering move list: nested"), 0);
2853 ics_getting_history = H_FALSE;
2855 case H_GOT_REQ_HEADER:
2856 ics_getting_history = H_GETTING_MOVES;
2857 started = STARTED_MOVES;
2859 if (oldi > next_out) {
2860 SendToPlayer(&buf[next_out], oldi - next_out);
2863 case H_GOT_UNREQ_HEADER:
2864 ics_getting_history = H_GETTING_MOVES;
2865 started = STARTED_MOVES_NOHIDE;
2868 case H_GOT_UNWANTED_HEADER:
2869 ics_getting_history = H_FALSE;
2875 if (looking_at(buf, &i, "% ") ||
2876 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2877 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2878 if(suppressKibitz) next_out = i;
2879 savingComment = FALSE;
2883 case STARTED_MOVES_NOHIDE:
2884 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2885 parse[parse_pos + i - oldi] = NULLCHAR;
2886 ParseGameHistory(parse);
2888 if (appData.zippyPlay && first.initDone) {
2889 FeedMovesToProgram(&first, forwardMostMove);
2890 if (gameMode == IcsPlayingWhite) {
2891 if (WhiteOnMove(forwardMostMove)) {
2892 if (first.sendTime) {
2893 if (first.useColors) {
2894 SendToProgram("black\n", &first);
2896 SendTimeRemaining(&first, TRUE);
2898 if (first.useColors) {
2899 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2901 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2902 first.maybeThinking = TRUE;
2904 if (first.usePlayother) {
2905 if (first.sendTime) {
2906 SendTimeRemaining(&first, TRUE);
2908 SendToProgram("playother\n", &first);
2914 } else if (gameMode == IcsPlayingBlack) {
2915 if (!WhiteOnMove(forwardMostMove)) {
2916 if (first.sendTime) {
2917 if (first.useColors) {
2918 SendToProgram("white\n", &first);
2920 SendTimeRemaining(&first, FALSE);
2922 if (first.useColors) {
2923 SendToProgram("black\n", &first);
2925 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2926 first.maybeThinking = TRUE;
2928 if (first.usePlayother) {
2929 if (first.sendTime) {
2930 SendTimeRemaining(&first, FALSE);
2932 SendToProgram("playother\n", &first);
2941 if (gameMode == IcsObserving && ics_gamenum == -1) {
2942 /* Moves came from oldmoves or moves command
2943 while we weren't doing anything else.
2945 currentMove = forwardMostMove;
2946 ClearHighlights();/*!!could figure this out*/
2947 flipView = appData.flipView;
2948 DrawPosition(TRUE, boards[currentMove]);
2949 DisplayBothClocks();
2950 sprintf(str, "%s vs. %s",
2951 gameInfo.white, gameInfo.black);
2955 /* Moves were history of an active game */
2956 if (gameInfo.resultDetails != NULL) {
2957 free(gameInfo.resultDetails);
2958 gameInfo.resultDetails = NULL;
2961 HistorySet(parseList, backwardMostMove,
2962 forwardMostMove, currentMove-1);
2963 DisplayMove(currentMove - 1);
2964 if (started == STARTED_MOVES) next_out = i;
2965 started = STARTED_NONE;
2966 ics_getting_history = H_FALSE;
2969 case STARTED_OBSERVE:
2970 started = STARTED_NONE;
2971 SendToICS(ics_prefix);
2972 SendToICS("refresh\n");
2978 if(bookHit) { // [HGM] book: simulate book reply
2979 static char bookMove[MSG_SIZ]; // a bit generous?
2981 programStats.nodes = programStats.depth = programStats.time =
2982 programStats.score = programStats.got_only_move = 0;
2983 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2985 strcpy(bookMove, "move ");
2986 strcat(bookMove, bookHit);
2987 HandleMachineMove(bookMove, &first);
2992 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2993 started == STARTED_HOLDINGS ||
2994 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2995 /* Accumulate characters in move list or board */
2996 parse[parse_pos++] = buf[i];
2999 /* Start of game messages. Mostly we detect start of game
3000 when the first board image arrives. On some versions
3001 of the ICS, though, we need to do a "refresh" after starting
3002 to observe in order to get the current board right away. */
3003 if (looking_at(buf, &i, "Adding game * to observation list")) {
3004 started = STARTED_OBSERVE;
3008 /* Handle auto-observe */
3009 if (appData.autoObserve &&
3010 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3011 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3013 /* Choose the player that was highlighted, if any. */
3014 if (star_match[0][0] == '\033' ||
3015 star_match[1][0] != '\033') {
3016 player = star_match[0];
3018 player = star_match[2];
3020 sprintf(str, "%sobserve %s\n",
3021 ics_prefix, StripHighlightAndTitle(player));
3024 /* Save ratings from notify string */
3025 strcpy(player1Name, star_match[0]);
3026 player1Rating = string_to_rating(star_match[1]);
3027 strcpy(player2Name, star_match[2]);
3028 player2Rating = string_to_rating(star_match[3]);
3030 if (appData.debugMode)
3032 "Ratings from 'Game notification:' %s %d, %s %d\n",
3033 player1Name, player1Rating,
3034 player2Name, player2Rating);
3039 /* Deal with automatic examine mode after a game,
3040 and with IcsObserving -> IcsExamining transition */
3041 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3042 looking_at(buf, &i, "has made you an examiner of game *")) {
3044 int gamenum = atoi(star_match[0]);
3045 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3046 gamenum == ics_gamenum) {
3047 /* We were already playing or observing this game;
3048 no need to refetch history */
3049 gameMode = IcsExamining;
3051 pauseExamForwardMostMove = forwardMostMove;
3052 } else if (currentMove < forwardMostMove) {
3053 ForwardInner(forwardMostMove);
3056 /* I don't think this case really can happen */
3057 SendToICS(ics_prefix);
3058 SendToICS("refresh\n");
3063 /* Error messages */
3064 // if (ics_user_moved) {
3065 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3066 if (looking_at(buf, &i, "Illegal move") ||
3067 looking_at(buf, &i, "Not a legal move") ||
3068 looking_at(buf, &i, "Your king is in check") ||
3069 looking_at(buf, &i, "It isn't your turn") ||
3070 looking_at(buf, &i, "It is not your move")) {
3072 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3073 currentMove = forwardMostMove-1;
3074 DisplayMove(currentMove - 1); /* before DMError */
3075 DrawPosition(FALSE, boards[currentMove]);
3076 SwitchClocks(forwardMostMove-1); // [HGM] race
3077 DisplayBothClocks();
3079 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3085 if (looking_at(buf, &i, "still have time") ||
3086 looking_at(buf, &i, "not out of time") ||
3087 looking_at(buf, &i, "either player is out of time") ||
3088 looking_at(buf, &i, "has timeseal; checking")) {
3089 /* We must have called his flag a little too soon */
3090 whiteFlag = blackFlag = FALSE;
3094 if (looking_at(buf, &i, "added * seconds to") ||
3095 looking_at(buf, &i, "seconds were added to")) {
3096 /* Update the clocks */
3097 SendToICS(ics_prefix);
3098 SendToICS("refresh\n");
3102 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3103 ics_clock_paused = TRUE;
3108 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3109 ics_clock_paused = FALSE;
3114 /* Grab player ratings from the Creating: message.
3115 Note we have to check for the special case when
3116 the ICS inserts things like [white] or [black]. */
3117 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3118 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3120 0 player 1 name (not necessarily white)
3122 2 empty, white, or black (IGNORED)
3123 3 player 2 name (not necessarily black)
3126 The names/ratings are sorted out when the game
3127 actually starts (below).
3129 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3130 player1Rating = string_to_rating(star_match[1]);
3131 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3132 player2Rating = string_to_rating(star_match[4]);
3134 if (appData.debugMode)
3136 "Ratings from 'Creating:' %s %d, %s %d\n",
3137 player1Name, player1Rating,
3138 player2Name, player2Rating);
3143 /* Improved generic start/end-of-game messages */
3144 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3145 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3146 /* If tkind == 0: */
3147 /* star_match[0] is the game number */
3148 /* [1] is the white player's name */
3149 /* [2] is the black player's name */
3150 /* For end-of-game: */
3151 /* [3] is the reason for the game end */
3152 /* [4] is a PGN end game-token, preceded by " " */
3153 /* For start-of-game: */
3154 /* [3] begins with "Creating" or "Continuing" */
3155 /* [4] is " *" or empty (don't care). */
3156 int gamenum = atoi(star_match[0]);
3157 char *whitename, *blackname, *why, *endtoken;
3158 ChessMove endtype = (ChessMove) 0;
3161 whitename = star_match[1];
3162 blackname = star_match[2];
3163 why = star_match[3];
3164 endtoken = star_match[4];
3166 whitename = star_match[1];
3167 blackname = star_match[3];
3168 why = star_match[5];
3169 endtoken = star_match[6];
3172 /* Game start messages */
3173 if (strncmp(why, "Creating ", 9) == 0 ||
3174 strncmp(why, "Continuing ", 11) == 0) {
3175 gs_gamenum = gamenum;
3176 strcpy(gs_kind, strchr(why, ' ') + 1);
3177 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3179 if (appData.zippyPlay) {
3180 ZippyGameStart(whitename, blackname);
3186 /* Game end messages */
3187 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3188 ics_gamenum != gamenum) {
3191 while (endtoken[0] == ' ') endtoken++;
3192 switch (endtoken[0]) {
3195 endtype = GameUnfinished;
3198 endtype = BlackWins;
3201 if (endtoken[1] == '/')
3202 endtype = GameIsDrawn;
3204 endtype = WhiteWins;
3207 GameEnds(endtype, why, GE_ICS);
3209 if (appData.zippyPlay && first.initDone) {
3210 ZippyGameEnd(endtype, why);
3211 if (first.pr == NULL) {
3212 /* Start the next process early so that we'll
3213 be ready for the next challenge */
3214 StartChessProgram(&first);
3216 /* Send "new" early, in case this command takes
3217 a long time to finish, so that we'll be ready
3218 for the next challenge. */
3219 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3226 if (looking_at(buf, &i, "Removing game * from observation") ||
3227 looking_at(buf, &i, "no longer observing game *") ||
3228 looking_at(buf, &i, "Game * (*) has no examiners")) {
3229 if (gameMode == IcsObserving &&
3230 atoi(star_match[0]) == ics_gamenum)
3232 /* icsEngineAnalyze */
3233 if (appData.icsEngineAnalyze) {
3240 ics_user_moved = FALSE;
3245 if (looking_at(buf, &i, "no longer examining game *")) {
3246 if (gameMode == IcsExamining &&
3247 atoi(star_match[0]) == ics_gamenum)
3251 ics_user_moved = FALSE;
3256 /* Advance leftover_start past any newlines we find,
3257 so only partial lines can get reparsed */
3258 if (looking_at(buf, &i, "\n")) {
3259 prevColor = curColor;
3260 if (curColor != ColorNormal) {
3261 if (oldi > next_out) {
3262 SendToPlayer(&buf[next_out], oldi - next_out);
3265 Colorize(ColorNormal, FALSE);
3266 curColor = ColorNormal;
3268 if (started == STARTED_BOARD) {
3269 started = STARTED_NONE;
3270 parse[parse_pos] = NULLCHAR;
3271 ParseBoard12(parse);
3274 /* Send premove here */
3275 if (appData.premove) {
3277 if (currentMove == 0 &&
3278 gameMode == IcsPlayingWhite &&
3279 appData.premoveWhite) {
3280 sprintf(str, "%s\n", appData.premoveWhiteText);
3281 if (appData.debugMode)
3282 fprintf(debugFP, "Sending premove:\n");
3284 } else if (currentMove == 1 &&
3285 gameMode == IcsPlayingBlack &&
3286 appData.premoveBlack) {
3287 sprintf(str, "%s\n", appData.premoveBlackText);
3288 if (appData.debugMode)
3289 fprintf(debugFP, "Sending premove:\n");
3291 } else if (gotPremove) {
3293 ClearPremoveHighlights();
3294 if (appData.debugMode)
3295 fprintf(debugFP, "Sending premove:\n");
3296 UserMoveEvent(premoveFromX, premoveFromY,
3297 premoveToX, premoveToY,
3302 /* Usually suppress following prompt */
3303 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3304 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3305 if (looking_at(buf, &i, "*% ")) {
3306 savingComment = FALSE;
3311 } else if (started == STARTED_HOLDINGS) {
3313 char new_piece[MSG_SIZ];
3314 started = STARTED_NONE;
3315 parse[parse_pos] = NULLCHAR;
3316 if (appData.debugMode)
3317 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3318 parse, currentMove);
3319 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3320 gamenum == ics_gamenum) {
3321 if (gameInfo.variant == VariantNormal) {
3322 /* [HGM] We seem to switch variant during a game!
3323 * Presumably no holdings were displayed, so we have
3324 * to move the position two files to the right to
3325 * create room for them!
3327 VariantClass newVariant;
3328 switch(gameInfo.boardWidth) { // base guess on board width
3329 case 9: newVariant = VariantShogi; break;
3330 case 10: newVariant = VariantGreat; break;
3331 default: newVariant = VariantCrazyhouse; break;
3333 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3334 /* Get a move list just to see the header, which
3335 will tell us whether this is really bug or zh */
3336 if (ics_getting_history == H_FALSE) {
3337 ics_getting_history = H_REQUESTED;
3338 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3342 new_piece[0] = NULLCHAR;
3343 sscanf(parse, "game %d white [%s black [%s <- %s",
3344 &gamenum, white_holding, black_holding,
3346 white_holding[strlen(white_holding)-1] = NULLCHAR;
3347 black_holding[strlen(black_holding)-1] = NULLCHAR;
3348 /* [HGM] copy holdings to board holdings area */
3349 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3350 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3351 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3353 if (appData.zippyPlay && first.initDone) {
3354 ZippyHoldings(white_holding, black_holding,
3358 if (tinyLayout || smallLayout) {
3359 char wh[16], bh[16];
3360 PackHolding(wh, white_holding);
3361 PackHolding(bh, black_holding);
3362 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3363 gameInfo.white, gameInfo.black);
3365 sprintf(str, "%s [%s] vs. %s [%s]",
3366 gameInfo.white, white_holding,
3367 gameInfo.black, black_holding);
3370 DrawPosition(FALSE, boards[currentMove]);
3373 /* Suppress following prompt */
3374 if (looking_at(buf, &i, "*% ")) {
3375 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3376 savingComment = FALSE;
3384 i++; /* skip unparsed character and loop back */
3387 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3388 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3389 // SendToPlayer(&buf[next_out], i - next_out);
3390 started != STARTED_HOLDINGS && leftover_start > next_out) {
3391 SendToPlayer(&buf[next_out], leftover_start - next_out);
3395 leftover_len = buf_len - leftover_start;
3396 /* if buffer ends with something we couldn't parse,
3397 reparse it after appending the next read */
3399 } else if (count == 0) {
3400 RemoveInputSource(isr);
3401 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3403 DisplayFatalError(_("Error reading from ICS"), error, 1);
3408 /* Board style 12 looks like this:
3410 <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
3412 * The "<12> " is stripped before it gets to this routine. The two
3413 * trailing 0's (flip state and clock ticking) are later addition, and
3414 * some chess servers may not have them, or may have only the first.
3415 * Additional trailing fields may be added in the future.
3418 #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"
3420 #define RELATION_OBSERVING_PLAYED 0
3421 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3422 #define RELATION_PLAYING_MYMOVE 1
3423 #define RELATION_PLAYING_NOTMYMOVE -1
3424 #define RELATION_EXAMINING 2
3425 #define RELATION_ISOLATED_BOARD -3
3426 #define RELATION_STARTING_POSITION -4 /* FICS only */
3429 ParseBoard12(string)
3432 GameMode newGameMode;
3433 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3434 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3435 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3436 char to_play, board_chars[200];
3437 char move_str[500], str[500], elapsed_time[500];
3438 char black[32], white[32];
3440 int prevMove = currentMove;
3443 int fromX, fromY, toX, toY;
3445 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3446 char *bookHit = NULL; // [HGM] book
3447 Boolean weird = FALSE, reqFlag = FALSE;
3449 fromX = fromY = toX = toY = -1;
3453 if (appData.debugMode)
3454 fprintf(debugFP, _("Parsing board: %s\n"), string);
3456 move_str[0] = NULLCHAR;
3457 elapsed_time[0] = NULLCHAR;
3458 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3460 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3461 if(string[i] == ' ') { ranks++; files = 0; }
3463 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3466 for(j = 0; j <i; j++) board_chars[j] = string[j];
3467 board_chars[i] = '\0';
3470 n = sscanf(string, PATTERN, &to_play, &double_push,
3471 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3472 &gamenum, white, black, &relation, &basetime, &increment,
3473 &white_stren, &black_stren, &white_time, &black_time,
3474 &moveNum, str, elapsed_time, move_str, &ics_flip,
3478 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3479 DisplayError(str, 0);
3483 /* Convert the move number to internal form */
3484 moveNum = (moveNum - 1) * 2;
3485 if (to_play == 'B') moveNum++;
3486 if (moveNum >= MAX_MOVES) {
3487 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3493 case RELATION_OBSERVING_PLAYED:
3494 case RELATION_OBSERVING_STATIC:
3495 if (gamenum == -1) {
3496 /* Old ICC buglet */
3497 relation = RELATION_OBSERVING_STATIC;
3499 newGameMode = IcsObserving;
3501 case RELATION_PLAYING_MYMOVE:
3502 case RELATION_PLAYING_NOTMYMOVE:
3504 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3505 IcsPlayingWhite : IcsPlayingBlack;
3507 case RELATION_EXAMINING:
3508 newGameMode = IcsExamining;
3510 case RELATION_ISOLATED_BOARD:
3512 /* Just display this board. If user was doing something else,
3513 we will forget about it until the next board comes. */
3514 newGameMode = IcsIdle;
3516 case RELATION_STARTING_POSITION:
3517 newGameMode = gameMode;
3521 /* Modify behavior for initial board display on move listing
3524 switch (ics_getting_history) {
3528 case H_GOT_REQ_HEADER:
3529 case H_GOT_UNREQ_HEADER:
3530 /* This is the initial position of the current game */
3531 gamenum = ics_gamenum;
3532 moveNum = 0; /* old ICS bug workaround */
3533 if (to_play == 'B') {
3534 startedFromSetupPosition = TRUE;
3535 blackPlaysFirst = TRUE;
3537 if (forwardMostMove == 0) forwardMostMove = 1;
3538 if (backwardMostMove == 0) backwardMostMove = 1;
3539 if (currentMove == 0) currentMove = 1;
3541 newGameMode = gameMode;
3542 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3544 case H_GOT_UNWANTED_HEADER:
3545 /* This is an initial board that we don't want */
3547 case H_GETTING_MOVES:
3548 /* Should not happen */
3549 DisplayError(_("Error gathering move list: extra board"), 0);
3550 ics_getting_history = H_FALSE;
3554 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3555 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3556 /* [HGM] We seem to have switched variant unexpectedly
3557 * Try to guess new variant from board size
3559 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3560 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3561 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3562 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3563 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3564 if(!weird) newVariant = VariantNormal;
3565 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3566 /* Get a move list just to see the header, which
3567 will tell us whether this is really bug or zh */
3568 if (ics_getting_history == H_FALSE) {
3569 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3570 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3575 /* Take action if this is the first board of a new game, or of a
3576 different game than is currently being displayed. */
3577 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3578 relation == RELATION_ISOLATED_BOARD) {
3580 /* Forget the old game and get the history (if any) of the new one */
3581 if (gameMode != BeginningOfGame) {
3585 if (appData.autoRaiseBoard) BoardToTop();
3587 if (gamenum == -1) {
3588 newGameMode = IcsIdle;
3589 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3590 appData.getMoveList && !reqFlag) {
3591 /* Need to get game history */
3592 ics_getting_history = H_REQUESTED;
3593 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3597 /* Initially flip the board to have black on the bottom if playing
3598 black or if the ICS flip flag is set, but let the user change
3599 it with the Flip View button. */
3600 flipView = appData.autoFlipView ?
3601 (newGameMode == IcsPlayingBlack) || ics_flip :
3604 /* Done with values from previous mode; copy in new ones */
3605 gameMode = newGameMode;
3607 ics_gamenum = gamenum;
3608 if (gamenum == gs_gamenum) {
3609 int klen = strlen(gs_kind);
3610 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3611 sprintf(str, "ICS %s", gs_kind);
3612 gameInfo.event = StrSave(str);
3614 gameInfo.event = StrSave("ICS game");
3616 gameInfo.site = StrSave(appData.icsHost);
3617 gameInfo.date = PGNDate();
3618 gameInfo.round = StrSave("-");
3619 gameInfo.white = StrSave(white);
3620 gameInfo.black = StrSave(black);
3621 timeControl = basetime * 60 * 1000;
3623 timeIncrement = increment * 1000;
3624 movesPerSession = 0;
3625 gameInfo.timeControl = TimeControlTagValue();
3626 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3627 if (appData.debugMode) {
3628 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3629 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3630 setbuf(debugFP, NULL);
3633 gameInfo.outOfBook = NULL;
3635 /* Do we have the ratings? */
3636 if (strcmp(player1Name, white) == 0 &&
3637 strcmp(player2Name, black) == 0) {
3638 if (appData.debugMode)
3639 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3640 player1Rating, player2Rating);
3641 gameInfo.whiteRating = player1Rating;
3642 gameInfo.blackRating = player2Rating;
3643 } else if (strcmp(player2Name, white) == 0 &&
3644 strcmp(player1Name, black) == 0) {
3645 if (appData.debugMode)
3646 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3647 player2Rating, player1Rating);
3648 gameInfo.whiteRating = player2Rating;
3649 gameInfo.blackRating = player1Rating;
3651 player1Name[0] = player2Name[0] = NULLCHAR;
3653 /* Silence shouts if requested */
3654 if (appData.quietPlay &&
3655 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3656 SendToICS(ics_prefix);
3657 SendToICS("set shout 0\n");
3661 /* Deal with midgame name changes */
3663 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3664 if (gameInfo.white) free(gameInfo.white);
3665 gameInfo.white = StrSave(white);
3667 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3668 if (gameInfo.black) free(gameInfo.black);
3669 gameInfo.black = StrSave(black);
3673 /* Throw away game result if anything actually changes in examine mode */
3674 if (gameMode == IcsExamining && !newGame) {
3675 gameInfo.result = GameUnfinished;
3676 if (gameInfo.resultDetails != NULL) {
3677 free(gameInfo.resultDetails);
3678 gameInfo.resultDetails = NULL;
3682 /* In pausing && IcsExamining mode, we ignore boards coming
3683 in if they are in a different variation than we are. */
3684 if (pauseExamInvalid) return;
3685 if (pausing && gameMode == IcsExamining) {
3686 if (moveNum <= pauseExamForwardMostMove) {
3687 pauseExamInvalid = TRUE;
3688 forwardMostMove = pauseExamForwardMostMove;
3693 if (appData.debugMode) {
3694 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3696 /* Parse the board */
3697 for (k = 0; k < ranks; k++) {
3698 for (j = 0; j < files; j++)
3699 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3700 if(gameInfo.holdingsWidth > 1) {
3701 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3702 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3705 CopyBoard(boards[moveNum], board);
3706 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3708 startedFromSetupPosition =
3709 !CompareBoards(board, initialPosition);
3710 if(startedFromSetupPosition)
3711 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3714 /* [HGM] Set castling rights. Take the outermost Rooks,
3715 to make it also work for FRC opening positions. Note that board12
3716 is really defective for later FRC positions, as it has no way to
3717 indicate which Rook can castle if they are on the same side of King.
3718 For the initial position we grant rights to the outermost Rooks,
3719 and remember thos rights, and we then copy them on positions
3720 later in an FRC game. This means WB might not recognize castlings with
3721 Rooks that have moved back to their original position as illegal,
3722 but in ICS mode that is not its job anyway.
3724 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3725 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3727 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3728 if(board[0][i] == WhiteRook) j = i;
3729 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3730 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3731 if(board[0][i] == WhiteRook) j = i;
3732 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3733 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3734 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3735 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3736 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3737 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3738 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3740 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3741 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3742 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3743 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3744 if(board[BOARD_HEIGHT-1][k] == bKing)
3745 initialRights[5] = castlingRights[moveNum][5] = k;
3746 if(gameInfo.variant == VariantTwoKings) {
3747 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3748 if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3749 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3752 r = castlingRights[moveNum][0] = initialRights[0];
3753 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3754 r = castlingRights[moveNum][1] = initialRights[1];
3755 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3756 r = castlingRights[moveNum][3] = initialRights[3];
3757 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3758 r = castlingRights[moveNum][4] = initialRights[4];
3759 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3760 /* wildcastle kludge: always assume King has rights */
3761 r = castlingRights[moveNum][2] = initialRights[2];
3762 r = castlingRights[moveNum][5] = initialRights[5];
3764 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3765 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3768 if (ics_getting_history == H_GOT_REQ_HEADER ||
3769 ics_getting_history == H_GOT_UNREQ_HEADER) {
3770 /* This was an initial position from a move list, not
3771 the current position */
3775 /* Update currentMove and known move number limits */
3776 newMove = newGame || moveNum > forwardMostMove;
3779 forwardMostMove = backwardMostMove = currentMove = moveNum;
3780 if (gameMode == IcsExamining && moveNum == 0) {
3781 /* Workaround for ICS limitation: we are not told the wild
3782 type when starting to examine a game. But if we ask for
3783 the move list, the move list header will tell us */
3784 ics_getting_history = H_REQUESTED;
3785 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3788 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3789 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3791 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3792 /* [HGM] applied this also to an engine that is silently watching */
3793 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3794 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3795 gameInfo.variant == currentlyInitializedVariant) {
3796 takeback = forwardMostMove - moveNum;
3797 for (i = 0; i < takeback; i++) {
3798 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3799 SendToProgram("undo\n", &first);
3804 forwardMostMove = moveNum;
3805 if (!pausing || currentMove > forwardMostMove)
3806 currentMove = forwardMostMove;
3808 /* New part of history that is not contiguous with old part */
3809 if (pausing && gameMode == IcsExamining) {
3810 pauseExamInvalid = TRUE;
3811 forwardMostMove = pauseExamForwardMostMove;
3814 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3816 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3817 // [HGM] when we will receive the move list we now request, it will be
3818 // fed to the engine from the first move on. So if the engine is not
3819 // in the initial position now, bring it there.
3820 InitChessProgram(&first, 0);
3823 ics_getting_history = H_REQUESTED;
3824 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3827 forwardMostMove = backwardMostMove = currentMove = moveNum;
3830 /* Update the clocks */
3831 if (strchr(elapsed_time, '.')) {
3833 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3834 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3836 /* Time is in seconds */
3837 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3838 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3843 if (appData.zippyPlay && newGame &&
3844 gameMode != IcsObserving && gameMode != IcsIdle &&
3845 gameMode != IcsExamining)
3846 ZippyFirstBoard(moveNum, basetime, increment);
3849 /* Put the move on the move list, first converting
3850 to canonical algebraic form. */
3852 if (appData.debugMode) {
3853 if (appData.debugMode) { int f = forwardMostMove;
3854 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3855 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3857 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3858 fprintf(debugFP, "moveNum = %d\n", moveNum);
3859 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3860 setbuf(debugFP, NULL);
3862 if (moveNum <= backwardMostMove) {
3863 /* We don't know what the board looked like before
3865 strcpy(parseList[moveNum - 1], move_str);
3866 strcat(parseList[moveNum - 1], " ");
3867 strcat(parseList[moveNum - 1], elapsed_time);
3868 moveList[moveNum - 1][0] = NULLCHAR;
3869 } else if (strcmp(move_str, "none") == 0) {
3870 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3871 /* Again, we don't know what the board looked like;
3872 this is really the start of the game. */
3873 parseList[moveNum - 1][0] = NULLCHAR;
3874 moveList[moveNum - 1][0] = NULLCHAR;
3875 backwardMostMove = moveNum;
3876 startedFromSetupPosition = TRUE;
3877 fromX = fromY = toX = toY = -1;
3879 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3880 // So we parse the long-algebraic move string in stead of the SAN move
3881 int valid; char buf[MSG_SIZ], *prom;
3883 // str looks something like "Q/a1-a2"; kill the slash
3885 sprintf(buf, "%c%s", str[0], str+2);
3886 else strcpy(buf, str); // might be castling
3887 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3888 strcat(buf, prom); // long move lacks promo specification!
3889 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3890 if(appData.debugMode)
3891 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3892 strcpy(move_str, buf);
3894 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3895 &fromX, &fromY, &toX, &toY, &promoChar)
3896 || ParseOneMove(buf, moveNum - 1, &moveType,
3897 &fromX, &fromY, &toX, &toY, &promoChar);
3898 // end of long SAN patch
3900 (void) CoordsToAlgebraic(boards[moveNum - 1],
3901 PosFlags(moveNum - 1), EP_UNKNOWN,
3902 fromY, fromX, toY, toX, promoChar,
3903 parseList[moveNum-1]);
3904 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3905 castlingRights[moveNum]) ) {
3911 if(gameInfo.variant != VariantShogi)
3912 strcat(parseList[moveNum - 1], "+");
3915 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3916 strcat(parseList[moveNum - 1], "#");
3919 strcat(parseList[moveNum - 1], " ");
3920 strcat(parseList[moveNum - 1], elapsed_time);
3921 /* currentMoveString is set as a side-effect of ParseOneMove */
3922 strcpy(moveList[moveNum - 1], currentMoveString);
3923 strcat(moveList[moveNum - 1], "\n");
3925 /* Move from ICS was illegal!? Punt. */
3926 if (appData.debugMode) {
3927 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3928 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3930 strcpy(parseList[moveNum - 1], move_str);
3931 strcat(parseList[moveNum - 1], " ");
3932 strcat(parseList[moveNum - 1], elapsed_time);
3933 moveList[moveNum - 1][0] = NULLCHAR;
3934 fromX = fromY = toX = toY = -1;
3937 if (appData.debugMode) {
3938 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3939 setbuf(debugFP, NULL);
3943 /* Send move to chess program (BEFORE animating it). */
3944 if (appData.zippyPlay && !newGame && newMove &&
3945 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3947 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3948 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3949 if (moveList[moveNum - 1][0] == NULLCHAR) {
3950 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3952 DisplayError(str, 0);
3954 if (first.sendTime) {
3955 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3957 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3958 if (firstMove && !bookHit) {
3960 if (first.useColors) {
3961 SendToProgram(gameMode == IcsPlayingWhite ?
3963 "black\ngo\n", &first);
3965 SendToProgram("go\n", &first);
3967 first.maybeThinking = TRUE;
3970 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3971 if (moveList[moveNum - 1][0] == NULLCHAR) {
3972 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3973 DisplayError(str, 0);
3975 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3976 SendMoveToProgram(moveNum - 1, &first);
3983 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3984 /* If move comes from a remote source, animate it. If it
3985 isn't remote, it will have already been animated. */
3986 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3987 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3989 if (!pausing && appData.highlightLastMove) {
3990 SetHighlights(fromX, fromY, toX, toY);
3994 /* Start the clocks */
3995 whiteFlag = blackFlag = FALSE;
3996 appData.clockMode = !(basetime == 0 && increment == 0);
3998 ics_clock_paused = TRUE;
4000 } else if (ticking == 1) {
4001 ics_clock_paused = FALSE;
4003 if (gameMode == IcsIdle ||
4004 relation == RELATION_OBSERVING_STATIC ||
4005 relation == RELATION_EXAMINING ||
4007 DisplayBothClocks();
4011 /* Display opponents and material strengths */
4012 if (gameInfo.variant != VariantBughouse &&
4013 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4014 if (tinyLayout || smallLayout) {
4015 if(gameInfo.variant == VariantNormal)
4016 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4017 gameInfo.white, white_stren, gameInfo.black, black_stren,
4018 basetime, increment);
4020 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4021 gameInfo.white, white_stren, gameInfo.black, black_stren,
4022 basetime, increment, (int) gameInfo.variant);
4024 if(gameInfo.variant == VariantNormal)
4025 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4026 gameInfo.white, white_stren, gameInfo.black, black_stren,
4027 basetime, increment);
4029 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4030 gameInfo.white, white_stren, gameInfo.black, black_stren,
4031 basetime, increment, VariantName(gameInfo.variant));
4034 if (appData.debugMode) {
4035 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4040 /* Display the board */
4041 if (!pausing && !appData.noGUI) {
4043 if (appData.premove)
4045 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4046 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4047 ClearPremoveHighlights();
4049 DrawPosition(FALSE, boards[currentMove]);
4050 DisplayMove(moveNum - 1);
4051 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4052 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4053 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4054 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4058 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4060 if(bookHit) { // [HGM] book: simulate book reply
4061 static char bookMove[MSG_SIZ]; // a bit generous?
4063 programStats.nodes = programStats.depth = programStats.time =
4064 programStats.score = programStats.got_only_move = 0;
4065 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4067 strcpy(bookMove, "move ");
4068 strcat(bookMove, bookHit);
4069 HandleMachineMove(bookMove, &first);
4078 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4079 ics_getting_history = H_REQUESTED;
4080 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4086 AnalysisPeriodicEvent(force)
4089 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4090 && !force) || !appData.periodicUpdates)
4093 /* Send . command to Crafty to collect stats */
4094 SendToProgram(".\n", &first);
4096 /* Don't send another until we get a response (this makes
4097 us stop sending to old Crafty's which don't understand
4098 the "." command (sending illegal cmds resets node count & time,
4099 which looks bad)) */
4100 programStats.ok_to_send = 0;
4103 void ics_update_width(new_width)
4106 ics_printf("set width %d\n", new_width);
4110 SendMoveToProgram(moveNum, cps)
4112 ChessProgramState *cps;
4116 if (cps->useUsermove) {
4117 SendToProgram("usermove ", cps);
4121 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4122 int len = space - parseList[moveNum];
4123 memcpy(buf, parseList[moveNum], len);
4125 buf[len] = NULLCHAR;
4127 sprintf(buf, "%s\n", parseList[moveNum]);
4129 SendToProgram(buf, cps);
4131 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4132 AlphaRank(moveList[moveNum], 4);
4133 SendToProgram(moveList[moveNum], cps);
4134 AlphaRank(moveList[moveNum], 4); // and back
4136 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4137 * the engine. It would be nice to have a better way to identify castle
4139 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4140 && cps->useOOCastle) {
4141 int fromX = moveList[moveNum][0] - AAA;
4142 int fromY = moveList[moveNum][1] - ONE;
4143 int toX = moveList[moveNum][2] - AAA;
4144 int toY = moveList[moveNum][3] - ONE;
4145 if((boards[moveNum][fromY][fromX] == WhiteKing
4146 && boards[moveNum][toY][toX] == WhiteRook)
4147 || (boards[moveNum][fromY][fromX] == BlackKing
4148 && boards[moveNum][toY][toX] == BlackRook)) {
4149 if(toX > fromX) SendToProgram("O-O\n", cps);
4150 else SendToProgram("O-O-O\n", cps);
4152 else SendToProgram(moveList[moveNum], cps);
4154 else SendToProgram(moveList[moveNum], cps);
4155 /* End of additions by Tord */
4158 /* [HGM] setting up the opening has brought engine in force mode! */
4159 /* Send 'go' if we are in a mode where machine should play. */
4160 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4161 (gameMode == TwoMachinesPlay ||
4163 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4165 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4166 SendToProgram("go\n", cps);
4167 if (appData.debugMode) {
4168 fprintf(debugFP, "(extra)\n");
4171 setboardSpoiledMachineBlack = 0;
4175 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4177 int fromX, fromY, toX, toY;
4179 char user_move[MSG_SIZ];
4183 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4184 (int)moveType, fromX, fromY, toX, toY);
4185 DisplayError(user_move + strlen("say "), 0);
4187 case WhiteKingSideCastle:
4188 case BlackKingSideCastle:
4189 case WhiteQueenSideCastleWild:
4190 case BlackQueenSideCastleWild:
4192 case WhiteHSideCastleFR:
4193 case BlackHSideCastleFR:
4195 sprintf(user_move, "o-o\n");
4197 case WhiteQueenSideCastle:
4198 case BlackQueenSideCastle:
4199 case WhiteKingSideCastleWild:
4200 case BlackKingSideCastleWild:
4202 case WhiteASideCastleFR:
4203 case BlackASideCastleFR:
4205 sprintf(user_move, "o-o-o\n");
4207 case WhitePromotionQueen:
4208 case BlackPromotionQueen:
4209 case WhitePromotionRook:
4210 case BlackPromotionRook:
4211 case WhitePromotionBishop:
4212 case BlackPromotionBishop:
4213 case WhitePromotionKnight:
4214 case BlackPromotionKnight:
4215 case WhitePromotionKing:
4216 case BlackPromotionKing:
4217 case WhitePromotionChancellor:
4218 case BlackPromotionChancellor:
4219 case WhitePromotionArchbishop:
4220 case BlackPromotionArchbishop:
4221 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4222 sprintf(user_move, "%c%c%c%c=%c\n",
4223 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4224 PieceToChar(WhiteFerz));
4225 else if(gameInfo.variant == VariantGreat)
4226 sprintf(user_move, "%c%c%c%c=%c\n",
4227 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4228 PieceToChar(WhiteMan));
4230 sprintf(user_move, "%c%c%c%c=%c\n",
4231 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4232 PieceToChar(PromoPiece(moveType)));
4236 sprintf(user_move, "%c@%c%c\n",
4237 ToUpper(PieceToChar((ChessSquare) fromX)),
4238 AAA + toX, ONE + toY);
4241 case WhiteCapturesEnPassant:
4242 case BlackCapturesEnPassant:
4243 case IllegalMove: /* could be a variant we don't quite understand */
4244 sprintf(user_move, "%c%c%c%c\n",
4245 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4248 SendToICS(user_move);
4249 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4250 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4254 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4259 if (rf == DROP_RANK) {
4260 sprintf(move, "%c@%c%c\n",
4261 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4263 if (promoChar == 'x' || promoChar == NULLCHAR) {
4264 sprintf(move, "%c%c%c%c\n",
4265 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4267 sprintf(move, "%c%c%c%c%c\n",
4268 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4274 ProcessICSInitScript(f)
4279 while (fgets(buf, MSG_SIZ, f)) {
4280 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4287 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4289 AlphaRank(char *move, int n)
4291 // char *p = move, c; int x, y;
4293 if (appData.debugMode) {
4294 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4298 move[2]>='0' && move[2]<='9' &&
4299 move[3]>='a' && move[3]<='x' ) {
4301 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4302 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4304 if(move[0]>='0' && move[0]<='9' &&
4305 move[1]>='a' && move[1]<='x' &&
4306 move[2]>='0' && move[2]<='9' &&
4307 move[3]>='a' && move[3]<='x' ) {
4308 /* input move, Shogi -> normal */
4309 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4310 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4311 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4312 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4315 move[3]>='0' && move[3]<='9' &&
4316 move[2]>='a' && move[2]<='x' ) {
4318 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4319 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4322 move[0]>='a' && move[0]<='x' &&
4323 move[3]>='0' && move[3]<='9' &&
4324 move[2]>='a' && move[2]<='x' ) {
4325 /* output move, normal -> Shogi */
4326 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4327 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4328 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4329 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4330 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4332 if (appData.debugMode) {
4333 fprintf(debugFP, " out = '%s'\n", move);
4337 /* Parser for moves from gnuchess, ICS, or user typein box */
4339 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4342 ChessMove *moveType;
4343 int *fromX, *fromY, *toX, *toY;
4346 if (appData.debugMode) {
4347 fprintf(debugFP, "move to parse: %s\n", move);
4349 *moveType = yylexstr(moveNum, move);
4351 switch (*moveType) {
4352 case WhitePromotionChancellor:
4353 case BlackPromotionChancellor:
4354 case WhitePromotionArchbishop:
4355 case BlackPromotionArchbishop:
4356 case WhitePromotionQueen:
4357 case BlackPromotionQueen:
4358 case WhitePromotionRook:
4359 case BlackPromotionRook:
4360 case WhitePromotionBishop:
4361 case BlackPromotionBishop:
4362 case WhitePromotionKnight:
4363 case BlackPromotionKnight:
4364 case WhitePromotionKing:
4365 case BlackPromotionKing:
4367 case WhiteCapturesEnPassant:
4368 case BlackCapturesEnPassant:
4369 case WhiteKingSideCastle:
4370 case WhiteQueenSideCastle:
4371 case BlackKingSideCastle:
4372 case BlackQueenSideCastle:
4373 case WhiteKingSideCastleWild:
4374 case WhiteQueenSideCastleWild:
4375 case BlackKingSideCastleWild:
4376 case BlackQueenSideCastleWild:
4377 /* Code added by Tord: */
4378 case WhiteHSideCastleFR:
4379 case WhiteASideCastleFR:
4380 case BlackHSideCastleFR:
4381 case BlackASideCastleFR:
4382 /* End of code added by Tord */
4383 case IllegalMove: /* bug or odd chess variant */
4384 *fromX = currentMoveString[0] - AAA;
4385 *fromY = currentMoveString[1] - ONE;
4386 *toX = currentMoveString[2] - AAA;
4387 *toY = currentMoveString[3] - ONE;
4388 *promoChar = currentMoveString[4];
4389 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4390 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4391 if (appData.debugMode) {
4392 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4394 *fromX = *fromY = *toX = *toY = 0;
4397 if (appData.testLegality) {
4398 return (*moveType != IllegalMove);
4400 return !(*fromX == *toX && *fromY == *toY);
4405 *fromX = *moveType == WhiteDrop ?
4406 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4407 (int) CharToPiece(ToLower(currentMoveString[0]));
4409 *toX = currentMoveString[2] - AAA;
4410 *toY = currentMoveString[3] - ONE;
4411 *promoChar = NULLCHAR;
4415 case ImpossibleMove:
4416 case (ChessMove) 0: /* end of file */
4425 if (appData.debugMode) {
4426 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4429 *fromX = *fromY = *toX = *toY = 0;
4430 *promoChar = NULLCHAR;
4435 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4436 // All positions will have equal probability, but the current method will not provide a unique
4437 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4443 int piecesLeft[(int)BlackPawn];
4444 int seed, nrOfShuffles;
4446 void GetPositionNumber()
4447 { // sets global variable seed
4450 seed = appData.defaultFrcPosition;
4451 if(seed < 0) { // randomize based on time for negative FRC position numbers
4452 for(i=0; i<50; i++) seed += random();
4453 seed = random() ^ random() >> 8 ^ random() << 8;
4454 if(seed<0) seed = -seed;
4458 int put(Board board, int pieceType, int rank, int n, int shade)
4459 // put the piece on the (n-1)-th empty squares of the given shade
4463 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4464 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4465 board[rank][i] = (ChessSquare) pieceType;
4466 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4468 piecesLeft[pieceType]--;
4476 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4477 // calculate where the next piece goes, (any empty square), and put it there
4481 i = seed % squaresLeft[shade];
4482 nrOfShuffles *= squaresLeft[shade];
4483 seed /= squaresLeft[shade];
4484 put(board, pieceType, rank, i, shade);
4487 void AddTwoPieces(Board board, int pieceType, int rank)
4488 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4490 int i, n=squaresLeft[ANY], j=n-1, k;
4492 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4493 i = seed % k; // pick one
4496 while(i >= j) i -= j--;
4497 j = n - 1 - j; i += j;
4498 put(board, pieceType, rank, j, ANY);
4499 put(board, pieceType, rank, i, ANY);
4502 void SetUpShuffle(Board board, int number)
4506 GetPositionNumber(); nrOfShuffles = 1;
4508 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4509 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4510 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4512 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4514 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4515 p = (int) board[0][i];
4516 if(p < (int) BlackPawn) piecesLeft[p] ++;
4517 board[0][i] = EmptySquare;
4520 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4521 // shuffles restricted to allow normal castling put KRR first
4522 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4523 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4524 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4525 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4526 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4527 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4528 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4529 put(board, WhiteRook, 0, 0, ANY);
4530 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4533 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4534 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4535 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4536 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4537 while(piecesLeft[p] >= 2) {
4538 AddOnePiece(board, p, 0, LITE);
4539 AddOnePiece(board, p, 0, DARK);
4541 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4544 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4545 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4546 // but we leave King and Rooks for last, to possibly obey FRC restriction
4547 if(p == (int)WhiteRook) continue;
4548 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4549 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4552 // now everything is placed, except perhaps King (Unicorn) and Rooks
4554 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4555 // Last King gets castling rights
4556 while(piecesLeft[(int)WhiteUnicorn]) {
4557 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4558 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4561 while(piecesLeft[(int)WhiteKing]) {
4562 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4563 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4568 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4569 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4572 // Only Rooks can be left; simply place them all
4573 while(piecesLeft[(int)WhiteRook]) {
4574 i = put(board, WhiteRook, 0, 0, ANY);
4575 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4578 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4580 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4583 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4584 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4587 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4590 int SetCharTable( char *table, const char * map )
4591 /* [HGM] moved here from winboard.c because of its general usefulness */
4592 /* Basically a safe strcpy that uses the last character as King */
4594 int result = FALSE; int NrPieces;
4596 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4597 && NrPieces >= 12 && !(NrPieces&1)) {
4598 int i; /* [HGM] Accept even length from 12 to 34 */
4600 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4601 for( i=0; i<NrPieces/2-1; i++ ) {
4603 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4605 table[(int) WhiteKing] = map[NrPieces/2-1];
4606 table[(int) BlackKing] = map[NrPieces-1];
4614 void Prelude(Board board)
4615 { // [HGM] superchess: random selection of exo-pieces
4616 int i, j, k; ChessSquare p;
4617 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4619 GetPositionNumber(); // use FRC position number
4621 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4622 SetCharTable(pieceToChar, appData.pieceToCharTable);
4623 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4624 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4627 j = seed%4; seed /= 4;
4628 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4629 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4630 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4631 j = seed%3 + (seed%3 >= j); seed /= 3;
4632 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4633 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4634 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4635 j = seed%3; seed /= 3;
4636 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4637 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4638 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4639 j = seed%2 + (seed%2 >= j); seed /= 2;
4640 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4641 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4642 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4643 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4644 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4645 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4646 put(board, exoPieces[0], 0, 0, ANY);
4647 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4651 InitPosition(redraw)
4654 ChessSquare (* pieces)[BOARD_SIZE];
4655 int i, j, pawnRow, overrule,
4656 oldx = gameInfo.boardWidth,
4657 oldy = gameInfo.boardHeight,
4658 oldh = gameInfo.holdingsWidth,
4659 oldv = gameInfo.variant;
4661 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4663 /* [AS] Initialize pv info list [HGM] and game status */
4665 for( i=0; i<MAX_MOVES; i++ ) {
4666 pvInfoList[i].depth = 0;
4667 epStatus[i]=EP_NONE;
4668 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4671 initialRulePlies = 0; /* 50-move counter start */
4673 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4674 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4678 /* [HGM] logic here is completely changed. In stead of full positions */
4679 /* the initialized data only consist of the two backranks. The switch */
4680 /* selects which one we will use, which is than copied to the Board */
4681 /* initialPosition, which for the rest is initialized by Pawns and */
4682 /* empty squares. This initial position is then copied to boards[0], */
4683 /* possibly after shuffling, so that it remains available. */
4685 gameInfo.holdingsWidth = 0; /* default board sizes */
4686 gameInfo.boardWidth = 8;
4687 gameInfo.boardHeight = 8;
4688 gameInfo.holdingsSize = 0;
4689 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4690 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4691 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4693 switch (gameInfo.variant) {
4694 case VariantFischeRandom:
4695 shuffleOpenings = TRUE;
4699 case VariantShatranj:
4700 pieces = ShatranjArray;
4701 nrCastlingRights = 0;
4702 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4705 pieces = makrukArray;
4706 nrCastlingRights = 0;
4707 startedFromSetupPosition = TRUE;
4708 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4710 case VariantTwoKings:
4711 pieces = twoKingsArray;
4713 case VariantCapaRandom:
4714 shuffleOpenings = TRUE;
4715 case VariantCapablanca:
4716 pieces = CapablancaArray;
4717 gameInfo.boardWidth = 10;
4718 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4721 pieces = GothicArray;
4722 gameInfo.boardWidth = 10;
4723 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4726 pieces = JanusArray;
4727 gameInfo.boardWidth = 10;
4728 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4729 nrCastlingRights = 6;
4730 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4731 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4732 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4733 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4734 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4735 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4738 pieces = FalconArray;
4739 gameInfo.boardWidth = 10;
4740 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4742 case VariantXiangqi:
4743 pieces = XiangqiArray;
4744 gameInfo.boardWidth = 9;
4745 gameInfo.boardHeight = 10;
4746 nrCastlingRights = 0;
4747 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4750 pieces = ShogiArray;
4751 gameInfo.boardWidth = 9;
4752 gameInfo.boardHeight = 9;
4753 gameInfo.holdingsSize = 7;
4754 nrCastlingRights = 0;
4755 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4757 case VariantCourier:
4758 pieces = CourierArray;
4759 gameInfo.boardWidth = 12;
4760 nrCastlingRights = 0;
4761 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4762 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4764 case VariantKnightmate:
4765 pieces = KnightmateArray;
4766 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4769 pieces = fairyArray;
4770 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4773 pieces = GreatArray;
4774 gameInfo.boardWidth = 10;
4775 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4776 gameInfo.holdingsSize = 8;
4780 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4781 gameInfo.holdingsSize = 8;
4782 startedFromSetupPosition = TRUE;
4784 case VariantCrazyhouse:
4785 case VariantBughouse:
4787 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4788 gameInfo.holdingsSize = 5;
4790 case VariantWildCastle:
4792 /* !!?shuffle with kings guaranteed to be on d or e file */
4793 shuffleOpenings = 1;
4795 case VariantNoCastle:
4797 nrCastlingRights = 0;
4798 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4799 /* !!?unconstrained back-rank shuffle */
4800 shuffleOpenings = 1;
4805 if(appData.NrFiles >= 0) {
4806 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4807 gameInfo.boardWidth = appData.NrFiles;
4809 if(appData.NrRanks >= 0) {
4810 gameInfo.boardHeight = appData.NrRanks;
4812 if(appData.holdingsSize >= 0) {
4813 i = appData.holdingsSize;
4814 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4815 gameInfo.holdingsSize = i;
4817 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4818 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4819 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4821 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4822 if(pawnRow < 1) pawnRow = 1;
4823 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4825 /* User pieceToChar list overrules defaults */
4826 if(appData.pieceToCharTable != NULL)
4827 SetCharTable(pieceToChar, appData.pieceToCharTable);
4829 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4831 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4832 s = (ChessSquare) 0; /* account holding counts in guard band */
4833 for( i=0; i<BOARD_HEIGHT; i++ )
4834 initialPosition[i][j] = s;
4836 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4837 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4838 initialPosition[pawnRow][j] = WhitePawn;
4839 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4840 if(gameInfo.variant == VariantXiangqi) {
4842 initialPosition[pawnRow][j] =
4843 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4844 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4845 initialPosition[2][j] = WhiteCannon;
4846 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4850 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4852 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4855 initialPosition[1][j] = WhiteBishop;
4856 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4858 initialPosition[1][j] = WhiteRook;
4859 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4862 if( nrCastlingRights == -1) {
4863 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4864 /* This sets default castling rights from none to normal corners */
4865 /* Variants with other castling rights must set them themselves above */
4866 nrCastlingRights = 6;
4868 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4869 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4870 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4871 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4872 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4873 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4876 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4877 if(gameInfo.variant == VariantGreat) { // promotion commoners
4878 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4879 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4880 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4881 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4883 if (appData.debugMode) {
4884 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4886 if(shuffleOpenings) {
4887 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4888 startedFromSetupPosition = TRUE;
4890 if(startedFromPositionFile) {
4891 /* [HGM] loadPos: use PositionFile for every new game */
4892 CopyBoard(initialPosition, filePosition);
4893 for(i=0; i<nrCastlingRights; i++)
4894 castlingRights[0][i] = initialRights[i] = fileRights[i];
4895 startedFromSetupPosition = TRUE;
4898 CopyBoard(boards[0], initialPosition);
4900 if(oldx != gameInfo.boardWidth ||
4901 oldy != gameInfo.boardHeight ||
4902 oldh != gameInfo.holdingsWidth
4904 || oldv == VariantGothic || // For licensing popups
4905 gameInfo.variant == VariantGothic
4908 || oldv == VariantFalcon ||
4909 gameInfo.variant == VariantFalcon
4912 InitDrawingSizes(-2 ,0);
4915 DrawPosition(TRUE, boards[currentMove]);
4919 SendBoard(cps, moveNum)
4920 ChessProgramState *cps;
4923 char message[MSG_SIZ];
4925 if (cps->useSetboard) {
4926 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4927 sprintf(message, "setboard %s\n", fen);
4928 SendToProgram(message, cps);
4934 /* Kludge to set black to move, avoiding the troublesome and now
4935 * deprecated "black" command.
4937 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4939 SendToProgram("edit\n", cps);
4940 SendToProgram("#\n", cps);
4941 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4942 bp = &boards[moveNum][i][BOARD_LEFT];
4943 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4944 if ((int) *bp < (int) BlackPawn) {
4945 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4947 if(message[0] == '+' || message[0] == '~') {
4948 sprintf(message, "%c%c%c+\n",
4949 PieceToChar((ChessSquare)(DEMOTED *bp)),
4952 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4953 message[1] = BOARD_RGHT - 1 - j + '1';
4954 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4956 SendToProgram(message, cps);
4961 SendToProgram("c\n", cps);
4962 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4963 bp = &boards[moveNum][i][BOARD_LEFT];
4964 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4965 if (((int) *bp != (int) EmptySquare)
4966 && ((int) *bp >= (int) BlackPawn)) {
4967 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4969 if(message[0] == '+' || message[0] == '~') {
4970 sprintf(message, "%c%c%c+\n",
4971 PieceToChar((ChessSquare)(DEMOTED *bp)),
4974 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4975 message[1] = BOARD_RGHT - 1 - j + '1';
4976 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4978 SendToProgram(message, cps);
4983 SendToProgram(".\n", cps);
4985 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4989 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4991 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4992 /* [HGM] add Shogi promotions */
4993 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4998 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4999 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5001 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5002 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5005 piece = boards[currentMove][fromY][fromX];
5006 if(gameInfo.variant == VariantShogi) {
5007 promotionZoneSize = 3;
5008 highestPromotingPiece = (int)WhiteFerz;
5009 } else if(gameInfo.variant == VariantMakruk) {
5010 promotionZoneSize = 3;
5013 // next weed out all moves that do not touch the promotion zone at all
5014 if((int)piece >= BlackPawn) {
5015 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5017 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5019 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5020 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5023 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5025 // weed out mandatory Shogi promotions
5026 if(gameInfo.variant == VariantShogi) {
5027 if(piece >= BlackPawn) {
5028 if(toY == 0 && piece == BlackPawn ||
5029 toY == 0 && piece == BlackQueen ||
5030 toY <= 1 && piece == BlackKnight) {
5035 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5036 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5037 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5044 // weed out obviously illegal Pawn moves
5045 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5046 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5047 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5048 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5049 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5050 // note we are not allowed to test for valid (non-)capture, due to premove
5053 // we either have a choice what to promote to, or (in Shogi) whether to promote
5054 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5055 *promoChoice = PieceToChar(BlackFerz); // no choice
5058 if(appData.alwaysPromoteToQueen) { // predetermined
5059 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5060 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5061 else *promoChoice = PieceToChar(BlackQueen);
5065 // suppress promotion popup on illegal moves that are not premoves
5066 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5067 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5068 if(appData.testLegality && !premove) {
5069 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5070 epStatus[currentMove], castlingRights[currentMove],
5071 fromY, fromX, toY, toX, NULLCHAR);
5072 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5073 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5081 InPalace(row, column)
5083 { /* [HGM] for Xiangqi */
5084 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5085 column < (BOARD_WIDTH + 4)/2 &&
5086 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5091 PieceForSquare (x, y)
5095 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5098 return boards[currentMove][y][x];
5102 OKToStartUserMove(x, y)
5105 ChessSquare from_piece;
5108 if (matchMode) return FALSE;
5109 if (gameMode == EditPosition) return TRUE;
5111 if (x >= 0 && y >= 0)
5112 from_piece = boards[currentMove][y][x];
5114 from_piece = EmptySquare;
5116 if (from_piece == EmptySquare) return FALSE;
5118 white_piece = (int)from_piece >= (int)WhitePawn &&
5119 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5122 case PlayFromGameFile:
5124 case TwoMachinesPlay:
5132 case MachinePlaysWhite:
5133 case IcsPlayingBlack:
5134 if (appData.zippyPlay) return FALSE;
5136 DisplayMoveError(_("You are playing Black"));
5141 case MachinePlaysBlack:
5142 case IcsPlayingWhite:
5143 if (appData.zippyPlay) return FALSE;
5145 DisplayMoveError(_("You are playing White"));
5151 if (!white_piece && WhiteOnMove(currentMove)) {
5152 DisplayMoveError(_("It is White's turn"));
5155 if (white_piece && !WhiteOnMove(currentMove)) {
5156 DisplayMoveError(_("It is Black's turn"));
5159 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5160 /* Editing correspondence game history */
5161 /* Could disallow this or prompt for confirmation */
5164 if (currentMove < forwardMostMove) {
5165 /* Discarding moves */
5166 /* Could prompt for confirmation here,
5167 but I don't think that's such a good idea */
5168 forwardMostMove = currentMove;
5172 case BeginningOfGame:
5173 if (appData.icsActive) return FALSE;
5174 if (!appData.noChessProgram) {
5176 DisplayMoveError(_("You are playing White"));
5183 if (!white_piece && WhiteOnMove(currentMove)) {
5184 DisplayMoveError(_("It is White's turn"));
5187 if (white_piece && !WhiteOnMove(currentMove)) {
5188 DisplayMoveError(_("It is Black's turn"));
5197 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5198 && gameMode != AnalyzeFile && gameMode != Training) {
5199 DisplayMoveError(_("Displayed position is not current"));
5205 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5206 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5207 int lastLoadGameUseList = FALSE;
5208 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5209 ChessMove lastLoadGameStart = (ChessMove) 0;
5212 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5213 int fromX, fromY, toX, toY;
5218 ChessSquare pdown, pup;
5220 /* Check if the user is playing in turn. This is complicated because we
5221 let the user "pick up" a piece before it is his turn. So the piece he
5222 tried to pick up may have been captured by the time he puts it down!
5223 Therefore we use the color the user is supposed to be playing in this
5224 test, not the color of the piece that is currently on the starting
5225 square---except in EditGame mode, where the user is playing both
5226 sides; fortunately there the capture race can't happen. (It can
5227 now happen in IcsExamining mode, but that's just too bad. The user
5228 will get a somewhat confusing message in that case.)
5232 case PlayFromGameFile:
5234 case TwoMachinesPlay:
5238 /* We switched into a game mode where moves are not accepted,
5239 perhaps while the mouse button was down. */
5240 return ImpossibleMove;
5242 case MachinePlaysWhite:
5243 /* User is moving for Black */
5244 if (WhiteOnMove(currentMove)) {
5245 DisplayMoveError(_("It is White's turn"));
5246 return ImpossibleMove;
5250 case MachinePlaysBlack:
5251 /* User is moving for White */
5252 if (!WhiteOnMove(currentMove)) {
5253 DisplayMoveError(_("It is Black's turn"));
5254 return ImpossibleMove;
5260 case BeginningOfGame:
5263 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5264 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5265 /* User is moving for Black */
5266 if (WhiteOnMove(currentMove)) {
5267 DisplayMoveError(_("It is White's turn"));
5268 return ImpossibleMove;
5271 /* User is moving for White */
5272 if (!WhiteOnMove(currentMove)) {
5273 DisplayMoveError(_("It is Black's turn"));
5274 return ImpossibleMove;
5279 case IcsPlayingBlack:
5280 /* User is moving for Black */
5281 if (WhiteOnMove(currentMove)) {
5282 if (!appData.premove) {
5283 DisplayMoveError(_("It is White's turn"));
5284 } else if (toX >= 0 && toY >= 0) {
5287 premoveFromX = fromX;
5288 premoveFromY = fromY;
5289 premovePromoChar = promoChar;
5291 if (appData.debugMode)
5292 fprintf(debugFP, "Got premove: fromX %d,"
5293 "fromY %d, toX %d, toY %d\n",
5294 fromX, fromY, toX, toY);
5296 return ImpossibleMove;
5300 case IcsPlayingWhite:
5301 /* User is moving for White */
5302 if (!WhiteOnMove(currentMove)) {
5303 if (!appData.premove) {
5304 DisplayMoveError(_("It is Black's turn"));
5305 } else if (toX >= 0 && toY >= 0) {
5308 premoveFromX = fromX;
5309 premoveFromY = fromY;
5310 premovePromoChar = promoChar;
5312 if (appData.debugMode)
5313 fprintf(debugFP, "Got premove: fromX %d,"
5314 "fromY %d, toX %d, toY %d\n",
5315 fromX, fromY, toX, toY);
5317 return ImpossibleMove;
5325 /* EditPosition, empty square, or different color piece;
5326 click-click move is possible */
5327 if (toX == -2 || toY == -2) {
5328 boards[0][fromY][fromX] = EmptySquare;
5329 return AmbiguousMove;
5330 } else if (toX >= 0 && toY >= 0) {
5331 boards[0][toY][toX] = boards[0][fromY][fromX];
5332 boards[0][fromY][fromX] = EmptySquare;
5333 return AmbiguousMove;
5335 return ImpossibleMove;
5338 if(toX < 0 || toY < 0) return ImpossibleMove;
5339 pdown = boards[currentMove][fromY][fromX];
5340 pup = boards[currentMove][toY][toX];
5342 /* [HGM] If move started in holdings, it means a drop */
5343 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5344 if( pup != EmptySquare ) return ImpossibleMove;
5345 if(appData.testLegality) {
5346 /* it would be more logical if LegalityTest() also figured out
5347 * which drops are legal. For now we forbid pawns on back rank.
5348 * Shogi is on its own here...
5350 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5351 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5352 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5354 return WhiteDrop; /* Not needed to specify white or black yet */
5357 userOfferedDraw = FALSE;
5359 /* [HGM] always test for legality, to get promotion info */
5360 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5361 epStatus[currentMove], castlingRights[currentMove],
5362 fromY, fromX, toY, toX, promoChar);
5363 /* [HGM] but possibly ignore an IllegalMove result */
5364 if (appData.testLegality) {
5365 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5366 DisplayMoveError(_("Illegal move"));
5367 return ImpossibleMove;
5372 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5373 function is made into one that returns an OK move type if FinishMove
5374 should be called. This to give the calling driver routine the
5375 opportunity to finish the userMove input with a promotion popup,
5376 without bothering the user with this for invalid or illegal moves */
5378 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5381 /* Common tail of UserMoveEvent and DropMenuEvent */
5383 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5385 int fromX, fromY, toX, toY;
5386 /*char*/int promoChar;
5390 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5391 // [HGM] superchess: suppress promotions to non-available piece
5392 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5393 if(WhiteOnMove(currentMove)) {
5394 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5396 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5400 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5401 move type in caller when we know the move is a legal promotion */
5402 if(moveType == NormalMove && promoChar)
5403 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5405 /* [HGM] convert drag-and-drop piece drops to standard form */
5406 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5407 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5408 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5409 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5410 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5411 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5412 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5413 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5417 /* [HGM] <popupFix> The following if has been moved here from
5418 UserMoveEvent(). Because it seemed to belong here (why not allow
5419 piece drops in training games?), and because it can only be
5420 performed after it is known to what we promote. */
5421 if (gameMode == Training) {
5422 /* compare the move played on the board to the next move in the
5423 * game. If they match, display the move and the opponent's response.
5424 * If they don't match, display an error message.
5427 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5428 CopyBoard(testBoard, boards[currentMove]);
5429 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5431 if (CompareBoards(testBoard, boards[currentMove+1])) {
5432 ForwardInner(currentMove+1);
5434 /* Autoplay the opponent's response.
5435 * if appData.animate was TRUE when Training mode was entered,
5436 * the response will be animated.
5438 saveAnimate = appData.animate;
5439 appData.animate = animateTraining;
5440 ForwardInner(currentMove+1);
5441 appData.animate = saveAnimate;
5443 /* check for the end of the game */
5444 if (currentMove >= forwardMostMove) {
5445 gameMode = PlayFromGameFile;
5447 SetTrainingModeOff();
5448 DisplayInformation(_("End of game"));
5451 DisplayError(_("Incorrect move"), 0);
5456 /* Ok, now we know that the move is good, so we can kill
5457 the previous line in Analysis Mode */
5458 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5459 forwardMostMove = currentMove;
5462 /* If we need the chess program but it's dead, restart it */
5463 ResurrectChessProgram();
5465 /* A user move restarts a paused game*/
5469 thinkOutput[0] = NULLCHAR;
5471 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5473 if (gameMode == BeginningOfGame) {
5474 if (appData.noChessProgram) {
5475 gameMode = EditGame;
5479 gameMode = MachinePlaysBlack;
5482 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5484 if (first.sendName) {
5485 sprintf(buf, "name %s\n", gameInfo.white);
5486 SendToProgram(buf, &first);
5493 /* Relay move to ICS or chess engine */
5494 if (appData.icsActive) {
5495 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5496 gameMode == IcsExamining) {
5497 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5501 if (first.sendTime && (gameMode == BeginningOfGame ||
5502 gameMode == MachinePlaysWhite ||
5503 gameMode == MachinePlaysBlack)) {
5504 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5506 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5507 // [HGM] book: if program might be playing, let it use book
5508 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5509 first.maybeThinking = TRUE;
5510 } else SendMoveToProgram(forwardMostMove-1, &first);
5511 if (currentMove == cmailOldMove + 1) {
5512 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5516 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5520 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5521 EP_UNKNOWN, castlingRights[currentMove]) ) {
5527 if (WhiteOnMove(currentMove)) {
5528 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5530 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5534 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5539 case MachinePlaysBlack:
5540 case MachinePlaysWhite:
5541 /* disable certain menu options while machine is thinking */
5542 SetMachineThinkingEnables();
5549 if(bookHit) { // [HGM] book: simulate book reply
5550 static char bookMove[MSG_SIZ]; // a bit generous?
5552 programStats.nodes = programStats.depth = programStats.time =
5553 programStats.score = programStats.got_only_move = 0;
5554 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5556 strcpy(bookMove, "move ");
5557 strcat(bookMove, bookHit);
5558 HandleMachineMove(bookMove, &first);
5564 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5565 int fromX, fromY, toX, toY;
5568 /* [HGM] This routine was added to allow calling of its two logical
5569 parts from other modules in the old way. Before, UserMoveEvent()
5570 automatically called FinishMove() if the move was OK, and returned
5571 otherwise. I separated the two, in order to make it possible to
5572 slip a promotion popup in between. But that it always needs two
5573 calls, to the first part, (now called UserMoveTest() ), and to
5574 FinishMove if the first part succeeded. Calls that do not need
5575 to do anything in between, can call this routine the old way.
5577 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5578 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5579 if(moveType == AmbiguousMove)
5580 DrawPosition(FALSE, boards[currentMove]);
5581 else if(moveType != ImpossibleMove && moveType != Comment)
5582 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5585 void LeftClick(ClickType clickType, int xPix, int yPix)
5588 Boolean saveAnimate;
5589 static int second = 0, promotionChoice = 0;
5590 char promoChoice = NULLCHAR;
5592 if (clickType == Press) ErrorPopDown();
5594 x = EventToSquare(xPix, BOARD_WIDTH);
5595 y = EventToSquare(yPix, BOARD_HEIGHT);
5596 if (!flipView && y >= 0) {
5597 y = BOARD_HEIGHT - 1 - y;
5599 if (flipView && x >= 0) {
5600 x = BOARD_WIDTH - 1 - x;
5603 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5604 if(clickType == Release) return; // ignore upclick of click-click destination
5605 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5606 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5607 if(gameInfo.holdingsWidth &&
5608 (WhiteOnMove(currentMove)
5609 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5610 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5611 // click in right holdings, for determining promotion piece
5612 ChessSquare p = boards[currentMove][y][x];
5613 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5614 if(p != EmptySquare) {
5615 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5620 DrawPosition(FALSE, boards[currentMove]);
5624 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5625 if(clickType == Press
5626 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5627 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5628 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5632 if (clickType == Press) {
5634 if (OKToStartUserMove(x, y)) {
5638 DragPieceBegin(xPix, yPix);
5639 if (appData.highlightDragging) {
5640 SetHighlights(x, y, -1, -1);
5648 if (clickType == Press && gameMode != EditPosition) {
5653 // ignore off-board to clicks
5654 if(y < 0 || x < 0) return;
5656 /* Check if clicking again on the same color piece */
5657 fromP = boards[currentMove][fromY][fromX];
5658 toP = boards[currentMove][y][x];
5659 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5660 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5661 WhitePawn <= toP && toP <= WhiteKing &&
5662 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5663 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5664 (BlackPawn <= fromP && fromP <= BlackKing &&
5665 BlackPawn <= toP && toP <= BlackKing &&
5666 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5667 !(fromP == BlackKing && toP == BlackRook && frc))) {
5668 /* Clicked again on same color piece -- changed his mind */
5669 second = (x == fromX && y == fromY);
5670 if (appData.highlightDragging) {
5671 SetHighlights(x, y, -1, -1);
5675 if (OKToStartUserMove(x, y)) {
5678 DragPieceBegin(xPix, yPix);
5682 // ignore clicks on holdings
5683 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5686 if (clickType == Release && x == fromX && y == fromY) {
5687 DragPieceEnd(xPix, yPix);
5688 if (appData.animateDragging) {
5689 /* Undo animation damage if any */
5690 DrawPosition(FALSE, NULL);
5693 /* Second up/down in same square; just abort move */
5698 ClearPremoveHighlights();
5700 /* First upclick in same square; start click-click mode */
5701 SetHighlights(x, y, -1, -1);
5706 /* we now have a different from- and (possibly off-board) to-square */
5707 /* Completed move */
5710 saveAnimate = appData.animate;
5711 if (clickType == Press) {
5712 /* Finish clickclick move */
5713 if (appData.animate || appData.highlightLastMove) {
5714 SetHighlights(fromX, fromY, toX, toY);
5719 /* Finish drag move */
5720 if (appData.highlightLastMove) {
5721 SetHighlights(fromX, fromY, toX, toY);
5725 DragPieceEnd(xPix, yPix);
5726 /* Don't animate move and drag both */
5727 appData.animate = FALSE;
5730 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5731 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5734 DrawPosition(TRUE, NULL);
5738 // off-board moves should not be highlighted
5739 if(x < 0 || x < 0) ClearHighlights();
5741 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5742 SetHighlights(fromX, fromY, toX, toY);
5743 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5744 // [HGM] super: promotion to captured piece selected from holdings
5745 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5746 promotionChoice = TRUE;
5747 // kludge follows to temporarily execute move on display, without promoting yet
5748 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5749 boards[currentMove][toY][toX] = p;
5750 DrawPosition(FALSE, boards[currentMove]);
5751 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5752 boards[currentMove][toY][toX] = q;
5753 DisplayMessage("Click in holdings to choose piece", "");
5758 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5759 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5760 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5763 appData.animate = saveAnimate;
5764 if (appData.animate || appData.animateDragging) {
5765 /* Undo animation damage if needed */
5766 DrawPosition(FALSE, NULL);
5770 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5772 // char * hint = lastHint;
5773 FrontEndProgramStats stats;
5775 stats.which = cps == &first ? 0 : 1;
5776 stats.depth = cpstats->depth;
5777 stats.nodes = cpstats->nodes;
5778 stats.score = cpstats->score;
5779 stats.time = cpstats->time;
5780 stats.pv = cpstats->movelist;
5781 stats.hint = lastHint;
5782 stats.an_move_index = 0;
5783 stats.an_move_count = 0;
5785 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5786 stats.hint = cpstats->move_name;
5787 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5788 stats.an_move_count = cpstats->nr_moves;
5791 SetProgramStats( &stats );
5794 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5795 { // [HGM] book: this routine intercepts moves to simulate book replies
5796 char *bookHit = NULL;
5798 //first determine if the incoming move brings opponent into his book
5799 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5800 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5801 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5802 if(bookHit != NULL && !cps->bookSuspend) {
5803 // make sure opponent is not going to reply after receiving move to book position
5804 SendToProgram("force\n", cps);
5805 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5807 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5808 // now arrange restart after book miss
5810 // after a book hit we never send 'go', and the code after the call to this routine
5811 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5813 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5814 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5815 SendToProgram(buf, cps);
5816 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5817 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5818 SendToProgram("go\n", cps);
5819 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5820 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5821 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5822 SendToProgram("go\n", cps);
5823 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5825 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5829 ChessProgramState *savedState;
5830 void DeferredBookMove(void)
5832 if(savedState->lastPing != savedState->lastPong)
5833 ScheduleDelayedEvent(DeferredBookMove, 10);
5835 HandleMachineMove(savedMessage, savedState);
5839 HandleMachineMove(message, cps)
5841 ChessProgramState *cps;
5843 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5844 char realname[MSG_SIZ];
5845 int fromX, fromY, toX, toY;
5852 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5854 * Kludge to ignore BEL characters
5856 while (*message == '\007') message++;
5859 * [HGM] engine debug message: ignore lines starting with '#' character
5861 if(cps->debug && *message == '#') return;
5864 * Look for book output
5866 if (cps == &first && bookRequested) {
5867 if (message[0] == '\t' || message[0] == ' ') {
5868 /* Part of the book output is here; append it */
5869 strcat(bookOutput, message);
5870 strcat(bookOutput, " \n");
5872 } else if (bookOutput[0] != NULLCHAR) {
5873 /* All of book output has arrived; display it */
5874 char *p = bookOutput;
5875 while (*p != NULLCHAR) {
5876 if (*p == '\t') *p = ' ';
5879 DisplayInformation(bookOutput);
5880 bookRequested = FALSE;
5881 /* Fall through to parse the current output */
5886 * Look for machine move.
5888 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5889 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5891 /* This method is only useful on engines that support ping */
5892 if (cps->lastPing != cps->lastPong) {
5893 if (gameMode == BeginningOfGame) {
5894 /* Extra move from before last new; ignore */
5895 if (appData.debugMode) {
5896 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5899 if (appData.debugMode) {
5900 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5901 cps->which, gameMode);
5904 SendToProgram("undo\n", cps);
5910 case BeginningOfGame:
5911 /* Extra move from before last reset; ignore */
5912 if (appData.debugMode) {
5913 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5920 /* Extra move after we tried to stop. The mode test is
5921 not a reliable way of detecting this problem, but it's
5922 the best we can do on engines that don't support ping.
5924 if (appData.debugMode) {
5925 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5926 cps->which, gameMode);
5928 SendToProgram("undo\n", cps);
5931 case MachinePlaysWhite:
5932 case IcsPlayingWhite:
5933 machineWhite = TRUE;
5936 case MachinePlaysBlack:
5937 case IcsPlayingBlack:
5938 machineWhite = FALSE;
5941 case TwoMachinesPlay:
5942 machineWhite = (cps->twoMachinesColor[0] == 'w');
5945 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5946 if (appData.debugMode) {
5948 "Ignoring move out of turn by %s, gameMode %d"
5949 ", forwardMost %d\n",
5950 cps->which, gameMode, forwardMostMove);
5955 if (appData.debugMode) { int f = forwardMostMove;
5956 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5957 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5959 if(cps->alphaRank) AlphaRank(machineMove, 4);
5960 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5961 &fromX, &fromY, &toX, &toY, &promoChar)) {
5962 /* Machine move could not be parsed; ignore it. */
5963 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5964 machineMove, cps->which);
5965 DisplayError(buf1, 0);
5966 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5967 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5968 if (gameMode == TwoMachinesPlay) {
5969 GameEnds(machineWhite ? BlackWins : WhiteWins,
5975 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5976 /* So we have to redo legality test with true e.p. status here, */
5977 /* to make sure an illegal e.p. capture does not slip through, */
5978 /* to cause a forfeit on a justified illegal-move complaint */
5979 /* of the opponent. */
5980 if( gameMode==TwoMachinesPlay && appData.testLegality
5981 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5984 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5985 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5986 fromY, fromX, toY, toX, promoChar);
5987 if (appData.debugMode) {
5989 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5990 castlingRights[forwardMostMove][i], castlingRank[i]);
5991 fprintf(debugFP, "castling rights\n");
5993 if(moveType == IllegalMove) {
5994 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5995 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5996 GameEnds(machineWhite ? BlackWins : WhiteWins,
5999 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6000 /* [HGM] Kludge to handle engines that send FRC-style castling
6001 when they shouldn't (like TSCP-Gothic) */
6003 case WhiteASideCastleFR:
6004 case BlackASideCastleFR:
6006 currentMoveString[2]++;
6008 case WhiteHSideCastleFR:
6009 case BlackHSideCastleFR:
6011 currentMoveString[2]--;
6013 default: ; // nothing to do, but suppresses warning of pedantic compilers
6016 hintRequested = FALSE;
6017 lastHint[0] = NULLCHAR;
6018 bookRequested = FALSE;
6019 /* Program may be pondering now */
6020 cps->maybeThinking = TRUE;
6021 if (cps->sendTime == 2) cps->sendTime = 1;
6022 if (cps->offeredDraw) cps->offeredDraw--;
6025 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6027 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6029 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6030 char buf[3*MSG_SIZ];
6032 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6033 programStats.score / 100.,
6035 programStats.time / 100.,
6036 (unsigned int)programStats.nodes,
6037 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6038 programStats.movelist);
6040 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6044 /* currentMoveString is set as a side-effect of ParseOneMove */
6045 strcpy(machineMove, currentMoveString);
6046 strcat(machineMove, "\n");
6047 strcpy(moveList[forwardMostMove], machineMove);
6049 /* [AS] Save move info and clear stats for next move */
6050 pvInfoList[ forwardMostMove ].score = programStats.score;
6051 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6052 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6053 ClearProgramStats();
6054 thinkOutput[0] = NULLCHAR;
6055 hiddenThinkOutputState = 0;
6057 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6059 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6060 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6063 while( count < adjudicateLossPlies ) {
6064 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6067 score = -score; /* Flip score for winning side */
6070 if( score > adjudicateLossThreshold ) {
6077 if( count >= adjudicateLossPlies ) {
6078 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6080 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6081 "Xboard adjudication",
6088 if( gameMode == TwoMachinesPlay ) {
6089 // [HGM] some adjudications useful with buggy engines
6090 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6091 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6094 if( appData.testLegality )
6095 { /* [HGM] Some more adjudications for obstinate engines */
6096 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6097 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6098 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6099 static int moveCount = 6;
6101 char *reason = NULL;
6103 /* Count what is on board. */
6104 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6105 { ChessSquare p = boards[forwardMostMove][i][j];
6109 { /* count B,N,R and other of each side */
6112 NrK++; break; // [HGM] atomic: count Kings
6116 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6117 bishopsColor |= 1 << ((i^j)&1);
6122 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6123 bishopsColor |= 1 << ((i^j)&1);
6138 PawnAdvance += m; NrPawns++;
6140 NrPieces += (p != EmptySquare);
6141 NrW += ((int)p < (int)BlackPawn);
6142 if(gameInfo.variant == VariantXiangqi &&
6143 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6144 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6145 NrW -= ((int)p < (int)BlackPawn);
6149 /* Some material-based adjudications that have to be made before stalemate test */
6150 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6151 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6152 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6153 if(appData.checkMates) {
6154 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6155 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6156 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6157 "Xboard adjudication: King destroyed", GE_XBOARD );
6162 /* Bare King in Shatranj (loses) or Losers (wins) */
6163 if( NrW == 1 || NrPieces - NrW == 1) {
6164 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6165 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6166 if(appData.checkMates) {
6167 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6168 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6169 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6170 "Xboard adjudication: Bare king", GE_XBOARD );
6174 if( gameInfo.variant == VariantShatranj && --bare < 0)
6176 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6177 if(appData.checkMates) {
6178 /* but only adjudicate if adjudication enabled */
6179 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6180 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6181 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6182 "Xboard adjudication: Bare king", GE_XBOARD );
6189 // don't wait for engine to announce game end if we can judge ourselves
6190 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6191 castlingRights[forwardMostMove]) ) {
6193 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6194 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6195 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6196 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6199 reason = "Xboard adjudication: 3rd check";
6200 epStatus[forwardMostMove] = EP_CHECKMATE;
6210 reason = "Xboard adjudication: Stalemate";
6211 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6212 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6213 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6214 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6215 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6216 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6217 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6218 EP_CHECKMATE : EP_WINS);
6219 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6220 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6224 reason = "Xboard adjudication: Checkmate";
6225 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6229 switch(i = epStatus[forwardMostMove]) {
6231 result = GameIsDrawn; break;
6233 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6235 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6237 result = (ChessMove) 0;
6239 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6240 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6241 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6242 GameEnds( result, reason, GE_XBOARD );
6246 /* Next absolutely insufficient mating material. */
6247 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6248 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6249 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6250 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6251 { /* KBK, KNK, KK of KBKB with like Bishops */
6253 /* always flag draws, for judging claims */
6254 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6256 if(appData.materialDraws) {
6257 /* but only adjudicate them if adjudication enabled */
6258 SendToProgram("force\n", cps->other); // suppress reply
6259 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6260 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6261 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6266 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6268 ( NrWR == 1 && NrBR == 1 /* KRKR */
6269 || NrWQ==1 && NrBQ==1 /* KQKQ */
6270 || NrWN==2 || NrBN==2 /* KNNK */
6271 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6273 if(--moveCount < 0 && appData.trivialDraws)
6274 { /* if the first 3 moves do not show a tactical win, declare draw */
6275 SendToProgram("force\n", cps->other); // suppress reply
6276 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6277 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6278 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6281 } else moveCount = 6;
6285 /* Check for rep-draws */
6287 for(k = forwardMostMove-2;
6288 k>=backwardMostMove && k>=forwardMostMove-100 &&
6289 epStatus[k] < EP_UNKNOWN &&
6290 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6293 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6294 /* compare castling rights */
6295 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6296 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6297 rights++; /* King lost rights, while rook still had them */
6298 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6299 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6300 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6301 rights++; /* but at least one rook lost them */
6303 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6304 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6306 if( castlingRights[forwardMostMove][5] >= 0 ) {
6307 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6308 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6311 if( rights == 0 && ++count > appData.drawRepeats-2
6312 && appData.drawRepeats > 1) {
6313 /* adjudicate after user-specified nr of repeats */
6314 SendToProgram("force\n", cps->other); // suppress reply
6315 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6316 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6317 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6318 // [HGM] xiangqi: check for forbidden perpetuals
6319 int m, ourPerpetual = 1, hisPerpetual = 1;
6320 for(m=forwardMostMove; m>k; m-=2) {
6321 if(MateTest(boards[m], PosFlags(m),
6322 EP_NONE, castlingRights[m]) != MT_CHECK)
6323 ourPerpetual = 0; // the current mover did not always check
6324 if(MateTest(boards[m-1], PosFlags(m-1),
6325 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6326 hisPerpetual = 0; // the opponent did not always check
6328 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6329 ourPerpetual, hisPerpetual);
6330 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6331 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6332 "Xboard adjudication: perpetual checking", GE_XBOARD );
6335 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6336 break; // (or we would have caught him before). Abort repetition-checking loop.
6337 // Now check for perpetual chases
6338 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6339 hisPerpetual = PerpetualChase(k, forwardMostMove);
6340 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6341 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6342 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6343 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6346 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6347 break; // Abort repetition-checking loop.
6349 // if neither of us is checking or chasing all the time, or both are, it is draw
6351 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6354 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6355 epStatus[forwardMostMove] = EP_REP_DRAW;
6359 /* Now we test for 50-move draws. Determine ply count */
6360 count = forwardMostMove;
6361 /* look for last irreversble move */
6362 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6364 /* if we hit starting position, add initial plies */
6365 if( count == backwardMostMove )
6366 count -= initialRulePlies;
6367 count = forwardMostMove - count;
6369 epStatus[forwardMostMove] = EP_RULE_DRAW;
6370 /* this is used to judge if draw claims are legal */
6371 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6372 SendToProgram("force\n", cps->other); // suppress reply
6373 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6374 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6375 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6379 /* if draw offer is pending, treat it as a draw claim
6380 * when draw condition present, to allow engines a way to
6381 * claim draws before making their move to avoid a race
6382 * condition occurring after their move
6384 if( cps->other->offeredDraw || cps->offeredDraw ) {
6386 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6387 p = "Draw claim: 50-move rule";
6388 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6389 p = "Draw claim: 3-fold repetition";
6390 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6391 p = "Draw claim: insufficient mating material";
6393 SendToProgram("force\n", cps->other); // suppress reply
6394 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6395 GameEnds( GameIsDrawn, p, GE_XBOARD );
6396 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6402 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6403 SendToProgram("force\n", cps->other); // suppress reply
6404 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6405 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6407 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6414 if (gameMode == TwoMachinesPlay) {
6415 /* [HGM] relaying draw offers moved to after reception of move */
6416 /* and interpreting offer as claim if it brings draw condition */
6417 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6418 SendToProgram("draw\n", cps->other);
6420 if (cps->other->sendTime) {
6421 SendTimeRemaining(cps->other,
6422 cps->other->twoMachinesColor[0] == 'w');
6424 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6425 if (firstMove && !bookHit) {
6427 if (cps->other->useColors) {
6428 SendToProgram(cps->other->twoMachinesColor, cps->other);
6430 SendToProgram("go\n", cps->other);
6432 cps->other->maybeThinking = TRUE;
6435 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6437 if (!pausing && appData.ringBellAfterMoves) {
6442 * Reenable menu items that were disabled while
6443 * machine was thinking
6445 if (gameMode != TwoMachinesPlay)
6446 SetUserThinkingEnables();
6448 // [HGM] book: after book hit opponent has received move and is now in force mode
6449 // force the book reply into it, and then fake that it outputted this move by jumping
6450 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6452 static char bookMove[MSG_SIZ]; // a bit generous?
6454 strcpy(bookMove, "move ");
6455 strcat(bookMove, bookHit);
6458 programStats.nodes = programStats.depth = programStats.time =
6459 programStats.score = programStats.got_only_move = 0;
6460 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6462 if(cps->lastPing != cps->lastPong) {
6463 savedMessage = message; // args for deferred call
6465 ScheduleDelayedEvent(DeferredBookMove, 10);
6474 /* Set special modes for chess engines. Later something general
6475 * could be added here; for now there is just one kludge feature,
6476 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6477 * when "xboard" is given as an interactive command.
6479 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6480 cps->useSigint = FALSE;
6481 cps->useSigterm = FALSE;
6483 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6484 ParseFeatures(message+8, cps);
6485 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6488 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6489 * want this, I was asked to put it in, and obliged.
6491 if (!strncmp(message, "setboard ", 9)) {
6492 Board initial_position; int i;
6494 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6496 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6497 DisplayError(_("Bad FEN received from engine"), 0);
6501 CopyBoard(boards[0], initial_position);
6502 initialRulePlies = FENrulePlies;
6503 epStatus[0] = FENepStatus;
6504 for( i=0; i<nrCastlingRights; i++ )
6505 castlingRights[0][i] = FENcastlingRights[i];
6506 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6507 else gameMode = MachinePlaysBlack;
6508 DrawPosition(FALSE, boards[currentMove]);
6514 * Look for communication commands
6516 if (!strncmp(message, "telluser ", 9)) {
6517 DisplayNote(message + 9);
6520 if (!strncmp(message, "tellusererror ", 14)) {
6521 DisplayError(message + 14, 0);
6524 if (!strncmp(message, "tellopponent ", 13)) {
6525 if (appData.icsActive) {
6527 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6531 DisplayNote(message + 13);
6535 if (!strncmp(message, "tellothers ", 11)) {
6536 if (appData.icsActive) {
6538 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6544 if (!strncmp(message, "tellall ", 8)) {
6545 if (appData.icsActive) {
6547 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6551 DisplayNote(message + 8);
6555 if (strncmp(message, "warning", 7) == 0) {
6556 /* Undocumented feature, use tellusererror in new code */
6557 DisplayError(message, 0);
6560 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6561 strcpy(realname, cps->tidy);
6562 strcat(realname, " query");
6563 AskQuestion(realname, buf2, buf1, cps->pr);
6566 /* Commands from the engine directly to ICS. We don't allow these to be
6567 * sent until we are logged on. Crafty kibitzes have been known to
6568 * interfere with the login process.
6571 if (!strncmp(message, "tellics ", 8)) {
6572 SendToICS(message + 8);
6576 if (!strncmp(message, "tellicsnoalias ", 15)) {
6577 SendToICS(ics_prefix);
6578 SendToICS(message + 15);
6582 /* The following are for backward compatibility only */
6583 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6584 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6585 SendToICS(ics_prefix);
6591 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6595 * If the move is illegal, cancel it and redraw the board.
6596 * Also deal with other error cases. Matching is rather loose
6597 * here to accommodate engines written before the spec.
6599 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6600 strncmp(message, "Error", 5) == 0) {
6601 if (StrStr(message, "name") ||
6602 StrStr(message, "rating") || StrStr(message, "?") ||
6603 StrStr(message, "result") || StrStr(message, "board") ||
6604 StrStr(message, "bk") || StrStr(message, "computer") ||
6605 StrStr(message, "variant") || StrStr(message, "hint") ||
6606 StrStr(message, "random") || StrStr(message, "depth") ||
6607 StrStr(message, "accepted")) {
6610 if (StrStr(message, "protover")) {
6611 /* Program is responding to input, so it's apparently done
6612 initializing, and this error message indicates it is
6613 protocol version 1. So we don't need to wait any longer
6614 for it to initialize and send feature commands. */
6615 FeatureDone(cps, 1);
6616 cps->protocolVersion = 1;
6619 cps->maybeThinking = FALSE;
6621 if (StrStr(message, "draw")) {
6622 /* Program doesn't have "draw" command */
6623 cps->sendDrawOffers = 0;
6626 if (cps->sendTime != 1 &&
6627 (StrStr(message, "time") || StrStr(message, "otim"))) {
6628 /* Program apparently doesn't have "time" or "otim" command */
6632 if (StrStr(message, "analyze")) {
6633 cps->analysisSupport = FALSE;
6634 cps->analyzing = FALSE;
6636 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6637 DisplayError(buf2, 0);
6640 if (StrStr(message, "(no matching move)st")) {
6641 /* Special kludge for GNU Chess 4 only */
6642 cps->stKludge = TRUE;
6643 SendTimeControl(cps, movesPerSession, timeControl,
6644 timeIncrement, appData.searchDepth,
6648 if (StrStr(message, "(no matching move)sd")) {
6649 /* Special kludge for GNU Chess 4 only */
6650 cps->sdKludge = TRUE;
6651 SendTimeControl(cps, movesPerSession, timeControl,
6652 timeIncrement, appData.searchDepth,
6656 if (!StrStr(message, "llegal")) {
6659 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6660 gameMode == IcsIdle) return;
6661 if (forwardMostMove <= backwardMostMove) return;
6662 if (pausing) PauseEvent();
6663 if(appData.forceIllegal) {
6664 // [HGM] illegal: machine refused move; force position after move into it
6665 SendToProgram("force\n", cps);
6666 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6667 // we have a real problem now, as SendBoard will use the a2a3 kludge
6668 // when black is to move, while there might be nothing on a2 or black
6669 // might already have the move. So send the board as if white has the move.
6670 // But first we must change the stm of the engine, as it refused the last move
6671 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6672 if(WhiteOnMove(forwardMostMove)) {
6673 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6674 SendBoard(cps, forwardMostMove); // kludgeless board
6676 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6677 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6678 SendBoard(cps, forwardMostMove+1); // kludgeless board
6680 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6681 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6682 gameMode == TwoMachinesPlay)
6683 SendToProgram("go\n", cps);
6686 if (gameMode == PlayFromGameFile) {
6687 /* Stop reading this game file */
6688 gameMode = EditGame;
6691 currentMove = forwardMostMove-1;
6692 DisplayMove(currentMove-1); /* before DisplayMoveError */
6693 SwitchClocks(forwardMostMove-1); // [HGM] race
6694 DisplayBothClocks();
6695 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6696 parseList[currentMove], cps->which);
6697 DisplayMoveError(buf1);
6698 DrawPosition(FALSE, boards[currentMove]);
6700 /* [HGM] illegal-move claim should forfeit game when Xboard */
6701 /* only passes fully legal moves */
6702 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6703 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6704 "False illegal-move claim", GE_XBOARD );
6708 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6709 /* Program has a broken "time" command that
6710 outputs a string not ending in newline.
6716 * If chess program startup fails, exit with an error message.
6717 * Attempts to recover here are futile.
6719 if ((StrStr(message, "unknown host") != NULL)
6720 || (StrStr(message, "No remote directory") != NULL)
6721 || (StrStr(message, "not found") != NULL)
6722 || (StrStr(message, "No such file") != NULL)
6723 || (StrStr(message, "can't alloc") != NULL)
6724 || (StrStr(message, "Permission denied") != NULL)) {
6726 cps->maybeThinking = FALSE;
6727 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6728 cps->which, cps->program, cps->host, message);
6729 RemoveInputSource(cps->isr);
6730 DisplayFatalError(buf1, 0, 1);
6735 * Look for hint output
6737 if (sscanf(message, "Hint: %s", buf1) == 1) {
6738 if (cps == &first && hintRequested) {
6739 hintRequested = FALSE;
6740 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6741 &fromX, &fromY, &toX, &toY, &promoChar)) {
6742 (void) CoordsToAlgebraic(boards[forwardMostMove],
6743 PosFlags(forwardMostMove), EP_UNKNOWN,
6744 fromY, fromX, toY, toX, promoChar, buf1);
6745 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6746 DisplayInformation(buf2);
6748 /* Hint move could not be parsed!? */
6749 snprintf(buf2, sizeof(buf2),
6750 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6752 DisplayError(buf2, 0);
6755 strcpy(lastHint, buf1);
6761 * Ignore other messages if game is not in progress
6763 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6764 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6767 * look for win, lose, draw, or draw offer
6769 if (strncmp(message, "1-0", 3) == 0) {
6770 char *p, *q, *r = "";
6771 p = strchr(message, '{');
6779 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6781 } else if (strncmp(message, "0-1", 3) == 0) {
6782 char *p, *q, *r = "";
6783 p = strchr(message, '{');
6791 /* Kludge for Arasan 4.1 bug */
6792 if (strcmp(r, "Black resigns") == 0) {
6793 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6796 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6798 } else if (strncmp(message, "1/2", 3) == 0) {
6799 char *p, *q, *r = "";
6800 p = strchr(message, '{');
6809 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6812 } else if (strncmp(message, "White resign", 12) == 0) {
6813 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6815 } else if (strncmp(message, "Black resign", 12) == 0) {
6816 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6818 } else if (strncmp(message, "White matches", 13) == 0 ||
6819 strncmp(message, "Black matches", 13) == 0 ) {
6820 /* [HGM] ignore GNUShogi noises */
6822 } else if (strncmp(message, "White", 5) == 0 &&
6823 message[5] != '(' &&
6824 StrStr(message, "Black") == NULL) {
6825 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6827 } else if (strncmp(message, "Black", 5) == 0 &&
6828 message[5] != '(') {
6829 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6831 } else if (strcmp(message, "resign") == 0 ||
6832 strcmp(message, "computer resigns") == 0) {
6834 case MachinePlaysBlack:
6835 case IcsPlayingBlack:
6836 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6838 case MachinePlaysWhite:
6839 case IcsPlayingWhite:
6840 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6842 case TwoMachinesPlay:
6843 if (cps->twoMachinesColor[0] == 'w')
6844 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6846 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6853 } else if (strncmp(message, "opponent mates", 14) == 0) {
6855 case MachinePlaysBlack:
6856 case IcsPlayingBlack:
6857 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6859 case MachinePlaysWhite:
6860 case IcsPlayingWhite:
6861 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6863 case TwoMachinesPlay:
6864 if (cps->twoMachinesColor[0] == 'w')
6865 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6867 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6874 } else if (strncmp(message, "computer mates", 14) == 0) {
6876 case MachinePlaysBlack:
6877 case IcsPlayingBlack:
6878 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6880 case MachinePlaysWhite:
6881 case IcsPlayingWhite:
6882 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6884 case TwoMachinesPlay:
6885 if (cps->twoMachinesColor[0] == 'w')
6886 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6888 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6895 } else if (strncmp(message, "checkmate", 9) == 0) {
6896 if (WhiteOnMove(forwardMostMove)) {
6897 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6899 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6902 } else if (strstr(message, "Draw") != NULL ||
6903 strstr(message, "game is a draw") != NULL) {
6904 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6906 } else if (strstr(message, "offer") != NULL &&
6907 strstr(message, "draw") != NULL) {
6909 if (appData.zippyPlay && first.initDone) {
6910 /* Relay offer to ICS */
6911 SendToICS(ics_prefix);
6912 SendToICS("draw\n");
6915 cps->offeredDraw = 2; /* valid until this engine moves twice */
6916 if (gameMode == TwoMachinesPlay) {
6917 if (cps->other->offeredDraw) {
6918 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6919 /* [HGM] in two-machine mode we delay relaying draw offer */
6920 /* until after we also have move, to see if it is really claim */
6922 } else if (gameMode == MachinePlaysWhite ||
6923 gameMode == MachinePlaysBlack) {
6924 if (userOfferedDraw) {
6925 DisplayInformation(_("Machine accepts your draw offer"));
6926 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6928 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6935 * Look for thinking output
6937 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6938 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6940 int plylev, mvleft, mvtot, curscore, time;
6941 char mvname[MOVE_LEN];
6945 int prefixHint = FALSE;
6946 mvname[0] = NULLCHAR;
6949 case MachinePlaysBlack:
6950 case IcsPlayingBlack:
6951 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6953 case MachinePlaysWhite:
6954 case IcsPlayingWhite:
6955 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6960 case IcsObserving: /* [DM] icsEngineAnalyze */
6961 if (!appData.icsEngineAnalyze) ignore = TRUE;
6963 case TwoMachinesPlay:
6964 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6975 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6976 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6978 if (plyext != ' ' && plyext != '\t') {
6982 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6983 if( cps->scoreIsAbsolute &&
6984 ( gameMode == MachinePlaysBlack ||
6985 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6986 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6987 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6988 !WhiteOnMove(currentMove)
6991 curscore = -curscore;
6995 programStats.depth = plylev;
6996 programStats.nodes = nodes;
6997 programStats.time = time;
6998 programStats.score = curscore;
6999 programStats.got_only_move = 0;
7001 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7004 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7005 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7006 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7007 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7008 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7009 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7010 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7011 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7014 /* Buffer overflow protection */
7015 if (buf1[0] != NULLCHAR) {
7016 if (strlen(buf1) >= sizeof(programStats.movelist)
7017 && appData.debugMode) {
7019 "PV is too long; using the first %u bytes.\n",
7020 (unsigned) sizeof(programStats.movelist) - 1);
7023 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7025 sprintf(programStats.movelist, " no PV\n");
7028 if (programStats.seen_stat) {
7029 programStats.ok_to_send = 1;
7032 if (strchr(programStats.movelist, '(') != NULL) {
7033 programStats.line_is_book = 1;
7034 programStats.nr_moves = 0;
7035 programStats.moves_left = 0;
7037 programStats.line_is_book = 0;
7040 SendProgramStatsToFrontend( cps, &programStats );
7043 [AS] Protect the thinkOutput buffer from overflow... this
7044 is only useful if buf1 hasn't overflowed first!
7046 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7048 (gameMode == TwoMachinesPlay ?
7049 ToUpper(cps->twoMachinesColor[0]) : ' '),
7050 ((double) curscore) / 100.0,
7051 prefixHint ? lastHint : "",
7052 prefixHint ? " " : "" );
7054 if( buf1[0] != NULLCHAR ) {
7055 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7057 if( strlen(buf1) > max_len ) {
7058 if( appData.debugMode) {
7059 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7061 buf1[max_len+1] = '\0';
7064 strcat( thinkOutput, buf1 );
7067 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7068 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7069 DisplayMove(currentMove - 1);
7073 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7074 /* crafty (9.25+) says "(only move) <move>"
7075 * if there is only 1 legal move
7077 sscanf(p, "(only move) %s", buf1);
7078 sprintf(thinkOutput, "%s (only move)", buf1);
7079 sprintf(programStats.movelist, "%s (only move)", buf1);
7080 programStats.depth = 1;
7081 programStats.nr_moves = 1;
7082 programStats.moves_left = 1;
7083 programStats.nodes = 1;
7084 programStats.time = 1;
7085 programStats.got_only_move = 1;
7087 /* Not really, but we also use this member to
7088 mean "line isn't going to change" (Crafty
7089 isn't searching, so stats won't change) */
7090 programStats.line_is_book = 1;
7092 SendProgramStatsToFrontend( cps, &programStats );
7094 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7095 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7096 DisplayMove(currentMove - 1);
7099 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7100 &time, &nodes, &plylev, &mvleft,
7101 &mvtot, mvname) >= 5) {
7102 /* The stat01: line is from Crafty (9.29+) in response
7103 to the "." command */
7104 programStats.seen_stat = 1;
7105 cps->maybeThinking = TRUE;
7107 if (programStats.got_only_move || !appData.periodicUpdates)
7110 programStats.depth = plylev;
7111 programStats.time = time;
7112 programStats.nodes = nodes;
7113 programStats.moves_left = mvleft;
7114 programStats.nr_moves = mvtot;
7115 strcpy(programStats.move_name, mvname);
7116 programStats.ok_to_send = 1;
7117 programStats.movelist[0] = '\0';
7119 SendProgramStatsToFrontend( cps, &programStats );
7123 } else if (strncmp(message,"++",2) == 0) {
7124 /* Crafty 9.29+ outputs this */
7125 programStats.got_fail = 2;
7128 } else if (strncmp(message,"--",2) == 0) {
7129 /* Crafty 9.29+ outputs this */
7130 programStats.got_fail = 1;
7133 } else if (thinkOutput[0] != NULLCHAR &&
7134 strncmp(message, " ", 4) == 0) {
7135 unsigned message_len;
7138 while (*p && *p == ' ') p++;
7140 message_len = strlen( p );
7142 /* [AS] Avoid buffer overflow */
7143 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7144 strcat(thinkOutput, " ");
7145 strcat(thinkOutput, p);
7148 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7149 strcat(programStats.movelist, " ");
7150 strcat(programStats.movelist, p);
7153 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7154 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7155 DisplayMove(currentMove - 1);
7163 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7164 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7166 ChessProgramStats cpstats;
7168 if (plyext != ' ' && plyext != '\t') {
7172 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7173 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7174 curscore = -curscore;
7177 cpstats.depth = plylev;
7178 cpstats.nodes = nodes;
7179 cpstats.time = time;
7180 cpstats.score = curscore;
7181 cpstats.got_only_move = 0;
7182 cpstats.movelist[0] = '\0';
7184 if (buf1[0] != NULLCHAR) {
7185 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7188 cpstats.ok_to_send = 0;
7189 cpstats.line_is_book = 0;
7190 cpstats.nr_moves = 0;
7191 cpstats.moves_left = 0;
7193 SendProgramStatsToFrontend( cps, &cpstats );
7200 /* Parse a game score from the character string "game", and
7201 record it as the history of the current game. The game
7202 score is NOT assumed to start from the standard position.
7203 The display is not updated in any way.
7206 ParseGameHistory(game)
7210 int fromX, fromY, toX, toY, boardIndex;
7215 if (appData.debugMode)
7216 fprintf(debugFP, "Parsing game history: %s\n", game);
7218 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7219 gameInfo.site = StrSave(appData.icsHost);
7220 gameInfo.date = PGNDate();
7221 gameInfo.round = StrSave("-");
7223 /* Parse out names of players */
7224 while (*game == ' ') game++;
7226 while (*game != ' ') *p++ = *game++;
7228 gameInfo.white = StrSave(buf);
7229 while (*game == ' ') game++;
7231 while (*game != ' ' && *game != '\n') *p++ = *game++;
7233 gameInfo.black = StrSave(buf);
7236 boardIndex = blackPlaysFirst ? 1 : 0;
7239 yyboardindex = boardIndex;
7240 moveType = (ChessMove) yylex();
7242 case IllegalMove: /* maybe suicide chess, etc. */
7243 if (appData.debugMode) {
7244 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7245 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7246 setbuf(debugFP, NULL);
7248 case WhitePromotionChancellor:
7249 case BlackPromotionChancellor:
7250 case WhitePromotionArchbishop:
7251 case BlackPromotionArchbishop:
7252 case WhitePromotionQueen:
7253 case BlackPromotionQueen:
7254 case WhitePromotionRook:
7255 case BlackPromotionRook:
7256 case WhitePromotionBishop:
7257 case BlackPromotionBishop:
7258 case WhitePromotionKnight:
7259 case BlackPromotionKnight:
7260 case WhitePromotionKing:
7261 case BlackPromotionKing:
7263 case WhiteCapturesEnPassant:
7264 case BlackCapturesEnPassant:
7265 case WhiteKingSideCastle:
7266 case WhiteQueenSideCastle:
7267 case BlackKingSideCastle:
7268 case BlackQueenSideCastle:
7269 case WhiteKingSideCastleWild:
7270 case WhiteQueenSideCastleWild:
7271 case BlackKingSideCastleWild:
7272 case BlackQueenSideCastleWild:
7274 case WhiteHSideCastleFR:
7275 case WhiteASideCastleFR:
7276 case BlackHSideCastleFR:
7277 case BlackASideCastleFR:
7279 fromX = currentMoveString[0] - AAA;
7280 fromY = currentMoveString[1] - ONE;
7281 toX = currentMoveString[2] - AAA;
7282 toY = currentMoveString[3] - ONE;
7283 promoChar = currentMoveString[4];
7287 fromX = moveType == WhiteDrop ?
7288 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7289 (int) CharToPiece(ToLower(currentMoveString[0]));
7291 toX = currentMoveString[2] - AAA;
7292 toY = currentMoveString[3] - ONE;
7293 promoChar = NULLCHAR;
7297 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7298 if (appData.debugMode) {
7299 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7300 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7301 setbuf(debugFP, NULL);
7303 DisplayError(buf, 0);
7305 case ImpossibleMove:
7307 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7308 if (appData.debugMode) {
7309 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7310 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7311 setbuf(debugFP, NULL);
7313 DisplayError(buf, 0);
7315 case (ChessMove) 0: /* end of file */
7316 if (boardIndex < backwardMostMove) {
7317 /* Oops, gap. How did that happen? */
7318 DisplayError(_("Gap in move list"), 0);
7321 backwardMostMove = blackPlaysFirst ? 1 : 0;
7322 if (boardIndex > forwardMostMove) {
7323 forwardMostMove = boardIndex;
7327 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7328 strcat(parseList[boardIndex-1], " ");
7329 strcat(parseList[boardIndex-1], yy_text);
7341 case GameUnfinished:
7342 if (gameMode == IcsExamining) {
7343 if (boardIndex < backwardMostMove) {
7344 /* Oops, gap. How did that happen? */
7347 backwardMostMove = blackPlaysFirst ? 1 : 0;
7350 gameInfo.result = moveType;
7351 p = strchr(yy_text, '{');
7352 if (p == NULL) p = strchr(yy_text, '(');
7355 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7357 q = strchr(p, *p == '{' ? '}' : ')');
7358 if (q != NULL) *q = NULLCHAR;
7361 gameInfo.resultDetails = StrSave(p);
7364 if (boardIndex >= forwardMostMove &&
7365 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7366 backwardMostMove = blackPlaysFirst ? 1 : 0;
7369 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7370 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7371 parseList[boardIndex]);
7372 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7373 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7374 /* currentMoveString is set as a side-effect of yylex */
7375 strcpy(moveList[boardIndex], currentMoveString);
7376 strcat(moveList[boardIndex], "\n");
7378 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7379 castlingRights[boardIndex], &epStatus[boardIndex]);
7380 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7381 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7387 if(gameInfo.variant != VariantShogi)
7388 strcat(parseList[boardIndex - 1], "+");
7392 strcat(parseList[boardIndex - 1], "#");
7399 /* Apply a move to the given board */
7401 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7402 int fromX, fromY, toX, toY;
7408 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7409 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7411 /* [HGM] compute & store e.p. status and castling rights for new position */
7412 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7415 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7419 if( board[toY][toX] != EmptySquare )
7422 if( board[fromY][fromX] == WhitePawn ) {
7423 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7426 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7427 gameInfo.variant != VariantBerolina || toX < fromX)
7428 *ep = toX | berolina;
7429 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7430 gameInfo.variant != VariantBerolina || toX > fromX)
7434 if( board[fromY][fromX] == BlackPawn ) {
7435 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7437 if( toY-fromY== -2) {
7438 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7439 gameInfo.variant != VariantBerolina || toX < fromX)
7440 *ep = toX | berolina;
7441 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7442 gameInfo.variant != VariantBerolina || toX > fromX)
7447 for(i=0; i<nrCastlingRights; i++) {
7448 if(castling[i] == fromX && castlingRank[i] == fromY ||
7449 castling[i] == toX && castlingRank[i] == toY
7450 ) castling[i] = -1; // revoke for moved or captured piece
7455 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7456 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7457 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7459 if (fromX == toX && fromY == toY) return;
7461 if (fromY == DROP_RANK) {
7463 piece = board[toY][toX] = (ChessSquare) fromX;
7465 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7466 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7467 if(gameInfo.variant == VariantKnightmate)
7468 king += (int) WhiteUnicorn - (int) WhiteKing;
7470 /* Code added by Tord: */
7471 /* FRC castling assumed when king captures friendly rook. */
7472 if (board[fromY][fromX] == WhiteKing &&
7473 board[toY][toX] == WhiteRook) {
7474 board[fromY][fromX] = EmptySquare;
7475 board[toY][toX] = EmptySquare;
7477 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7479 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7481 } else if (board[fromY][fromX] == BlackKing &&
7482 board[toY][toX] == BlackRook) {
7483 board[fromY][fromX] = EmptySquare;
7484 board[toY][toX] = EmptySquare;
7486 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7488 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7490 /* End of code added by Tord */
7492 } else if (board[fromY][fromX] == king
7493 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7494 && toY == fromY && toX > fromX+1) {
7495 board[fromY][fromX] = EmptySquare;
7496 board[toY][toX] = king;
7497 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7498 board[fromY][BOARD_RGHT-1] = EmptySquare;
7499 } else if (board[fromY][fromX] == king
7500 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7501 && toY == fromY && toX < fromX-1) {
7502 board[fromY][fromX] = EmptySquare;
7503 board[toY][toX] = king;
7504 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7505 board[fromY][BOARD_LEFT] = EmptySquare;
7506 } else if (board[fromY][fromX] == WhitePawn
7507 && toY >= BOARD_HEIGHT-promoRank
7508 && gameInfo.variant != VariantXiangqi
7510 /* white pawn promotion */
7511 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7512 if (board[toY][toX] == EmptySquare) {
7513 board[toY][toX] = WhiteQueen;
7515 if(gameInfo.variant==VariantBughouse ||
7516 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7517 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7518 board[fromY][fromX] = EmptySquare;
7519 } else if ((fromY == BOARD_HEIGHT-4)
7521 && gameInfo.variant != VariantXiangqi
7522 && gameInfo.variant != VariantBerolina
7523 && (board[fromY][fromX] == WhitePawn)
7524 && (board[toY][toX] == EmptySquare)) {
7525 board[fromY][fromX] = EmptySquare;
7526 board[toY][toX] = WhitePawn;
7527 captured = board[toY - 1][toX];
7528 board[toY - 1][toX] = EmptySquare;
7529 } else if ((fromY == BOARD_HEIGHT-4)
7531 && gameInfo.variant == VariantBerolina
7532 && (board[fromY][fromX] == WhitePawn)
7533 && (board[toY][toX] == EmptySquare)) {
7534 board[fromY][fromX] = EmptySquare;
7535 board[toY][toX] = WhitePawn;
7536 if(oldEP & EP_BEROLIN_A) {
7537 captured = board[fromY][fromX-1];
7538 board[fromY][fromX-1] = EmptySquare;
7539 }else{ captured = board[fromY][fromX+1];
7540 board[fromY][fromX+1] = EmptySquare;
7542 } else if (board[fromY][fromX] == king
7543 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7544 && toY == fromY && toX > fromX+1) {
7545 board[fromY][fromX] = EmptySquare;
7546 board[toY][toX] = king;
7547 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7548 board[fromY][BOARD_RGHT-1] = EmptySquare;
7549 } else if (board[fromY][fromX] == king
7550 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7551 && toY == fromY && toX < fromX-1) {
7552 board[fromY][fromX] = EmptySquare;
7553 board[toY][toX] = king;
7554 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7555 board[fromY][BOARD_LEFT] = EmptySquare;
7556 } else if (fromY == 7 && fromX == 3
7557 && board[fromY][fromX] == BlackKing
7558 && toY == 7 && toX == 5) {
7559 board[fromY][fromX] = EmptySquare;
7560 board[toY][toX] = BlackKing;
7561 board[fromY][7] = EmptySquare;
7562 board[toY][4] = BlackRook;
7563 } else if (fromY == 7 && fromX == 3
7564 && board[fromY][fromX] == BlackKing
7565 && toY == 7 && toX == 1) {
7566 board[fromY][fromX] = EmptySquare;
7567 board[toY][toX] = BlackKing;
7568 board[fromY][0] = EmptySquare;
7569 board[toY][2] = BlackRook;
7570 } else if (board[fromY][fromX] == BlackPawn
7572 && gameInfo.variant != VariantXiangqi
7574 /* black pawn promotion */
7575 board[toY][toX] = CharToPiece(ToLower(promoChar));
7576 if (board[toY][toX] == EmptySquare) {
7577 board[toY][toX] = BlackQueen;
7579 if(gameInfo.variant==VariantBughouse ||
7580 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7581 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7582 board[fromY][fromX] = EmptySquare;
7583 } else if ((fromY == 3)
7585 && gameInfo.variant != VariantXiangqi
7586 && gameInfo.variant != VariantBerolina
7587 && (board[fromY][fromX] == BlackPawn)
7588 && (board[toY][toX] == EmptySquare)) {
7589 board[fromY][fromX] = EmptySquare;
7590 board[toY][toX] = BlackPawn;
7591 captured = board[toY + 1][toX];
7592 board[toY + 1][toX] = EmptySquare;
7593 } else if ((fromY == 3)
7595 && gameInfo.variant == VariantBerolina
7596 && (board[fromY][fromX] == BlackPawn)
7597 && (board[toY][toX] == EmptySquare)) {
7598 board[fromY][fromX] = EmptySquare;
7599 board[toY][toX] = BlackPawn;
7600 if(oldEP & EP_BEROLIN_A) {
7601 captured = board[fromY][fromX-1];
7602 board[fromY][fromX-1] = EmptySquare;
7603 }else{ captured = board[fromY][fromX+1];
7604 board[fromY][fromX+1] = EmptySquare;
7607 board[toY][toX] = board[fromY][fromX];
7608 board[fromY][fromX] = EmptySquare;
7611 /* [HGM] now we promote for Shogi, if needed */
7612 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7613 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7616 if (gameInfo.holdingsWidth != 0) {
7618 /* !!A lot more code needs to be written to support holdings */
7619 /* [HGM] OK, so I have written it. Holdings are stored in the */
7620 /* penultimate board files, so they are automaticlly stored */
7621 /* in the game history. */
7622 if (fromY == DROP_RANK) {
7623 /* Delete from holdings, by decreasing count */
7624 /* and erasing image if necessary */
7626 if(p < (int) BlackPawn) { /* white drop */
7627 p -= (int)WhitePawn;
7628 p = PieceToNumber((ChessSquare)p);
7629 if(p >= gameInfo.holdingsSize) p = 0;
7630 if(--board[p][BOARD_WIDTH-2] <= 0)
7631 board[p][BOARD_WIDTH-1] = EmptySquare;
7632 if((int)board[p][BOARD_WIDTH-2] < 0)
7633 board[p][BOARD_WIDTH-2] = 0;
7634 } else { /* black drop */
7635 p -= (int)BlackPawn;
7636 p = PieceToNumber((ChessSquare)p);
7637 if(p >= gameInfo.holdingsSize) p = 0;
7638 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7639 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7640 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7641 board[BOARD_HEIGHT-1-p][1] = 0;
7644 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7645 && gameInfo.variant != VariantBughouse ) {
7646 /* [HGM] holdings: Add to holdings, if holdings exist */
7647 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7648 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7649 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7652 if (p >= (int) BlackPawn) {
7653 p -= (int)BlackPawn;
7654 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7655 /* in Shogi restore piece to its original first */
7656 captured = (ChessSquare) (DEMOTED captured);
7659 p = PieceToNumber((ChessSquare)p);
7660 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7661 board[p][BOARD_WIDTH-2]++;
7662 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7664 p -= (int)WhitePawn;
7665 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7666 captured = (ChessSquare) (DEMOTED captured);
7669 p = PieceToNumber((ChessSquare)p);
7670 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7671 board[BOARD_HEIGHT-1-p][1]++;
7672 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7675 } else if (gameInfo.variant == VariantAtomic) {
7676 if (captured != EmptySquare) {
7678 for (y = toY-1; y <= toY+1; y++) {
7679 for (x = toX-1; x <= toX+1; x++) {
7680 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7681 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7682 board[y][x] = EmptySquare;
7686 board[toY][toX] = EmptySquare;
7689 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7690 /* [HGM] Shogi promotions */
7691 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7694 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7695 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7696 // [HGM] superchess: take promotion piece out of holdings
7697 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7698 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7699 if(!--board[k][BOARD_WIDTH-2])
7700 board[k][BOARD_WIDTH-1] = EmptySquare;
7702 if(!--board[BOARD_HEIGHT-1-k][1])
7703 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7709 /* Updates forwardMostMove */
7711 MakeMove(fromX, fromY, toX, toY, promoChar)
7712 int fromX, fromY, toX, toY;
7715 // forwardMostMove++; // [HGM] bare: moved downstream
7717 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7718 int timeLeft; static int lastLoadFlag=0; int king, piece;
7719 piece = boards[forwardMostMove][fromY][fromX];
7720 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7721 if(gameInfo.variant == VariantKnightmate)
7722 king += (int) WhiteUnicorn - (int) WhiteKing;
7723 if(forwardMostMove == 0) {
7725 fprintf(serverMoves, "%s;", second.tidy);
7726 fprintf(serverMoves, "%s;", first.tidy);
7727 if(!blackPlaysFirst)
7728 fprintf(serverMoves, "%s;", second.tidy);
7729 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7730 lastLoadFlag = loadFlag;
7732 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7733 // print castling suffix
7734 if( toY == fromY && piece == king ) {
7736 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7738 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7741 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7742 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7743 boards[forwardMostMove][toY][toX] == EmptySquare
7745 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7747 if(promoChar != NULLCHAR)
7748 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7750 fprintf(serverMoves, "/%d/%d",
7751 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7752 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7753 else timeLeft = blackTimeRemaining/1000;
7754 fprintf(serverMoves, "/%d", timeLeft);
7756 fflush(serverMoves);
7759 if (forwardMostMove+1 >= MAX_MOVES) {
7760 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7764 if (commentList[forwardMostMove+1] != NULL) {
7765 free(commentList[forwardMostMove+1]);
7766 commentList[forwardMostMove+1] = NULL;
7768 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7769 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7770 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7771 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7772 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7773 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
7774 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7775 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7776 gameInfo.result = GameUnfinished;
7777 if (gameInfo.resultDetails != NULL) {
7778 free(gameInfo.resultDetails);
7779 gameInfo.resultDetails = NULL;
7781 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7782 moveList[forwardMostMove - 1]);
7783 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7784 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7785 fromY, fromX, toY, toX, promoChar,
7786 parseList[forwardMostMove - 1]);
7787 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7788 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7789 castlingRights[forwardMostMove]) ) {
7795 if(gameInfo.variant != VariantShogi)
7796 strcat(parseList[forwardMostMove - 1], "+");
7800 strcat(parseList[forwardMostMove - 1], "#");
7803 if (appData.debugMode) {
7804 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7809 /* Updates currentMove if not pausing */
7811 ShowMove(fromX, fromY, toX, toY)
7813 int instant = (gameMode == PlayFromGameFile) ?
7814 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7815 if(appData.noGUI) return;
7816 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7818 if (forwardMostMove == currentMove + 1) {
7819 AnimateMove(boards[forwardMostMove - 1],
7820 fromX, fromY, toX, toY);
7822 if (appData.highlightLastMove) {
7823 SetHighlights(fromX, fromY, toX, toY);
7826 currentMove = forwardMostMove;
7829 if (instant) return;
7831 DisplayMove(currentMove - 1);
7832 DrawPosition(FALSE, boards[currentMove]);
7833 DisplayBothClocks();
7834 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7837 void SendEgtPath(ChessProgramState *cps)
7838 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7839 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7841 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7844 char c, *q = name+1, *r, *s;
7846 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7847 while(*p && *p != ',') *q++ = *p++;
7849 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7850 strcmp(name, ",nalimov:") == 0 ) {
7851 // take nalimov path from the menu-changeable option first, if it is defined
7852 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7853 SendToProgram(buf,cps); // send egtbpath command for nalimov
7855 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7856 (s = StrStr(appData.egtFormats, name)) != NULL) {
7857 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7858 s = r = StrStr(s, ":") + 1; // beginning of path info
7859 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7860 c = *r; *r = 0; // temporarily null-terminate path info
7861 *--q = 0; // strip of trailig ':' from name
7862 sprintf(buf, "egtpath %s %s\n", name+1, s);
7864 SendToProgram(buf,cps); // send egtbpath command for this format
7866 if(*p == ',') p++; // read away comma to position for next format name
7871 InitChessProgram(cps, setup)
7872 ChessProgramState *cps;
7873 int setup; /* [HGM] needed to setup FRC opening position */
7875 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7876 if (appData.noChessProgram) return;
7877 hintRequested = FALSE;
7878 bookRequested = FALSE;
7880 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7881 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7882 if(cps->memSize) { /* [HGM] memory */
7883 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7884 SendToProgram(buf, cps);
7886 SendEgtPath(cps); /* [HGM] EGT */
7887 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7888 sprintf(buf, "cores %d\n", appData.smpCores);
7889 SendToProgram(buf, cps);
7892 SendToProgram(cps->initString, cps);
7893 if (gameInfo.variant != VariantNormal &&
7894 gameInfo.variant != VariantLoadable
7895 /* [HGM] also send variant if board size non-standard */
7896 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7898 char *v = VariantName(gameInfo.variant);
7899 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7900 /* [HGM] in protocol 1 we have to assume all variants valid */
7901 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7902 DisplayFatalError(buf, 0, 1);
7906 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7907 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7908 if( gameInfo.variant == VariantXiangqi )
7909 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7910 if( gameInfo.variant == VariantShogi )
7911 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7912 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7913 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7914 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7915 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7916 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7917 if( gameInfo.variant == VariantCourier )
7918 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7919 if( gameInfo.variant == VariantSuper )
7920 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7921 if( gameInfo.variant == VariantGreat )
7922 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7925 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7926 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7927 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7928 if(StrStr(cps->variants, b) == NULL) {
7929 // specific sized variant not known, check if general sizing allowed
7930 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7931 if(StrStr(cps->variants, "boardsize") == NULL) {
7932 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7933 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7934 DisplayFatalError(buf, 0, 1);
7937 /* [HGM] here we really should compare with the maximum supported board size */
7940 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7941 sprintf(buf, "variant %s\n", b);
7942 SendToProgram(buf, cps);
7944 currentlyInitializedVariant = gameInfo.variant;
7946 /* [HGM] send opening position in FRC to first engine */
7948 SendToProgram("force\n", cps);
7950 /* engine is now in force mode! Set flag to wake it up after first move. */
7951 setboardSpoiledMachineBlack = 1;
7955 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7956 SendToProgram(buf, cps);
7958 cps->maybeThinking = FALSE;
7959 cps->offeredDraw = 0;
7960 if (!appData.icsActive) {
7961 SendTimeControl(cps, movesPerSession, timeControl,
7962 timeIncrement, appData.searchDepth,
7965 if (appData.showThinking
7966 // [HGM] thinking: four options require thinking output to be sent
7967 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7969 SendToProgram("post\n", cps);
7971 SendToProgram("hard\n", cps);
7972 if (!appData.ponderNextMove) {
7973 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7974 it without being sure what state we are in first. "hard"
7975 is not a toggle, so that one is OK.
7977 SendToProgram("easy\n", cps);
7980 sprintf(buf, "ping %d\n", ++cps->lastPing);
7981 SendToProgram(buf, cps);
7983 cps->initDone = TRUE;
7988 StartChessProgram(cps)
7989 ChessProgramState *cps;
7994 if (appData.noChessProgram) return;
7995 cps->initDone = FALSE;
7997 if (strcmp(cps->host, "localhost") == 0) {
7998 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7999 } else if (*appData.remoteShell == NULLCHAR) {
8000 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8002 if (*appData.remoteUser == NULLCHAR) {
8003 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8006 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8007 cps->host, appData.remoteUser, cps->program);
8009 err = StartChildProcess(buf, "", &cps->pr);
8013 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8014 DisplayFatalError(buf, err, 1);
8020 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8021 if (cps->protocolVersion > 1) {
8022 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8023 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8024 cps->comboCnt = 0; // and values of combo boxes
8025 SendToProgram(buf, cps);
8027 SendToProgram("xboard\n", cps);
8033 TwoMachinesEventIfReady P((void))
8035 if (first.lastPing != first.lastPong) {
8036 DisplayMessage("", _("Waiting for first chess program"));
8037 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8040 if (second.lastPing != second.lastPong) {
8041 DisplayMessage("", _("Waiting for second chess program"));
8042 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8050 NextMatchGame P((void))
8052 int index; /* [HGM] autoinc: step load index during match */
8054 if (*appData.loadGameFile != NULLCHAR) {
8055 index = appData.loadGameIndex;
8056 if(index < 0) { // [HGM] autoinc
8057 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8058 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8060 LoadGameFromFile(appData.loadGameFile,
8062 appData.loadGameFile, FALSE);
8063 } else if (*appData.loadPositionFile != NULLCHAR) {
8064 index = appData.loadPositionIndex;
8065 if(index < 0) { // [HGM] autoinc
8066 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8067 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8069 LoadPositionFromFile(appData.loadPositionFile,
8071 appData.loadPositionFile);
8073 TwoMachinesEventIfReady();
8076 void UserAdjudicationEvent( int result )
8078 ChessMove gameResult = GameIsDrawn;
8081 gameResult = WhiteWins;
8083 else if( result < 0 ) {
8084 gameResult = BlackWins;
8087 if( gameMode == TwoMachinesPlay ) {
8088 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8093 // [HGM] save: calculate checksum of game to make games easily identifiable
8094 int StringCheckSum(char *s)
8097 if(s==NULL) return 0;
8098 while(*s) i = i*259 + *s++;
8105 for(i=backwardMostMove; i<forwardMostMove; i++) {
8106 sum += pvInfoList[i].depth;
8107 sum += StringCheckSum(parseList[i]);
8108 sum += StringCheckSum(commentList[i]);
8111 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8112 return sum + StringCheckSum(commentList[i]);
8113 } // end of save patch
8116 GameEnds(result, resultDetails, whosays)
8118 char *resultDetails;
8121 GameMode nextGameMode;
8125 if(endingGame) return; /* [HGM] crash: forbid recursion */
8128 if (appData.debugMode) {
8129 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8130 result, resultDetails ? resultDetails : "(null)", whosays);
8133 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8135 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8136 /* If we are playing on ICS, the server decides when the
8137 game is over, but the engine can offer to draw, claim
8141 if (appData.zippyPlay && first.initDone) {
8142 if (result == GameIsDrawn) {
8143 /* In case draw still needs to be claimed */
8144 SendToICS(ics_prefix);
8145 SendToICS("draw\n");
8146 } else if (StrCaseStr(resultDetails, "resign")) {
8147 SendToICS(ics_prefix);
8148 SendToICS("resign\n");
8152 endingGame = 0; /* [HGM] crash */
8156 /* If we're loading the game from a file, stop */
8157 if (whosays == GE_FILE) {
8158 (void) StopLoadGameTimer();
8162 /* Cancel draw offers */
8163 first.offeredDraw = second.offeredDraw = 0;
8165 /* If this is an ICS game, only ICS can really say it's done;
8166 if not, anyone can. */
8167 isIcsGame = (gameMode == IcsPlayingWhite ||
8168 gameMode == IcsPlayingBlack ||
8169 gameMode == IcsObserving ||
8170 gameMode == IcsExamining);
8172 if (!isIcsGame || whosays == GE_ICS) {
8173 /* OK -- not an ICS game, or ICS said it was done */
8175 if (!isIcsGame && !appData.noChessProgram)
8176 SetUserThinkingEnables();
8178 /* [HGM] if a machine claims the game end we verify this claim */
8179 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8180 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8182 ChessMove trueResult = (ChessMove) -1;
8184 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8185 first.twoMachinesColor[0] :
8186 second.twoMachinesColor[0] ;
8188 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8189 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8190 /* [HGM] verify: engine mate claims accepted if they were flagged */
8191 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8193 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8194 /* [HGM] verify: engine mate claims accepted if they were flagged */
8195 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8197 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8198 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8201 // now verify win claims, but not in drop games, as we don't understand those yet
8202 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8203 || gameInfo.variant == VariantGreat) &&
8204 (result == WhiteWins && claimer == 'w' ||
8205 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8206 if (appData.debugMode) {
8207 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8208 result, epStatus[forwardMostMove], forwardMostMove);
8210 if(result != trueResult) {
8211 sprintf(buf, "False win claim: '%s'", resultDetails);
8212 result = claimer == 'w' ? BlackWins : WhiteWins;
8213 resultDetails = buf;
8216 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8217 && (forwardMostMove <= backwardMostMove ||
8218 epStatus[forwardMostMove-1] > EP_DRAWS ||
8219 (claimer=='b')==(forwardMostMove&1))
8221 /* [HGM] verify: draws that were not flagged are false claims */
8222 sprintf(buf, "False draw claim: '%s'", resultDetails);
8223 result = claimer == 'w' ? BlackWins : WhiteWins;
8224 resultDetails = buf;
8226 /* (Claiming a loss is accepted no questions asked!) */
8228 /* [HGM] bare: don't allow bare King to win */
8229 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8230 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8231 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8232 && result != GameIsDrawn)
8233 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8234 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8235 int p = (int)boards[forwardMostMove][i][j] - color;
8236 if(p >= 0 && p <= (int)WhiteKing) k++;
8238 if (appData.debugMode) {
8239 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8240 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8243 result = GameIsDrawn;
8244 sprintf(buf, "%s but bare king", resultDetails);
8245 resultDetails = buf;
8251 if(serverMoves != NULL && !loadFlag) { char c = '=';
8252 if(result==WhiteWins) c = '+';
8253 if(result==BlackWins) c = '-';
8254 if(resultDetails != NULL)
8255 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8257 if (resultDetails != NULL) {
8258 gameInfo.result = result;
8259 gameInfo.resultDetails = StrSave(resultDetails);
8261 /* display last move only if game was not loaded from file */
8262 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8263 DisplayMove(currentMove - 1);
8265 if (forwardMostMove != 0) {
8266 if (gameMode != PlayFromGameFile && gameMode != EditGame
8267 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8269 if (*appData.saveGameFile != NULLCHAR) {
8270 SaveGameToFile(appData.saveGameFile, TRUE);
8271 } else if (appData.autoSaveGames) {
8274 if (*appData.savePositionFile != NULLCHAR) {
8275 SavePositionToFile(appData.savePositionFile);
8280 /* Tell program how game ended in case it is learning */
8281 /* [HGM] Moved this to after saving the PGN, just in case */
8282 /* engine died and we got here through time loss. In that */
8283 /* case we will get a fatal error writing the pipe, which */
8284 /* would otherwise lose us the PGN. */
8285 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8286 /* output during GameEnds should never be fatal anymore */
8287 if (gameMode == MachinePlaysWhite ||
8288 gameMode == MachinePlaysBlack ||
8289 gameMode == TwoMachinesPlay ||
8290 gameMode == IcsPlayingWhite ||
8291 gameMode == IcsPlayingBlack ||
8292 gameMode == BeginningOfGame) {
8294 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8296 if (first.pr != NoProc) {
8297 SendToProgram(buf, &first);
8299 if (second.pr != NoProc &&
8300 gameMode == TwoMachinesPlay) {
8301 SendToProgram(buf, &second);
8306 if (appData.icsActive) {
8307 if (appData.quietPlay &&
8308 (gameMode == IcsPlayingWhite ||
8309 gameMode == IcsPlayingBlack)) {
8310 SendToICS(ics_prefix);
8311 SendToICS("set shout 1\n");
8313 nextGameMode = IcsIdle;
8314 ics_user_moved = FALSE;
8315 /* clean up premove. It's ugly when the game has ended and the
8316 * premove highlights are still on the board.
8320 ClearPremoveHighlights();
8321 DrawPosition(FALSE, boards[currentMove]);
8323 if (whosays == GE_ICS) {
8326 if (gameMode == IcsPlayingWhite)
8328 else if(gameMode == IcsPlayingBlack)
8332 if (gameMode == IcsPlayingBlack)
8334 else if(gameMode == IcsPlayingWhite)
8341 PlayIcsUnfinishedSound();
8344 } else if (gameMode == EditGame ||
8345 gameMode == PlayFromGameFile ||
8346 gameMode == AnalyzeMode ||
8347 gameMode == AnalyzeFile) {
8348 nextGameMode = gameMode;
8350 nextGameMode = EndOfGame;
8355 nextGameMode = gameMode;
8358 if (appData.noChessProgram) {
8359 gameMode = nextGameMode;
8361 endingGame = 0; /* [HGM] crash */
8366 /* Put first chess program into idle state */
8367 if (first.pr != NoProc &&
8368 (gameMode == MachinePlaysWhite ||
8369 gameMode == MachinePlaysBlack ||
8370 gameMode == TwoMachinesPlay ||
8371 gameMode == IcsPlayingWhite ||
8372 gameMode == IcsPlayingBlack ||
8373 gameMode == BeginningOfGame)) {
8374 SendToProgram("force\n", &first);
8375 if (first.usePing) {
8377 sprintf(buf, "ping %d\n", ++first.lastPing);
8378 SendToProgram(buf, &first);
8381 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8382 /* Kill off first chess program */
8383 if (first.isr != NULL)
8384 RemoveInputSource(first.isr);
8387 if (first.pr != NoProc) {
8389 DoSleep( appData.delayBeforeQuit );
8390 SendToProgram("quit\n", &first);
8391 DoSleep( appData.delayAfterQuit );
8392 DestroyChildProcess(first.pr, first.useSigterm);
8397 /* Put second chess program into idle state */
8398 if (second.pr != NoProc &&
8399 gameMode == TwoMachinesPlay) {
8400 SendToProgram("force\n", &second);
8401 if (second.usePing) {
8403 sprintf(buf, "ping %d\n", ++second.lastPing);
8404 SendToProgram(buf, &second);
8407 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8408 /* Kill off second chess program */
8409 if (second.isr != NULL)
8410 RemoveInputSource(second.isr);
8413 if (second.pr != NoProc) {
8414 DoSleep( appData.delayBeforeQuit );
8415 SendToProgram("quit\n", &second);
8416 DoSleep( appData.delayAfterQuit );
8417 DestroyChildProcess(second.pr, second.useSigterm);
8422 if (matchMode && gameMode == TwoMachinesPlay) {
8425 if (first.twoMachinesColor[0] == 'w') {
8432 if (first.twoMachinesColor[0] == 'b') {
8441 if (matchGame < appData.matchGames) {
8443 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8444 tmp = first.twoMachinesColor;
8445 first.twoMachinesColor = second.twoMachinesColor;
8446 second.twoMachinesColor = tmp;
8448 gameMode = nextGameMode;
8450 if(appData.matchPause>10000 || appData.matchPause<10)
8451 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8452 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8453 endingGame = 0; /* [HGM] crash */
8457 gameMode = nextGameMode;
8458 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8459 first.tidy, second.tidy,
8460 first.matchWins, second.matchWins,
8461 appData.matchGames - (first.matchWins + second.matchWins));
8462 DisplayFatalError(buf, 0, 0);
8465 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8466 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8468 gameMode = nextGameMode;
8470 endingGame = 0; /* [HGM] crash */
8473 /* Assumes program was just initialized (initString sent).
8474 Leaves program in force mode. */
8476 FeedMovesToProgram(cps, upto)
8477 ChessProgramState *cps;
8482 if (appData.debugMode)
8483 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8484 startedFromSetupPosition ? "position and " : "",
8485 backwardMostMove, upto, cps->which);
8486 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8487 // [HGM] variantswitch: make engine aware of new variant
8488 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8489 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8490 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8491 SendToProgram(buf, cps);
8492 currentlyInitializedVariant = gameInfo.variant;
8494 SendToProgram("force\n", cps);
8495 if (startedFromSetupPosition) {
8496 SendBoard(cps, backwardMostMove);
8497 if (appData.debugMode) {
8498 fprintf(debugFP, "feedMoves\n");
8501 for (i = backwardMostMove; i < upto; i++) {
8502 SendMoveToProgram(i, cps);
8508 ResurrectChessProgram()
8510 /* The chess program may have exited.
8511 If so, restart it and feed it all the moves made so far. */
8513 if (appData.noChessProgram || first.pr != NoProc) return;
8515 StartChessProgram(&first);
8516 InitChessProgram(&first, FALSE);
8517 FeedMovesToProgram(&first, currentMove);
8519 if (!first.sendTime) {
8520 /* can't tell gnuchess what its clock should read,
8521 so we bow to its notion. */
8523 timeRemaining[0][currentMove] = whiteTimeRemaining;
8524 timeRemaining[1][currentMove] = blackTimeRemaining;
8527 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8528 appData.icsEngineAnalyze) && first.analysisSupport) {
8529 SendToProgram("analyze\n", &first);
8530 first.analyzing = TRUE;
8543 if (appData.debugMode) {
8544 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8545 redraw, init, gameMode);
8547 pausing = pauseExamInvalid = FALSE;
8548 startedFromSetupPosition = blackPlaysFirst = FALSE;
8550 whiteFlag = blackFlag = FALSE;
8551 userOfferedDraw = FALSE;
8552 hintRequested = bookRequested = FALSE;
8553 first.maybeThinking = FALSE;
8554 second.maybeThinking = FALSE;
8555 first.bookSuspend = FALSE; // [HGM] book
8556 second.bookSuspend = FALSE;
8557 thinkOutput[0] = NULLCHAR;
8558 lastHint[0] = NULLCHAR;
8559 ClearGameInfo(&gameInfo);
8560 gameInfo.variant = StringToVariant(appData.variant);
8561 ics_user_moved = ics_clock_paused = FALSE;
8562 ics_getting_history = H_FALSE;
8564 white_holding[0] = black_holding[0] = NULLCHAR;
8565 ClearProgramStats();
8566 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8570 flipView = appData.flipView;
8571 ClearPremoveHighlights();
8573 alarmSounded = FALSE;
8575 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8576 if(appData.serverMovesName != NULL) {
8577 /* [HGM] prepare to make moves file for broadcasting */
8578 clock_t t = clock();
8579 if(serverMoves != NULL) fclose(serverMoves);
8580 serverMoves = fopen(appData.serverMovesName, "r");
8581 if(serverMoves != NULL) {
8582 fclose(serverMoves);
8583 /* delay 15 sec before overwriting, so all clients can see end */
8584 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8586 serverMoves = fopen(appData.serverMovesName, "w");
8590 gameMode = BeginningOfGame;
8592 if(appData.icsActive) gameInfo.variant = VariantNormal;
8593 currentMove = forwardMostMove = backwardMostMove = 0;
8594 InitPosition(redraw);
8595 for (i = 0; i < MAX_MOVES; i++) {
8596 if (commentList[i] != NULL) {
8597 free(commentList[i]);
8598 commentList[i] = NULL;
8602 timeRemaining[0][0] = whiteTimeRemaining;
8603 timeRemaining[1][0] = blackTimeRemaining;
8604 if (first.pr == NULL) {
8605 StartChessProgram(&first);
8608 InitChessProgram(&first, startedFromSetupPosition);
8611 DisplayMessage("", "");
8612 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8613 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8620 if (!AutoPlayOneMove())
8622 if (matchMode || appData.timeDelay == 0)
8624 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8626 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8635 int fromX, fromY, toX, toY;
8637 if (appData.debugMode) {
8638 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8641 if (gameMode != PlayFromGameFile)
8644 if (currentMove >= forwardMostMove) {
8645 gameMode = EditGame;
8648 /* [AS] Clear current move marker at the end of a game */
8649 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8654 toX = moveList[currentMove][2] - AAA;
8655 toY = moveList[currentMove][3] - ONE;
8657 if (moveList[currentMove][1] == '@') {
8658 if (appData.highlightLastMove) {
8659 SetHighlights(-1, -1, toX, toY);
8662 fromX = moveList[currentMove][0] - AAA;
8663 fromY = moveList[currentMove][1] - ONE;
8665 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8667 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8669 if (appData.highlightLastMove) {
8670 SetHighlights(fromX, fromY, toX, toY);
8673 DisplayMove(currentMove);
8674 SendMoveToProgram(currentMove++, &first);
8675 DisplayBothClocks();
8676 DrawPosition(FALSE, boards[currentMove]);
8677 // [HGM] PV info: always display, routine tests if empty
8678 DisplayComment(currentMove - 1, commentList[currentMove]);
8684 LoadGameOneMove(readAhead)
8685 ChessMove readAhead;
8687 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8688 char promoChar = NULLCHAR;
8693 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8694 gameMode != AnalyzeMode && gameMode != Training) {
8699 yyboardindex = forwardMostMove;
8700 if (readAhead != (ChessMove)0) {
8701 moveType = readAhead;
8703 if (gameFileFP == NULL)
8705 moveType = (ChessMove) yylex();
8711 if (appData.debugMode)
8712 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8714 if (*p == '{' || *p == '[' || *p == '(') {
8715 p[strlen(p) - 1] = NULLCHAR;
8719 /* append the comment but don't display it */
8720 while (*p == '\n') p++;
8721 AppendComment(currentMove, p);
8724 case WhiteCapturesEnPassant:
8725 case BlackCapturesEnPassant:
8726 case WhitePromotionChancellor:
8727 case BlackPromotionChancellor:
8728 case WhitePromotionArchbishop:
8729 case BlackPromotionArchbishop:
8730 case WhitePromotionCentaur:
8731 case BlackPromotionCentaur:
8732 case WhitePromotionQueen:
8733 case BlackPromotionQueen:
8734 case WhitePromotionRook:
8735 case BlackPromotionRook:
8736 case WhitePromotionBishop:
8737 case BlackPromotionBishop:
8738 case WhitePromotionKnight:
8739 case BlackPromotionKnight:
8740 case WhitePromotionKing:
8741 case BlackPromotionKing:
8743 case WhiteKingSideCastle:
8744 case WhiteQueenSideCastle:
8745 case BlackKingSideCastle:
8746 case BlackQueenSideCastle:
8747 case WhiteKingSideCastleWild:
8748 case WhiteQueenSideCastleWild:
8749 case BlackKingSideCastleWild:
8750 case BlackQueenSideCastleWild:
8752 case WhiteHSideCastleFR:
8753 case WhiteASideCastleFR:
8754 case BlackHSideCastleFR:
8755 case BlackASideCastleFR:
8757 if (appData.debugMode)
8758 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8759 fromX = currentMoveString[0] - AAA;
8760 fromY = currentMoveString[1] - ONE;
8761 toX = currentMoveString[2] - AAA;
8762 toY = currentMoveString[3] - ONE;
8763 promoChar = currentMoveString[4];
8768 if (appData.debugMode)
8769 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8770 fromX = moveType == WhiteDrop ?
8771 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8772 (int) CharToPiece(ToLower(currentMoveString[0]));
8774 toX = currentMoveString[2] - AAA;
8775 toY = currentMoveString[3] - ONE;
8781 case GameUnfinished:
8782 if (appData.debugMode)
8783 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8784 p = strchr(yy_text, '{');
8785 if (p == NULL) p = strchr(yy_text, '(');
8788 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8790 q = strchr(p, *p == '{' ? '}' : ')');
8791 if (q != NULL) *q = NULLCHAR;
8794 GameEnds(moveType, p, GE_FILE);
8796 if (cmailMsgLoaded) {
8798 flipView = WhiteOnMove(currentMove);
8799 if (moveType == GameUnfinished) flipView = !flipView;
8800 if (appData.debugMode)
8801 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8805 case (ChessMove) 0: /* end of file */
8806 if (appData.debugMode)
8807 fprintf(debugFP, "Parser hit end of file\n");
8808 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8809 EP_UNKNOWN, castlingRights[currentMove]) ) {
8815 if (WhiteOnMove(currentMove)) {
8816 GameEnds(BlackWins, "Black mates", GE_FILE);
8818 GameEnds(WhiteWins, "White mates", GE_FILE);
8822 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8829 if (lastLoadGameStart == GNUChessGame) {
8830 /* GNUChessGames have numbers, but they aren't move numbers */
8831 if (appData.debugMode)
8832 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8833 yy_text, (int) moveType);
8834 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8836 /* else fall thru */
8841 /* Reached start of next game in file */
8842 if (appData.debugMode)
8843 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8844 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8845 EP_UNKNOWN, castlingRights[currentMove]) ) {
8851 if (WhiteOnMove(currentMove)) {
8852 GameEnds(BlackWins, "Black mates", GE_FILE);
8854 GameEnds(WhiteWins, "White mates", GE_FILE);
8858 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8864 case PositionDiagram: /* should not happen; ignore */
8865 case ElapsedTime: /* ignore */
8866 case NAG: /* ignore */
8867 if (appData.debugMode)
8868 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8869 yy_text, (int) moveType);
8870 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8873 if (appData.testLegality) {
8874 if (appData.debugMode)
8875 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8876 sprintf(move, _("Illegal move: %d.%s%s"),
8877 (forwardMostMove / 2) + 1,
8878 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8879 DisplayError(move, 0);
8882 if (appData.debugMode)
8883 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8884 yy_text, currentMoveString);
8885 fromX = currentMoveString[0] - AAA;
8886 fromY = currentMoveString[1] - ONE;
8887 toX = currentMoveString[2] - AAA;
8888 toY = currentMoveString[3] - ONE;
8889 promoChar = currentMoveString[4];
8894 if (appData.debugMode)
8895 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8896 sprintf(move, _("Ambiguous move: %d.%s%s"),
8897 (forwardMostMove / 2) + 1,
8898 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8899 DisplayError(move, 0);
8904 case ImpossibleMove:
8905 if (appData.debugMode)
8906 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8907 sprintf(move, _("Illegal move: %d.%s%s"),
8908 (forwardMostMove / 2) + 1,
8909 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8910 DisplayError(move, 0);
8916 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8917 DrawPosition(FALSE, boards[currentMove]);
8918 DisplayBothClocks();
8919 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8920 DisplayComment(currentMove - 1, commentList[currentMove]);
8922 (void) StopLoadGameTimer();
8924 cmailOldMove = forwardMostMove;
8927 /* currentMoveString is set as a side-effect of yylex */
8928 strcat(currentMoveString, "\n");
8929 strcpy(moveList[forwardMostMove], currentMoveString);
8931 thinkOutput[0] = NULLCHAR;
8932 MakeMove(fromX, fromY, toX, toY, promoChar);
8933 currentMove = forwardMostMove;
8938 /* Load the nth game from the given file */
8940 LoadGameFromFile(filename, n, title, useList)
8944 /*Boolean*/ int useList;
8949 if (strcmp(filename, "-") == 0) {
8953 f = fopen(filename, "rb");
8955 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8956 DisplayError(buf, errno);
8960 if (fseek(f, 0, 0) == -1) {
8961 /* f is not seekable; probably a pipe */
8964 if (useList && n == 0) {
8965 int error = GameListBuild(f);
8967 DisplayError(_("Cannot build game list"), error);
8968 } else if (!ListEmpty(&gameList) &&
8969 ((ListGame *) gameList.tailPred)->number > 1) {
8970 GameListPopUp(f, title);
8977 return LoadGame(f, n, title, FALSE);
8982 MakeRegisteredMove()
8984 int fromX, fromY, toX, toY;
8986 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8987 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8990 if (appData.debugMode)
8991 fprintf(debugFP, "Restoring %s for game %d\n",
8992 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8994 thinkOutput[0] = NULLCHAR;
8995 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8996 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8997 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8998 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8999 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9000 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9001 MakeMove(fromX, fromY, toX, toY, promoChar);
9002 ShowMove(fromX, fromY, toX, toY);
9004 switch (MateTest(boards[currentMove], PosFlags(currentMove),
9005 EP_UNKNOWN, castlingRights[currentMove]) ) {
9012 if (WhiteOnMove(currentMove)) {
9013 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9015 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9020 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9027 if (WhiteOnMove(currentMove)) {
9028 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9030 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9035 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9046 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9048 CmailLoadGame(f, gameNumber, title, useList)
9056 if (gameNumber > nCmailGames) {
9057 DisplayError(_("No more games in this message"), 0);
9060 if (f == lastLoadGameFP) {
9061 int offset = gameNumber - lastLoadGameNumber;
9063 cmailMsg[0] = NULLCHAR;
9064 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9065 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9066 nCmailMovesRegistered--;
9068 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9069 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9070 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9073 if (! RegisterMove()) return FALSE;
9077 retVal = LoadGame(f, gameNumber, title, useList);
9079 /* Make move registered during previous look at this game, if any */
9080 MakeRegisteredMove();
9082 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9083 commentList[currentMove]
9084 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9085 DisplayComment(currentMove - 1, commentList[currentMove]);
9091 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9096 int gameNumber = lastLoadGameNumber + offset;
9097 if (lastLoadGameFP == NULL) {
9098 DisplayError(_("No game has been loaded yet"), 0);
9101 if (gameNumber <= 0) {
9102 DisplayError(_("Can't back up any further"), 0);
9105 if (cmailMsgLoaded) {
9106 return CmailLoadGame(lastLoadGameFP, gameNumber,
9107 lastLoadGameTitle, lastLoadGameUseList);
9109 return LoadGame(lastLoadGameFP, gameNumber,
9110 lastLoadGameTitle, lastLoadGameUseList);
9116 /* Load the nth game from open file f */
9118 LoadGame(f, gameNumber, title, useList)
9126 int gn = gameNumber;
9127 ListGame *lg = NULL;
9130 GameMode oldGameMode;
9131 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9133 if (appData.debugMode)
9134 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9136 if (gameMode == Training )
9137 SetTrainingModeOff();
9139 oldGameMode = gameMode;
9140 if (gameMode != BeginningOfGame) {
9145 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9146 fclose(lastLoadGameFP);
9150 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9153 fseek(f, lg->offset, 0);
9154 GameListHighlight(gameNumber);
9158 DisplayError(_("Game number out of range"), 0);
9163 if (fseek(f, 0, 0) == -1) {
9164 if (f == lastLoadGameFP ?
9165 gameNumber == lastLoadGameNumber + 1 :
9169 DisplayError(_("Can't seek on game file"), 0);
9175 lastLoadGameNumber = gameNumber;
9176 strcpy(lastLoadGameTitle, title);
9177 lastLoadGameUseList = useList;
9181 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9182 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9183 lg->gameInfo.black);
9185 } else if (*title != NULLCHAR) {
9186 if (gameNumber > 1) {
9187 sprintf(buf, "%s %d", title, gameNumber);
9190 DisplayTitle(title);
9194 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9195 gameMode = PlayFromGameFile;
9199 currentMove = forwardMostMove = backwardMostMove = 0;
9200 CopyBoard(boards[0], initialPosition);
9204 * Skip the first gn-1 games in the file.
9205 * Also skip over anything that precedes an identifiable
9206 * start of game marker, to avoid being confused by
9207 * garbage at the start of the file. Currently
9208 * recognized start of game markers are the move number "1",
9209 * the pattern "gnuchess .* game", the pattern
9210 * "^[#;%] [^ ]* game file", and a PGN tag block.
9211 * A game that starts with one of the latter two patterns
9212 * will also have a move number 1, possibly
9213 * following a position diagram.
9214 * 5-4-02: Let's try being more lenient and allowing a game to
9215 * start with an unnumbered move. Does that break anything?
9217 cm = lastLoadGameStart = (ChessMove) 0;
9219 yyboardindex = forwardMostMove;
9220 cm = (ChessMove) yylex();
9223 if (cmailMsgLoaded) {
9224 nCmailGames = CMAIL_MAX_GAMES - gn;
9227 DisplayError(_("Game not found in file"), 0);
9234 lastLoadGameStart = cm;
9238 switch (lastLoadGameStart) {
9245 gn--; /* count this game */
9246 lastLoadGameStart = cm;
9255 switch (lastLoadGameStart) {
9260 gn--; /* count this game */
9261 lastLoadGameStart = cm;
9264 lastLoadGameStart = cm; /* game counted already */
9272 yyboardindex = forwardMostMove;
9273 cm = (ChessMove) yylex();
9274 } while (cm == PGNTag || cm == Comment);
9281 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9282 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9283 != CMAIL_OLD_RESULT) {
9285 cmailResult[ CMAIL_MAX_GAMES
9286 - gn - 1] = CMAIL_OLD_RESULT;
9292 /* Only a NormalMove can be at the start of a game
9293 * without a position diagram. */
9294 if (lastLoadGameStart == (ChessMove) 0) {
9296 lastLoadGameStart = MoveNumberOne;
9305 if (appData.debugMode)
9306 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9308 if (cm == XBoardGame) {
9309 /* Skip any header junk before position diagram and/or move 1 */
9311 yyboardindex = forwardMostMove;
9312 cm = (ChessMove) yylex();
9314 if (cm == (ChessMove) 0 ||
9315 cm == GNUChessGame || cm == XBoardGame) {
9316 /* Empty game; pretend end-of-file and handle later */
9321 if (cm == MoveNumberOne || cm == PositionDiagram ||
9322 cm == PGNTag || cm == Comment)
9325 } else if (cm == GNUChessGame) {
9326 if (gameInfo.event != NULL) {
9327 free(gameInfo.event);
9329 gameInfo.event = StrSave(yy_text);
9332 startedFromSetupPosition = FALSE;
9333 while (cm == PGNTag) {
9334 if (appData.debugMode)
9335 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9336 err = ParsePGNTag(yy_text, &gameInfo);
9337 if (!err) numPGNTags++;
9339 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9340 if(gameInfo.variant != oldVariant) {
9341 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9343 oldVariant = gameInfo.variant;
9344 if (appData.debugMode)
9345 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9349 if (gameInfo.fen != NULL) {
9350 Board initial_position;
9351 startedFromSetupPosition = TRUE;
9352 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9354 DisplayError(_("Bad FEN position in file"), 0);
9357 CopyBoard(boards[0], initial_position);
9358 if (blackPlaysFirst) {
9359 currentMove = forwardMostMove = backwardMostMove = 1;
9360 CopyBoard(boards[1], initial_position);
9361 strcpy(moveList[0], "");
9362 strcpy(parseList[0], "");
9363 timeRemaining[0][1] = whiteTimeRemaining;
9364 timeRemaining[1][1] = blackTimeRemaining;
9365 if (commentList[0] != NULL) {
9366 commentList[1] = commentList[0];
9367 commentList[0] = NULL;
9370 currentMove = forwardMostMove = backwardMostMove = 0;
9372 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9374 initialRulePlies = FENrulePlies;
9375 epStatus[forwardMostMove] = FENepStatus;
9376 for( i=0; i< nrCastlingRights; i++ )
9377 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9379 yyboardindex = forwardMostMove;
9381 gameInfo.fen = NULL;
9384 yyboardindex = forwardMostMove;
9385 cm = (ChessMove) yylex();
9387 /* Handle comments interspersed among the tags */
9388 while (cm == Comment) {
9390 if (appData.debugMode)
9391 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9393 if (*p == '{' || *p == '[' || *p == '(') {
9394 p[strlen(p) - 1] = NULLCHAR;
9397 while (*p == '\n') p++;
9398 AppendComment(currentMove, p);
9399 yyboardindex = forwardMostMove;
9400 cm = (ChessMove) yylex();
9404 /* don't rely on existence of Event tag since if game was
9405 * pasted from clipboard the Event tag may not exist
9407 if (numPGNTags > 0){
9409 if (gameInfo.variant == VariantNormal) {
9410 gameInfo.variant = StringToVariant(gameInfo.event);
9413 if( appData.autoDisplayTags ) {
9414 tags = PGNTags(&gameInfo);
9415 TagsPopUp(tags, CmailMsg());
9420 /* Make something up, but don't display it now */
9425 if (cm == PositionDiagram) {
9428 Board initial_position;
9430 if (appData.debugMode)
9431 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9433 if (!startedFromSetupPosition) {
9435 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9436 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9446 initial_position[i][j++] = CharToPiece(*p);
9449 while (*p == ' ' || *p == '\t' ||
9450 *p == '\n' || *p == '\r') p++;
9452 if (strncmp(p, "black", strlen("black"))==0)
9453 blackPlaysFirst = TRUE;
9455 blackPlaysFirst = FALSE;
9456 startedFromSetupPosition = TRUE;
9458 CopyBoard(boards[0], initial_position);
9459 if (blackPlaysFirst) {
9460 currentMove = forwardMostMove = backwardMostMove = 1;
9461 CopyBoard(boards[1], initial_position);
9462 strcpy(moveList[0], "");
9463 strcpy(parseList[0], "");
9464 timeRemaining[0][1] = whiteTimeRemaining;
9465 timeRemaining[1][1] = blackTimeRemaining;
9466 if (commentList[0] != NULL) {
9467 commentList[1] = commentList[0];
9468 commentList[0] = NULL;
9471 currentMove = forwardMostMove = backwardMostMove = 0;
9474 yyboardindex = forwardMostMove;
9475 cm = (ChessMove) yylex();
9478 if (first.pr == NoProc) {
9479 StartChessProgram(&first);
9481 InitChessProgram(&first, FALSE);
9482 SendToProgram("force\n", &first);
9483 if (startedFromSetupPosition) {
9484 SendBoard(&first, forwardMostMove);
9485 if (appData.debugMode) {
9486 fprintf(debugFP, "Load Game\n");
9488 DisplayBothClocks();
9491 /* [HGM] server: flag to write setup moves in broadcast file as one */
9492 loadFlag = appData.suppressLoadMoves;
9494 while (cm == Comment) {
9496 if (appData.debugMode)
9497 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9499 if (*p == '{' || *p == '[' || *p == '(') {
9500 p[strlen(p) - 1] = NULLCHAR;
9503 while (*p == '\n') p++;
9504 AppendComment(currentMove, p);
9505 yyboardindex = forwardMostMove;
9506 cm = (ChessMove) yylex();
9509 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9510 cm == WhiteWins || cm == BlackWins ||
9511 cm == GameIsDrawn || cm == GameUnfinished) {
9512 DisplayMessage("", _("No moves in game"));
9513 if (cmailMsgLoaded) {
9514 if (appData.debugMode)
9515 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9519 DrawPosition(FALSE, boards[currentMove]);
9520 DisplayBothClocks();
9521 gameMode = EditGame;
9528 // [HGM] PV info: routine tests if comment empty
9529 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9530 DisplayComment(currentMove - 1, commentList[currentMove]);
9532 if (!matchMode && appData.timeDelay != 0)
9533 DrawPosition(FALSE, boards[currentMove]);
9535 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9536 programStats.ok_to_send = 1;
9539 /* if the first token after the PGN tags is a move
9540 * and not move number 1, retrieve it from the parser
9542 if (cm != MoveNumberOne)
9543 LoadGameOneMove(cm);
9545 /* load the remaining moves from the file */
9546 while (LoadGameOneMove((ChessMove)0)) {
9547 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9548 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9551 /* rewind to the start of the game */
9552 currentMove = backwardMostMove;
9554 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9556 if (oldGameMode == AnalyzeFile ||
9557 oldGameMode == AnalyzeMode) {
9561 if (matchMode || appData.timeDelay == 0) {
9563 gameMode = EditGame;
9565 } else if (appData.timeDelay > 0) {
9569 if (appData.debugMode)
9570 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9572 loadFlag = 0; /* [HGM] true game starts */
9576 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9578 ReloadPosition(offset)
9581 int positionNumber = lastLoadPositionNumber + offset;
9582 if (lastLoadPositionFP == NULL) {
9583 DisplayError(_("No position has been loaded yet"), 0);
9586 if (positionNumber <= 0) {
9587 DisplayError(_("Can't back up any further"), 0);
9590 return LoadPosition(lastLoadPositionFP, positionNumber,
9591 lastLoadPositionTitle);
9594 /* Load the nth position from the given file */
9596 LoadPositionFromFile(filename, n, title)
9604 if (strcmp(filename, "-") == 0) {
9605 return LoadPosition(stdin, n, "stdin");
9607 f = fopen(filename, "rb");
9609 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9610 DisplayError(buf, errno);
9613 return LoadPosition(f, n, title);
9618 /* Load the nth position from the given open file, and close it */
9620 LoadPosition(f, positionNumber, title)
9625 char *p, line[MSG_SIZ];
9626 Board initial_position;
9627 int i, j, fenMode, pn;
9629 if (gameMode == Training )
9630 SetTrainingModeOff();
9632 if (gameMode != BeginningOfGame) {
9635 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9636 fclose(lastLoadPositionFP);
9638 if (positionNumber == 0) positionNumber = 1;
9639 lastLoadPositionFP = f;
9640 lastLoadPositionNumber = positionNumber;
9641 strcpy(lastLoadPositionTitle, title);
9642 if (first.pr == NoProc) {
9643 StartChessProgram(&first);
9644 InitChessProgram(&first, FALSE);
9646 pn = positionNumber;
9647 if (positionNumber < 0) {
9648 /* Negative position number means to seek to that byte offset */
9649 if (fseek(f, -positionNumber, 0) == -1) {
9650 DisplayError(_("Can't seek on position file"), 0);
9655 if (fseek(f, 0, 0) == -1) {
9656 if (f == lastLoadPositionFP ?
9657 positionNumber == lastLoadPositionNumber + 1 :
9658 positionNumber == 1) {
9661 DisplayError(_("Can't seek on position file"), 0);
9666 /* See if this file is FEN or old-style xboard */
9667 if (fgets(line, MSG_SIZ, f) == NULL) {
9668 DisplayError(_("Position not found in file"), 0);
9671 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9672 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9675 if (fenMode || line[0] == '#') pn--;
9677 /* skip positions before number pn */
9678 if (fgets(line, MSG_SIZ, f) == NULL) {
9680 DisplayError(_("Position not found in file"), 0);
9683 if (fenMode || line[0] == '#') pn--;
9688 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9689 DisplayError(_("Bad FEN position in file"), 0);
9693 (void) fgets(line, MSG_SIZ, f);
9694 (void) fgets(line, MSG_SIZ, f);
9696 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9697 (void) fgets(line, MSG_SIZ, f);
9698 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9701 initial_position[i][j++] = CharToPiece(*p);
9705 blackPlaysFirst = FALSE;
9707 (void) fgets(line, MSG_SIZ, f);
9708 if (strncmp(line, "black", strlen("black"))==0)
9709 blackPlaysFirst = TRUE;
9712 startedFromSetupPosition = TRUE;
9714 SendToProgram("force\n", &first);
9715 CopyBoard(boards[0], initial_position);
9716 if (blackPlaysFirst) {
9717 currentMove = forwardMostMove = backwardMostMove = 1;
9718 strcpy(moveList[0], "");
9719 strcpy(parseList[0], "");
9720 CopyBoard(boards[1], initial_position);
9721 DisplayMessage("", _("Black to play"));
9723 currentMove = forwardMostMove = backwardMostMove = 0;
9724 DisplayMessage("", _("White to play"));
9726 /* [HGM] copy FEN attributes as well */
9728 initialRulePlies = FENrulePlies;
9729 epStatus[forwardMostMove] = FENepStatus;
9730 for( i=0; i< nrCastlingRights; i++ )
9731 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9733 SendBoard(&first, forwardMostMove);
9734 if (appData.debugMode) {
9736 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9737 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9738 fprintf(debugFP, "Load Position\n");
9741 if (positionNumber > 1) {
9742 sprintf(line, "%s %d", title, positionNumber);
9745 DisplayTitle(title);
9747 gameMode = EditGame;
9750 timeRemaining[0][1] = whiteTimeRemaining;
9751 timeRemaining[1][1] = blackTimeRemaining;
9752 DrawPosition(FALSE, boards[currentMove]);
9759 CopyPlayerNameIntoFileName(dest, src)
9762 while (*src != NULLCHAR && *src != ',') {
9767 *(*dest)++ = *src++;
9772 char *DefaultFileName(ext)
9775 static char def[MSG_SIZ];
9778 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9780 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9782 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9791 /* Save the current game to the given file */
9793 SaveGameToFile(filename, append)
9800 if (strcmp(filename, "-") == 0) {
9801 return SaveGame(stdout, 0, NULL);
9803 f = fopen(filename, append ? "a" : "w");
9805 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9806 DisplayError(buf, errno);
9809 return SaveGame(f, 0, NULL);
9818 static char buf[MSG_SIZ];
9821 p = strchr(str, ' ');
9822 if (p == NULL) return str;
9823 strncpy(buf, str, p - str);
9824 buf[p - str] = NULLCHAR;
9828 #define PGN_MAX_LINE 75
9830 #define PGN_SIDE_WHITE 0
9831 #define PGN_SIDE_BLACK 1
9834 static int FindFirstMoveOutOfBook( int side )
9838 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9839 int index = backwardMostMove;
9840 int has_book_hit = 0;
9842 if( (index % 2) != side ) {
9846 while( index < forwardMostMove ) {
9847 /* Check to see if engine is in book */
9848 int depth = pvInfoList[index].depth;
9849 int score = pvInfoList[index].score;
9855 else if( score == 0 && depth == 63 ) {
9856 in_book = 1; /* Zappa */
9858 else if( score == 2 && depth == 99 ) {
9859 in_book = 1; /* Abrok */
9862 has_book_hit += in_book;
9878 void GetOutOfBookInfo( char * buf )
9882 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9884 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9885 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9889 if( oob[0] >= 0 || oob[1] >= 0 ) {
9890 for( i=0; i<2; i++ ) {
9894 if( i > 0 && oob[0] >= 0 ) {
9898 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9899 sprintf( buf+strlen(buf), "%s%.2f",
9900 pvInfoList[idx].score >= 0 ? "+" : "",
9901 pvInfoList[idx].score / 100.0 );
9907 /* Save game in PGN style and close the file */
9912 int i, offset, linelen, newblock;
9916 int movelen, numlen, blank;
9917 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9919 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9921 tm = time((time_t *) NULL);
9923 PrintPGNTags(f, &gameInfo);
9925 if (backwardMostMove > 0 || startedFromSetupPosition) {
9926 char *fen = PositionToFEN(backwardMostMove, NULL);
9927 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9928 fprintf(f, "\n{--------------\n");
9929 PrintPosition(f, backwardMostMove);
9930 fprintf(f, "--------------}\n");
9934 /* [AS] Out of book annotation */
9935 if( appData.saveOutOfBookInfo ) {
9938 GetOutOfBookInfo( buf );
9940 if( buf[0] != '\0' ) {
9941 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9948 i = backwardMostMove;
9952 while (i < forwardMostMove) {
9953 /* Print comments preceding this move */
9954 if (commentList[i] != NULL) {
9955 if (linelen > 0) fprintf(f, "\n");
9956 fprintf(f, "{\n%s}\n", commentList[i]);
9961 /* Format move number */
9963 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9966 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9968 numtext[0] = NULLCHAR;
9971 numlen = strlen(numtext);
9974 /* Print move number */
9975 blank = linelen > 0 && numlen > 0;
9976 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9985 fprintf(f, "%s", numtext);
9989 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9990 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9993 blank = linelen > 0 && movelen > 0;
9994 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10003 fprintf(f, "%s", move_buffer);
10004 linelen += movelen;
10006 /* [AS] Add PV info if present */
10007 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10008 /* [HGM] add time */
10009 char buf[MSG_SIZ]; int seconds;
10011 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10013 if( seconds <= 0) buf[0] = 0; else
10014 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10015 seconds = (seconds + 4)/10; // round to full seconds
10016 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10017 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10020 sprintf( move_buffer, "{%s%.2f/%d%s}",
10021 pvInfoList[i].score >= 0 ? "+" : "",
10022 pvInfoList[i].score / 100.0,
10023 pvInfoList[i].depth,
10026 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10028 /* Print score/depth */
10029 blank = linelen > 0 && movelen > 0;
10030 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10039 fprintf(f, "%s", move_buffer);
10040 linelen += movelen;
10046 /* Start a new line */
10047 if (linelen > 0) fprintf(f, "\n");
10049 /* Print comments after last move */
10050 if (commentList[i] != NULL) {
10051 fprintf(f, "{\n%s}\n", commentList[i]);
10055 if (gameInfo.resultDetails != NULL &&
10056 gameInfo.resultDetails[0] != NULLCHAR) {
10057 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10058 PGNResult(gameInfo.result));
10060 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10064 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10068 /* Save game in old style and close the file */
10070 SaveGameOldStyle(f)
10076 tm = time((time_t *) NULL);
10078 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10081 if (backwardMostMove > 0 || startedFromSetupPosition) {
10082 fprintf(f, "\n[--------------\n");
10083 PrintPosition(f, backwardMostMove);
10084 fprintf(f, "--------------]\n");
10089 i = backwardMostMove;
10090 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10092 while (i < forwardMostMove) {
10093 if (commentList[i] != NULL) {
10094 fprintf(f, "[%s]\n", commentList[i]);
10097 if ((i % 2) == 1) {
10098 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10101 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10103 if (commentList[i] != NULL) {
10107 if (i >= forwardMostMove) {
10111 fprintf(f, "%s\n", parseList[i]);
10116 if (commentList[i] != NULL) {
10117 fprintf(f, "[%s]\n", commentList[i]);
10120 /* This isn't really the old style, but it's close enough */
10121 if (gameInfo.resultDetails != NULL &&
10122 gameInfo.resultDetails[0] != NULLCHAR) {
10123 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10124 gameInfo.resultDetails);
10126 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10133 /* Save the current game to open file f and close the file */
10135 SaveGame(f, dummy, dummy2)
10140 if (gameMode == EditPosition) EditPositionDone(TRUE);
10141 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10142 if (appData.oldSaveStyle)
10143 return SaveGameOldStyle(f);
10145 return SaveGamePGN(f);
10148 /* Save the current position to the given file */
10150 SavePositionToFile(filename)
10156 if (strcmp(filename, "-") == 0) {
10157 return SavePosition(stdout, 0, NULL);
10159 f = fopen(filename, "a");
10161 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10162 DisplayError(buf, errno);
10165 SavePosition(f, 0, NULL);
10171 /* Save the current position to the given open file and close the file */
10173 SavePosition(f, dummy, dummy2)
10181 if (gameMode == EditPosition) EditPositionDone(TRUE);
10182 if (appData.oldSaveStyle) {
10183 tm = time((time_t *) NULL);
10185 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10187 fprintf(f, "[--------------\n");
10188 PrintPosition(f, currentMove);
10189 fprintf(f, "--------------]\n");
10191 fen = PositionToFEN(currentMove, NULL);
10192 fprintf(f, "%s\n", fen);
10200 ReloadCmailMsgEvent(unregister)
10204 static char *inFilename = NULL;
10205 static char *outFilename;
10207 struct stat inbuf, outbuf;
10210 /* Any registered moves are unregistered if unregister is set, */
10211 /* i.e. invoked by the signal handler */
10213 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10214 cmailMoveRegistered[i] = FALSE;
10215 if (cmailCommentList[i] != NULL) {
10216 free(cmailCommentList[i]);
10217 cmailCommentList[i] = NULL;
10220 nCmailMovesRegistered = 0;
10223 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10224 cmailResult[i] = CMAIL_NOT_RESULT;
10228 if (inFilename == NULL) {
10229 /* Because the filenames are static they only get malloced once */
10230 /* and they never get freed */
10231 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10232 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10234 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10235 sprintf(outFilename, "%s.out", appData.cmailGameName);
10238 status = stat(outFilename, &outbuf);
10240 cmailMailedMove = FALSE;
10242 status = stat(inFilename, &inbuf);
10243 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10246 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10247 counts the games, notes how each one terminated, etc.
10249 It would be nice to remove this kludge and instead gather all
10250 the information while building the game list. (And to keep it
10251 in the game list nodes instead of having a bunch of fixed-size
10252 parallel arrays.) Note this will require getting each game's
10253 termination from the PGN tags, as the game list builder does
10254 not process the game moves. --mann
10256 cmailMsgLoaded = TRUE;
10257 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10259 /* Load first game in the file or popup game menu */
10260 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10262 #endif /* !WIN32 */
10270 char string[MSG_SIZ];
10272 if ( cmailMailedMove
10273 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10274 return TRUE; /* Allow free viewing */
10277 /* Unregister move to ensure that we don't leave RegisterMove */
10278 /* with the move registered when the conditions for registering no */
10280 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10281 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10282 nCmailMovesRegistered --;
10284 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10286 free(cmailCommentList[lastLoadGameNumber - 1]);
10287 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10291 if (cmailOldMove == -1) {
10292 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10296 if (currentMove > cmailOldMove + 1) {
10297 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10301 if (currentMove < cmailOldMove) {
10302 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10306 if (forwardMostMove > currentMove) {
10307 /* Silently truncate extra moves */
10311 if ( (currentMove == cmailOldMove + 1)
10312 || ( (currentMove == cmailOldMove)
10313 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10314 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10315 if (gameInfo.result != GameUnfinished) {
10316 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10319 if (commentList[currentMove] != NULL) {
10320 cmailCommentList[lastLoadGameNumber - 1]
10321 = StrSave(commentList[currentMove]);
10323 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10325 if (appData.debugMode)
10326 fprintf(debugFP, "Saving %s for game %d\n",
10327 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10330 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10332 f = fopen(string, "w");
10333 if (appData.oldSaveStyle) {
10334 SaveGameOldStyle(f); /* also closes the file */
10336 sprintf(string, "%s.pos.out", appData.cmailGameName);
10337 f = fopen(string, "w");
10338 SavePosition(f, 0, NULL); /* also closes the file */
10340 fprintf(f, "{--------------\n");
10341 PrintPosition(f, currentMove);
10342 fprintf(f, "--------------}\n\n");
10344 SaveGame(f, 0, NULL); /* also closes the file*/
10347 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10348 nCmailMovesRegistered ++;
10349 } else if (nCmailGames == 1) {
10350 DisplayError(_("You have not made a move yet"), 0);
10361 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10362 FILE *commandOutput;
10363 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10364 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10370 if (! cmailMsgLoaded) {
10371 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10375 if (nCmailGames == nCmailResults) {
10376 DisplayError(_("No unfinished games"), 0);
10380 #if CMAIL_PROHIBIT_REMAIL
10381 if (cmailMailedMove) {
10382 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);
10383 DisplayError(msg, 0);
10388 if (! (cmailMailedMove || RegisterMove())) return;
10390 if ( cmailMailedMove
10391 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10392 sprintf(string, partCommandString,
10393 appData.debugMode ? " -v" : "", appData.cmailGameName);
10394 commandOutput = popen(string, "r");
10396 if (commandOutput == NULL) {
10397 DisplayError(_("Failed to invoke cmail"), 0);
10399 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10400 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10402 if (nBuffers > 1) {
10403 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10404 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10405 nBytes = MSG_SIZ - 1;
10407 (void) memcpy(msg, buffer, nBytes);
10409 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10411 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10412 cmailMailedMove = TRUE; /* Prevent >1 moves */
10415 for (i = 0; i < nCmailGames; i ++) {
10416 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10421 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10423 sprintf(buffer, "%s/%s.%s.archive",
10425 appData.cmailGameName,
10427 LoadGameFromFile(buffer, 1, buffer, FALSE);
10428 cmailMsgLoaded = FALSE;
10432 DisplayInformation(msg);
10433 pclose(commandOutput);
10436 if ((*cmailMsg) != '\0') {
10437 DisplayInformation(cmailMsg);
10442 #endif /* !WIN32 */
10451 int prependComma = 0;
10453 char string[MSG_SIZ]; /* Space for game-list */
10456 if (!cmailMsgLoaded) return "";
10458 if (cmailMailedMove) {
10459 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10461 /* Create a list of games left */
10462 sprintf(string, "[");
10463 for (i = 0; i < nCmailGames; i ++) {
10464 if (! ( cmailMoveRegistered[i]
10465 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10466 if (prependComma) {
10467 sprintf(number, ",%d", i + 1);
10469 sprintf(number, "%d", i + 1);
10473 strcat(string, number);
10476 strcat(string, "]");
10478 if (nCmailMovesRegistered + nCmailResults == 0) {
10479 switch (nCmailGames) {
10482 _("Still need to make move for game\n"));
10487 _("Still need to make moves for both games\n"));
10492 _("Still need to make moves for all %d games\n"),
10497 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10500 _("Still need to make a move for game %s\n"),
10505 if (nCmailResults == nCmailGames) {
10506 sprintf(cmailMsg, _("No unfinished games\n"));
10508 sprintf(cmailMsg, _("Ready to send mail\n"));
10514 _("Still need to make moves for games %s\n"),
10526 if (gameMode == Training)
10527 SetTrainingModeOff();
10530 cmailMsgLoaded = FALSE;
10531 if (appData.icsActive) {
10532 SendToICS(ics_prefix);
10533 SendToICS("refresh\n");
10543 /* Give up on clean exit */
10547 /* Keep trying for clean exit */
10551 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10553 if (telnetISR != NULL) {
10554 RemoveInputSource(telnetISR);
10556 if (icsPR != NoProc) {
10557 DestroyChildProcess(icsPR, TRUE);
10560 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10561 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10563 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10564 /* make sure this other one finishes before killing it! */
10565 if(endingGame) { int count = 0;
10566 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10567 while(endingGame && count++ < 10) DoSleep(1);
10568 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10571 /* Kill off chess programs */
10572 if (first.pr != NoProc) {
10575 DoSleep( appData.delayBeforeQuit );
10576 SendToProgram("quit\n", &first);
10577 DoSleep( appData.delayAfterQuit );
10578 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10580 if (second.pr != NoProc) {
10581 DoSleep( appData.delayBeforeQuit );
10582 SendToProgram("quit\n", &second);
10583 DoSleep( appData.delayAfterQuit );
10584 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10586 if (first.isr != NULL) {
10587 RemoveInputSource(first.isr);
10589 if (second.isr != NULL) {
10590 RemoveInputSource(second.isr);
10593 ShutDownFrontEnd();
10600 if (appData.debugMode)
10601 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10605 if (gameMode == MachinePlaysWhite ||
10606 gameMode == MachinePlaysBlack) {
10609 DisplayBothClocks();
10611 if (gameMode == PlayFromGameFile) {
10612 if (appData.timeDelay >= 0)
10613 AutoPlayGameLoop();
10614 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10615 Reset(FALSE, TRUE);
10616 SendToICS(ics_prefix);
10617 SendToICS("refresh\n");
10618 } else if (currentMove < forwardMostMove) {
10619 ForwardInner(forwardMostMove);
10621 pauseExamInvalid = FALSE;
10623 switch (gameMode) {
10627 pauseExamForwardMostMove = forwardMostMove;
10628 pauseExamInvalid = FALSE;
10631 case IcsPlayingWhite:
10632 case IcsPlayingBlack:
10636 case PlayFromGameFile:
10637 (void) StopLoadGameTimer();
10641 case BeginningOfGame:
10642 if (appData.icsActive) return;
10643 /* else fall through */
10644 case MachinePlaysWhite:
10645 case MachinePlaysBlack:
10646 case TwoMachinesPlay:
10647 if (forwardMostMove == 0)
10648 return; /* don't pause if no one has moved */
10649 if ((gameMode == MachinePlaysWhite &&
10650 !WhiteOnMove(forwardMostMove)) ||
10651 (gameMode == MachinePlaysBlack &&
10652 WhiteOnMove(forwardMostMove))) {
10665 char title[MSG_SIZ];
10667 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10668 strcpy(title, _("Edit comment"));
10670 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10671 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10672 parseList[currentMove - 1]);
10675 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10682 char *tags = PGNTags(&gameInfo);
10683 EditTagsPopUp(tags);
10690 if (appData.noChessProgram || gameMode == AnalyzeMode)
10693 if (gameMode != AnalyzeFile) {
10694 if (!appData.icsEngineAnalyze) {
10696 if (gameMode != EditGame) return;
10698 ResurrectChessProgram();
10699 SendToProgram("analyze\n", &first);
10700 first.analyzing = TRUE;
10701 /*first.maybeThinking = TRUE;*/
10702 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10703 EngineOutputPopUp();
10705 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10710 StartAnalysisClock();
10711 GetTimeMark(&lastNodeCountTime);
10718 if (appData.noChessProgram || gameMode == AnalyzeFile)
10721 if (gameMode != AnalyzeMode) {
10723 if (gameMode != EditGame) return;
10724 ResurrectChessProgram();
10725 SendToProgram("analyze\n", &first);
10726 first.analyzing = TRUE;
10727 /*first.maybeThinking = TRUE;*/
10728 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10729 EngineOutputPopUp();
10731 gameMode = AnalyzeFile;
10736 StartAnalysisClock();
10737 GetTimeMark(&lastNodeCountTime);
10742 MachineWhiteEvent()
10745 char *bookHit = NULL;
10747 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10751 if (gameMode == PlayFromGameFile ||
10752 gameMode == TwoMachinesPlay ||
10753 gameMode == Training ||
10754 gameMode == AnalyzeMode ||
10755 gameMode == EndOfGame)
10758 if (gameMode == EditPosition)
10759 EditPositionDone(TRUE);
10761 if (!WhiteOnMove(currentMove)) {
10762 DisplayError(_("It is not White's turn"), 0);
10766 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10769 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10770 gameMode == AnalyzeFile)
10773 ResurrectChessProgram(); /* in case it isn't running */
10774 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10775 gameMode = MachinePlaysWhite;
10778 gameMode = MachinePlaysWhite;
10782 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10784 if (first.sendName) {
10785 sprintf(buf, "name %s\n", gameInfo.black);
10786 SendToProgram(buf, &first);
10788 if (first.sendTime) {
10789 if (first.useColors) {
10790 SendToProgram("black\n", &first); /*gnu kludge*/
10792 SendTimeRemaining(&first, TRUE);
10794 if (first.useColors) {
10795 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10797 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10798 SetMachineThinkingEnables();
10799 first.maybeThinking = TRUE;
10803 if (appData.autoFlipView && !flipView) {
10804 flipView = !flipView;
10805 DrawPosition(FALSE, NULL);
10806 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10809 if(bookHit) { // [HGM] book: simulate book reply
10810 static char bookMove[MSG_SIZ]; // a bit generous?
10812 programStats.nodes = programStats.depth = programStats.time =
10813 programStats.score = programStats.got_only_move = 0;
10814 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10816 strcpy(bookMove, "move ");
10817 strcat(bookMove, bookHit);
10818 HandleMachineMove(bookMove, &first);
10823 MachineBlackEvent()
10826 char *bookHit = NULL;
10828 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10832 if (gameMode == PlayFromGameFile ||
10833 gameMode == TwoMachinesPlay ||
10834 gameMode == Training ||
10835 gameMode == AnalyzeMode ||
10836 gameMode == EndOfGame)
10839 if (gameMode == EditPosition)
10840 EditPositionDone(TRUE);
10842 if (WhiteOnMove(currentMove)) {
10843 DisplayError(_("It is not Black's turn"), 0);
10847 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10850 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10851 gameMode == AnalyzeFile)
10854 ResurrectChessProgram(); /* in case it isn't running */
10855 gameMode = MachinePlaysBlack;
10859 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10861 if (first.sendName) {
10862 sprintf(buf, "name %s\n", gameInfo.white);
10863 SendToProgram(buf, &first);
10865 if (first.sendTime) {
10866 if (first.useColors) {
10867 SendToProgram("white\n", &first); /*gnu kludge*/
10869 SendTimeRemaining(&first, FALSE);
10871 if (first.useColors) {
10872 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10874 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10875 SetMachineThinkingEnables();
10876 first.maybeThinking = TRUE;
10879 if (appData.autoFlipView && flipView) {
10880 flipView = !flipView;
10881 DrawPosition(FALSE, NULL);
10882 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10884 if(bookHit) { // [HGM] book: simulate book reply
10885 static char bookMove[MSG_SIZ]; // a bit generous?
10887 programStats.nodes = programStats.depth = programStats.time =
10888 programStats.score = programStats.got_only_move = 0;
10889 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10891 strcpy(bookMove, "move ");
10892 strcat(bookMove, bookHit);
10893 HandleMachineMove(bookMove, &first);
10899 DisplayTwoMachinesTitle()
10902 if (appData.matchGames > 0) {
10903 if (first.twoMachinesColor[0] == 'w') {
10904 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10905 gameInfo.white, gameInfo.black,
10906 first.matchWins, second.matchWins,
10907 matchGame - 1 - (first.matchWins + second.matchWins));
10909 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10910 gameInfo.white, gameInfo.black,
10911 second.matchWins, first.matchWins,
10912 matchGame - 1 - (first.matchWins + second.matchWins));
10915 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10921 TwoMachinesEvent P((void))
10925 ChessProgramState *onmove;
10926 char *bookHit = NULL;
10928 if (appData.noChessProgram) return;
10930 switch (gameMode) {
10931 case TwoMachinesPlay:
10933 case MachinePlaysWhite:
10934 case MachinePlaysBlack:
10935 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10936 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10940 case BeginningOfGame:
10941 case PlayFromGameFile:
10944 if (gameMode != EditGame) return;
10947 EditPositionDone(TRUE);
10958 forwardMostMove = currentMove;
10959 ResurrectChessProgram(); /* in case first program isn't running */
10961 if (second.pr == NULL) {
10962 StartChessProgram(&second);
10963 if (second.protocolVersion == 1) {
10964 TwoMachinesEventIfReady();
10966 /* kludge: allow timeout for initial "feature" command */
10968 DisplayMessage("", _("Starting second chess program"));
10969 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10973 DisplayMessage("", "");
10974 InitChessProgram(&second, FALSE);
10975 SendToProgram("force\n", &second);
10976 if (startedFromSetupPosition) {
10977 SendBoard(&second, backwardMostMove);
10978 if (appData.debugMode) {
10979 fprintf(debugFP, "Two Machines\n");
10982 for (i = backwardMostMove; i < forwardMostMove; i++) {
10983 SendMoveToProgram(i, &second);
10986 gameMode = TwoMachinesPlay;
10990 DisplayTwoMachinesTitle();
10992 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10998 SendToProgram(first.computerString, &first);
10999 if (first.sendName) {
11000 sprintf(buf, "name %s\n", second.tidy);
11001 SendToProgram(buf, &first);
11003 SendToProgram(second.computerString, &second);
11004 if (second.sendName) {
11005 sprintf(buf, "name %s\n", first.tidy);
11006 SendToProgram(buf, &second);
11010 if (!first.sendTime || !second.sendTime) {
11011 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11012 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11014 if (onmove->sendTime) {
11015 if (onmove->useColors) {
11016 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11018 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11020 if (onmove->useColors) {
11021 SendToProgram(onmove->twoMachinesColor, onmove);
11023 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11024 // SendToProgram("go\n", onmove);
11025 onmove->maybeThinking = TRUE;
11026 SetMachineThinkingEnables();
11030 if(bookHit) { // [HGM] book: simulate book reply
11031 static char bookMove[MSG_SIZ]; // a bit generous?
11033 programStats.nodes = programStats.depth = programStats.time =
11034 programStats.score = programStats.got_only_move = 0;
11035 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11037 strcpy(bookMove, "move ");
11038 strcat(bookMove, bookHit);
11039 savedMessage = bookMove; // args for deferred call
11040 savedState = onmove;
11041 ScheduleDelayedEvent(DeferredBookMove, 1);
11048 if (gameMode == Training) {
11049 SetTrainingModeOff();
11050 gameMode = PlayFromGameFile;
11051 DisplayMessage("", _("Training mode off"));
11053 gameMode = Training;
11054 animateTraining = appData.animate;
11056 /* make sure we are not already at the end of the game */
11057 if (currentMove < forwardMostMove) {
11058 SetTrainingModeOn();
11059 DisplayMessage("", _("Training mode on"));
11061 gameMode = PlayFromGameFile;
11062 DisplayError(_("Already at end of game"), 0);
11071 if (!appData.icsActive) return;
11072 switch (gameMode) {
11073 case IcsPlayingWhite:
11074 case IcsPlayingBlack:
11077 case BeginningOfGame:
11085 EditPositionDone(TRUE);
11098 gameMode = IcsIdle;
11109 switch (gameMode) {
11111 SetTrainingModeOff();
11113 case MachinePlaysWhite:
11114 case MachinePlaysBlack:
11115 case BeginningOfGame:
11116 SendToProgram("force\n", &first);
11117 SetUserThinkingEnables();
11119 case PlayFromGameFile:
11120 (void) StopLoadGameTimer();
11121 if (gameFileFP != NULL) {
11126 EditPositionDone(TRUE);
11131 SendToProgram("force\n", &first);
11133 case TwoMachinesPlay:
11134 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11135 ResurrectChessProgram();
11136 SetUserThinkingEnables();
11139 ResurrectChessProgram();
11141 case IcsPlayingBlack:
11142 case IcsPlayingWhite:
11143 DisplayError(_("Warning: You are still playing a game"), 0);
11146 DisplayError(_("Warning: You are still observing a game"), 0);
11149 DisplayError(_("Warning: You are still examining a game"), 0);
11160 first.offeredDraw = second.offeredDraw = 0;
11162 if (gameMode == PlayFromGameFile) {
11163 whiteTimeRemaining = timeRemaining[0][currentMove];
11164 blackTimeRemaining = timeRemaining[1][currentMove];
11168 if (gameMode == MachinePlaysWhite ||
11169 gameMode == MachinePlaysBlack ||
11170 gameMode == TwoMachinesPlay ||
11171 gameMode == EndOfGame) {
11172 i = forwardMostMove;
11173 while (i > currentMove) {
11174 SendToProgram("undo\n", &first);
11177 whiteTimeRemaining = timeRemaining[0][currentMove];
11178 blackTimeRemaining = timeRemaining[1][currentMove];
11179 DisplayBothClocks();
11180 if (whiteFlag || blackFlag) {
11181 whiteFlag = blackFlag = 0;
11186 gameMode = EditGame;
11193 EditPositionEvent()
11195 if (gameMode == EditPosition) {
11201 if (gameMode != EditGame) return;
11203 gameMode = EditPosition;
11206 if (currentMove > 0)
11207 CopyBoard(boards[0], boards[currentMove]);
11209 blackPlaysFirst = !WhiteOnMove(currentMove);
11211 currentMove = forwardMostMove = backwardMostMove = 0;
11212 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11219 /* [DM] icsEngineAnalyze - possible call from other functions */
11220 if (appData.icsEngineAnalyze) {
11221 appData.icsEngineAnalyze = FALSE;
11223 DisplayMessage("",_("Close ICS engine analyze..."));
11225 if (first.analysisSupport && first.analyzing) {
11226 SendToProgram("exit\n", &first);
11227 first.analyzing = FALSE;
11229 thinkOutput[0] = NULLCHAR;
11233 EditPositionDone(Boolean fakeRights)
11235 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11237 startedFromSetupPosition = TRUE;
11238 InitChessProgram(&first, FALSE);
11240 { /* don't do this if we just pasted FEN */
11241 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11242 if(boards[0][0][BOARD_WIDTH>>1] == king)
11244 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11245 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11248 castlingRights[0][2] = -1;
11249 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11251 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11252 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11255 castlingRights[0][5] = -1;
11257 SendToProgram("force\n", &first);
11258 if (blackPlaysFirst) {
11259 strcpy(moveList[0], "");
11260 strcpy(parseList[0], "");
11261 currentMove = forwardMostMove = backwardMostMove = 1;
11262 CopyBoard(boards[1], boards[0]);
11263 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11265 epStatus[1] = epStatus[0];
11266 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11269 currentMove = forwardMostMove = backwardMostMove = 0;
11271 SendBoard(&first, forwardMostMove);
11272 if (appData.debugMode) {
11273 fprintf(debugFP, "EditPosDone\n");
11276 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11277 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11278 gameMode = EditGame;
11280 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11281 ClearHighlights(); /* [AS] */
11284 /* Pause for `ms' milliseconds */
11285 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11295 } while (SubtractTimeMarks(&m2, &m1) < ms);
11298 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11300 SendMultiLineToICS(buf)
11303 char temp[MSG_SIZ+1], *p;
11310 strncpy(temp, buf, len);
11315 if (*p == '\n' || *p == '\r')
11320 strcat(temp, "\n");
11322 SendToPlayer(temp, strlen(temp));
11326 SetWhiteToPlayEvent()
11328 if (gameMode == EditPosition) {
11329 blackPlaysFirst = FALSE;
11330 DisplayBothClocks(); /* works because currentMove is 0 */
11331 } else if (gameMode == IcsExamining) {
11332 SendToICS(ics_prefix);
11333 SendToICS("tomove white\n");
11338 SetBlackToPlayEvent()
11340 if (gameMode == EditPosition) {
11341 blackPlaysFirst = TRUE;
11342 currentMove = 1; /* kludge */
11343 DisplayBothClocks();
11345 } else if (gameMode == IcsExamining) {
11346 SendToICS(ics_prefix);
11347 SendToICS("tomove black\n");
11352 EditPositionMenuEvent(selection, x, y)
11353 ChessSquare selection;
11357 ChessSquare piece = boards[0][y][x];
11359 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11361 switch (selection) {
11363 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11364 SendToICS(ics_prefix);
11365 SendToICS("bsetup clear\n");
11366 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11367 SendToICS(ics_prefix);
11368 SendToICS("clearboard\n");
11370 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11371 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11372 for (y = 0; y < BOARD_HEIGHT; y++) {
11373 if (gameMode == IcsExamining) {
11374 if (boards[currentMove][y][x] != EmptySquare) {
11375 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11380 boards[0][y][x] = p;
11385 if (gameMode == EditPosition) {
11386 DrawPosition(FALSE, boards[0]);
11391 SetWhiteToPlayEvent();
11395 SetBlackToPlayEvent();
11399 if (gameMode == IcsExamining) {
11400 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11403 boards[0][y][x] = EmptySquare;
11404 DrawPosition(FALSE, boards[0]);
11409 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11410 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11411 selection = (ChessSquare) (PROMOTED piece);
11412 } else if(piece == EmptySquare) selection = WhiteSilver;
11413 else selection = (ChessSquare)((int)piece - 1);
11417 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11418 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11419 selection = (ChessSquare) (DEMOTED piece);
11420 } else if(piece == EmptySquare) selection = BlackSilver;
11421 else selection = (ChessSquare)((int)piece + 1);
11426 if(gameInfo.variant == VariantShatranj ||
11427 gameInfo.variant == VariantXiangqi ||
11428 gameInfo.variant == VariantCourier ||
11429 gameInfo.variant == VariantMakruk )
11430 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11435 if(gameInfo.variant == VariantXiangqi)
11436 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11437 if(gameInfo.variant == VariantKnightmate)
11438 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11441 if (gameMode == IcsExamining) {
11442 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11443 PieceToChar(selection), AAA + x, ONE + y);
11446 boards[0][y][x] = selection;
11447 DrawPosition(FALSE, boards[0]);
11455 DropMenuEvent(selection, x, y)
11456 ChessSquare selection;
11459 ChessMove moveType;
11461 switch (gameMode) {
11462 case IcsPlayingWhite:
11463 case MachinePlaysBlack:
11464 if (!WhiteOnMove(currentMove)) {
11465 DisplayMoveError(_("It is Black's turn"));
11468 moveType = WhiteDrop;
11470 case IcsPlayingBlack:
11471 case MachinePlaysWhite:
11472 if (WhiteOnMove(currentMove)) {
11473 DisplayMoveError(_("It is White's turn"));
11476 moveType = BlackDrop;
11479 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11485 if (moveType == BlackDrop && selection < BlackPawn) {
11486 selection = (ChessSquare) ((int) selection
11487 + (int) BlackPawn - (int) WhitePawn);
11489 if (boards[currentMove][y][x] != EmptySquare) {
11490 DisplayMoveError(_("That square is occupied"));
11494 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11500 /* Accept a pending offer of any kind from opponent */
11502 if (appData.icsActive) {
11503 SendToICS(ics_prefix);
11504 SendToICS("accept\n");
11505 } else if (cmailMsgLoaded) {
11506 if (currentMove == cmailOldMove &&
11507 commentList[cmailOldMove] != NULL &&
11508 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11509 "Black offers a draw" : "White offers a draw")) {
11511 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11512 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11514 DisplayError(_("There is no pending offer on this move"), 0);
11515 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11518 /* Not used for offers from chess program */
11525 /* Decline a pending offer of any kind from opponent */
11527 if (appData.icsActive) {
11528 SendToICS(ics_prefix);
11529 SendToICS("decline\n");
11530 } else if (cmailMsgLoaded) {
11531 if (currentMove == cmailOldMove &&
11532 commentList[cmailOldMove] != NULL &&
11533 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11534 "Black offers a draw" : "White offers a draw")) {
11536 AppendComment(cmailOldMove, "Draw declined");
11537 DisplayComment(cmailOldMove - 1, "Draw declined");
11540 DisplayError(_("There is no pending offer on this move"), 0);
11543 /* Not used for offers from chess program */
11550 /* Issue ICS rematch command */
11551 if (appData.icsActive) {
11552 SendToICS(ics_prefix);
11553 SendToICS("rematch\n");
11560 /* Call your opponent's flag (claim a win on time) */
11561 if (appData.icsActive) {
11562 SendToICS(ics_prefix);
11563 SendToICS("flag\n");
11565 switch (gameMode) {
11568 case MachinePlaysWhite:
11571 GameEnds(GameIsDrawn, "Both players ran out of time",
11574 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11576 DisplayError(_("Your opponent is not out of time"), 0);
11579 case MachinePlaysBlack:
11582 GameEnds(GameIsDrawn, "Both players ran out of time",
11585 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11587 DisplayError(_("Your opponent is not out of time"), 0);
11597 /* Offer draw or accept pending draw offer from opponent */
11599 if (appData.icsActive) {
11600 /* Note: tournament rules require draw offers to be
11601 made after you make your move but before you punch
11602 your clock. Currently ICS doesn't let you do that;
11603 instead, you immediately punch your clock after making
11604 a move, but you can offer a draw at any time. */
11606 SendToICS(ics_prefix);
11607 SendToICS("draw\n");
11608 } else if (cmailMsgLoaded) {
11609 if (currentMove == cmailOldMove &&
11610 commentList[cmailOldMove] != NULL &&
11611 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11612 "Black offers a draw" : "White offers a draw")) {
11613 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11614 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11615 } else if (currentMove == cmailOldMove + 1) {
11616 char *offer = WhiteOnMove(cmailOldMove) ?
11617 "White offers a draw" : "Black offers a draw";
11618 AppendComment(currentMove, offer);
11619 DisplayComment(currentMove - 1, offer);
11620 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11622 DisplayError(_("You must make your move before offering a draw"), 0);
11623 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11625 } else if (first.offeredDraw) {
11626 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11628 if (first.sendDrawOffers) {
11629 SendToProgram("draw\n", &first);
11630 userOfferedDraw = TRUE;
11638 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11640 if (appData.icsActive) {
11641 SendToICS(ics_prefix);
11642 SendToICS("adjourn\n");
11644 /* Currently GNU Chess doesn't offer or accept Adjourns */
11652 /* Offer Abort or accept pending Abort offer from opponent */
11654 if (appData.icsActive) {
11655 SendToICS(ics_prefix);
11656 SendToICS("abort\n");
11658 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11665 /* Resign. You can do this even if it's not your turn. */
11667 if (appData.icsActive) {
11668 SendToICS(ics_prefix);
11669 SendToICS("resign\n");
11671 switch (gameMode) {
11672 case MachinePlaysWhite:
11673 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11675 case MachinePlaysBlack:
11676 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11679 if (cmailMsgLoaded) {
11681 if (WhiteOnMove(cmailOldMove)) {
11682 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11684 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11686 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11697 StopObservingEvent()
11699 /* Stop observing current games */
11700 SendToICS(ics_prefix);
11701 SendToICS("unobserve\n");
11705 StopExaminingEvent()
11707 /* Stop observing current game */
11708 SendToICS(ics_prefix);
11709 SendToICS("unexamine\n");
11713 ForwardInner(target)
11718 if (appData.debugMode)
11719 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11720 target, currentMove, forwardMostMove);
11722 if (gameMode == EditPosition)
11725 if (gameMode == PlayFromGameFile && !pausing)
11728 if (gameMode == IcsExamining && pausing)
11729 limit = pauseExamForwardMostMove;
11731 limit = forwardMostMove;
11733 if (target > limit) target = limit;
11735 if (target > 0 && moveList[target - 1][0]) {
11736 int fromX, fromY, toX, toY;
11737 toX = moveList[target - 1][2] - AAA;
11738 toY = moveList[target - 1][3] - ONE;
11739 if (moveList[target - 1][1] == '@') {
11740 if (appData.highlightLastMove) {
11741 SetHighlights(-1, -1, toX, toY);
11744 fromX = moveList[target - 1][0] - AAA;
11745 fromY = moveList[target - 1][1] - ONE;
11746 if (target == currentMove + 1) {
11747 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11749 if (appData.highlightLastMove) {
11750 SetHighlights(fromX, fromY, toX, toY);
11754 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11755 gameMode == Training || gameMode == PlayFromGameFile ||
11756 gameMode == AnalyzeFile) {
11757 while (currentMove < target) {
11758 SendMoveToProgram(currentMove++, &first);
11761 currentMove = target;
11764 if (gameMode == EditGame || gameMode == EndOfGame) {
11765 whiteTimeRemaining = timeRemaining[0][currentMove];
11766 blackTimeRemaining = timeRemaining[1][currentMove];
11768 DisplayBothClocks();
11769 DisplayMove(currentMove - 1);
11770 DrawPosition(FALSE, boards[currentMove]);
11771 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11772 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11773 DisplayComment(currentMove - 1, commentList[currentMove]);
11781 if (gameMode == IcsExamining && !pausing) {
11782 SendToICS(ics_prefix);
11783 SendToICS("forward\n");
11785 ForwardInner(currentMove + 1);
11792 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11793 /* to optimze, we temporarily turn off analysis mode while we feed
11794 * the remaining moves to the engine. Otherwise we get analysis output
11797 if (first.analysisSupport) {
11798 SendToProgram("exit\nforce\n", &first);
11799 first.analyzing = FALSE;
11803 if (gameMode == IcsExamining && !pausing) {
11804 SendToICS(ics_prefix);
11805 SendToICS("forward 999999\n");
11807 ForwardInner(forwardMostMove);
11810 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11811 /* we have fed all the moves, so reactivate analysis mode */
11812 SendToProgram("analyze\n", &first);
11813 first.analyzing = TRUE;
11814 /*first.maybeThinking = TRUE;*/
11815 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11820 BackwardInner(target)
11823 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11825 if (appData.debugMode)
11826 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11827 target, currentMove, forwardMostMove);
11829 if (gameMode == EditPosition) return;
11830 if (currentMove <= backwardMostMove) {
11832 DrawPosition(full_redraw, boards[currentMove]);
11835 if (gameMode == PlayFromGameFile && !pausing)
11838 if (moveList[target][0]) {
11839 int fromX, fromY, toX, toY;
11840 toX = moveList[target][2] - AAA;
11841 toY = moveList[target][3] - ONE;
11842 if (moveList[target][1] == '@') {
11843 if (appData.highlightLastMove) {
11844 SetHighlights(-1, -1, toX, toY);
11847 fromX = moveList[target][0] - AAA;
11848 fromY = moveList[target][1] - ONE;
11849 if (target == currentMove - 1) {
11850 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11852 if (appData.highlightLastMove) {
11853 SetHighlights(fromX, fromY, toX, toY);
11857 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11858 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11859 while (currentMove > target) {
11860 SendToProgram("undo\n", &first);
11864 currentMove = target;
11867 if (gameMode == EditGame || gameMode == EndOfGame) {
11868 whiteTimeRemaining = timeRemaining[0][currentMove];
11869 blackTimeRemaining = timeRemaining[1][currentMove];
11871 DisplayBothClocks();
11872 DisplayMove(currentMove - 1);
11873 DrawPosition(full_redraw, boards[currentMove]);
11874 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11875 // [HGM] PV info: routine tests if comment empty
11876 DisplayComment(currentMove - 1, commentList[currentMove]);
11882 if (gameMode == IcsExamining && !pausing) {
11883 SendToICS(ics_prefix);
11884 SendToICS("backward\n");
11886 BackwardInner(currentMove - 1);
11893 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11894 /* to optimize, we temporarily turn off analysis mode while we undo
11895 * all the moves. Otherwise we get analysis output after each undo.
11897 if (first.analysisSupport) {
11898 SendToProgram("exit\nforce\n", &first);
11899 first.analyzing = FALSE;
11903 if (gameMode == IcsExamining && !pausing) {
11904 SendToICS(ics_prefix);
11905 SendToICS("backward 999999\n");
11907 BackwardInner(backwardMostMove);
11910 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11911 /* we have fed all the moves, so reactivate analysis mode */
11912 SendToProgram("analyze\n", &first);
11913 first.analyzing = TRUE;
11914 /*first.maybeThinking = TRUE;*/
11915 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11922 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11923 if (to >= forwardMostMove) to = forwardMostMove;
11924 if (to <= backwardMostMove) to = backwardMostMove;
11925 if (to < currentMove) {
11935 if (gameMode != IcsExamining) {
11936 DisplayError(_("You are not examining a game"), 0);
11940 DisplayError(_("You can't revert while pausing"), 0);
11943 SendToICS(ics_prefix);
11944 SendToICS("revert\n");
11950 switch (gameMode) {
11951 case MachinePlaysWhite:
11952 case MachinePlaysBlack:
11953 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11954 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11957 if (forwardMostMove < 2) return;
11958 currentMove = forwardMostMove = forwardMostMove - 2;
11959 whiteTimeRemaining = timeRemaining[0][currentMove];
11960 blackTimeRemaining = timeRemaining[1][currentMove];
11961 DisplayBothClocks();
11962 DisplayMove(currentMove - 1);
11963 ClearHighlights();/*!! could figure this out*/
11964 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11965 SendToProgram("remove\n", &first);
11966 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11969 case BeginningOfGame:
11973 case IcsPlayingWhite:
11974 case IcsPlayingBlack:
11975 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11976 SendToICS(ics_prefix);
11977 SendToICS("takeback 2\n");
11979 SendToICS(ics_prefix);
11980 SendToICS("takeback 1\n");
11989 ChessProgramState *cps;
11991 switch (gameMode) {
11992 case MachinePlaysWhite:
11993 if (!WhiteOnMove(forwardMostMove)) {
11994 DisplayError(_("It is your turn"), 0);
11999 case MachinePlaysBlack:
12000 if (WhiteOnMove(forwardMostMove)) {
12001 DisplayError(_("It is your turn"), 0);
12006 case TwoMachinesPlay:
12007 if (WhiteOnMove(forwardMostMove) ==
12008 (first.twoMachinesColor[0] == 'w')) {
12014 case BeginningOfGame:
12018 SendToProgram("?\n", cps);
12022 TruncateGameEvent()
12025 if (gameMode != EditGame) return;
12032 if (forwardMostMove > currentMove) {
12033 if (gameInfo.resultDetails != NULL) {
12034 free(gameInfo.resultDetails);
12035 gameInfo.resultDetails = NULL;
12036 gameInfo.result = GameUnfinished;
12038 forwardMostMove = currentMove;
12039 HistorySet(parseList, backwardMostMove, forwardMostMove,
12047 if (appData.noChessProgram) return;
12048 switch (gameMode) {
12049 case MachinePlaysWhite:
12050 if (WhiteOnMove(forwardMostMove)) {
12051 DisplayError(_("Wait until your turn"), 0);
12055 case BeginningOfGame:
12056 case MachinePlaysBlack:
12057 if (!WhiteOnMove(forwardMostMove)) {
12058 DisplayError(_("Wait until your turn"), 0);
12063 DisplayError(_("No hint available"), 0);
12066 SendToProgram("hint\n", &first);
12067 hintRequested = TRUE;
12073 if (appData.noChessProgram) return;
12074 switch (gameMode) {
12075 case MachinePlaysWhite:
12076 if (WhiteOnMove(forwardMostMove)) {
12077 DisplayError(_("Wait until your turn"), 0);
12081 case BeginningOfGame:
12082 case MachinePlaysBlack:
12083 if (!WhiteOnMove(forwardMostMove)) {
12084 DisplayError(_("Wait until your turn"), 0);
12089 EditPositionDone(TRUE);
12091 case TwoMachinesPlay:
12096 SendToProgram("bk\n", &first);
12097 bookOutput[0] = NULLCHAR;
12098 bookRequested = TRUE;
12104 char *tags = PGNTags(&gameInfo);
12105 TagsPopUp(tags, CmailMsg());
12109 /* end button procedures */
12112 PrintPosition(fp, move)
12118 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12119 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12120 char c = PieceToChar(boards[move][i][j]);
12121 fputc(c == 'x' ? '.' : c, fp);
12122 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12125 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12126 fprintf(fp, "white to play\n");
12128 fprintf(fp, "black to play\n");
12135 if (gameInfo.white != NULL) {
12136 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12142 /* Find last component of program's own name, using some heuristics */
12144 TidyProgramName(prog, host, buf)
12145 char *prog, *host, buf[MSG_SIZ];
12148 int local = (strcmp(host, "localhost") == 0);
12149 while (!local && (p = strchr(prog, ';')) != NULL) {
12151 while (*p == ' ') p++;
12154 if (*prog == '"' || *prog == '\'') {
12155 q = strchr(prog + 1, *prog);
12157 q = strchr(prog, ' ');
12159 if (q == NULL) q = prog + strlen(prog);
12161 while (p >= prog && *p != '/' && *p != '\\') p--;
12163 if(p == prog && *p == '"') p++;
12164 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12165 memcpy(buf, p, q - p);
12166 buf[q - p] = NULLCHAR;
12174 TimeControlTagValue()
12177 if (!appData.clockMode) {
12179 } else if (movesPerSession > 0) {
12180 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12181 } else if (timeIncrement == 0) {
12182 sprintf(buf, "%ld", timeControl/1000);
12184 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12186 return StrSave(buf);
12192 /* This routine is used only for certain modes */
12193 VariantClass v = gameInfo.variant;
12194 ClearGameInfo(&gameInfo);
12195 gameInfo.variant = v;
12197 switch (gameMode) {
12198 case MachinePlaysWhite:
12199 gameInfo.event = StrSave( appData.pgnEventHeader );
12200 gameInfo.site = StrSave(HostName());
12201 gameInfo.date = PGNDate();
12202 gameInfo.round = StrSave("-");
12203 gameInfo.white = StrSave(first.tidy);
12204 gameInfo.black = StrSave(UserName());
12205 gameInfo.timeControl = TimeControlTagValue();
12208 case MachinePlaysBlack:
12209 gameInfo.event = StrSave( appData.pgnEventHeader );
12210 gameInfo.site = StrSave(HostName());
12211 gameInfo.date = PGNDate();
12212 gameInfo.round = StrSave("-");
12213 gameInfo.white = StrSave(UserName());
12214 gameInfo.black = StrSave(first.tidy);
12215 gameInfo.timeControl = TimeControlTagValue();
12218 case TwoMachinesPlay:
12219 gameInfo.event = StrSave( appData.pgnEventHeader );
12220 gameInfo.site = StrSave(HostName());
12221 gameInfo.date = PGNDate();
12222 if (matchGame > 0) {
12224 sprintf(buf, "%d", matchGame);
12225 gameInfo.round = StrSave(buf);
12227 gameInfo.round = StrSave("-");
12229 if (first.twoMachinesColor[0] == 'w') {
12230 gameInfo.white = StrSave(first.tidy);
12231 gameInfo.black = StrSave(second.tidy);
12233 gameInfo.white = StrSave(second.tidy);
12234 gameInfo.black = StrSave(first.tidy);
12236 gameInfo.timeControl = TimeControlTagValue();
12240 gameInfo.event = StrSave("Edited game");
12241 gameInfo.site = StrSave(HostName());
12242 gameInfo.date = PGNDate();
12243 gameInfo.round = StrSave("-");
12244 gameInfo.white = StrSave("-");
12245 gameInfo.black = StrSave("-");
12249 gameInfo.event = StrSave("Edited position");
12250 gameInfo.site = StrSave(HostName());
12251 gameInfo.date = PGNDate();
12252 gameInfo.round = StrSave("-");
12253 gameInfo.white = StrSave("-");
12254 gameInfo.black = StrSave("-");
12257 case IcsPlayingWhite:
12258 case IcsPlayingBlack:
12263 case PlayFromGameFile:
12264 gameInfo.event = StrSave("Game from non-PGN file");
12265 gameInfo.site = StrSave(HostName());
12266 gameInfo.date = PGNDate();
12267 gameInfo.round = StrSave("-");
12268 gameInfo.white = StrSave("?");
12269 gameInfo.black = StrSave("?");
12278 ReplaceComment(index, text)
12284 while (*text == '\n') text++;
12285 len = strlen(text);
12286 while (len > 0 && text[len - 1] == '\n') len--;
12288 if (commentList[index] != NULL)
12289 free(commentList[index]);
12292 commentList[index] = NULL;
12295 commentList[index] = (char *) malloc(len + 2);
12296 strncpy(commentList[index], text, len);
12297 commentList[index][len] = '\n';
12298 commentList[index][len + 1] = NULLCHAR;
12311 if (ch == '\r') continue;
12313 } while (ch != '\0');
12317 AppendComment(index, text)
12324 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12327 while (*text == '\n') text++;
12328 len = strlen(text);
12329 while (len > 0 && text[len - 1] == '\n') len--;
12331 if (len == 0) return;
12333 if (commentList[index] != NULL) {
12334 old = commentList[index];
12335 oldlen = strlen(old);
12336 commentList[index] = (char *) malloc(oldlen + len + 2);
12337 strcpy(commentList[index], old);
12339 strncpy(&commentList[index][oldlen], text, len);
12340 commentList[index][oldlen + len] = '\n';
12341 commentList[index][oldlen + len + 1] = NULLCHAR;
12343 commentList[index] = (char *) malloc(len + 2);
12344 strncpy(commentList[index], text, len);
12345 commentList[index][len] = '\n';
12346 commentList[index][len + 1] = NULLCHAR;
12350 static char * FindStr( char * text, char * sub_text )
12352 char * result = strstr( text, sub_text );
12354 if( result != NULL ) {
12355 result += strlen( sub_text );
12361 /* [AS] Try to extract PV info from PGN comment */
12362 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12363 char *GetInfoFromComment( int index, char * text )
12367 if( text != NULL && index > 0 ) {
12370 int time = -1, sec = 0, deci;
12371 char * s_eval = FindStr( text, "[%eval " );
12372 char * s_emt = FindStr( text, "[%emt " );
12374 if( s_eval != NULL || s_emt != NULL ) {
12378 if( s_eval != NULL ) {
12379 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12383 if( delim != ']' ) {
12388 if( s_emt != NULL ) {
12392 /* We expect something like: [+|-]nnn.nn/dd */
12395 sep = strchr( text, '/' );
12396 if( sep == NULL || sep < (text+4) ) {
12400 time = -1; sec = -1; deci = -1;
12401 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12402 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12403 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12404 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12408 if( score_lo < 0 || score_lo >= 100 ) {
12412 if(sec >= 0) time = 600*time + 10*sec; else
12413 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12415 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12417 /* [HGM] PV time: now locate end of PV info */
12418 while( *++sep >= '0' && *sep <= '9'); // strip depth
12420 while( *++sep >= '0' && *sep <= '9'); // strip time
12422 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12424 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12425 while(*sep == ' ') sep++;
12436 pvInfoList[index-1].depth = depth;
12437 pvInfoList[index-1].score = score;
12438 pvInfoList[index-1].time = 10*time; // centi-sec
12444 SendToProgram(message, cps)
12446 ChessProgramState *cps;
12448 int count, outCount, error;
12451 if (cps->pr == NULL) return;
12454 if (appData.debugMode) {
12457 fprintf(debugFP, "%ld >%-6s: %s",
12458 SubtractTimeMarks(&now, &programStartTime),
12459 cps->which, message);
12462 count = strlen(message);
12463 outCount = OutputToProcess(cps->pr, message, count, &error);
12464 if (outCount < count && !exiting
12465 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12466 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12467 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12468 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12469 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12470 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12472 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12474 gameInfo.resultDetails = StrSave(buf);
12476 DisplayFatalError(buf, error, 1);
12481 ReceiveFromProgram(isr, closure, message, count, error)
12482 InputSourceRef isr;
12490 ChessProgramState *cps = (ChessProgramState *)closure;
12492 if (isr != cps->isr) return; /* Killed intentionally */
12496 _("Error: %s chess program (%s) exited unexpectedly"),
12497 cps->which, cps->program);
12498 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12499 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12500 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12501 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12503 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12505 gameInfo.resultDetails = StrSave(buf);
12507 RemoveInputSource(cps->isr);
12508 DisplayFatalError(buf, 0, 1);
12511 _("Error reading from %s chess program (%s)"),
12512 cps->which, cps->program);
12513 RemoveInputSource(cps->isr);
12515 /* [AS] Program is misbehaving badly... kill it */
12516 if( count == -2 ) {
12517 DestroyChildProcess( cps->pr, 9 );
12521 DisplayFatalError(buf, error, 1);
12526 if ((end_str = strchr(message, '\r')) != NULL)
12527 *end_str = NULLCHAR;
12528 if ((end_str = strchr(message, '\n')) != NULL)
12529 *end_str = NULLCHAR;
12531 if (appData.debugMode) {
12532 TimeMark now; int print = 1;
12533 char *quote = ""; char c; int i;
12535 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12536 char start = message[0];
12537 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12538 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12539 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12540 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12541 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12542 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12543 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12544 sscanf(message, "pong %c", &c)!=1 && start != '#')
12545 { quote = "# "; print = (appData.engineComments == 2); }
12546 message[0] = start; // restore original message
12550 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12551 SubtractTimeMarks(&now, &programStartTime), cps->which,
12557 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12558 if (appData.icsEngineAnalyze) {
12559 if (strstr(message, "whisper") != NULL ||
12560 strstr(message, "kibitz") != NULL ||
12561 strstr(message, "tellics") != NULL) return;
12564 HandleMachineMove(message, cps);
12569 SendTimeControl(cps, mps, tc, inc, sd, st)
12570 ChessProgramState *cps;
12571 int mps, inc, sd, st;
12577 if( timeControl_2 > 0 ) {
12578 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12579 tc = timeControl_2;
12582 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12583 inc /= cps->timeOdds;
12584 st /= cps->timeOdds;
12586 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12589 /* Set exact time per move, normally using st command */
12590 if (cps->stKludge) {
12591 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12593 if (seconds == 0) {
12594 sprintf(buf, "level 1 %d\n", st/60);
12596 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12599 sprintf(buf, "st %d\n", st);
12602 /* Set conventional or incremental time control, using level command */
12603 if (seconds == 0) {
12604 /* Note old gnuchess bug -- minutes:seconds used to not work.
12605 Fixed in later versions, but still avoid :seconds
12606 when seconds is 0. */
12607 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12609 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12610 seconds, inc/1000);
12613 SendToProgram(buf, cps);
12615 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12616 /* Orthogonally, limit search to given depth */
12618 if (cps->sdKludge) {
12619 sprintf(buf, "depth\n%d\n", sd);
12621 sprintf(buf, "sd %d\n", sd);
12623 SendToProgram(buf, cps);
12626 if(cps->nps > 0) { /* [HGM] nps */
12627 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12629 sprintf(buf, "nps %d\n", cps->nps);
12630 SendToProgram(buf, cps);
12635 ChessProgramState *WhitePlayer()
12636 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12638 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12639 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12645 SendTimeRemaining(cps, machineWhite)
12646 ChessProgramState *cps;
12647 int /*boolean*/ machineWhite;
12649 char message[MSG_SIZ];
12652 /* Note: this routine must be called when the clocks are stopped
12653 or when they have *just* been set or switched; otherwise
12654 it will be off by the time since the current tick started.
12656 if (machineWhite) {
12657 time = whiteTimeRemaining / 10;
12658 otime = blackTimeRemaining / 10;
12660 time = blackTimeRemaining / 10;
12661 otime = whiteTimeRemaining / 10;
12663 /* [HGM] translate opponent's time by time-odds factor */
12664 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12665 if (appData.debugMode) {
12666 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12669 if (time <= 0) time = 1;
12670 if (otime <= 0) otime = 1;
12672 sprintf(message, "time %ld\n", time);
12673 SendToProgram(message, cps);
12675 sprintf(message, "otim %ld\n", otime);
12676 SendToProgram(message, cps);
12680 BoolFeature(p, name, loc, cps)
12684 ChessProgramState *cps;
12687 int len = strlen(name);
12689 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12691 sscanf(*p, "%d", &val);
12693 while (**p && **p != ' ') (*p)++;
12694 sprintf(buf, "accepted %s\n", name);
12695 SendToProgram(buf, cps);
12702 IntFeature(p, name, loc, cps)
12706 ChessProgramState *cps;
12709 int len = strlen(name);
12710 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12712 sscanf(*p, "%d", loc);
12713 while (**p && **p != ' ') (*p)++;
12714 sprintf(buf, "accepted %s\n", name);
12715 SendToProgram(buf, cps);
12722 StringFeature(p, name, loc, cps)
12726 ChessProgramState *cps;
12729 int len = strlen(name);
12730 if (strncmp((*p), name, len) == 0
12731 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12733 sscanf(*p, "%[^\"]", loc);
12734 while (**p && **p != '\"') (*p)++;
12735 if (**p == '\"') (*p)++;
12736 sprintf(buf, "accepted %s\n", name);
12737 SendToProgram(buf, cps);
12744 ParseOption(Option *opt, ChessProgramState *cps)
12745 // [HGM] options: process the string that defines an engine option, and determine
12746 // name, type, default value, and allowed value range
12748 char *p, *q, buf[MSG_SIZ];
12749 int n, min = (-1)<<31, max = 1<<31, def;
12751 if(p = strstr(opt->name, " -spin ")) {
12752 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12753 if(max < min) max = min; // enforce consistency
12754 if(def < min) def = min;
12755 if(def > max) def = max;
12760 } else if((p = strstr(opt->name, " -slider "))) {
12761 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12762 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12763 if(max < min) max = min; // enforce consistency
12764 if(def < min) def = min;
12765 if(def > max) def = max;
12769 opt->type = Spin; // Slider;
12770 } else if((p = strstr(opt->name, " -string "))) {
12771 opt->textValue = p+9;
12772 opt->type = TextBox;
12773 } else if((p = strstr(opt->name, " -file "))) {
12774 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12775 opt->textValue = p+7;
12776 opt->type = TextBox; // FileName;
12777 } else if((p = strstr(opt->name, " -path "))) {
12778 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12779 opt->textValue = p+7;
12780 opt->type = TextBox; // PathName;
12781 } else if(p = strstr(opt->name, " -check ")) {
12782 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12783 opt->value = (def != 0);
12784 opt->type = CheckBox;
12785 } else if(p = strstr(opt->name, " -combo ")) {
12786 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12787 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12788 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12789 opt->value = n = 0;
12790 while(q = StrStr(q, " /// ")) {
12791 n++; *q = 0; // count choices, and null-terminate each of them
12793 if(*q == '*') { // remember default, which is marked with * prefix
12797 cps->comboList[cps->comboCnt++] = q;
12799 cps->comboList[cps->comboCnt++] = NULL;
12801 opt->type = ComboBox;
12802 } else if(p = strstr(opt->name, " -button")) {
12803 opt->type = Button;
12804 } else if(p = strstr(opt->name, " -save")) {
12805 opt->type = SaveButton;
12806 } else return FALSE;
12807 *p = 0; // terminate option name
12808 // now look if the command-line options define a setting for this engine option.
12809 if(cps->optionSettings && cps->optionSettings[0])
12810 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12811 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12812 sprintf(buf, "option %s", p);
12813 if(p = strstr(buf, ",")) *p = 0;
12815 SendToProgram(buf, cps);
12821 FeatureDone(cps, val)
12822 ChessProgramState* cps;
12825 DelayedEventCallback cb = GetDelayedEvent();
12826 if ((cb == InitBackEnd3 && cps == &first) ||
12827 (cb == TwoMachinesEventIfReady && cps == &second)) {
12828 CancelDelayedEvent();
12829 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12831 cps->initDone = val;
12834 /* Parse feature command from engine */
12836 ParseFeatures(args, cps)
12838 ChessProgramState *cps;
12846 while (*p == ' ') p++;
12847 if (*p == NULLCHAR) return;
12849 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12850 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12851 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12852 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12853 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12854 if (BoolFeature(&p, "reuse", &val, cps)) {
12855 /* Engine can disable reuse, but can't enable it if user said no */
12856 if (!val) cps->reuse = FALSE;
12859 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12860 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12861 if (gameMode == TwoMachinesPlay) {
12862 DisplayTwoMachinesTitle();
12868 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12869 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12870 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12871 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12872 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12873 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12874 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12875 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12876 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12877 if (IntFeature(&p, "done", &val, cps)) {
12878 FeatureDone(cps, val);
12881 /* Added by Tord: */
12882 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12883 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12884 /* End of additions by Tord */
12886 /* [HGM] added features: */
12887 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12888 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12889 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12890 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12891 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12892 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12893 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12894 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12895 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12896 SendToProgram(buf, cps);
12899 if(cps->nrOptions >= MAX_OPTIONS) {
12901 sprintf(buf, "%s engine has too many options\n", cps->which);
12902 DisplayError(buf, 0);
12906 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12907 /* End of additions by HGM */
12909 /* unknown feature: complain and skip */
12911 while (*q && *q != '=') q++;
12912 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12913 SendToProgram(buf, cps);
12919 while (*p && *p != '\"') p++;
12920 if (*p == '\"') p++;
12922 while (*p && *p != ' ') p++;
12930 PeriodicUpdatesEvent(newState)
12933 if (newState == appData.periodicUpdates)
12936 appData.periodicUpdates=newState;
12938 /* Display type changes, so update it now */
12939 // DisplayAnalysis();
12941 /* Get the ball rolling again... */
12943 AnalysisPeriodicEvent(1);
12944 StartAnalysisClock();
12949 PonderNextMoveEvent(newState)
12952 if (newState == appData.ponderNextMove) return;
12953 if (gameMode == EditPosition) EditPositionDone(TRUE);
12955 SendToProgram("hard\n", &first);
12956 if (gameMode == TwoMachinesPlay) {
12957 SendToProgram("hard\n", &second);
12960 SendToProgram("easy\n", &first);
12961 thinkOutput[0] = NULLCHAR;
12962 if (gameMode == TwoMachinesPlay) {
12963 SendToProgram("easy\n", &second);
12966 appData.ponderNextMove = newState;
12970 NewSettingEvent(option, command, value)
12976 if (gameMode == EditPosition) EditPositionDone(TRUE);
12977 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12978 SendToProgram(buf, &first);
12979 if (gameMode == TwoMachinesPlay) {
12980 SendToProgram(buf, &second);
12985 ShowThinkingEvent()
12986 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12988 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12989 int newState = appData.showThinking
12990 // [HGM] thinking: other features now need thinking output as well
12991 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12993 if (oldState == newState) return;
12994 oldState = newState;
12995 if (gameMode == EditPosition) EditPositionDone(TRUE);
12997 SendToProgram("post\n", &first);
12998 if (gameMode == TwoMachinesPlay) {
12999 SendToProgram("post\n", &second);
13002 SendToProgram("nopost\n", &first);
13003 thinkOutput[0] = NULLCHAR;
13004 if (gameMode == TwoMachinesPlay) {
13005 SendToProgram("nopost\n", &second);
13008 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13012 AskQuestionEvent(title, question, replyPrefix, which)
13013 char *title; char *question; char *replyPrefix; char *which;
13015 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13016 if (pr == NoProc) return;
13017 AskQuestion(title, question, replyPrefix, pr);
13021 DisplayMove(moveNumber)
13024 char message[MSG_SIZ];
13026 char cpThinkOutput[MSG_SIZ];
13028 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13030 if (moveNumber == forwardMostMove - 1 ||
13031 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13033 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13035 if (strchr(cpThinkOutput, '\n')) {
13036 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13039 *cpThinkOutput = NULLCHAR;
13042 /* [AS] Hide thinking from human user */
13043 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13044 *cpThinkOutput = NULLCHAR;
13045 if( thinkOutput[0] != NULLCHAR ) {
13048 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13049 cpThinkOutput[i] = '.';
13051 cpThinkOutput[i] = NULLCHAR;
13052 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13056 if (moveNumber == forwardMostMove - 1 &&
13057 gameInfo.resultDetails != NULL) {
13058 if (gameInfo.resultDetails[0] == NULLCHAR) {
13059 sprintf(res, " %s", PGNResult(gameInfo.result));
13061 sprintf(res, " {%s} %s",
13062 gameInfo.resultDetails, PGNResult(gameInfo.result));
13068 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13069 DisplayMessage(res, cpThinkOutput);
13071 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13072 WhiteOnMove(moveNumber) ? " " : ".. ",
13073 parseList[moveNumber], res);
13074 DisplayMessage(message, cpThinkOutput);
13079 DisplayComment(moveNumber, text)
13083 char title[MSG_SIZ];
13084 char buf[8000]; // comment can be long!
13087 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13088 strcpy(title, "Comment");
13090 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13091 WhiteOnMove(moveNumber) ? " " : ".. ",
13092 parseList[moveNumber]);
13094 // [HGM] PV info: display PV info together with (or as) comment
13095 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13096 if(text == NULL) text = "";
13097 score = pvInfoList[moveNumber].score;
13098 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13099 depth, (pvInfoList[moveNumber].time+50)/100, text);
13102 if (text != NULL && (appData.autoDisplayComment || commentUp))
13103 CommentPopUp(title, text);
13106 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13107 * might be busy thinking or pondering. It can be omitted if your
13108 * gnuchess is configured to stop thinking immediately on any user
13109 * input. However, that gnuchess feature depends on the FIONREAD
13110 * ioctl, which does not work properly on some flavors of Unix.
13114 ChessProgramState *cps;
13117 if (!cps->useSigint) return;
13118 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13119 switch (gameMode) {
13120 case MachinePlaysWhite:
13121 case MachinePlaysBlack:
13122 case TwoMachinesPlay:
13123 case IcsPlayingWhite:
13124 case IcsPlayingBlack:
13127 /* Skip if we know it isn't thinking */
13128 if (!cps->maybeThinking) return;
13129 if (appData.debugMode)
13130 fprintf(debugFP, "Interrupting %s\n", cps->which);
13131 InterruptChildProcess(cps->pr);
13132 cps->maybeThinking = FALSE;
13137 #endif /*ATTENTION*/
13143 if (whiteTimeRemaining <= 0) {
13146 if (appData.icsActive) {
13147 if (appData.autoCallFlag &&
13148 gameMode == IcsPlayingBlack && !blackFlag) {
13149 SendToICS(ics_prefix);
13150 SendToICS("flag\n");
13154 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13156 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13157 if (appData.autoCallFlag) {
13158 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13165 if (blackTimeRemaining <= 0) {
13168 if (appData.icsActive) {
13169 if (appData.autoCallFlag &&
13170 gameMode == IcsPlayingWhite && !whiteFlag) {
13171 SendToICS(ics_prefix);
13172 SendToICS("flag\n");
13176 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13178 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13179 if (appData.autoCallFlag) {
13180 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13193 if (!appData.clockMode || appData.icsActive ||
13194 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13197 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13199 if ( !WhiteOnMove(forwardMostMove) )
13200 /* White made time control */
13201 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13202 /* [HGM] time odds: correct new time quota for time odds! */
13203 / WhitePlayer()->timeOdds;
13205 /* Black made time control */
13206 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13207 / WhitePlayer()->other->timeOdds;
13211 DisplayBothClocks()
13213 int wom = gameMode == EditPosition ?
13214 !blackPlaysFirst : WhiteOnMove(currentMove);
13215 DisplayWhiteClock(whiteTimeRemaining, wom);
13216 DisplayBlackClock(blackTimeRemaining, !wom);
13220 /* Timekeeping seems to be a portability nightmare. I think everyone
13221 has ftime(), but I'm really not sure, so I'm including some ifdefs
13222 to use other calls if you don't. Clocks will be less accurate if
13223 you have neither ftime nor gettimeofday.
13226 /* VS 2008 requires the #include outside of the function */
13227 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13228 #include <sys/timeb.h>
13231 /* Get the current time as a TimeMark */
13236 #if HAVE_GETTIMEOFDAY
13238 struct timeval timeVal;
13239 struct timezone timeZone;
13241 gettimeofday(&timeVal, &timeZone);
13242 tm->sec = (long) timeVal.tv_sec;
13243 tm->ms = (int) (timeVal.tv_usec / 1000L);
13245 #else /*!HAVE_GETTIMEOFDAY*/
13248 // include <sys/timeb.h> / moved to just above start of function
13249 struct timeb timeB;
13252 tm->sec = (long) timeB.time;
13253 tm->ms = (int) timeB.millitm;
13255 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13256 tm->sec = (long) time(NULL);
13262 /* Return the difference in milliseconds between two
13263 time marks. We assume the difference will fit in a long!
13266 SubtractTimeMarks(tm2, tm1)
13267 TimeMark *tm2, *tm1;
13269 return 1000L*(tm2->sec - tm1->sec) +
13270 (long) (tm2->ms - tm1->ms);
13275 * Code to manage the game clocks.
13277 * In tournament play, black starts the clock and then white makes a move.
13278 * We give the human user a slight advantage if he is playing white---the
13279 * clocks don't run until he makes his first move, so it takes zero time.
13280 * Also, we don't account for network lag, so we could get out of sync
13281 * with GNU Chess's clock -- but then, referees are always right.
13284 static TimeMark tickStartTM;
13285 static long intendedTickLength;
13288 NextTickLength(timeRemaining)
13289 long timeRemaining;
13291 long nominalTickLength, nextTickLength;
13293 if (timeRemaining > 0L && timeRemaining <= 10000L)
13294 nominalTickLength = 100L;
13296 nominalTickLength = 1000L;
13297 nextTickLength = timeRemaining % nominalTickLength;
13298 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13300 return nextTickLength;
13303 /* Adjust clock one minute up or down */
13305 AdjustClock(Boolean which, int dir)
13307 if(which) blackTimeRemaining += 60000*dir;
13308 else whiteTimeRemaining += 60000*dir;
13309 DisplayBothClocks();
13312 /* Stop clocks and reset to a fresh time control */
13316 (void) StopClockTimer();
13317 if (appData.icsActive) {
13318 whiteTimeRemaining = blackTimeRemaining = 0;
13319 } else { /* [HGM] correct new time quote for time odds */
13320 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13321 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13323 if (whiteFlag || blackFlag) {
13325 whiteFlag = blackFlag = FALSE;
13327 DisplayBothClocks();
13330 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13332 /* Decrement running clock by amount of time that has passed */
13336 long timeRemaining;
13337 long lastTickLength, fudge;
13340 if (!appData.clockMode) return;
13341 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13345 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13347 /* Fudge if we woke up a little too soon */
13348 fudge = intendedTickLength - lastTickLength;
13349 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13351 if (WhiteOnMove(forwardMostMove)) {
13352 if(whiteNPS >= 0) lastTickLength = 0;
13353 timeRemaining = whiteTimeRemaining -= lastTickLength;
13354 DisplayWhiteClock(whiteTimeRemaining - fudge,
13355 WhiteOnMove(currentMove));
13357 if(blackNPS >= 0) lastTickLength = 0;
13358 timeRemaining = blackTimeRemaining -= lastTickLength;
13359 DisplayBlackClock(blackTimeRemaining - fudge,
13360 !WhiteOnMove(currentMove));
13363 if (CheckFlags()) return;
13366 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13367 StartClockTimer(intendedTickLength);
13369 /* if the time remaining has fallen below the alarm threshold, sound the
13370 * alarm. if the alarm has sounded and (due to a takeback or time control
13371 * with increment) the time remaining has increased to a level above the
13372 * threshold, reset the alarm so it can sound again.
13375 if (appData.icsActive && appData.icsAlarm) {
13377 /* make sure we are dealing with the user's clock */
13378 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13379 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13382 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13383 alarmSounded = FALSE;
13384 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13386 alarmSounded = TRUE;
13392 /* A player has just moved, so stop the previously running
13393 clock and (if in clock mode) start the other one.
13394 We redisplay both clocks in case we're in ICS mode, because
13395 ICS gives us an update to both clocks after every move.
13396 Note that this routine is called *after* forwardMostMove
13397 is updated, so the last fractional tick must be subtracted
13398 from the color that is *not* on move now.
13401 SwitchClocks(int newMoveNr)
13403 long lastTickLength;
13405 int flagged = FALSE;
13409 if (StopClockTimer() && appData.clockMode) {
13410 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13411 if (!WhiteOnMove(forwardMostMove)) {
13412 if(blackNPS >= 0) lastTickLength = 0;
13413 blackTimeRemaining -= lastTickLength;
13414 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13415 // if(pvInfoList[forwardMostMove-1].time == -1)
13416 pvInfoList[forwardMostMove-1].time = // use GUI time
13417 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13419 if(whiteNPS >= 0) lastTickLength = 0;
13420 whiteTimeRemaining -= lastTickLength;
13421 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13422 // if(pvInfoList[forwardMostMove-1].time == -1)
13423 pvInfoList[forwardMostMove-1].time =
13424 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13426 flagged = CheckFlags();
13428 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13429 CheckTimeControl();
13431 if (flagged || !appData.clockMode) return;
13433 switch (gameMode) {
13434 case MachinePlaysBlack:
13435 case MachinePlaysWhite:
13436 case BeginningOfGame:
13437 if (pausing) return;
13441 case PlayFromGameFile:
13450 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13451 whiteTimeRemaining : blackTimeRemaining);
13452 StartClockTimer(intendedTickLength);
13456 /* Stop both clocks */
13460 long lastTickLength;
13463 if (!StopClockTimer()) return;
13464 if (!appData.clockMode) return;
13468 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13469 if (WhiteOnMove(forwardMostMove)) {
13470 if(whiteNPS >= 0) lastTickLength = 0;
13471 whiteTimeRemaining -= lastTickLength;
13472 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13474 if(blackNPS >= 0) lastTickLength = 0;
13475 blackTimeRemaining -= lastTickLength;
13476 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13481 /* Start clock of player on move. Time may have been reset, so
13482 if clock is already running, stop and restart it. */
13486 (void) StopClockTimer(); /* in case it was running already */
13487 DisplayBothClocks();
13488 if (CheckFlags()) return;
13490 if (!appData.clockMode) return;
13491 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13493 GetTimeMark(&tickStartTM);
13494 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13495 whiteTimeRemaining : blackTimeRemaining);
13497 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13498 whiteNPS = blackNPS = -1;
13499 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13500 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13501 whiteNPS = first.nps;
13502 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13503 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13504 blackNPS = first.nps;
13505 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13506 whiteNPS = second.nps;
13507 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13508 blackNPS = second.nps;
13509 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13511 StartClockTimer(intendedTickLength);
13518 long second, minute, hour, day;
13520 static char buf[32];
13522 if (ms > 0 && ms <= 9900) {
13523 /* convert milliseconds to tenths, rounding up */
13524 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13526 sprintf(buf, " %03.1f ", tenths/10.0);
13530 /* convert milliseconds to seconds, rounding up */
13531 /* use floating point to avoid strangeness of integer division
13532 with negative dividends on many machines */
13533 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13540 day = second / (60 * 60 * 24);
13541 second = second % (60 * 60 * 24);
13542 hour = second / (60 * 60);
13543 second = second % (60 * 60);
13544 minute = second / 60;
13545 second = second % 60;
13548 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13549 sign, day, hour, minute, second);
13551 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13553 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13560 * This is necessary because some C libraries aren't ANSI C compliant yet.
13563 StrStr(string, match)
13564 char *string, *match;
13568 length = strlen(match);
13570 for (i = strlen(string) - length; i >= 0; i--, string++)
13571 if (!strncmp(match, string, length))
13578 StrCaseStr(string, match)
13579 char *string, *match;
13583 length = strlen(match);
13585 for (i = strlen(string) - length; i >= 0; i--, string++) {
13586 for (j = 0; j < length; j++) {
13587 if (ToLower(match[j]) != ToLower(string[j]))
13590 if (j == length) return string;
13604 c1 = ToLower(*s1++);
13605 c2 = ToLower(*s2++);
13606 if (c1 > c2) return 1;
13607 if (c1 < c2) return -1;
13608 if (c1 == NULLCHAR) return 0;
13617 return isupper(c) ? tolower(c) : c;
13625 return islower(c) ? toupper(c) : c;
13627 #endif /* !_amigados */
13635 if ((ret = (char *) malloc(strlen(s) + 1))) {
13642 StrSavePtr(s, savePtr)
13643 char *s, **savePtr;
13648 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13649 strcpy(*savePtr, s);
13661 clock = time((time_t *)NULL);
13662 tm = localtime(&clock);
13663 sprintf(buf, "%04d.%02d.%02d",
13664 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13665 return StrSave(buf);
13670 PositionToFEN(move, overrideCastling)
13672 char *overrideCastling;
13674 int i, j, fromX, fromY, toX, toY;
13681 whiteToPlay = (gameMode == EditPosition) ?
13682 !blackPlaysFirst : (move % 2 == 0);
13685 /* Piece placement data */
13686 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13688 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13689 if (boards[move][i][j] == EmptySquare) {
13691 } else { ChessSquare piece = boards[move][i][j];
13692 if (emptycount > 0) {
13693 if(emptycount<10) /* [HGM] can be >= 10 */
13694 *p++ = '0' + emptycount;
13695 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13698 if(PieceToChar(piece) == '+') {
13699 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13701 piece = (ChessSquare)(DEMOTED piece);
13703 *p++ = PieceToChar(piece);
13705 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13706 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13711 if (emptycount > 0) {
13712 if(emptycount<10) /* [HGM] can be >= 10 */
13713 *p++ = '0' + emptycount;
13714 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13721 /* [HGM] print Crazyhouse or Shogi holdings */
13722 if( gameInfo.holdingsWidth ) {
13723 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13725 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13726 piece = boards[move][i][BOARD_WIDTH-1];
13727 if( piece != EmptySquare )
13728 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13729 *p++ = PieceToChar(piece);
13731 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13732 piece = boards[move][BOARD_HEIGHT-i-1][0];
13733 if( piece != EmptySquare )
13734 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13735 *p++ = PieceToChar(piece);
13738 if( q == p ) *p++ = '-';
13744 *p++ = whiteToPlay ? 'w' : 'b';
13747 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13748 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13750 if(nrCastlingRights) {
13752 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13753 /* [HGM] write directly from rights */
13754 if(castlingRights[move][2] >= 0 &&
13755 castlingRights[move][0] >= 0 )
13756 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13757 if(castlingRights[move][2] >= 0 &&
13758 castlingRights[move][1] >= 0 )
13759 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13760 if(castlingRights[move][5] >= 0 &&
13761 castlingRights[move][3] >= 0 )
13762 *p++ = castlingRights[move][3] + AAA;
13763 if(castlingRights[move][5] >= 0 &&
13764 castlingRights[move][4] >= 0 )
13765 *p++ = castlingRights[move][4] + AAA;
13768 /* [HGM] write true castling rights */
13769 if( nrCastlingRights == 6 ) {
13770 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13771 castlingRights[move][2] >= 0 ) *p++ = 'K';
13772 if(castlingRights[move][1] == BOARD_LEFT &&
13773 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13774 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13775 castlingRights[move][5] >= 0 ) *p++ = 'k';
13776 if(castlingRights[move][4] == BOARD_LEFT &&
13777 castlingRights[move][5] >= 0 ) *p++ = 'q';
13780 if (q == p) *p++ = '-'; /* No castling rights */
13784 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13785 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
13786 /* En passant target square */
13787 if (move > backwardMostMove) {
13788 fromX = moveList[move - 1][0] - AAA;
13789 fromY = moveList[move - 1][1] - ONE;
13790 toX = moveList[move - 1][2] - AAA;
13791 toY = moveList[move - 1][3] - ONE;
13792 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13793 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13794 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13796 /* 2-square pawn move just happened */
13798 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13802 } else if(move == backwardMostMove) {
13803 // [HGM] perhaps we should always do it like this, and forget the above?
13804 if(epStatus[move] >= 0) {
13805 *p++ = epStatus[move] + AAA;
13806 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13817 /* [HGM] find reversible plies */
13818 { int i = 0, j=move;
13820 if (appData.debugMode) { int k;
13821 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13822 for(k=backwardMostMove; k<=forwardMostMove; k++)
13823 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13827 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13828 if( j == backwardMostMove ) i += initialRulePlies;
13829 sprintf(p, "%d ", i);
13830 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13832 /* Fullmove number */
13833 sprintf(p, "%d", (move / 2) + 1);
13835 return StrSave(buf);
13839 ParseFEN(board, blackPlaysFirst, fen)
13841 int *blackPlaysFirst;
13851 /* [HGM] by default clear Crazyhouse holdings, if present */
13852 if(gameInfo.holdingsWidth) {
13853 for(i=0; i<BOARD_HEIGHT; i++) {
13854 board[i][0] = EmptySquare; /* black holdings */
13855 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13856 board[i][1] = (ChessSquare) 0; /* black counts */
13857 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13861 /* Piece placement data */
13862 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13865 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13866 if (*p == '/') p++;
13867 emptycount = gameInfo.boardWidth - j;
13868 while (emptycount--)
13869 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13871 #if(BOARD_SIZE >= 10)
13872 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13873 p++; emptycount=10;
13874 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13875 while (emptycount--)
13876 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13878 } else if (isdigit(*p)) {
13879 emptycount = *p++ - '0';
13880 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13881 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13882 while (emptycount--)
13883 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13884 } else if (*p == '+' || isalpha(*p)) {
13885 if (j >= gameInfo.boardWidth) return FALSE;
13887 piece = CharToPiece(*++p);
13888 if(piece == EmptySquare) return FALSE; /* unknown piece */
13889 piece = (ChessSquare) (PROMOTED piece ); p++;
13890 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13891 } else piece = CharToPiece(*p++);
13893 if(piece==EmptySquare) return FALSE; /* unknown piece */
13894 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13895 piece = (ChessSquare) (PROMOTED piece);
13896 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13899 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13905 while (*p == '/' || *p == ' ') p++;
13907 /* [HGM] look for Crazyhouse holdings here */
13908 while(*p==' ') p++;
13909 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13911 if(*p == '-' ) *p++; /* empty holdings */ else {
13912 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13913 /* if we would allow FEN reading to set board size, we would */
13914 /* have to add holdings and shift the board read so far here */
13915 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13917 if((int) piece >= (int) BlackPawn ) {
13918 i = (int)piece - (int)BlackPawn;
13919 i = PieceToNumber((ChessSquare)i);
13920 if( i >= gameInfo.holdingsSize ) return FALSE;
13921 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13922 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13924 i = (int)piece - (int)WhitePawn;
13925 i = PieceToNumber((ChessSquare)i);
13926 if( i >= gameInfo.holdingsSize ) return FALSE;
13927 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13928 board[i][BOARD_WIDTH-2]++; /* black holdings */
13932 if(*p == ']') *p++;
13935 while(*p == ' ') p++;
13940 *blackPlaysFirst = FALSE;
13943 *blackPlaysFirst = TRUE;
13949 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13950 /* return the extra info in global variiables */
13952 /* set defaults in case FEN is incomplete */
13953 FENepStatus = EP_UNKNOWN;
13954 for(i=0; i<nrCastlingRights; i++ ) {
13955 FENcastlingRights[i] =
13956 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13957 } /* assume possible unless obviously impossible */
13958 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13959 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13960 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13961 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13962 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13963 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13964 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13965 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13968 while(*p==' ') p++;
13969 if(nrCastlingRights) {
13970 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13971 /* castling indicator present, so default becomes no castlings */
13972 for(i=0; i<nrCastlingRights; i++ ) {
13973 FENcastlingRights[i] = -1;
13976 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13977 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13978 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13979 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13980 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13982 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13983 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13984 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13986 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13987 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13988 if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13989 && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13990 if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13991 && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13994 for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13995 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13996 FENcastlingRights[2] = whiteKingFile;
13999 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14000 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
14001 FENcastlingRights[2] = whiteKingFile;
14004 for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
14005 FENcastlingRights[3] = i != blackKingFile ? i : -1;
14006 FENcastlingRights[5] = blackKingFile;
14009 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14010 FENcastlingRights[4] = i != blackKingFile ? i : -1;
14011 FENcastlingRights[5] = blackKingFile;
14014 default: /* FRC castlings */
14015 if(c >= 'a') { /* black rights */
14016 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14017 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14018 if(i == BOARD_RGHT) break;
14019 FENcastlingRights[5] = i;
14021 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14022 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14024 FENcastlingRights[3] = c;
14026 FENcastlingRights[4] = c;
14027 } else { /* white rights */
14028 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14029 if(board[0][i] == WhiteKing) break;
14030 if(i == BOARD_RGHT) break;
14031 FENcastlingRights[2] = i;
14032 c -= AAA - 'a' + 'A';
14033 if(board[0][c] >= WhiteKing) break;
14035 FENcastlingRights[0] = c;
14037 FENcastlingRights[1] = c;
14041 for(i=0; i<nrCastlingRights; i++)
14042 if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14043 if (appData.debugMode) {
14044 fprintf(debugFP, "FEN castling rights:");
14045 for(i=0; i<nrCastlingRights; i++)
14046 fprintf(debugFP, " %d", FENcastlingRights[i]);
14047 fprintf(debugFP, "\n");
14050 while(*p==' ') p++;
14053 /* read e.p. field in games that know e.p. capture */
14054 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14055 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14057 p++; FENepStatus = EP_NONE;
14059 char c = *p++ - AAA;
14061 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14062 if(*p >= '0' && *p <='9') *p++;
14068 if(sscanf(p, "%d", &i) == 1) {
14069 FENrulePlies = i; /* 50-move ply counter */
14070 /* (The move number is still ignored) */
14077 EditPositionPasteFEN(char *fen)
14080 Board initial_position;
14082 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14083 DisplayError(_("Bad FEN position in clipboard"), 0);
14086 int savedBlackPlaysFirst = blackPlaysFirst;
14087 EditPositionEvent();
14088 blackPlaysFirst = savedBlackPlaysFirst;
14089 CopyBoard(boards[0], initial_position);
14090 /* [HGM] copy FEN attributes as well */
14092 initialRulePlies = FENrulePlies;
14093 epStatus[0] = FENepStatus;
14094 for( i=0; i<nrCastlingRights; i++ )
14095 castlingRights[0][i] = FENcastlingRights[i];
14097 EditPositionDone(FALSE);
14098 DisplayBothClocks();
14099 DrawPosition(FALSE, boards[currentMove]);
14104 static char cseq[12] = "\\ ";
14106 Boolean set_cont_sequence(char *new_seq)
14111 // handle bad attempts to set the sequence
14113 return 0; // acceptable error - no debug
14115 len = strlen(new_seq);
14116 ret = (len > 0) && (len < sizeof(cseq));
14118 strcpy(cseq, new_seq);
14119 else if (appData.debugMode)
14120 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14125 reformat a source message so words don't cross the width boundary. internal
14126 newlines are not removed. returns the wrapped size (no null character unless
14127 included in source message). If dest is NULL, only calculate the size required
14128 for the dest buffer. lp argument indicats line position upon entry, and it's
14129 passed back upon exit.
14131 int wrap(char *dest, char *src, int count, int width, int *lp)
14133 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14135 cseq_len = strlen(cseq);
14136 old_line = line = *lp;
14137 ansi = len = clen = 0;
14139 for (i=0; i < count; i++)
14141 if (src[i] == '\033')
14144 // if we hit the width, back up
14145 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14147 // store i & len in case the word is too long
14148 old_i = i, old_len = len;
14150 // find the end of the last word
14151 while (i && src[i] != ' ' && src[i] != '\n')
14157 // word too long? restore i & len before splitting it
14158 if ((old_i-i+clen) >= width)
14165 if (i && src[i-1] == ' ')
14168 if (src[i] != ' ' && src[i] != '\n')
14175 // now append the newline and continuation sequence
14180 strncpy(dest+len, cseq, cseq_len);
14188 dest[len] = src[i];
14192 if (src[i] == '\n')
14197 if (dest && appData.debugMode)
14199 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14200 count, width, line, len, *lp);
14201 show_bytes(debugFP, src, count);
14202 fprintf(debugFP, "\ndest: ");
14203 show_bytes(debugFP, dest, len);
14204 fprintf(debugFP, "\n");
14206 *lp = dest ? line : old_line;