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;
2518 } // [HGM] chat: end of patch
2520 if (appData.zippyTalk || appData.zippyPlay) {
2521 /* [DM] Backup address for color zippy lines */
2525 if (loggedOn == TRUE)
2526 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2527 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2529 if (ZippyControl(buf, &i) ||
2530 ZippyConverse(buf, &i) ||
2531 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2533 if (!appData.colorize) continue;
2537 } // [DM] 'else { ' deleted
2539 /* Regular tells and says */
2540 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2541 looking_at(buf, &i, "* (your partner) tells you: ") ||
2542 looking_at(buf, &i, "* says: ") ||
2543 /* Don't color "message" or "messages" output */
2544 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2545 looking_at(buf, &i, "*. * at *:*: ") ||
2546 looking_at(buf, &i, "--* (*:*): ") ||
2547 /* Message notifications (same color as tells) */
2548 looking_at(buf, &i, "* has left a message ") ||
2549 looking_at(buf, &i, "* just sent you a message:\n") ||
2550 /* Whispers and kibitzes */
2551 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2552 looking_at(buf, &i, "* kibitzes: ") ||
2554 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2556 if (tkind == 1 && strchr(star_match[0], ':')) {
2557 /* Avoid "tells you:" spoofs in channels */
2560 if (star_match[0][0] == NULLCHAR ||
2561 strchr(star_match[0], ' ') ||
2562 (tkind == 3 && strchr(star_match[1], ' '))) {
2563 /* Reject bogus matches */
2566 if (appData.colorize) {
2567 if (oldi > next_out) {
2568 SendToPlayer(&buf[next_out], oldi - next_out);
2573 Colorize(ColorTell, FALSE);
2574 curColor = ColorTell;
2577 Colorize(ColorKibitz, FALSE);
2578 curColor = ColorKibitz;
2581 p = strrchr(star_match[1], '(');
2588 Colorize(ColorChannel1, FALSE);
2589 curColor = ColorChannel1;
2591 Colorize(ColorChannel, FALSE);
2592 curColor = ColorChannel;
2596 curColor = ColorNormal;
2600 if (started == STARTED_NONE && appData.autoComment &&
2601 (gameMode == IcsObserving ||
2602 gameMode == IcsPlayingWhite ||
2603 gameMode == IcsPlayingBlack)) {
2604 parse_pos = i - oldi;
2605 memcpy(parse, &buf[oldi], parse_pos);
2606 parse[parse_pos] = NULLCHAR;
2607 started = STARTED_COMMENT;
2608 savingComment = TRUE;
2610 started = STARTED_CHATTER;
2611 savingComment = FALSE;
2618 if (looking_at(buf, &i, "* s-shouts: ") ||
2619 looking_at(buf, &i, "* c-shouts: ")) {
2620 if (appData.colorize) {
2621 if (oldi > next_out) {
2622 SendToPlayer(&buf[next_out], oldi - next_out);
2625 Colorize(ColorSShout, FALSE);
2626 curColor = ColorSShout;
2629 started = STARTED_CHATTER;
2633 if (looking_at(buf, &i, "--->")) {
2638 if (looking_at(buf, &i, "* shouts: ") ||
2639 looking_at(buf, &i, "--> ")) {
2640 if (appData.colorize) {
2641 if (oldi > next_out) {
2642 SendToPlayer(&buf[next_out], oldi - next_out);
2645 Colorize(ColorShout, FALSE);
2646 curColor = ColorShout;
2649 started = STARTED_CHATTER;
2653 if (looking_at( buf, &i, "Challenge:")) {
2654 if (appData.colorize) {
2655 if (oldi > next_out) {
2656 SendToPlayer(&buf[next_out], oldi - next_out);
2659 Colorize(ColorChallenge, FALSE);
2660 curColor = ColorChallenge;
2666 if (looking_at(buf, &i, "* offers you") ||
2667 looking_at(buf, &i, "* offers to be") ||
2668 looking_at(buf, &i, "* would like to") ||
2669 looking_at(buf, &i, "* requests to") ||
2670 looking_at(buf, &i, "Your opponent offers") ||
2671 looking_at(buf, &i, "Your opponent requests")) {
2673 if (appData.colorize) {
2674 if (oldi > next_out) {
2675 SendToPlayer(&buf[next_out], oldi - next_out);
2678 Colorize(ColorRequest, FALSE);
2679 curColor = ColorRequest;
2684 if (looking_at(buf, &i, "* (*) seeking")) {
2685 if (appData.colorize) {
2686 if (oldi > next_out) {
2687 SendToPlayer(&buf[next_out], oldi - next_out);
2690 Colorize(ColorSeek, FALSE);
2691 curColor = ColorSeek;
2696 if (looking_at(buf, &i, "\\ ")) {
2697 if (prevColor != ColorNormal) {
2698 if (oldi > next_out) {
2699 SendToPlayer(&buf[next_out], oldi - next_out);
2702 Colorize(prevColor, TRUE);
2703 curColor = prevColor;
2705 if (savingComment) {
2706 parse_pos = i - oldi;
2707 memcpy(parse, &buf[oldi], parse_pos);
2708 parse[parse_pos] = NULLCHAR;
2709 started = STARTED_COMMENT;
2710 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2711 chattingPartner = savingComment - 3; // kludge to remember the box
2713 started = STARTED_CHATTER;
2718 if (looking_at(buf, &i, "Black Strength :") ||
2719 looking_at(buf, &i, "<<< style 10 board >>>") ||
2720 looking_at(buf, &i, "<10>") ||
2721 looking_at(buf, &i, "#@#")) {
2722 /* Wrong board style */
2724 SendToICS(ics_prefix);
2725 SendToICS("set style 12\n");
2726 SendToICS(ics_prefix);
2727 SendToICS("refresh\n");
2731 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2733 have_sent_ICS_logon = 1;
2737 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2738 (looking_at(buf, &i, "\n<12> ") ||
2739 looking_at(buf, &i, "<12> "))) {
2741 if (oldi > next_out) {
2742 SendToPlayer(&buf[next_out], oldi - next_out);
2745 started = STARTED_BOARD;
2750 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2751 looking_at(buf, &i, "<b1> ")) {
2752 if (oldi > next_out) {
2753 SendToPlayer(&buf[next_out], oldi - next_out);
2756 started = STARTED_HOLDINGS;
2761 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2763 /* Header for a move list -- first line */
2765 switch (ics_getting_history) {
2769 case BeginningOfGame:
2770 /* User typed "moves" or "oldmoves" while we
2771 were idle. Pretend we asked for these
2772 moves and soak them up so user can step
2773 through them and/or save them.
2776 gameMode = IcsObserving;
2779 ics_getting_history = H_GOT_UNREQ_HEADER;
2781 case EditGame: /*?*/
2782 case EditPosition: /*?*/
2783 /* Should above feature work in these modes too? */
2784 /* For now it doesn't */
2785 ics_getting_history = H_GOT_UNWANTED_HEADER;
2788 ics_getting_history = H_GOT_UNWANTED_HEADER;
2793 /* Is this the right one? */
2794 if (gameInfo.white && gameInfo.black &&
2795 strcmp(gameInfo.white, star_match[0]) == 0 &&
2796 strcmp(gameInfo.black, star_match[2]) == 0) {
2798 ics_getting_history = H_GOT_REQ_HEADER;
2801 case H_GOT_REQ_HEADER:
2802 case H_GOT_UNREQ_HEADER:
2803 case H_GOT_UNWANTED_HEADER:
2804 case H_GETTING_MOVES:
2805 /* Should not happen */
2806 DisplayError(_("Error gathering move list: two headers"), 0);
2807 ics_getting_history = H_FALSE;
2811 /* Save player ratings into gameInfo if needed */
2812 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2813 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2814 (gameInfo.whiteRating == -1 ||
2815 gameInfo.blackRating == -1)) {
2817 gameInfo.whiteRating = string_to_rating(star_match[1]);
2818 gameInfo.blackRating = string_to_rating(star_match[3]);
2819 if (appData.debugMode)
2820 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2821 gameInfo.whiteRating, gameInfo.blackRating);
2826 if (looking_at(buf, &i,
2827 "* * match, initial time: * minute*, increment: * second")) {
2828 /* Header for a move list -- second line */
2829 /* Initial board will follow if this is a wild game */
2830 if (gameInfo.event != NULL) free(gameInfo.event);
2831 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2832 gameInfo.event = StrSave(str);
2833 /* [HGM] we switched variant. Translate boards if needed. */
2834 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2838 if (looking_at(buf, &i, "Move ")) {
2839 /* Beginning of a move list */
2840 switch (ics_getting_history) {
2842 /* Normally should not happen */
2843 /* Maybe user hit reset while we were parsing */
2846 /* Happens if we are ignoring a move list that is not
2847 * the one we just requested. Common if the user
2848 * tries to observe two games without turning off
2851 case H_GETTING_MOVES:
2852 /* Should not happen */
2853 DisplayError(_("Error gathering move list: nested"), 0);
2854 ics_getting_history = H_FALSE;
2856 case H_GOT_REQ_HEADER:
2857 ics_getting_history = H_GETTING_MOVES;
2858 started = STARTED_MOVES;
2860 if (oldi > next_out) {
2861 SendToPlayer(&buf[next_out], oldi - next_out);
2864 case H_GOT_UNREQ_HEADER:
2865 ics_getting_history = H_GETTING_MOVES;
2866 started = STARTED_MOVES_NOHIDE;
2869 case H_GOT_UNWANTED_HEADER:
2870 ics_getting_history = H_FALSE;
2876 if (looking_at(buf, &i, "% ") ||
2877 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2878 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2879 if(suppressKibitz) next_out = i;
2880 savingComment = FALSE;
2884 case STARTED_MOVES_NOHIDE:
2885 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2886 parse[parse_pos + i - oldi] = NULLCHAR;
2887 ParseGameHistory(parse);
2889 if (appData.zippyPlay && first.initDone) {
2890 FeedMovesToProgram(&first, forwardMostMove);
2891 if (gameMode == IcsPlayingWhite) {
2892 if (WhiteOnMove(forwardMostMove)) {
2893 if (first.sendTime) {
2894 if (first.useColors) {
2895 SendToProgram("black\n", &first);
2897 SendTimeRemaining(&first, TRUE);
2899 if (first.useColors) {
2900 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2902 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2903 first.maybeThinking = TRUE;
2905 if (first.usePlayother) {
2906 if (first.sendTime) {
2907 SendTimeRemaining(&first, TRUE);
2909 SendToProgram("playother\n", &first);
2915 } else if (gameMode == IcsPlayingBlack) {
2916 if (!WhiteOnMove(forwardMostMove)) {
2917 if (first.sendTime) {
2918 if (first.useColors) {
2919 SendToProgram("white\n", &first);
2921 SendTimeRemaining(&first, FALSE);
2923 if (first.useColors) {
2924 SendToProgram("black\n", &first);
2926 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2927 first.maybeThinking = TRUE;
2929 if (first.usePlayother) {
2930 if (first.sendTime) {
2931 SendTimeRemaining(&first, FALSE);
2933 SendToProgram("playother\n", &first);
2942 if (gameMode == IcsObserving && ics_gamenum == -1) {
2943 /* Moves came from oldmoves or moves command
2944 while we weren't doing anything else.
2946 currentMove = forwardMostMove;
2947 ClearHighlights();/*!!could figure this out*/
2948 flipView = appData.flipView;
2949 DrawPosition(TRUE, boards[currentMove]);
2950 DisplayBothClocks();
2951 sprintf(str, "%s vs. %s",
2952 gameInfo.white, gameInfo.black);
2956 /* Moves were history of an active game */
2957 if (gameInfo.resultDetails != NULL) {
2958 free(gameInfo.resultDetails);
2959 gameInfo.resultDetails = NULL;
2962 HistorySet(parseList, backwardMostMove,
2963 forwardMostMove, currentMove-1);
2964 DisplayMove(currentMove - 1);
2965 if (started == STARTED_MOVES) next_out = i;
2966 started = STARTED_NONE;
2967 ics_getting_history = H_FALSE;
2970 case STARTED_OBSERVE:
2971 started = STARTED_NONE;
2972 SendToICS(ics_prefix);
2973 SendToICS("refresh\n");
2979 if(bookHit) { // [HGM] book: simulate book reply
2980 static char bookMove[MSG_SIZ]; // a bit generous?
2982 programStats.nodes = programStats.depth = programStats.time =
2983 programStats.score = programStats.got_only_move = 0;
2984 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2986 strcpy(bookMove, "move ");
2987 strcat(bookMove, bookHit);
2988 HandleMachineMove(bookMove, &first);
2993 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2994 started == STARTED_HOLDINGS ||
2995 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2996 /* Accumulate characters in move list or board */
2997 parse[parse_pos++] = buf[i];
3000 /* Start of game messages. Mostly we detect start of game
3001 when the first board image arrives. On some versions
3002 of the ICS, though, we need to do a "refresh" after starting
3003 to observe in order to get the current board right away. */
3004 if (looking_at(buf, &i, "Adding game * to observation list")) {
3005 started = STARTED_OBSERVE;
3009 /* Handle auto-observe */
3010 if (appData.autoObserve &&
3011 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3012 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3014 /* Choose the player that was highlighted, if any. */
3015 if (star_match[0][0] == '\033' ||
3016 star_match[1][0] != '\033') {
3017 player = star_match[0];
3019 player = star_match[2];
3021 sprintf(str, "%sobserve %s\n",
3022 ics_prefix, StripHighlightAndTitle(player));
3025 /* Save ratings from notify string */
3026 strcpy(player1Name, star_match[0]);
3027 player1Rating = string_to_rating(star_match[1]);
3028 strcpy(player2Name, star_match[2]);
3029 player2Rating = string_to_rating(star_match[3]);
3031 if (appData.debugMode)
3033 "Ratings from 'Game notification:' %s %d, %s %d\n",
3034 player1Name, player1Rating,
3035 player2Name, player2Rating);
3040 /* Deal with automatic examine mode after a game,
3041 and with IcsObserving -> IcsExamining transition */
3042 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3043 looking_at(buf, &i, "has made you an examiner of game *")) {
3045 int gamenum = atoi(star_match[0]);
3046 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3047 gamenum == ics_gamenum) {
3048 /* We were already playing or observing this game;
3049 no need to refetch history */
3050 gameMode = IcsExamining;
3052 pauseExamForwardMostMove = forwardMostMove;
3053 } else if (currentMove < forwardMostMove) {
3054 ForwardInner(forwardMostMove);
3057 /* I don't think this case really can happen */
3058 SendToICS(ics_prefix);
3059 SendToICS("refresh\n");
3064 /* Error messages */
3065 // if (ics_user_moved) {
3066 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3067 if (looking_at(buf, &i, "Illegal move") ||
3068 looking_at(buf, &i, "Not a legal move") ||
3069 looking_at(buf, &i, "Your king is in check") ||
3070 looking_at(buf, &i, "It isn't your turn") ||
3071 looking_at(buf, &i, "It is not your move")) {
3073 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3074 currentMove = forwardMostMove-1;
3075 DisplayMove(currentMove - 1); /* before DMError */
3076 DrawPosition(FALSE, boards[currentMove]);
3077 SwitchClocks(forwardMostMove-1); // [HGM] race
3078 DisplayBothClocks();
3080 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3086 if (looking_at(buf, &i, "still have time") ||
3087 looking_at(buf, &i, "not out of time") ||
3088 looking_at(buf, &i, "either player is out of time") ||
3089 looking_at(buf, &i, "has timeseal; checking")) {
3090 /* We must have called his flag a little too soon */
3091 whiteFlag = blackFlag = FALSE;
3095 if (looking_at(buf, &i, "added * seconds to") ||
3096 looking_at(buf, &i, "seconds were added to")) {
3097 /* Update the clocks */
3098 SendToICS(ics_prefix);
3099 SendToICS("refresh\n");
3103 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3104 ics_clock_paused = TRUE;
3109 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3110 ics_clock_paused = FALSE;
3115 /* Grab player ratings from the Creating: message.
3116 Note we have to check for the special case when
3117 the ICS inserts things like [white] or [black]. */
3118 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3119 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3121 0 player 1 name (not necessarily white)
3123 2 empty, white, or black (IGNORED)
3124 3 player 2 name (not necessarily black)
3127 The names/ratings are sorted out when the game
3128 actually starts (below).
3130 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3131 player1Rating = string_to_rating(star_match[1]);
3132 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3133 player2Rating = string_to_rating(star_match[4]);
3135 if (appData.debugMode)
3137 "Ratings from 'Creating:' %s %d, %s %d\n",
3138 player1Name, player1Rating,
3139 player2Name, player2Rating);
3144 /* Improved generic start/end-of-game messages */
3145 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3146 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3147 /* If tkind == 0: */
3148 /* star_match[0] is the game number */
3149 /* [1] is the white player's name */
3150 /* [2] is the black player's name */
3151 /* For end-of-game: */
3152 /* [3] is the reason for the game end */
3153 /* [4] is a PGN end game-token, preceded by " " */
3154 /* For start-of-game: */
3155 /* [3] begins with "Creating" or "Continuing" */
3156 /* [4] is " *" or empty (don't care). */
3157 int gamenum = atoi(star_match[0]);
3158 char *whitename, *blackname, *why, *endtoken;
3159 ChessMove endtype = (ChessMove) 0;
3162 whitename = star_match[1];
3163 blackname = star_match[2];
3164 why = star_match[3];
3165 endtoken = star_match[4];
3167 whitename = star_match[1];
3168 blackname = star_match[3];
3169 why = star_match[5];
3170 endtoken = star_match[6];
3173 /* Game start messages */
3174 if (strncmp(why, "Creating ", 9) == 0 ||
3175 strncmp(why, "Continuing ", 11) == 0) {
3176 gs_gamenum = gamenum;
3177 strcpy(gs_kind, strchr(why, ' ') + 1);
3178 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3180 if (appData.zippyPlay) {
3181 ZippyGameStart(whitename, blackname);
3187 /* Game end messages */
3188 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3189 ics_gamenum != gamenum) {
3192 while (endtoken[0] == ' ') endtoken++;
3193 switch (endtoken[0]) {
3196 endtype = GameUnfinished;
3199 endtype = BlackWins;
3202 if (endtoken[1] == '/')
3203 endtype = GameIsDrawn;
3205 endtype = WhiteWins;
3208 GameEnds(endtype, why, GE_ICS);
3210 if (appData.zippyPlay && first.initDone) {
3211 ZippyGameEnd(endtype, why);
3212 if (first.pr == NULL) {
3213 /* Start the next process early so that we'll
3214 be ready for the next challenge */
3215 StartChessProgram(&first);
3217 /* Send "new" early, in case this command takes
3218 a long time to finish, so that we'll be ready
3219 for the next challenge. */
3220 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3227 if (looking_at(buf, &i, "Removing game * from observation") ||
3228 looking_at(buf, &i, "no longer observing game *") ||
3229 looking_at(buf, &i, "Game * (*) has no examiners")) {
3230 if (gameMode == IcsObserving &&
3231 atoi(star_match[0]) == ics_gamenum)
3233 /* icsEngineAnalyze */
3234 if (appData.icsEngineAnalyze) {
3241 ics_user_moved = FALSE;
3246 if (looking_at(buf, &i, "no longer examining game *")) {
3247 if (gameMode == IcsExamining &&
3248 atoi(star_match[0]) == ics_gamenum)
3252 ics_user_moved = FALSE;
3257 /* Advance leftover_start past any newlines we find,
3258 so only partial lines can get reparsed */
3259 if (looking_at(buf, &i, "\n")) {
3260 prevColor = curColor;
3261 if (curColor != ColorNormal) {
3262 if (oldi > next_out) {
3263 SendToPlayer(&buf[next_out], oldi - next_out);
3266 Colorize(ColorNormal, FALSE);
3267 curColor = ColorNormal;
3269 if (started == STARTED_BOARD) {
3270 started = STARTED_NONE;
3271 parse[parse_pos] = NULLCHAR;
3272 ParseBoard12(parse);
3275 /* Send premove here */
3276 if (appData.premove) {
3278 if (currentMove == 0 &&
3279 gameMode == IcsPlayingWhite &&
3280 appData.premoveWhite) {
3281 sprintf(str, "%s\n", appData.premoveWhiteText);
3282 if (appData.debugMode)
3283 fprintf(debugFP, "Sending premove:\n");
3285 } else if (currentMove == 1 &&
3286 gameMode == IcsPlayingBlack &&
3287 appData.premoveBlack) {
3288 sprintf(str, "%s\n", appData.premoveBlackText);
3289 if (appData.debugMode)
3290 fprintf(debugFP, "Sending premove:\n");
3292 } else if (gotPremove) {
3294 ClearPremoveHighlights();
3295 if (appData.debugMode)
3296 fprintf(debugFP, "Sending premove:\n");
3297 UserMoveEvent(premoveFromX, premoveFromY,
3298 premoveToX, premoveToY,
3303 /* Usually suppress following prompt */
3304 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3305 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3306 if (looking_at(buf, &i, "*% ")) {
3307 savingComment = FALSE;
3312 } else if (started == STARTED_HOLDINGS) {
3314 char new_piece[MSG_SIZ];
3315 started = STARTED_NONE;
3316 parse[parse_pos] = NULLCHAR;
3317 if (appData.debugMode)
3318 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3319 parse, currentMove);
3320 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3321 gamenum == ics_gamenum) {
3322 if (gameInfo.variant == VariantNormal) {
3323 /* [HGM] We seem to switch variant during a game!
3324 * Presumably no holdings were displayed, so we have
3325 * to move the position two files to the right to
3326 * create room for them!
3328 VariantClass newVariant;
3329 switch(gameInfo.boardWidth) { // base guess on board width
3330 case 9: newVariant = VariantShogi; break;
3331 case 10: newVariant = VariantGreat; break;
3332 default: newVariant = VariantCrazyhouse; break;
3334 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3335 /* Get a move list just to see the header, which
3336 will tell us whether this is really bug or zh */
3337 if (ics_getting_history == H_FALSE) {
3338 ics_getting_history = H_REQUESTED;
3339 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3343 new_piece[0] = NULLCHAR;
3344 sscanf(parse, "game %d white [%s black [%s <- %s",
3345 &gamenum, white_holding, black_holding,
3347 white_holding[strlen(white_holding)-1] = NULLCHAR;
3348 black_holding[strlen(black_holding)-1] = NULLCHAR;
3349 /* [HGM] copy holdings to board holdings area */
3350 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3351 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3352 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3354 if (appData.zippyPlay && first.initDone) {
3355 ZippyHoldings(white_holding, black_holding,
3359 if (tinyLayout || smallLayout) {
3360 char wh[16], bh[16];
3361 PackHolding(wh, white_holding);
3362 PackHolding(bh, black_holding);
3363 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3364 gameInfo.white, gameInfo.black);
3366 sprintf(str, "%s [%s] vs. %s [%s]",
3367 gameInfo.white, white_holding,
3368 gameInfo.black, black_holding);
3371 DrawPosition(FALSE, boards[currentMove]);
3374 /* Suppress following prompt */
3375 if (looking_at(buf, &i, "*% ")) {
3376 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3377 savingComment = FALSE;
3385 i++; /* skip unparsed character and loop back */
3388 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3389 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3390 // SendToPlayer(&buf[next_out], i - next_out);
3391 started != STARTED_HOLDINGS && leftover_start > next_out) {
3392 SendToPlayer(&buf[next_out], leftover_start - next_out);
3396 leftover_len = buf_len - leftover_start;
3397 /* if buffer ends with something we couldn't parse,
3398 reparse it after appending the next read */
3400 } else if (count == 0) {
3401 RemoveInputSource(isr);
3402 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3404 DisplayFatalError(_("Error reading from ICS"), error, 1);
3409 /* Board style 12 looks like this:
3411 <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
3413 * The "<12> " is stripped before it gets to this routine. The two
3414 * trailing 0's (flip state and clock ticking) are later addition, and
3415 * some chess servers may not have them, or may have only the first.
3416 * Additional trailing fields may be added in the future.
3419 #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"
3421 #define RELATION_OBSERVING_PLAYED 0
3422 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3423 #define RELATION_PLAYING_MYMOVE 1
3424 #define RELATION_PLAYING_NOTMYMOVE -1
3425 #define RELATION_EXAMINING 2
3426 #define RELATION_ISOLATED_BOARD -3
3427 #define RELATION_STARTING_POSITION -4 /* FICS only */
3430 ParseBoard12(string)
3433 GameMode newGameMode;
3434 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3435 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3436 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3437 char to_play, board_chars[200];
3438 char move_str[500], str[500], elapsed_time[500];
3439 char black[32], white[32];
3441 int prevMove = currentMove;
3444 int fromX, fromY, toX, toY;
3446 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3447 char *bookHit = NULL; // [HGM] book
3448 Boolean weird = FALSE, reqFlag = FALSE;
3450 fromX = fromY = toX = toY = -1;
3454 if (appData.debugMode)
3455 fprintf(debugFP, _("Parsing board: %s\n"), string);
3457 move_str[0] = NULLCHAR;
3458 elapsed_time[0] = NULLCHAR;
3459 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3461 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3462 if(string[i] == ' ') { ranks++; files = 0; }
3464 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3467 for(j = 0; j <i; j++) board_chars[j] = string[j];
3468 board_chars[i] = '\0';
3471 n = sscanf(string, PATTERN, &to_play, &double_push,
3472 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3473 &gamenum, white, black, &relation, &basetime, &increment,
3474 &white_stren, &black_stren, &white_time, &black_time,
3475 &moveNum, str, elapsed_time, move_str, &ics_flip,
3479 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3480 DisplayError(str, 0);
3484 /* Convert the move number to internal form */
3485 moveNum = (moveNum - 1) * 2;
3486 if (to_play == 'B') moveNum++;
3487 if (moveNum >= MAX_MOVES) {
3488 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3494 case RELATION_OBSERVING_PLAYED:
3495 case RELATION_OBSERVING_STATIC:
3496 if (gamenum == -1) {
3497 /* Old ICC buglet */
3498 relation = RELATION_OBSERVING_STATIC;
3500 newGameMode = IcsObserving;
3502 case RELATION_PLAYING_MYMOVE:
3503 case RELATION_PLAYING_NOTMYMOVE:
3505 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3506 IcsPlayingWhite : IcsPlayingBlack;
3508 case RELATION_EXAMINING:
3509 newGameMode = IcsExamining;
3511 case RELATION_ISOLATED_BOARD:
3513 /* Just display this board. If user was doing something else,
3514 we will forget about it until the next board comes. */
3515 newGameMode = IcsIdle;
3517 case RELATION_STARTING_POSITION:
3518 newGameMode = gameMode;
3522 /* Modify behavior for initial board display on move listing
3525 switch (ics_getting_history) {
3529 case H_GOT_REQ_HEADER:
3530 case H_GOT_UNREQ_HEADER:
3531 /* This is the initial position of the current game */
3532 gamenum = ics_gamenum;
3533 moveNum = 0; /* old ICS bug workaround */
3534 if (to_play == 'B') {
3535 startedFromSetupPosition = TRUE;
3536 blackPlaysFirst = TRUE;
3538 if (forwardMostMove == 0) forwardMostMove = 1;
3539 if (backwardMostMove == 0) backwardMostMove = 1;
3540 if (currentMove == 0) currentMove = 1;
3542 newGameMode = gameMode;
3543 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3545 case H_GOT_UNWANTED_HEADER:
3546 /* This is an initial board that we don't want */
3548 case H_GETTING_MOVES:
3549 /* Should not happen */
3550 DisplayError(_("Error gathering move list: extra board"), 0);
3551 ics_getting_history = H_FALSE;
3555 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3556 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3557 /* [HGM] We seem to have switched variant unexpectedly
3558 * Try to guess new variant from board size
3560 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3561 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3562 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3563 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3564 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3565 if(!weird) newVariant = VariantNormal;
3566 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3567 /* Get a move list just to see the header, which
3568 will tell us whether this is really bug or zh */
3569 if (ics_getting_history == H_FALSE) {
3570 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3571 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3576 /* Take action if this is the first board of a new game, or of a
3577 different game than is currently being displayed. */
3578 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3579 relation == RELATION_ISOLATED_BOARD) {
3581 /* Forget the old game and get the history (if any) of the new one */
3582 if (gameMode != BeginningOfGame) {
3586 if (appData.autoRaiseBoard) BoardToTop();
3588 if (gamenum == -1) {
3589 newGameMode = IcsIdle;
3590 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3591 appData.getMoveList && !reqFlag) {
3592 /* Need to get game history */
3593 ics_getting_history = H_REQUESTED;
3594 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3598 /* Initially flip the board to have black on the bottom if playing
3599 black or if the ICS flip flag is set, but let the user change
3600 it with the Flip View button. */
3601 flipView = appData.autoFlipView ?
3602 (newGameMode == IcsPlayingBlack) || ics_flip :
3605 /* Done with values from previous mode; copy in new ones */
3606 gameMode = newGameMode;
3608 ics_gamenum = gamenum;
3609 if (gamenum == gs_gamenum) {
3610 int klen = strlen(gs_kind);
3611 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3612 sprintf(str, "ICS %s", gs_kind);
3613 gameInfo.event = StrSave(str);
3615 gameInfo.event = StrSave("ICS game");
3617 gameInfo.site = StrSave(appData.icsHost);
3618 gameInfo.date = PGNDate();
3619 gameInfo.round = StrSave("-");
3620 gameInfo.white = StrSave(white);
3621 gameInfo.black = StrSave(black);
3622 timeControl = basetime * 60 * 1000;
3624 timeIncrement = increment * 1000;
3625 movesPerSession = 0;
3626 gameInfo.timeControl = TimeControlTagValue();
3627 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3628 if (appData.debugMode) {
3629 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3630 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3631 setbuf(debugFP, NULL);
3634 gameInfo.outOfBook = NULL;
3636 /* Do we have the ratings? */
3637 if (strcmp(player1Name, white) == 0 &&
3638 strcmp(player2Name, black) == 0) {
3639 if (appData.debugMode)
3640 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3641 player1Rating, player2Rating);
3642 gameInfo.whiteRating = player1Rating;
3643 gameInfo.blackRating = player2Rating;
3644 } else if (strcmp(player2Name, white) == 0 &&
3645 strcmp(player1Name, black) == 0) {
3646 if (appData.debugMode)
3647 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3648 player2Rating, player1Rating);
3649 gameInfo.whiteRating = player2Rating;
3650 gameInfo.blackRating = player1Rating;
3652 player1Name[0] = player2Name[0] = NULLCHAR;
3654 /* Silence shouts if requested */
3655 if (appData.quietPlay &&
3656 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3657 SendToICS(ics_prefix);
3658 SendToICS("set shout 0\n");
3662 /* Deal with midgame name changes */
3664 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3665 if (gameInfo.white) free(gameInfo.white);
3666 gameInfo.white = StrSave(white);
3668 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3669 if (gameInfo.black) free(gameInfo.black);
3670 gameInfo.black = StrSave(black);
3674 /* Throw away game result if anything actually changes in examine mode */
3675 if (gameMode == IcsExamining && !newGame) {
3676 gameInfo.result = GameUnfinished;
3677 if (gameInfo.resultDetails != NULL) {
3678 free(gameInfo.resultDetails);
3679 gameInfo.resultDetails = NULL;
3683 /* In pausing && IcsExamining mode, we ignore boards coming
3684 in if they are in a different variation than we are. */
3685 if (pauseExamInvalid) return;
3686 if (pausing && gameMode == IcsExamining) {
3687 if (moveNum <= pauseExamForwardMostMove) {
3688 pauseExamInvalid = TRUE;
3689 forwardMostMove = pauseExamForwardMostMove;
3694 if (appData.debugMode) {
3695 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3697 /* Parse the board */
3698 for (k = 0; k < ranks; k++) {
3699 for (j = 0; j < files; j++)
3700 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3701 if(gameInfo.holdingsWidth > 1) {
3702 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3703 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3706 CopyBoard(boards[moveNum], board);
3707 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3709 startedFromSetupPosition =
3710 !CompareBoards(board, initialPosition);
3711 if(startedFromSetupPosition)
3712 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3715 /* [HGM] Set castling rights. Take the outermost Rooks,
3716 to make it also work for FRC opening positions. Note that board12
3717 is really defective for later FRC positions, as it has no way to
3718 indicate which Rook can castle if they are on the same side of King.
3719 For the initial position we grant rights to the outermost Rooks,
3720 and remember thos rights, and we then copy them on positions
3721 later in an FRC game. This means WB might not recognize castlings with
3722 Rooks that have moved back to their original position as illegal,
3723 but in ICS mode that is not its job anyway.
3725 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3726 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3728 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3729 if(board[0][i] == WhiteRook) j = i;
3730 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3731 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3732 if(board[0][i] == WhiteRook) j = i;
3733 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3734 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3735 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3736 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3737 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3738 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3739 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3741 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3742 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3743 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3744 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3745 if(board[BOARD_HEIGHT-1][k] == bKing)
3746 initialRights[5] = castlingRights[moveNum][5] = k;
3747 if(gameInfo.variant == VariantTwoKings) {
3748 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3749 if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3750 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3753 r = castlingRights[moveNum][0] = initialRights[0];
3754 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3755 r = castlingRights[moveNum][1] = initialRights[1];
3756 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3757 r = castlingRights[moveNum][3] = initialRights[3];
3758 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3759 r = castlingRights[moveNum][4] = initialRights[4];
3760 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3761 /* wildcastle kludge: always assume King has rights */
3762 r = castlingRights[moveNum][2] = initialRights[2];
3763 r = castlingRights[moveNum][5] = initialRights[5];
3765 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3766 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3769 if (ics_getting_history == H_GOT_REQ_HEADER ||
3770 ics_getting_history == H_GOT_UNREQ_HEADER) {
3771 /* This was an initial position from a move list, not
3772 the current position */
3776 /* Update currentMove and known move number limits */
3777 newMove = newGame || moveNum > forwardMostMove;
3780 forwardMostMove = backwardMostMove = currentMove = moveNum;
3781 if (gameMode == IcsExamining && moveNum == 0) {
3782 /* Workaround for ICS limitation: we are not told the wild
3783 type when starting to examine a game. But if we ask for
3784 the move list, the move list header will tell us */
3785 ics_getting_history = H_REQUESTED;
3786 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3789 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3790 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3792 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3793 /* [HGM] applied this also to an engine that is silently watching */
3794 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3795 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3796 gameInfo.variant == currentlyInitializedVariant) {
3797 takeback = forwardMostMove - moveNum;
3798 for (i = 0; i < takeback; i++) {
3799 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3800 SendToProgram("undo\n", &first);
3805 forwardMostMove = moveNum;
3806 if (!pausing || currentMove > forwardMostMove)
3807 currentMove = forwardMostMove;
3809 /* New part of history that is not contiguous with old part */
3810 if (pausing && gameMode == IcsExamining) {
3811 pauseExamInvalid = TRUE;
3812 forwardMostMove = pauseExamForwardMostMove;
3815 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3817 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3818 // [HGM] when we will receive the move list we now request, it will be
3819 // fed to the engine from the first move on. So if the engine is not
3820 // in the initial position now, bring it there.
3821 InitChessProgram(&first, 0);
3824 ics_getting_history = H_REQUESTED;
3825 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3828 forwardMostMove = backwardMostMove = currentMove = moveNum;
3831 /* Update the clocks */
3832 if (strchr(elapsed_time, '.')) {
3834 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3835 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3837 /* Time is in seconds */
3838 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3839 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3844 if (appData.zippyPlay && newGame &&
3845 gameMode != IcsObserving && gameMode != IcsIdle &&
3846 gameMode != IcsExamining)
3847 ZippyFirstBoard(moveNum, basetime, increment);
3850 /* Put the move on the move list, first converting
3851 to canonical algebraic form. */
3853 if (appData.debugMode) {
3854 if (appData.debugMode) { int f = forwardMostMove;
3855 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3856 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3858 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3859 fprintf(debugFP, "moveNum = %d\n", moveNum);
3860 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3861 setbuf(debugFP, NULL);
3863 if (moveNum <= backwardMostMove) {
3864 /* We don't know what the board looked like before
3866 strcpy(parseList[moveNum - 1], move_str);
3867 strcat(parseList[moveNum - 1], " ");
3868 strcat(parseList[moveNum - 1], elapsed_time);
3869 moveList[moveNum - 1][0] = NULLCHAR;
3870 } else if (strcmp(move_str, "none") == 0) {
3871 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3872 /* Again, we don't know what the board looked like;
3873 this is really the start of the game. */
3874 parseList[moveNum - 1][0] = NULLCHAR;
3875 moveList[moveNum - 1][0] = NULLCHAR;
3876 backwardMostMove = moveNum;
3877 startedFromSetupPosition = TRUE;
3878 fromX = fromY = toX = toY = -1;
3880 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3881 // So we parse the long-algebraic move string in stead of the SAN move
3882 int valid; char buf[MSG_SIZ], *prom;
3884 // str looks something like "Q/a1-a2"; kill the slash
3886 sprintf(buf, "%c%s", str[0], str+2);
3887 else strcpy(buf, str); // might be castling
3888 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3889 strcat(buf, prom); // long move lacks promo specification!
3890 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3891 if(appData.debugMode)
3892 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3893 strcpy(move_str, buf);
3895 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3896 &fromX, &fromY, &toX, &toY, &promoChar)
3897 || ParseOneMove(buf, moveNum - 1, &moveType,
3898 &fromX, &fromY, &toX, &toY, &promoChar);
3899 // end of long SAN patch
3901 (void) CoordsToAlgebraic(boards[moveNum - 1],
3902 PosFlags(moveNum - 1), EP_UNKNOWN,
3903 fromY, fromX, toY, toX, promoChar,
3904 parseList[moveNum-1]);
3905 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3906 castlingRights[moveNum]) ) {
3912 if(gameInfo.variant != VariantShogi)
3913 strcat(parseList[moveNum - 1], "+");
3916 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3917 strcat(parseList[moveNum - 1], "#");
3920 strcat(parseList[moveNum - 1], " ");
3921 strcat(parseList[moveNum - 1], elapsed_time);
3922 /* currentMoveString is set as a side-effect of ParseOneMove */
3923 strcpy(moveList[moveNum - 1], currentMoveString);
3924 strcat(moveList[moveNum - 1], "\n");
3926 /* Move from ICS was illegal!? Punt. */
3927 if (appData.debugMode) {
3928 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3929 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3931 strcpy(parseList[moveNum - 1], move_str);
3932 strcat(parseList[moveNum - 1], " ");
3933 strcat(parseList[moveNum - 1], elapsed_time);
3934 moveList[moveNum - 1][0] = NULLCHAR;
3935 fromX = fromY = toX = toY = -1;
3938 if (appData.debugMode) {
3939 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3940 setbuf(debugFP, NULL);
3944 /* Send move to chess program (BEFORE animating it). */
3945 if (appData.zippyPlay && !newGame && newMove &&
3946 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3948 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3949 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3950 if (moveList[moveNum - 1][0] == NULLCHAR) {
3951 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3953 DisplayError(str, 0);
3955 if (first.sendTime) {
3956 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3958 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3959 if (firstMove && !bookHit) {
3961 if (first.useColors) {
3962 SendToProgram(gameMode == IcsPlayingWhite ?
3964 "black\ngo\n", &first);
3966 SendToProgram("go\n", &first);
3968 first.maybeThinking = TRUE;
3971 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3972 if (moveList[moveNum - 1][0] == NULLCHAR) {
3973 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3974 DisplayError(str, 0);
3976 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3977 SendMoveToProgram(moveNum - 1, &first);
3984 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3985 /* If move comes from a remote source, animate it. If it
3986 isn't remote, it will have already been animated. */
3987 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3988 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3990 if (!pausing && appData.highlightLastMove) {
3991 SetHighlights(fromX, fromY, toX, toY);
3995 /* Start the clocks */
3996 whiteFlag = blackFlag = FALSE;
3997 appData.clockMode = !(basetime == 0 && increment == 0);
3999 ics_clock_paused = TRUE;
4001 } else if (ticking == 1) {
4002 ics_clock_paused = FALSE;
4004 if (gameMode == IcsIdle ||
4005 relation == RELATION_OBSERVING_STATIC ||
4006 relation == RELATION_EXAMINING ||
4008 DisplayBothClocks();
4012 /* Display opponents and material strengths */
4013 if (gameInfo.variant != VariantBughouse &&
4014 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4015 if (tinyLayout || smallLayout) {
4016 if(gameInfo.variant == VariantNormal)
4017 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4018 gameInfo.white, white_stren, gameInfo.black, black_stren,
4019 basetime, increment);
4021 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4022 gameInfo.white, white_stren, gameInfo.black, black_stren,
4023 basetime, increment, (int) gameInfo.variant);
4025 if(gameInfo.variant == VariantNormal)
4026 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4027 gameInfo.white, white_stren, gameInfo.black, black_stren,
4028 basetime, increment);
4030 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4031 gameInfo.white, white_stren, gameInfo.black, black_stren,
4032 basetime, increment, VariantName(gameInfo.variant));
4035 if (appData.debugMode) {
4036 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4041 /* Display the board */
4042 if (!pausing && !appData.noGUI) {
4044 if (appData.premove)
4046 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4047 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4048 ClearPremoveHighlights();
4050 DrawPosition(FALSE, boards[currentMove]);
4051 DisplayMove(moveNum - 1);
4052 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4053 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4054 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4055 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4059 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4061 if(bookHit) { // [HGM] book: simulate book reply
4062 static char bookMove[MSG_SIZ]; // a bit generous?
4064 programStats.nodes = programStats.depth = programStats.time =
4065 programStats.score = programStats.got_only_move = 0;
4066 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4068 strcpy(bookMove, "move ");
4069 strcat(bookMove, bookHit);
4070 HandleMachineMove(bookMove, &first);
4079 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4080 ics_getting_history = H_REQUESTED;
4081 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4087 AnalysisPeriodicEvent(force)
4090 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4091 && !force) || !appData.periodicUpdates)
4094 /* Send . command to Crafty to collect stats */
4095 SendToProgram(".\n", &first);
4097 /* Don't send another until we get a response (this makes
4098 us stop sending to old Crafty's which don't understand
4099 the "." command (sending illegal cmds resets node count & time,
4100 which looks bad)) */
4101 programStats.ok_to_send = 0;
4104 void ics_update_width(new_width)
4107 ics_printf("set width %d\n", new_width);
4111 SendMoveToProgram(moveNum, cps)
4113 ChessProgramState *cps;
4117 if (cps->useUsermove) {
4118 SendToProgram("usermove ", cps);
4122 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4123 int len = space - parseList[moveNum];
4124 memcpy(buf, parseList[moveNum], len);
4126 buf[len] = NULLCHAR;
4128 sprintf(buf, "%s\n", parseList[moveNum]);
4130 SendToProgram(buf, cps);
4132 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4133 AlphaRank(moveList[moveNum], 4);
4134 SendToProgram(moveList[moveNum], cps);
4135 AlphaRank(moveList[moveNum], 4); // and back
4137 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4138 * the engine. It would be nice to have a better way to identify castle
4140 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4141 && cps->useOOCastle) {
4142 int fromX = moveList[moveNum][0] - AAA;
4143 int fromY = moveList[moveNum][1] - ONE;
4144 int toX = moveList[moveNum][2] - AAA;
4145 int toY = moveList[moveNum][3] - ONE;
4146 if((boards[moveNum][fromY][fromX] == WhiteKing
4147 && boards[moveNum][toY][toX] == WhiteRook)
4148 || (boards[moveNum][fromY][fromX] == BlackKing
4149 && boards[moveNum][toY][toX] == BlackRook)) {
4150 if(toX > fromX) SendToProgram("O-O\n", cps);
4151 else SendToProgram("O-O-O\n", cps);
4153 else SendToProgram(moveList[moveNum], cps);
4155 else SendToProgram(moveList[moveNum], cps);
4156 /* End of additions by Tord */
4159 /* [HGM] setting up the opening has brought engine in force mode! */
4160 /* Send 'go' if we are in a mode where machine should play. */
4161 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4162 (gameMode == TwoMachinesPlay ||
4164 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4166 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4167 SendToProgram("go\n", cps);
4168 if (appData.debugMode) {
4169 fprintf(debugFP, "(extra)\n");
4172 setboardSpoiledMachineBlack = 0;
4176 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4178 int fromX, fromY, toX, toY;
4180 char user_move[MSG_SIZ];
4184 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4185 (int)moveType, fromX, fromY, toX, toY);
4186 DisplayError(user_move + strlen("say "), 0);
4188 case WhiteKingSideCastle:
4189 case BlackKingSideCastle:
4190 case WhiteQueenSideCastleWild:
4191 case BlackQueenSideCastleWild:
4193 case WhiteHSideCastleFR:
4194 case BlackHSideCastleFR:
4196 sprintf(user_move, "o-o\n");
4198 case WhiteQueenSideCastle:
4199 case BlackQueenSideCastle:
4200 case WhiteKingSideCastleWild:
4201 case BlackKingSideCastleWild:
4203 case WhiteASideCastleFR:
4204 case BlackASideCastleFR:
4206 sprintf(user_move, "o-o-o\n");
4208 case WhitePromotionQueen:
4209 case BlackPromotionQueen:
4210 case WhitePromotionRook:
4211 case BlackPromotionRook:
4212 case WhitePromotionBishop:
4213 case BlackPromotionBishop:
4214 case WhitePromotionKnight:
4215 case BlackPromotionKnight:
4216 case WhitePromotionKing:
4217 case BlackPromotionKing:
4218 case WhitePromotionChancellor:
4219 case BlackPromotionChancellor:
4220 case WhitePromotionArchbishop:
4221 case BlackPromotionArchbishop:
4222 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4223 sprintf(user_move, "%c%c%c%c=%c\n",
4224 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4225 PieceToChar(WhiteFerz));
4226 else if(gameInfo.variant == VariantGreat)
4227 sprintf(user_move, "%c%c%c%c=%c\n",
4228 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4229 PieceToChar(WhiteMan));
4231 sprintf(user_move, "%c%c%c%c=%c\n",
4232 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4233 PieceToChar(PromoPiece(moveType)));
4237 sprintf(user_move, "%c@%c%c\n",
4238 ToUpper(PieceToChar((ChessSquare) fromX)),
4239 AAA + toX, ONE + toY);
4242 case WhiteCapturesEnPassant:
4243 case BlackCapturesEnPassant:
4244 case IllegalMove: /* could be a variant we don't quite understand */
4245 sprintf(user_move, "%c%c%c%c\n",
4246 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4249 SendToICS(user_move);
4250 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4251 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4255 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4260 if (rf == DROP_RANK) {
4261 sprintf(move, "%c@%c%c\n",
4262 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4264 if (promoChar == 'x' || promoChar == NULLCHAR) {
4265 sprintf(move, "%c%c%c%c\n",
4266 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4268 sprintf(move, "%c%c%c%c%c\n",
4269 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4275 ProcessICSInitScript(f)
4280 while (fgets(buf, MSG_SIZ, f)) {
4281 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4288 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4290 AlphaRank(char *move, int n)
4292 // char *p = move, c; int x, y;
4294 if (appData.debugMode) {
4295 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4299 move[2]>='0' && move[2]<='9' &&
4300 move[3]>='a' && move[3]<='x' ) {
4302 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4303 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4305 if(move[0]>='0' && move[0]<='9' &&
4306 move[1]>='a' && move[1]<='x' &&
4307 move[2]>='0' && move[2]<='9' &&
4308 move[3]>='a' && move[3]<='x' ) {
4309 /* input move, Shogi -> normal */
4310 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4311 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4312 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4313 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4316 move[3]>='0' && move[3]<='9' &&
4317 move[2]>='a' && move[2]<='x' ) {
4319 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4320 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4323 move[0]>='a' && move[0]<='x' &&
4324 move[3]>='0' && move[3]<='9' &&
4325 move[2]>='a' && move[2]<='x' ) {
4326 /* output move, normal -> Shogi */
4327 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4328 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4329 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4330 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4331 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4333 if (appData.debugMode) {
4334 fprintf(debugFP, " out = '%s'\n", move);
4338 /* Parser for moves from gnuchess, ICS, or user typein box */
4340 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4343 ChessMove *moveType;
4344 int *fromX, *fromY, *toX, *toY;
4347 if (appData.debugMode) {
4348 fprintf(debugFP, "move to parse: %s\n", move);
4350 *moveType = yylexstr(moveNum, move);
4352 switch (*moveType) {
4353 case WhitePromotionChancellor:
4354 case BlackPromotionChancellor:
4355 case WhitePromotionArchbishop:
4356 case BlackPromotionArchbishop:
4357 case WhitePromotionQueen:
4358 case BlackPromotionQueen:
4359 case WhitePromotionRook:
4360 case BlackPromotionRook:
4361 case WhitePromotionBishop:
4362 case BlackPromotionBishop:
4363 case WhitePromotionKnight:
4364 case BlackPromotionKnight:
4365 case WhitePromotionKing:
4366 case BlackPromotionKing:
4368 case WhiteCapturesEnPassant:
4369 case BlackCapturesEnPassant:
4370 case WhiteKingSideCastle:
4371 case WhiteQueenSideCastle:
4372 case BlackKingSideCastle:
4373 case BlackQueenSideCastle:
4374 case WhiteKingSideCastleWild:
4375 case WhiteQueenSideCastleWild:
4376 case BlackKingSideCastleWild:
4377 case BlackQueenSideCastleWild:
4378 /* Code added by Tord: */
4379 case WhiteHSideCastleFR:
4380 case WhiteASideCastleFR:
4381 case BlackHSideCastleFR:
4382 case BlackASideCastleFR:
4383 /* End of code added by Tord */
4384 case IllegalMove: /* bug or odd chess variant */
4385 *fromX = currentMoveString[0] - AAA;
4386 *fromY = currentMoveString[1] - ONE;
4387 *toX = currentMoveString[2] - AAA;
4388 *toY = currentMoveString[3] - ONE;
4389 *promoChar = currentMoveString[4];
4390 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4391 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4392 if (appData.debugMode) {
4393 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4395 *fromX = *fromY = *toX = *toY = 0;
4398 if (appData.testLegality) {
4399 return (*moveType != IllegalMove);
4401 return !(*fromX == *toX && *fromY == *toY);
4406 *fromX = *moveType == WhiteDrop ?
4407 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4408 (int) CharToPiece(ToLower(currentMoveString[0]));
4410 *toX = currentMoveString[2] - AAA;
4411 *toY = currentMoveString[3] - ONE;
4412 *promoChar = NULLCHAR;
4416 case ImpossibleMove:
4417 case (ChessMove) 0: /* end of file */
4426 if (appData.debugMode) {
4427 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4430 *fromX = *fromY = *toX = *toY = 0;
4431 *promoChar = NULLCHAR;
4436 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4437 // All positions will have equal probability, but the current method will not provide a unique
4438 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4444 int piecesLeft[(int)BlackPawn];
4445 int seed, nrOfShuffles;
4447 void GetPositionNumber()
4448 { // sets global variable seed
4451 seed = appData.defaultFrcPosition;
4452 if(seed < 0) { // randomize based on time for negative FRC position numbers
4453 for(i=0; i<50; i++) seed += random();
4454 seed = random() ^ random() >> 8 ^ random() << 8;
4455 if(seed<0) seed = -seed;
4459 int put(Board board, int pieceType, int rank, int n, int shade)
4460 // put the piece on the (n-1)-th empty squares of the given shade
4464 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4465 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4466 board[rank][i] = (ChessSquare) pieceType;
4467 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4469 piecesLeft[pieceType]--;
4477 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4478 // calculate where the next piece goes, (any empty square), and put it there
4482 i = seed % squaresLeft[shade];
4483 nrOfShuffles *= squaresLeft[shade];
4484 seed /= squaresLeft[shade];
4485 put(board, pieceType, rank, i, shade);
4488 void AddTwoPieces(Board board, int pieceType, int rank)
4489 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4491 int i, n=squaresLeft[ANY], j=n-1, k;
4493 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4494 i = seed % k; // pick one
4497 while(i >= j) i -= j--;
4498 j = n - 1 - j; i += j;
4499 put(board, pieceType, rank, j, ANY);
4500 put(board, pieceType, rank, i, ANY);
4503 void SetUpShuffle(Board board, int number)
4507 GetPositionNumber(); nrOfShuffles = 1;
4509 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4510 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4511 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4513 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4515 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4516 p = (int) board[0][i];
4517 if(p < (int) BlackPawn) piecesLeft[p] ++;
4518 board[0][i] = EmptySquare;
4521 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4522 // shuffles restricted to allow normal castling put KRR first
4523 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4524 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4525 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4526 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4527 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4528 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4529 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4530 put(board, WhiteRook, 0, 0, ANY);
4531 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4534 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4535 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4536 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4537 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4538 while(piecesLeft[p] >= 2) {
4539 AddOnePiece(board, p, 0, LITE);
4540 AddOnePiece(board, p, 0, DARK);
4542 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4545 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4546 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4547 // but we leave King and Rooks for last, to possibly obey FRC restriction
4548 if(p == (int)WhiteRook) continue;
4549 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4550 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4553 // now everything is placed, except perhaps King (Unicorn) and Rooks
4555 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4556 // Last King gets castling rights
4557 while(piecesLeft[(int)WhiteUnicorn]) {
4558 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4559 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4562 while(piecesLeft[(int)WhiteKing]) {
4563 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4564 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4569 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4570 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4573 // Only Rooks can be left; simply place them all
4574 while(piecesLeft[(int)WhiteRook]) {
4575 i = put(board, WhiteRook, 0, 0, ANY);
4576 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4579 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4581 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4584 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4585 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4588 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4591 int SetCharTable( char *table, const char * map )
4592 /* [HGM] moved here from winboard.c because of its general usefulness */
4593 /* Basically a safe strcpy that uses the last character as King */
4595 int result = FALSE; int NrPieces;
4597 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4598 && NrPieces >= 12 && !(NrPieces&1)) {
4599 int i; /* [HGM] Accept even length from 12 to 34 */
4601 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4602 for( i=0; i<NrPieces/2-1; i++ ) {
4604 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4606 table[(int) WhiteKing] = map[NrPieces/2-1];
4607 table[(int) BlackKing] = map[NrPieces-1];
4615 void Prelude(Board board)
4616 { // [HGM] superchess: random selection of exo-pieces
4617 int i, j, k; ChessSquare p;
4618 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4620 GetPositionNumber(); // use FRC position number
4622 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4623 SetCharTable(pieceToChar, appData.pieceToCharTable);
4624 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4625 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4628 j = seed%4; seed /= 4;
4629 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4630 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4631 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4632 j = seed%3 + (seed%3 >= j); seed /= 3;
4633 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4634 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4635 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4636 j = seed%3; seed /= 3;
4637 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4638 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4639 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4640 j = seed%2 + (seed%2 >= j); seed /= 2;
4641 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4642 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4643 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4644 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4645 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4646 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4647 put(board, exoPieces[0], 0, 0, ANY);
4648 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4652 InitPosition(redraw)
4655 ChessSquare (* pieces)[BOARD_SIZE];
4656 int i, j, pawnRow, overrule,
4657 oldx = gameInfo.boardWidth,
4658 oldy = gameInfo.boardHeight,
4659 oldh = gameInfo.holdingsWidth,
4660 oldv = gameInfo.variant;
4662 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4664 /* [AS] Initialize pv info list [HGM] and game status */
4666 for( i=0; i<MAX_MOVES; i++ ) {
4667 pvInfoList[i].depth = 0;
4668 epStatus[i]=EP_NONE;
4669 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4672 initialRulePlies = 0; /* 50-move counter start */
4674 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4675 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4679 /* [HGM] logic here is completely changed. In stead of full positions */
4680 /* the initialized data only consist of the two backranks. The switch */
4681 /* selects which one we will use, which is than copied to the Board */
4682 /* initialPosition, which for the rest is initialized by Pawns and */
4683 /* empty squares. This initial position is then copied to boards[0], */
4684 /* possibly after shuffling, so that it remains available. */
4686 gameInfo.holdingsWidth = 0; /* default board sizes */
4687 gameInfo.boardWidth = 8;
4688 gameInfo.boardHeight = 8;
4689 gameInfo.holdingsSize = 0;
4690 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4691 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4692 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4694 switch (gameInfo.variant) {
4695 case VariantFischeRandom:
4696 shuffleOpenings = TRUE;
4700 case VariantShatranj:
4701 pieces = ShatranjArray;
4702 nrCastlingRights = 0;
4703 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4706 pieces = makrukArray;
4707 nrCastlingRights = 0;
4708 startedFromSetupPosition = TRUE;
4709 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4711 case VariantTwoKings:
4712 pieces = twoKingsArray;
4714 case VariantCapaRandom:
4715 shuffleOpenings = TRUE;
4716 case VariantCapablanca:
4717 pieces = CapablancaArray;
4718 gameInfo.boardWidth = 10;
4719 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4722 pieces = GothicArray;
4723 gameInfo.boardWidth = 10;
4724 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4727 pieces = JanusArray;
4728 gameInfo.boardWidth = 10;
4729 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4730 nrCastlingRights = 6;
4731 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4732 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4733 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4734 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4735 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4736 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4739 pieces = FalconArray;
4740 gameInfo.boardWidth = 10;
4741 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4743 case VariantXiangqi:
4744 pieces = XiangqiArray;
4745 gameInfo.boardWidth = 9;
4746 gameInfo.boardHeight = 10;
4747 nrCastlingRights = 0;
4748 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4751 pieces = ShogiArray;
4752 gameInfo.boardWidth = 9;
4753 gameInfo.boardHeight = 9;
4754 gameInfo.holdingsSize = 7;
4755 nrCastlingRights = 0;
4756 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4758 case VariantCourier:
4759 pieces = CourierArray;
4760 gameInfo.boardWidth = 12;
4761 nrCastlingRights = 0;
4762 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4763 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4765 case VariantKnightmate:
4766 pieces = KnightmateArray;
4767 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4770 pieces = fairyArray;
4771 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4774 pieces = GreatArray;
4775 gameInfo.boardWidth = 10;
4776 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4777 gameInfo.holdingsSize = 8;
4781 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4782 gameInfo.holdingsSize = 8;
4783 startedFromSetupPosition = TRUE;
4785 case VariantCrazyhouse:
4786 case VariantBughouse:
4788 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4789 gameInfo.holdingsSize = 5;
4791 case VariantWildCastle:
4793 /* !!?shuffle with kings guaranteed to be on d or e file */
4794 shuffleOpenings = 1;
4796 case VariantNoCastle:
4798 nrCastlingRights = 0;
4799 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4800 /* !!?unconstrained back-rank shuffle */
4801 shuffleOpenings = 1;
4806 if(appData.NrFiles >= 0) {
4807 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4808 gameInfo.boardWidth = appData.NrFiles;
4810 if(appData.NrRanks >= 0) {
4811 gameInfo.boardHeight = appData.NrRanks;
4813 if(appData.holdingsSize >= 0) {
4814 i = appData.holdingsSize;
4815 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4816 gameInfo.holdingsSize = i;
4818 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4819 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4820 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4822 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4823 if(pawnRow < 1) pawnRow = 1;
4824 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4826 /* User pieceToChar list overrules defaults */
4827 if(appData.pieceToCharTable != NULL)
4828 SetCharTable(pieceToChar, appData.pieceToCharTable);
4830 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4832 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4833 s = (ChessSquare) 0; /* account holding counts in guard band */
4834 for( i=0; i<BOARD_HEIGHT; i++ )
4835 initialPosition[i][j] = s;
4837 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4838 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4839 initialPosition[pawnRow][j] = WhitePawn;
4840 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4841 if(gameInfo.variant == VariantXiangqi) {
4843 initialPosition[pawnRow][j] =
4844 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4845 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4846 initialPosition[2][j] = WhiteCannon;
4847 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4851 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4853 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4856 initialPosition[1][j] = WhiteBishop;
4857 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4859 initialPosition[1][j] = WhiteRook;
4860 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4863 if( nrCastlingRights == -1) {
4864 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4865 /* This sets default castling rights from none to normal corners */
4866 /* Variants with other castling rights must set them themselves above */
4867 nrCastlingRights = 6;
4869 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4870 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4871 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4872 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4873 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4874 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4877 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4878 if(gameInfo.variant == VariantGreat) { // promotion commoners
4879 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4880 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4881 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4882 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4884 if (appData.debugMode) {
4885 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4887 if(shuffleOpenings) {
4888 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4889 startedFromSetupPosition = TRUE;
4891 if(startedFromPositionFile) {
4892 /* [HGM] loadPos: use PositionFile for every new game */
4893 CopyBoard(initialPosition, filePosition);
4894 for(i=0; i<nrCastlingRights; i++)
4895 castlingRights[0][i] = initialRights[i] = fileRights[i];
4896 startedFromSetupPosition = TRUE;
4899 CopyBoard(boards[0], initialPosition);
4901 if(oldx != gameInfo.boardWidth ||
4902 oldy != gameInfo.boardHeight ||
4903 oldh != gameInfo.holdingsWidth
4905 || oldv == VariantGothic || // For licensing popups
4906 gameInfo.variant == VariantGothic
4909 || oldv == VariantFalcon ||
4910 gameInfo.variant == VariantFalcon
4913 InitDrawingSizes(-2 ,0);
4916 DrawPosition(TRUE, boards[currentMove]);
4920 SendBoard(cps, moveNum)
4921 ChessProgramState *cps;
4924 char message[MSG_SIZ];
4926 if (cps->useSetboard) {
4927 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4928 sprintf(message, "setboard %s\n", fen);
4929 SendToProgram(message, cps);
4935 /* Kludge to set black to move, avoiding the troublesome and now
4936 * deprecated "black" command.
4938 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4940 SendToProgram("edit\n", cps);
4941 SendToProgram("#\n", cps);
4942 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4943 bp = &boards[moveNum][i][BOARD_LEFT];
4944 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4945 if ((int) *bp < (int) BlackPawn) {
4946 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4948 if(message[0] == '+' || message[0] == '~') {
4949 sprintf(message, "%c%c%c+\n",
4950 PieceToChar((ChessSquare)(DEMOTED *bp)),
4953 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4954 message[1] = BOARD_RGHT - 1 - j + '1';
4955 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4957 SendToProgram(message, cps);
4962 SendToProgram("c\n", cps);
4963 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4964 bp = &boards[moveNum][i][BOARD_LEFT];
4965 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4966 if (((int) *bp != (int) EmptySquare)
4967 && ((int) *bp >= (int) BlackPawn)) {
4968 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4970 if(message[0] == '+' || message[0] == '~') {
4971 sprintf(message, "%c%c%c+\n",
4972 PieceToChar((ChessSquare)(DEMOTED *bp)),
4975 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4976 message[1] = BOARD_RGHT - 1 - j + '1';
4977 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4979 SendToProgram(message, cps);
4984 SendToProgram(".\n", cps);
4986 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4990 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4992 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4993 /* [HGM] add Shogi promotions */
4994 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4999 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5000 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5002 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5003 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5006 piece = boards[currentMove][fromY][fromX];
5007 if(gameInfo.variant == VariantShogi) {
5008 promotionZoneSize = 3;
5009 highestPromotingPiece = (int)WhiteFerz;
5010 } else if(gameInfo.variant == VariantMakruk) {
5011 promotionZoneSize = 3;
5014 // next weed out all moves that do not touch the promotion zone at all
5015 if((int)piece >= BlackPawn) {
5016 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5018 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5020 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5021 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5024 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5026 // weed out mandatory Shogi promotions
5027 if(gameInfo.variant == VariantShogi) {
5028 if(piece >= BlackPawn) {
5029 if(toY == 0 && piece == BlackPawn ||
5030 toY == 0 && piece == BlackQueen ||
5031 toY <= 1 && piece == BlackKnight) {
5036 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5037 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5038 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5045 // weed out obviously illegal Pawn moves
5046 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5047 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5048 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5049 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5050 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5051 // note we are not allowed to test for valid (non-)capture, due to premove
5054 // we either have a choice what to promote to, or (in Shogi) whether to promote
5055 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5056 *promoChoice = PieceToChar(BlackFerz); // no choice
5059 if(appData.alwaysPromoteToQueen) { // predetermined
5060 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5061 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5062 else *promoChoice = PieceToChar(BlackQueen);
5066 // suppress promotion popup on illegal moves that are not premoves
5067 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5068 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5069 if(appData.testLegality && !premove) {
5070 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5071 epStatus[currentMove], castlingRights[currentMove],
5072 fromY, fromX, toY, toX, NULLCHAR);
5073 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5074 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5082 InPalace(row, column)
5084 { /* [HGM] for Xiangqi */
5085 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5086 column < (BOARD_WIDTH + 4)/2 &&
5087 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5092 PieceForSquare (x, y)
5096 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5099 return boards[currentMove][y][x];
5103 OKToStartUserMove(x, y)
5106 ChessSquare from_piece;
5109 if (matchMode) return FALSE;
5110 if (gameMode == EditPosition) return TRUE;
5112 if (x >= 0 && y >= 0)
5113 from_piece = boards[currentMove][y][x];
5115 from_piece = EmptySquare;
5117 if (from_piece == EmptySquare) return FALSE;
5119 white_piece = (int)from_piece >= (int)WhitePawn &&
5120 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5123 case PlayFromGameFile:
5125 case TwoMachinesPlay:
5133 case MachinePlaysWhite:
5134 case IcsPlayingBlack:
5135 if (appData.zippyPlay) return FALSE;
5137 DisplayMoveError(_("You are playing Black"));
5142 case MachinePlaysBlack:
5143 case IcsPlayingWhite:
5144 if (appData.zippyPlay) return FALSE;
5146 DisplayMoveError(_("You are playing White"));
5152 if (!white_piece && WhiteOnMove(currentMove)) {
5153 DisplayMoveError(_("It is White's turn"));
5156 if (white_piece && !WhiteOnMove(currentMove)) {
5157 DisplayMoveError(_("It is Black's turn"));
5160 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5161 /* Editing correspondence game history */
5162 /* Could disallow this or prompt for confirmation */
5165 if (currentMove < forwardMostMove) {
5166 /* Discarding moves */
5167 /* Could prompt for confirmation here,
5168 but I don't think that's such a good idea */
5169 forwardMostMove = currentMove;
5173 case BeginningOfGame:
5174 if (appData.icsActive) return FALSE;
5175 if (!appData.noChessProgram) {
5177 DisplayMoveError(_("You are playing White"));
5184 if (!white_piece && WhiteOnMove(currentMove)) {
5185 DisplayMoveError(_("It is White's turn"));
5188 if (white_piece && !WhiteOnMove(currentMove)) {
5189 DisplayMoveError(_("It is Black's turn"));
5198 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5199 && gameMode != AnalyzeFile && gameMode != Training) {
5200 DisplayMoveError(_("Displayed position is not current"));
5206 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5207 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5208 int lastLoadGameUseList = FALSE;
5209 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5210 ChessMove lastLoadGameStart = (ChessMove) 0;
5213 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5214 int fromX, fromY, toX, toY;
5219 ChessSquare pdown, pup;
5221 /* Check if the user is playing in turn. This is complicated because we
5222 let the user "pick up" a piece before it is his turn. So the piece he
5223 tried to pick up may have been captured by the time he puts it down!
5224 Therefore we use the color the user is supposed to be playing in this
5225 test, not the color of the piece that is currently on the starting
5226 square---except in EditGame mode, where the user is playing both
5227 sides; fortunately there the capture race can't happen. (It can
5228 now happen in IcsExamining mode, but that's just too bad. The user
5229 will get a somewhat confusing message in that case.)
5233 case PlayFromGameFile:
5235 case TwoMachinesPlay:
5239 /* We switched into a game mode where moves are not accepted,
5240 perhaps while the mouse button was down. */
5241 return ImpossibleMove;
5243 case MachinePlaysWhite:
5244 /* User is moving for Black */
5245 if (WhiteOnMove(currentMove)) {
5246 DisplayMoveError(_("It is White's turn"));
5247 return ImpossibleMove;
5251 case MachinePlaysBlack:
5252 /* User is moving for White */
5253 if (!WhiteOnMove(currentMove)) {
5254 DisplayMoveError(_("It is Black's turn"));
5255 return ImpossibleMove;
5261 case BeginningOfGame:
5264 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5265 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5266 /* User is moving for Black */
5267 if (WhiteOnMove(currentMove)) {
5268 DisplayMoveError(_("It is White's turn"));
5269 return ImpossibleMove;
5272 /* User is moving for White */
5273 if (!WhiteOnMove(currentMove)) {
5274 DisplayMoveError(_("It is Black's turn"));
5275 return ImpossibleMove;
5280 case IcsPlayingBlack:
5281 /* User is moving for Black */
5282 if (WhiteOnMove(currentMove)) {
5283 if (!appData.premove) {
5284 DisplayMoveError(_("It is White's turn"));
5285 } else if (toX >= 0 && toY >= 0) {
5288 premoveFromX = fromX;
5289 premoveFromY = fromY;
5290 premovePromoChar = promoChar;
5292 if (appData.debugMode)
5293 fprintf(debugFP, "Got premove: fromX %d,"
5294 "fromY %d, toX %d, toY %d\n",
5295 fromX, fromY, toX, toY);
5297 return ImpossibleMove;
5301 case IcsPlayingWhite:
5302 /* User is moving for White */
5303 if (!WhiteOnMove(currentMove)) {
5304 if (!appData.premove) {
5305 DisplayMoveError(_("It is Black's turn"));
5306 } else if (toX >= 0 && toY >= 0) {
5309 premoveFromX = fromX;
5310 premoveFromY = fromY;
5311 premovePromoChar = promoChar;
5313 if (appData.debugMode)
5314 fprintf(debugFP, "Got premove: fromX %d,"
5315 "fromY %d, toX %d, toY %d\n",
5316 fromX, fromY, toX, toY);
5318 return ImpossibleMove;
5326 /* EditPosition, empty square, or different color piece;
5327 click-click move is possible */
5328 if (toX == -2 || toY == -2) {
5329 boards[0][fromY][fromX] = EmptySquare;
5330 return AmbiguousMove;
5331 } else if (toX >= 0 && toY >= 0) {
5332 boards[0][toY][toX] = boards[0][fromY][fromX];
5333 boards[0][fromY][fromX] = EmptySquare;
5334 return AmbiguousMove;
5336 return ImpossibleMove;
5339 if(toX < 0 || toY < 0) return ImpossibleMove;
5340 pdown = boards[currentMove][fromY][fromX];
5341 pup = boards[currentMove][toY][toX];
5343 /* [HGM] If move started in holdings, it means a drop */
5344 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5345 if( pup != EmptySquare ) return ImpossibleMove;
5346 if(appData.testLegality) {
5347 /* it would be more logical if LegalityTest() also figured out
5348 * which drops are legal. For now we forbid pawns on back rank.
5349 * Shogi is on its own here...
5351 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5352 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5353 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5355 return WhiteDrop; /* Not needed to specify white or black yet */
5358 userOfferedDraw = FALSE;
5360 /* [HGM] always test for legality, to get promotion info */
5361 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5362 epStatus[currentMove], castlingRights[currentMove],
5363 fromY, fromX, toY, toX, promoChar);
5364 /* [HGM] but possibly ignore an IllegalMove result */
5365 if (appData.testLegality) {
5366 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5367 DisplayMoveError(_("Illegal move"));
5368 return ImpossibleMove;
5373 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5374 function is made into one that returns an OK move type if FinishMove
5375 should be called. This to give the calling driver routine the
5376 opportunity to finish the userMove input with a promotion popup,
5377 without bothering the user with this for invalid or illegal moves */
5379 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5382 /* Common tail of UserMoveEvent and DropMenuEvent */
5384 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5386 int fromX, fromY, toX, toY;
5387 /*char*/int promoChar;
5391 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5392 // [HGM] superchess: suppress promotions to non-available piece
5393 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5394 if(WhiteOnMove(currentMove)) {
5395 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5397 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5401 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5402 move type in caller when we know the move is a legal promotion */
5403 if(moveType == NormalMove && promoChar)
5404 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5406 /* [HGM] convert drag-and-drop piece drops to standard form */
5407 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5408 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5409 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5410 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5411 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5412 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5413 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5414 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5418 /* [HGM] <popupFix> The following if has been moved here from
5419 UserMoveEvent(). Because it seemed to belong here (why not allow
5420 piece drops in training games?), and because it can only be
5421 performed after it is known to what we promote. */
5422 if (gameMode == Training) {
5423 /* compare the move played on the board to the next move in the
5424 * game. If they match, display the move and the opponent's response.
5425 * If they don't match, display an error message.
5428 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5429 CopyBoard(testBoard, boards[currentMove]);
5430 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5432 if (CompareBoards(testBoard, boards[currentMove+1])) {
5433 ForwardInner(currentMove+1);
5435 /* Autoplay the opponent's response.
5436 * if appData.animate was TRUE when Training mode was entered,
5437 * the response will be animated.
5439 saveAnimate = appData.animate;
5440 appData.animate = animateTraining;
5441 ForwardInner(currentMove+1);
5442 appData.animate = saveAnimate;
5444 /* check for the end of the game */
5445 if (currentMove >= forwardMostMove) {
5446 gameMode = PlayFromGameFile;
5448 SetTrainingModeOff();
5449 DisplayInformation(_("End of game"));
5452 DisplayError(_("Incorrect move"), 0);
5457 /* Ok, now we know that the move is good, so we can kill
5458 the previous line in Analysis Mode */
5459 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5460 forwardMostMove = currentMove;
5463 /* If we need the chess program but it's dead, restart it */
5464 ResurrectChessProgram();
5466 /* A user move restarts a paused game*/
5470 thinkOutput[0] = NULLCHAR;
5472 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5474 if (gameMode == BeginningOfGame) {
5475 if (appData.noChessProgram) {
5476 gameMode = EditGame;
5480 gameMode = MachinePlaysBlack;
5483 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5485 if (first.sendName) {
5486 sprintf(buf, "name %s\n", gameInfo.white);
5487 SendToProgram(buf, &first);
5494 /* Relay move to ICS or chess engine */
5495 if (appData.icsActive) {
5496 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5497 gameMode == IcsExamining) {
5498 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5502 if (first.sendTime && (gameMode == BeginningOfGame ||
5503 gameMode == MachinePlaysWhite ||
5504 gameMode == MachinePlaysBlack)) {
5505 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5507 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5508 // [HGM] book: if program might be playing, let it use book
5509 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5510 first.maybeThinking = TRUE;
5511 } else SendMoveToProgram(forwardMostMove-1, &first);
5512 if (currentMove == cmailOldMove + 1) {
5513 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5517 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5521 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5522 EP_UNKNOWN, castlingRights[currentMove]) ) {
5528 if (WhiteOnMove(currentMove)) {
5529 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5531 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5535 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5540 case MachinePlaysBlack:
5541 case MachinePlaysWhite:
5542 /* disable certain menu options while machine is thinking */
5543 SetMachineThinkingEnables();
5550 if(bookHit) { // [HGM] book: simulate book reply
5551 static char bookMove[MSG_SIZ]; // a bit generous?
5553 programStats.nodes = programStats.depth = programStats.time =
5554 programStats.score = programStats.got_only_move = 0;
5555 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5557 strcpy(bookMove, "move ");
5558 strcat(bookMove, bookHit);
5559 HandleMachineMove(bookMove, &first);
5565 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5566 int fromX, fromY, toX, toY;
5569 /* [HGM] This routine was added to allow calling of its two logical
5570 parts from other modules in the old way. Before, UserMoveEvent()
5571 automatically called FinishMove() if the move was OK, and returned
5572 otherwise. I separated the two, in order to make it possible to
5573 slip a promotion popup in between. But that it always needs two
5574 calls, to the first part, (now called UserMoveTest() ), and to
5575 FinishMove if the first part succeeded. Calls that do not need
5576 to do anything in between, can call this routine the old way.
5578 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5579 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5580 if(moveType == AmbiguousMove)
5581 DrawPosition(FALSE, boards[currentMove]);
5582 else if(moveType != ImpossibleMove && moveType != Comment)
5583 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5586 void LeftClick(ClickType clickType, int xPix, int yPix)
5589 Boolean saveAnimate;
5590 static int second = 0, promotionChoice = 0;
5591 char promoChoice = NULLCHAR;
5593 if (clickType == Press) ErrorPopDown();
5595 x = EventToSquare(xPix, BOARD_WIDTH);
5596 y = EventToSquare(yPix, BOARD_HEIGHT);
5597 if (!flipView && y >= 0) {
5598 y = BOARD_HEIGHT - 1 - y;
5600 if (flipView && x >= 0) {
5601 x = BOARD_WIDTH - 1 - x;
5604 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5605 if(clickType == Release) return; // ignore upclick of click-click destination
5606 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5607 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5608 if(gameInfo.holdingsWidth &&
5609 (WhiteOnMove(currentMove)
5610 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5611 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5612 // click in right holdings, for determining promotion piece
5613 ChessSquare p = boards[currentMove][y][x];
5614 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5615 if(p != EmptySquare) {
5616 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5621 DrawPosition(FALSE, boards[currentMove]);
5625 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5626 if(clickType == Press
5627 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5628 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5629 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5633 if (clickType == Press) {
5635 if (OKToStartUserMove(x, y)) {
5639 DragPieceBegin(xPix, yPix);
5640 if (appData.highlightDragging) {
5641 SetHighlights(x, y, -1, -1);
5649 if (clickType == Press && gameMode != EditPosition) {
5654 // ignore off-board to clicks
5655 if(y < 0 || x < 0) return;
5657 /* Check if clicking again on the same color piece */
5658 fromP = boards[currentMove][fromY][fromX];
5659 toP = boards[currentMove][y][x];
5660 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5661 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5662 WhitePawn <= toP && toP <= WhiteKing &&
5663 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5664 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5665 (BlackPawn <= fromP && fromP <= BlackKing &&
5666 BlackPawn <= toP && toP <= BlackKing &&
5667 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5668 !(fromP == BlackKing && toP == BlackRook && frc))) {
5669 /* Clicked again on same color piece -- changed his mind */
5670 second = (x == fromX && y == fromY);
5671 if (appData.highlightDragging) {
5672 SetHighlights(x, y, -1, -1);
5676 if (OKToStartUserMove(x, y)) {
5679 DragPieceBegin(xPix, yPix);
5683 // ignore clicks on holdings
5684 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5687 if (clickType == Release && x == fromX && y == fromY) {
5688 DragPieceEnd(xPix, yPix);
5689 if (appData.animateDragging) {
5690 /* Undo animation damage if any */
5691 DrawPosition(FALSE, NULL);
5694 /* Second up/down in same square; just abort move */
5699 ClearPremoveHighlights();
5701 /* First upclick in same square; start click-click mode */
5702 SetHighlights(x, y, -1, -1);
5707 /* we now have a different from- and (possibly off-board) to-square */
5708 /* Completed move */
5711 saveAnimate = appData.animate;
5712 if (clickType == Press) {
5713 /* Finish clickclick move */
5714 if (appData.animate || appData.highlightLastMove) {
5715 SetHighlights(fromX, fromY, toX, toY);
5720 /* Finish drag move */
5721 if (appData.highlightLastMove) {
5722 SetHighlights(fromX, fromY, toX, toY);
5726 DragPieceEnd(xPix, yPix);
5727 /* Don't animate move and drag both */
5728 appData.animate = FALSE;
5731 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5732 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5735 DrawPosition(TRUE, NULL);
5739 // off-board moves should not be highlighted
5740 if(x < 0 || x < 0) ClearHighlights();
5742 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5743 SetHighlights(fromX, fromY, toX, toY);
5744 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5745 // [HGM] super: promotion to captured piece selected from holdings
5746 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5747 promotionChoice = TRUE;
5748 // kludge follows to temporarily execute move on display, without promoting yet
5749 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5750 boards[currentMove][toY][toX] = p;
5751 DrawPosition(FALSE, boards[currentMove]);
5752 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5753 boards[currentMove][toY][toX] = q;
5754 DisplayMessage("Click in holdings to choose piece", "");
5759 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5760 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5761 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5764 appData.animate = saveAnimate;
5765 if (appData.animate || appData.animateDragging) {
5766 /* Undo animation damage if needed */
5767 DrawPosition(FALSE, NULL);
5771 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5773 // char * hint = lastHint;
5774 FrontEndProgramStats stats;
5776 stats.which = cps == &first ? 0 : 1;
5777 stats.depth = cpstats->depth;
5778 stats.nodes = cpstats->nodes;
5779 stats.score = cpstats->score;
5780 stats.time = cpstats->time;
5781 stats.pv = cpstats->movelist;
5782 stats.hint = lastHint;
5783 stats.an_move_index = 0;
5784 stats.an_move_count = 0;
5786 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5787 stats.hint = cpstats->move_name;
5788 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5789 stats.an_move_count = cpstats->nr_moves;
5792 SetProgramStats( &stats );
5795 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5796 { // [HGM] book: this routine intercepts moves to simulate book replies
5797 char *bookHit = NULL;
5799 //first determine if the incoming move brings opponent into his book
5800 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5801 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5802 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5803 if(bookHit != NULL && !cps->bookSuspend) {
5804 // make sure opponent is not going to reply after receiving move to book position
5805 SendToProgram("force\n", cps);
5806 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5808 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5809 // now arrange restart after book miss
5811 // after a book hit we never send 'go', and the code after the call to this routine
5812 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5814 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5815 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5816 SendToProgram(buf, cps);
5817 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5818 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5819 SendToProgram("go\n", cps);
5820 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5821 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5822 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5823 SendToProgram("go\n", cps);
5824 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5826 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5830 ChessProgramState *savedState;
5831 void DeferredBookMove(void)
5833 if(savedState->lastPing != savedState->lastPong)
5834 ScheduleDelayedEvent(DeferredBookMove, 10);
5836 HandleMachineMove(savedMessage, savedState);
5840 HandleMachineMove(message, cps)
5842 ChessProgramState *cps;
5844 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5845 char realname[MSG_SIZ];
5846 int fromX, fromY, toX, toY;
5853 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5855 * Kludge to ignore BEL characters
5857 while (*message == '\007') message++;
5860 * [HGM] engine debug message: ignore lines starting with '#' character
5862 if(cps->debug && *message == '#') return;
5865 * Look for book output
5867 if (cps == &first && bookRequested) {
5868 if (message[0] == '\t' || message[0] == ' ') {
5869 /* Part of the book output is here; append it */
5870 strcat(bookOutput, message);
5871 strcat(bookOutput, " \n");
5873 } else if (bookOutput[0] != NULLCHAR) {
5874 /* All of book output has arrived; display it */
5875 char *p = bookOutput;
5876 while (*p != NULLCHAR) {
5877 if (*p == '\t') *p = ' ';
5880 DisplayInformation(bookOutput);
5881 bookRequested = FALSE;
5882 /* Fall through to parse the current output */
5887 * Look for machine move.
5889 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5890 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5892 /* This method is only useful on engines that support ping */
5893 if (cps->lastPing != cps->lastPong) {
5894 if (gameMode == BeginningOfGame) {
5895 /* Extra move from before last new; ignore */
5896 if (appData.debugMode) {
5897 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5900 if (appData.debugMode) {
5901 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5902 cps->which, gameMode);
5905 SendToProgram("undo\n", cps);
5911 case BeginningOfGame:
5912 /* Extra move from before last reset; ignore */
5913 if (appData.debugMode) {
5914 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5921 /* Extra move after we tried to stop. The mode test is
5922 not a reliable way of detecting this problem, but it's
5923 the best we can do on engines that don't support ping.
5925 if (appData.debugMode) {
5926 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5927 cps->which, gameMode);
5929 SendToProgram("undo\n", cps);
5932 case MachinePlaysWhite:
5933 case IcsPlayingWhite:
5934 machineWhite = TRUE;
5937 case MachinePlaysBlack:
5938 case IcsPlayingBlack:
5939 machineWhite = FALSE;
5942 case TwoMachinesPlay:
5943 machineWhite = (cps->twoMachinesColor[0] == 'w');
5946 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5947 if (appData.debugMode) {
5949 "Ignoring move out of turn by %s, gameMode %d"
5950 ", forwardMost %d\n",
5951 cps->which, gameMode, forwardMostMove);
5956 if (appData.debugMode) { int f = forwardMostMove;
5957 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5958 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5960 if(cps->alphaRank) AlphaRank(machineMove, 4);
5961 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5962 &fromX, &fromY, &toX, &toY, &promoChar)) {
5963 /* Machine move could not be parsed; ignore it. */
5964 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5965 machineMove, cps->which);
5966 DisplayError(buf1, 0);
5967 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5968 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5969 if (gameMode == TwoMachinesPlay) {
5970 GameEnds(machineWhite ? BlackWins : WhiteWins,
5976 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5977 /* So we have to redo legality test with true e.p. status here, */
5978 /* to make sure an illegal e.p. capture does not slip through, */
5979 /* to cause a forfeit on a justified illegal-move complaint */
5980 /* of the opponent. */
5981 if( gameMode==TwoMachinesPlay && appData.testLegality
5982 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5985 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5986 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5987 fromY, fromX, toY, toX, promoChar);
5988 if (appData.debugMode) {
5990 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5991 castlingRights[forwardMostMove][i], castlingRank[i]);
5992 fprintf(debugFP, "castling rights\n");
5994 if(moveType == IllegalMove) {
5995 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5996 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5997 GameEnds(machineWhite ? BlackWins : WhiteWins,
6000 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6001 /* [HGM] Kludge to handle engines that send FRC-style castling
6002 when they shouldn't (like TSCP-Gothic) */
6004 case WhiteASideCastleFR:
6005 case BlackASideCastleFR:
6007 currentMoveString[2]++;
6009 case WhiteHSideCastleFR:
6010 case BlackHSideCastleFR:
6012 currentMoveString[2]--;
6014 default: ; // nothing to do, but suppresses warning of pedantic compilers
6017 hintRequested = FALSE;
6018 lastHint[0] = NULLCHAR;
6019 bookRequested = FALSE;
6020 /* Program may be pondering now */
6021 cps->maybeThinking = TRUE;
6022 if (cps->sendTime == 2) cps->sendTime = 1;
6023 if (cps->offeredDraw) cps->offeredDraw--;
6026 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6028 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6030 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6031 char buf[3*MSG_SIZ];
6033 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6034 programStats.score / 100.,
6036 programStats.time / 100.,
6037 (unsigned int)programStats.nodes,
6038 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6039 programStats.movelist);
6041 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6045 /* currentMoveString is set as a side-effect of ParseOneMove */
6046 strcpy(machineMove, currentMoveString);
6047 strcat(machineMove, "\n");
6048 strcpy(moveList[forwardMostMove], machineMove);
6050 /* [AS] Save move info and clear stats for next move */
6051 pvInfoList[ forwardMostMove ].score = programStats.score;
6052 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6053 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6054 ClearProgramStats();
6055 thinkOutput[0] = NULLCHAR;
6056 hiddenThinkOutputState = 0;
6058 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6060 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6061 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6064 while( count < adjudicateLossPlies ) {
6065 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6068 score = -score; /* Flip score for winning side */
6071 if( score > adjudicateLossThreshold ) {
6078 if( count >= adjudicateLossPlies ) {
6079 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6081 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6082 "Xboard adjudication",
6089 if( gameMode == TwoMachinesPlay ) {
6090 // [HGM] some adjudications useful with buggy engines
6091 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6092 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6095 if( appData.testLegality )
6096 { /* [HGM] Some more adjudications for obstinate engines */
6097 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6098 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6099 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6100 static int moveCount = 6;
6102 char *reason = NULL;
6104 /* Count what is on board. */
6105 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6106 { ChessSquare p = boards[forwardMostMove][i][j];
6110 { /* count B,N,R and other of each side */
6113 NrK++; break; // [HGM] atomic: count Kings
6117 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6118 bishopsColor |= 1 << ((i^j)&1);
6123 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6124 bishopsColor |= 1 << ((i^j)&1);
6139 PawnAdvance += m; NrPawns++;
6141 NrPieces += (p != EmptySquare);
6142 NrW += ((int)p < (int)BlackPawn);
6143 if(gameInfo.variant == VariantXiangqi &&
6144 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6145 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6146 NrW -= ((int)p < (int)BlackPawn);
6150 /* Some material-based adjudications that have to be made before stalemate test */
6151 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6152 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6153 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6154 if(appData.checkMates) {
6155 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6156 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6157 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6158 "Xboard adjudication: King destroyed", GE_XBOARD );
6163 /* Bare King in Shatranj (loses) or Losers (wins) */
6164 if( NrW == 1 || NrPieces - NrW == 1) {
6165 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6166 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6167 if(appData.checkMates) {
6168 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6169 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6170 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6171 "Xboard adjudication: Bare king", GE_XBOARD );
6175 if( gameInfo.variant == VariantShatranj && --bare < 0)
6177 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6178 if(appData.checkMates) {
6179 /* but only adjudicate if adjudication enabled */
6180 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6181 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6182 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6183 "Xboard adjudication: Bare king", GE_XBOARD );
6190 // don't wait for engine to announce game end if we can judge ourselves
6191 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6192 castlingRights[forwardMostMove]) ) {
6194 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6195 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6196 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6197 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6200 reason = "Xboard adjudication: 3rd check";
6201 epStatus[forwardMostMove] = EP_CHECKMATE;
6211 reason = "Xboard adjudication: Stalemate";
6212 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6213 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6214 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6215 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6216 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6217 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6218 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6219 EP_CHECKMATE : EP_WINS);
6220 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6221 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6225 reason = "Xboard adjudication: Checkmate";
6226 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6230 switch(i = epStatus[forwardMostMove]) {
6232 result = GameIsDrawn; break;
6234 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6236 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6238 result = (ChessMove) 0;
6240 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6241 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6242 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6243 GameEnds( result, reason, GE_XBOARD );
6247 /* Next absolutely insufficient mating material. */
6248 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6249 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6250 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6251 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6252 { /* KBK, KNK, KK of KBKB with like Bishops */
6254 /* always flag draws, for judging claims */
6255 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6257 if(appData.materialDraws) {
6258 /* but only adjudicate them if adjudication enabled */
6259 SendToProgram("force\n", cps->other); // suppress reply
6260 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6261 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6262 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6267 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6269 ( NrWR == 1 && NrBR == 1 /* KRKR */
6270 || NrWQ==1 && NrBQ==1 /* KQKQ */
6271 || NrWN==2 || NrBN==2 /* KNNK */
6272 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6274 if(--moveCount < 0 && appData.trivialDraws)
6275 { /* if the first 3 moves do not show a tactical win, declare draw */
6276 SendToProgram("force\n", cps->other); // suppress reply
6277 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6278 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6279 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6282 } else moveCount = 6;
6286 /* Check for rep-draws */
6288 for(k = forwardMostMove-2;
6289 k>=backwardMostMove && k>=forwardMostMove-100 &&
6290 epStatus[k] < EP_UNKNOWN &&
6291 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6294 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6295 /* compare castling rights */
6296 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6297 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6298 rights++; /* King lost rights, while rook still had them */
6299 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6300 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6301 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6302 rights++; /* but at least one rook lost them */
6304 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6305 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6307 if( castlingRights[forwardMostMove][5] >= 0 ) {
6308 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6309 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6312 if( rights == 0 && ++count > appData.drawRepeats-2
6313 && appData.drawRepeats > 1) {
6314 /* adjudicate after user-specified nr of repeats */
6315 SendToProgram("force\n", cps->other); // suppress reply
6316 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6317 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6318 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6319 // [HGM] xiangqi: check for forbidden perpetuals
6320 int m, ourPerpetual = 1, hisPerpetual = 1;
6321 for(m=forwardMostMove; m>k; m-=2) {
6322 if(MateTest(boards[m], PosFlags(m),
6323 EP_NONE, castlingRights[m]) != MT_CHECK)
6324 ourPerpetual = 0; // the current mover did not always check
6325 if(MateTest(boards[m-1], PosFlags(m-1),
6326 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6327 hisPerpetual = 0; // the opponent did not always check
6329 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6330 ourPerpetual, hisPerpetual);
6331 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6332 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6333 "Xboard adjudication: perpetual checking", GE_XBOARD );
6336 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6337 break; // (or we would have caught him before). Abort repetition-checking loop.
6338 // Now check for perpetual chases
6339 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6340 hisPerpetual = PerpetualChase(k, forwardMostMove);
6341 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6342 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6343 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6344 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6347 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6348 break; // Abort repetition-checking loop.
6350 // if neither of us is checking or chasing all the time, or both are, it is draw
6352 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6355 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6356 epStatus[forwardMostMove] = EP_REP_DRAW;
6360 /* Now we test for 50-move draws. Determine ply count */
6361 count = forwardMostMove;
6362 /* look for last irreversble move */
6363 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6365 /* if we hit starting position, add initial plies */
6366 if( count == backwardMostMove )
6367 count -= initialRulePlies;
6368 count = forwardMostMove - count;
6370 epStatus[forwardMostMove] = EP_RULE_DRAW;
6371 /* this is used to judge if draw claims are legal */
6372 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6373 SendToProgram("force\n", cps->other); // suppress reply
6374 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6375 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6376 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6380 /* if draw offer is pending, treat it as a draw claim
6381 * when draw condition present, to allow engines a way to
6382 * claim draws before making their move to avoid a race
6383 * condition occurring after their move
6385 if( cps->other->offeredDraw || cps->offeredDraw ) {
6387 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6388 p = "Draw claim: 50-move rule";
6389 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6390 p = "Draw claim: 3-fold repetition";
6391 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6392 p = "Draw claim: insufficient mating material";
6394 SendToProgram("force\n", cps->other); // suppress reply
6395 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6396 GameEnds( GameIsDrawn, p, GE_XBOARD );
6397 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6403 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6404 SendToProgram("force\n", cps->other); // suppress reply
6405 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6406 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6408 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6415 if (gameMode == TwoMachinesPlay) {
6416 /* [HGM] relaying draw offers moved to after reception of move */
6417 /* and interpreting offer as claim if it brings draw condition */
6418 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6419 SendToProgram("draw\n", cps->other);
6421 if (cps->other->sendTime) {
6422 SendTimeRemaining(cps->other,
6423 cps->other->twoMachinesColor[0] == 'w');
6425 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6426 if (firstMove && !bookHit) {
6428 if (cps->other->useColors) {
6429 SendToProgram(cps->other->twoMachinesColor, cps->other);
6431 SendToProgram("go\n", cps->other);
6433 cps->other->maybeThinking = TRUE;
6436 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6438 if (!pausing && appData.ringBellAfterMoves) {
6443 * Reenable menu items that were disabled while
6444 * machine was thinking
6446 if (gameMode != TwoMachinesPlay)
6447 SetUserThinkingEnables();
6449 // [HGM] book: after book hit opponent has received move and is now in force mode
6450 // force the book reply into it, and then fake that it outputted this move by jumping
6451 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6453 static char bookMove[MSG_SIZ]; // a bit generous?
6455 strcpy(bookMove, "move ");
6456 strcat(bookMove, bookHit);
6459 programStats.nodes = programStats.depth = programStats.time =
6460 programStats.score = programStats.got_only_move = 0;
6461 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6463 if(cps->lastPing != cps->lastPong) {
6464 savedMessage = message; // args for deferred call
6466 ScheduleDelayedEvent(DeferredBookMove, 10);
6475 /* Set special modes for chess engines. Later something general
6476 * could be added here; for now there is just one kludge feature,
6477 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6478 * when "xboard" is given as an interactive command.
6480 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6481 cps->useSigint = FALSE;
6482 cps->useSigterm = FALSE;
6484 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6485 ParseFeatures(message+8, cps);
6486 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6489 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6490 * want this, I was asked to put it in, and obliged.
6492 if (!strncmp(message, "setboard ", 9)) {
6493 Board initial_position; int i;
6495 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6497 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6498 DisplayError(_("Bad FEN received from engine"), 0);
6502 CopyBoard(boards[0], initial_position);
6503 initialRulePlies = FENrulePlies;
6504 epStatus[0] = FENepStatus;
6505 for( i=0; i<nrCastlingRights; i++ )
6506 castlingRights[0][i] = FENcastlingRights[i];
6507 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6508 else gameMode = MachinePlaysBlack;
6509 DrawPosition(FALSE, boards[currentMove]);
6515 * Look for communication commands
6517 if (!strncmp(message, "telluser ", 9)) {
6518 DisplayNote(message + 9);
6521 if (!strncmp(message, "tellusererror ", 14)) {
6522 DisplayError(message + 14, 0);
6525 if (!strncmp(message, "tellopponent ", 13)) {
6526 if (appData.icsActive) {
6528 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6532 DisplayNote(message + 13);
6536 if (!strncmp(message, "tellothers ", 11)) {
6537 if (appData.icsActive) {
6539 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6545 if (!strncmp(message, "tellall ", 8)) {
6546 if (appData.icsActive) {
6548 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6552 DisplayNote(message + 8);
6556 if (strncmp(message, "warning", 7) == 0) {
6557 /* Undocumented feature, use tellusererror in new code */
6558 DisplayError(message, 0);
6561 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6562 strcpy(realname, cps->tidy);
6563 strcat(realname, " query");
6564 AskQuestion(realname, buf2, buf1, cps->pr);
6567 /* Commands from the engine directly to ICS. We don't allow these to be
6568 * sent until we are logged on. Crafty kibitzes have been known to
6569 * interfere with the login process.
6572 if (!strncmp(message, "tellics ", 8)) {
6573 SendToICS(message + 8);
6577 if (!strncmp(message, "tellicsnoalias ", 15)) {
6578 SendToICS(ics_prefix);
6579 SendToICS(message + 15);
6583 /* The following are for backward compatibility only */
6584 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6585 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6586 SendToICS(ics_prefix);
6592 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6596 * If the move is illegal, cancel it and redraw the board.
6597 * Also deal with other error cases. Matching is rather loose
6598 * here to accommodate engines written before the spec.
6600 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6601 strncmp(message, "Error", 5) == 0) {
6602 if (StrStr(message, "name") ||
6603 StrStr(message, "rating") || StrStr(message, "?") ||
6604 StrStr(message, "result") || StrStr(message, "board") ||
6605 StrStr(message, "bk") || StrStr(message, "computer") ||
6606 StrStr(message, "variant") || StrStr(message, "hint") ||
6607 StrStr(message, "random") || StrStr(message, "depth") ||
6608 StrStr(message, "accepted")) {
6611 if (StrStr(message, "protover")) {
6612 /* Program is responding to input, so it's apparently done
6613 initializing, and this error message indicates it is
6614 protocol version 1. So we don't need to wait any longer
6615 for it to initialize and send feature commands. */
6616 FeatureDone(cps, 1);
6617 cps->protocolVersion = 1;
6620 cps->maybeThinking = FALSE;
6622 if (StrStr(message, "draw")) {
6623 /* Program doesn't have "draw" command */
6624 cps->sendDrawOffers = 0;
6627 if (cps->sendTime != 1 &&
6628 (StrStr(message, "time") || StrStr(message, "otim"))) {
6629 /* Program apparently doesn't have "time" or "otim" command */
6633 if (StrStr(message, "analyze")) {
6634 cps->analysisSupport = FALSE;
6635 cps->analyzing = FALSE;
6637 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6638 DisplayError(buf2, 0);
6641 if (StrStr(message, "(no matching move)st")) {
6642 /* Special kludge for GNU Chess 4 only */
6643 cps->stKludge = TRUE;
6644 SendTimeControl(cps, movesPerSession, timeControl,
6645 timeIncrement, appData.searchDepth,
6649 if (StrStr(message, "(no matching move)sd")) {
6650 /* Special kludge for GNU Chess 4 only */
6651 cps->sdKludge = TRUE;
6652 SendTimeControl(cps, movesPerSession, timeControl,
6653 timeIncrement, appData.searchDepth,
6657 if (!StrStr(message, "llegal")) {
6660 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6661 gameMode == IcsIdle) return;
6662 if (forwardMostMove <= backwardMostMove) return;
6663 if (pausing) PauseEvent();
6664 if(appData.forceIllegal) {
6665 // [HGM] illegal: machine refused move; force position after move into it
6666 SendToProgram("force\n", cps);
6667 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6668 // we have a real problem now, as SendBoard will use the a2a3 kludge
6669 // when black is to move, while there might be nothing on a2 or black
6670 // might already have the move. So send the board as if white has the move.
6671 // But first we must change the stm of the engine, as it refused the last move
6672 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6673 if(WhiteOnMove(forwardMostMove)) {
6674 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6675 SendBoard(cps, forwardMostMove); // kludgeless board
6677 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6678 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6679 SendBoard(cps, forwardMostMove+1); // kludgeless board
6681 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6682 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6683 gameMode == TwoMachinesPlay)
6684 SendToProgram("go\n", cps);
6687 if (gameMode == PlayFromGameFile) {
6688 /* Stop reading this game file */
6689 gameMode = EditGame;
6692 currentMove = forwardMostMove-1;
6693 DisplayMove(currentMove-1); /* before DisplayMoveError */
6694 SwitchClocks(forwardMostMove-1); // [HGM] race
6695 DisplayBothClocks();
6696 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6697 parseList[currentMove], cps->which);
6698 DisplayMoveError(buf1);
6699 DrawPosition(FALSE, boards[currentMove]);
6701 /* [HGM] illegal-move claim should forfeit game when Xboard */
6702 /* only passes fully legal moves */
6703 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6704 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6705 "False illegal-move claim", GE_XBOARD );
6709 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6710 /* Program has a broken "time" command that
6711 outputs a string not ending in newline.
6717 * If chess program startup fails, exit with an error message.
6718 * Attempts to recover here are futile.
6720 if ((StrStr(message, "unknown host") != NULL)
6721 || (StrStr(message, "No remote directory") != NULL)
6722 || (StrStr(message, "not found") != NULL)
6723 || (StrStr(message, "No such file") != NULL)
6724 || (StrStr(message, "can't alloc") != NULL)
6725 || (StrStr(message, "Permission denied") != NULL)) {
6727 cps->maybeThinking = FALSE;
6728 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6729 cps->which, cps->program, cps->host, message);
6730 RemoveInputSource(cps->isr);
6731 DisplayFatalError(buf1, 0, 1);
6736 * Look for hint output
6738 if (sscanf(message, "Hint: %s", buf1) == 1) {
6739 if (cps == &first && hintRequested) {
6740 hintRequested = FALSE;
6741 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6742 &fromX, &fromY, &toX, &toY, &promoChar)) {
6743 (void) CoordsToAlgebraic(boards[forwardMostMove],
6744 PosFlags(forwardMostMove), EP_UNKNOWN,
6745 fromY, fromX, toY, toX, promoChar, buf1);
6746 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6747 DisplayInformation(buf2);
6749 /* Hint move could not be parsed!? */
6750 snprintf(buf2, sizeof(buf2),
6751 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6753 DisplayError(buf2, 0);
6756 strcpy(lastHint, buf1);
6762 * Ignore other messages if game is not in progress
6764 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6765 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6768 * look for win, lose, draw, or draw offer
6770 if (strncmp(message, "1-0", 3) == 0) {
6771 char *p, *q, *r = "";
6772 p = strchr(message, '{');
6780 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6782 } else if (strncmp(message, "0-1", 3) == 0) {
6783 char *p, *q, *r = "";
6784 p = strchr(message, '{');
6792 /* Kludge for Arasan 4.1 bug */
6793 if (strcmp(r, "Black resigns") == 0) {
6794 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6797 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6799 } else if (strncmp(message, "1/2", 3) == 0) {
6800 char *p, *q, *r = "";
6801 p = strchr(message, '{');
6810 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6813 } else if (strncmp(message, "White resign", 12) == 0) {
6814 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6816 } else if (strncmp(message, "Black resign", 12) == 0) {
6817 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6819 } else if (strncmp(message, "White matches", 13) == 0 ||
6820 strncmp(message, "Black matches", 13) == 0 ) {
6821 /* [HGM] ignore GNUShogi noises */
6823 } else if (strncmp(message, "White", 5) == 0 &&
6824 message[5] != '(' &&
6825 StrStr(message, "Black") == NULL) {
6826 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6828 } else if (strncmp(message, "Black", 5) == 0 &&
6829 message[5] != '(') {
6830 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6832 } else if (strcmp(message, "resign") == 0 ||
6833 strcmp(message, "computer resigns") == 0) {
6835 case MachinePlaysBlack:
6836 case IcsPlayingBlack:
6837 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6839 case MachinePlaysWhite:
6840 case IcsPlayingWhite:
6841 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6843 case TwoMachinesPlay:
6844 if (cps->twoMachinesColor[0] == 'w')
6845 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6847 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6854 } else if (strncmp(message, "opponent mates", 14) == 0) {
6856 case MachinePlaysBlack:
6857 case IcsPlayingBlack:
6858 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6860 case MachinePlaysWhite:
6861 case IcsPlayingWhite:
6862 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6864 case TwoMachinesPlay:
6865 if (cps->twoMachinesColor[0] == 'w')
6866 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6868 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6875 } else if (strncmp(message, "computer mates", 14) == 0) {
6877 case MachinePlaysBlack:
6878 case IcsPlayingBlack:
6879 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6881 case MachinePlaysWhite:
6882 case IcsPlayingWhite:
6883 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6885 case TwoMachinesPlay:
6886 if (cps->twoMachinesColor[0] == 'w')
6887 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6889 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6896 } else if (strncmp(message, "checkmate", 9) == 0) {
6897 if (WhiteOnMove(forwardMostMove)) {
6898 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6900 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6903 } else if (strstr(message, "Draw") != NULL ||
6904 strstr(message, "game is a draw") != NULL) {
6905 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6907 } else if (strstr(message, "offer") != NULL &&
6908 strstr(message, "draw") != NULL) {
6910 if (appData.zippyPlay && first.initDone) {
6911 /* Relay offer to ICS */
6912 SendToICS(ics_prefix);
6913 SendToICS("draw\n");
6916 cps->offeredDraw = 2; /* valid until this engine moves twice */
6917 if (gameMode == TwoMachinesPlay) {
6918 if (cps->other->offeredDraw) {
6919 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6920 /* [HGM] in two-machine mode we delay relaying draw offer */
6921 /* until after we also have move, to see if it is really claim */
6923 } else if (gameMode == MachinePlaysWhite ||
6924 gameMode == MachinePlaysBlack) {
6925 if (userOfferedDraw) {
6926 DisplayInformation(_("Machine accepts your draw offer"));
6927 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6929 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6936 * Look for thinking output
6938 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6939 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6941 int plylev, mvleft, mvtot, curscore, time;
6942 char mvname[MOVE_LEN];
6946 int prefixHint = FALSE;
6947 mvname[0] = NULLCHAR;
6950 case MachinePlaysBlack:
6951 case IcsPlayingBlack:
6952 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6954 case MachinePlaysWhite:
6955 case IcsPlayingWhite:
6956 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6961 case IcsObserving: /* [DM] icsEngineAnalyze */
6962 if (!appData.icsEngineAnalyze) ignore = TRUE;
6964 case TwoMachinesPlay:
6965 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6976 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6977 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6979 if (plyext != ' ' && plyext != '\t') {
6983 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6984 if( cps->scoreIsAbsolute &&
6985 ( gameMode == MachinePlaysBlack ||
6986 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6987 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6988 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6989 !WhiteOnMove(currentMove)
6992 curscore = -curscore;
6996 programStats.depth = plylev;
6997 programStats.nodes = nodes;
6998 programStats.time = time;
6999 programStats.score = curscore;
7000 programStats.got_only_move = 0;
7002 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7005 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7006 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7007 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7008 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7009 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7010 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7011 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7012 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7015 /* Buffer overflow protection */
7016 if (buf1[0] != NULLCHAR) {
7017 if (strlen(buf1) >= sizeof(programStats.movelist)
7018 && appData.debugMode) {
7020 "PV is too long; using the first %u bytes.\n",
7021 (unsigned) sizeof(programStats.movelist) - 1);
7024 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7026 sprintf(programStats.movelist, " no PV\n");
7029 if (programStats.seen_stat) {
7030 programStats.ok_to_send = 1;
7033 if (strchr(programStats.movelist, '(') != NULL) {
7034 programStats.line_is_book = 1;
7035 programStats.nr_moves = 0;
7036 programStats.moves_left = 0;
7038 programStats.line_is_book = 0;
7041 SendProgramStatsToFrontend( cps, &programStats );
7044 [AS] Protect the thinkOutput buffer from overflow... this
7045 is only useful if buf1 hasn't overflowed first!
7047 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7049 (gameMode == TwoMachinesPlay ?
7050 ToUpper(cps->twoMachinesColor[0]) : ' '),
7051 ((double) curscore) / 100.0,
7052 prefixHint ? lastHint : "",
7053 prefixHint ? " " : "" );
7055 if( buf1[0] != NULLCHAR ) {
7056 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7058 if( strlen(buf1) > max_len ) {
7059 if( appData.debugMode) {
7060 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7062 buf1[max_len+1] = '\0';
7065 strcat( thinkOutput, buf1 );
7068 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7069 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7070 DisplayMove(currentMove - 1);
7074 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7075 /* crafty (9.25+) says "(only move) <move>"
7076 * if there is only 1 legal move
7078 sscanf(p, "(only move) %s", buf1);
7079 sprintf(thinkOutput, "%s (only move)", buf1);
7080 sprintf(programStats.movelist, "%s (only move)", buf1);
7081 programStats.depth = 1;
7082 programStats.nr_moves = 1;
7083 programStats.moves_left = 1;
7084 programStats.nodes = 1;
7085 programStats.time = 1;
7086 programStats.got_only_move = 1;
7088 /* Not really, but we also use this member to
7089 mean "line isn't going to change" (Crafty
7090 isn't searching, so stats won't change) */
7091 programStats.line_is_book = 1;
7093 SendProgramStatsToFrontend( cps, &programStats );
7095 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7096 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7097 DisplayMove(currentMove - 1);
7100 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7101 &time, &nodes, &plylev, &mvleft,
7102 &mvtot, mvname) >= 5) {
7103 /* The stat01: line is from Crafty (9.29+) in response
7104 to the "." command */
7105 programStats.seen_stat = 1;
7106 cps->maybeThinking = TRUE;
7108 if (programStats.got_only_move || !appData.periodicUpdates)
7111 programStats.depth = plylev;
7112 programStats.time = time;
7113 programStats.nodes = nodes;
7114 programStats.moves_left = mvleft;
7115 programStats.nr_moves = mvtot;
7116 strcpy(programStats.move_name, mvname);
7117 programStats.ok_to_send = 1;
7118 programStats.movelist[0] = '\0';
7120 SendProgramStatsToFrontend( cps, &programStats );
7124 } else if (strncmp(message,"++",2) == 0) {
7125 /* Crafty 9.29+ outputs this */
7126 programStats.got_fail = 2;
7129 } else if (strncmp(message,"--",2) == 0) {
7130 /* Crafty 9.29+ outputs this */
7131 programStats.got_fail = 1;
7134 } else if (thinkOutput[0] != NULLCHAR &&
7135 strncmp(message, " ", 4) == 0) {
7136 unsigned message_len;
7139 while (*p && *p == ' ') p++;
7141 message_len = strlen( p );
7143 /* [AS] Avoid buffer overflow */
7144 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7145 strcat(thinkOutput, " ");
7146 strcat(thinkOutput, p);
7149 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7150 strcat(programStats.movelist, " ");
7151 strcat(programStats.movelist, p);
7154 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7155 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7156 DisplayMove(currentMove - 1);
7164 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7165 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7167 ChessProgramStats cpstats;
7169 if (plyext != ' ' && plyext != '\t') {
7173 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7174 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7175 curscore = -curscore;
7178 cpstats.depth = plylev;
7179 cpstats.nodes = nodes;
7180 cpstats.time = time;
7181 cpstats.score = curscore;
7182 cpstats.got_only_move = 0;
7183 cpstats.movelist[0] = '\0';
7185 if (buf1[0] != NULLCHAR) {
7186 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7189 cpstats.ok_to_send = 0;
7190 cpstats.line_is_book = 0;
7191 cpstats.nr_moves = 0;
7192 cpstats.moves_left = 0;
7194 SendProgramStatsToFrontend( cps, &cpstats );
7201 /* Parse a game score from the character string "game", and
7202 record it as the history of the current game. The game
7203 score is NOT assumed to start from the standard position.
7204 The display is not updated in any way.
7207 ParseGameHistory(game)
7211 int fromX, fromY, toX, toY, boardIndex;
7216 if (appData.debugMode)
7217 fprintf(debugFP, "Parsing game history: %s\n", game);
7219 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7220 gameInfo.site = StrSave(appData.icsHost);
7221 gameInfo.date = PGNDate();
7222 gameInfo.round = StrSave("-");
7224 /* Parse out names of players */
7225 while (*game == ' ') game++;
7227 while (*game != ' ') *p++ = *game++;
7229 gameInfo.white = StrSave(buf);
7230 while (*game == ' ') game++;
7232 while (*game != ' ' && *game != '\n') *p++ = *game++;
7234 gameInfo.black = StrSave(buf);
7237 boardIndex = blackPlaysFirst ? 1 : 0;
7240 yyboardindex = boardIndex;
7241 moveType = (ChessMove) yylex();
7243 case IllegalMove: /* maybe suicide chess, etc. */
7244 if (appData.debugMode) {
7245 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7246 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7247 setbuf(debugFP, NULL);
7249 case WhitePromotionChancellor:
7250 case BlackPromotionChancellor:
7251 case WhitePromotionArchbishop:
7252 case BlackPromotionArchbishop:
7253 case WhitePromotionQueen:
7254 case BlackPromotionQueen:
7255 case WhitePromotionRook:
7256 case BlackPromotionRook:
7257 case WhitePromotionBishop:
7258 case BlackPromotionBishop:
7259 case WhitePromotionKnight:
7260 case BlackPromotionKnight:
7261 case WhitePromotionKing:
7262 case BlackPromotionKing:
7264 case WhiteCapturesEnPassant:
7265 case BlackCapturesEnPassant:
7266 case WhiteKingSideCastle:
7267 case WhiteQueenSideCastle:
7268 case BlackKingSideCastle:
7269 case BlackQueenSideCastle:
7270 case WhiteKingSideCastleWild:
7271 case WhiteQueenSideCastleWild:
7272 case BlackKingSideCastleWild:
7273 case BlackQueenSideCastleWild:
7275 case WhiteHSideCastleFR:
7276 case WhiteASideCastleFR:
7277 case BlackHSideCastleFR:
7278 case BlackASideCastleFR:
7280 fromX = currentMoveString[0] - AAA;
7281 fromY = currentMoveString[1] - ONE;
7282 toX = currentMoveString[2] - AAA;
7283 toY = currentMoveString[3] - ONE;
7284 promoChar = currentMoveString[4];
7288 fromX = moveType == WhiteDrop ?
7289 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7290 (int) CharToPiece(ToLower(currentMoveString[0]));
7292 toX = currentMoveString[2] - AAA;
7293 toY = currentMoveString[3] - ONE;
7294 promoChar = NULLCHAR;
7298 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7299 if (appData.debugMode) {
7300 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7301 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7302 setbuf(debugFP, NULL);
7304 DisplayError(buf, 0);
7306 case ImpossibleMove:
7308 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7309 if (appData.debugMode) {
7310 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7311 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7312 setbuf(debugFP, NULL);
7314 DisplayError(buf, 0);
7316 case (ChessMove) 0: /* end of file */
7317 if (boardIndex < backwardMostMove) {
7318 /* Oops, gap. How did that happen? */
7319 DisplayError(_("Gap in move list"), 0);
7322 backwardMostMove = blackPlaysFirst ? 1 : 0;
7323 if (boardIndex > forwardMostMove) {
7324 forwardMostMove = boardIndex;
7328 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7329 strcat(parseList[boardIndex-1], " ");
7330 strcat(parseList[boardIndex-1], yy_text);
7342 case GameUnfinished:
7343 if (gameMode == IcsExamining) {
7344 if (boardIndex < backwardMostMove) {
7345 /* Oops, gap. How did that happen? */
7348 backwardMostMove = blackPlaysFirst ? 1 : 0;
7351 gameInfo.result = moveType;
7352 p = strchr(yy_text, '{');
7353 if (p == NULL) p = strchr(yy_text, '(');
7356 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7358 q = strchr(p, *p == '{' ? '}' : ')');
7359 if (q != NULL) *q = NULLCHAR;
7362 gameInfo.resultDetails = StrSave(p);
7365 if (boardIndex >= forwardMostMove &&
7366 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7367 backwardMostMove = blackPlaysFirst ? 1 : 0;
7370 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7371 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7372 parseList[boardIndex]);
7373 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7374 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7375 /* currentMoveString is set as a side-effect of yylex */
7376 strcpy(moveList[boardIndex], currentMoveString);
7377 strcat(moveList[boardIndex], "\n");
7379 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7380 castlingRights[boardIndex], &epStatus[boardIndex]);
7381 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7382 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7388 if(gameInfo.variant != VariantShogi)
7389 strcat(parseList[boardIndex - 1], "+");
7393 strcat(parseList[boardIndex - 1], "#");
7400 /* Apply a move to the given board */
7402 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7403 int fromX, fromY, toX, toY;
7409 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7410 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7412 /* [HGM] compute & store e.p. status and castling rights for new position */
7413 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7416 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7420 if( board[toY][toX] != EmptySquare )
7423 if( board[fromY][fromX] == WhitePawn ) {
7424 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7427 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7428 gameInfo.variant != VariantBerolina || toX < fromX)
7429 *ep = toX | berolina;
7430 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7431 gameInfo.variant != VariantBerolina || toX > fromX)
7435 if( board[fromY][fromX] == BlackPawn ) {
7436 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7438 if( toY-fromY== -2) {
7439 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7440 gameInfo.variant != VariantBerolina || toX < fromX)
7441 *ep = toX | berolina;
7442 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7443 gameInfo.variant != VariantBerolina || toX > fromX)
7448 for(i=0; i<nrCastlingRights; i++) {
7449 if(castling[i] == fromX && castlingRank[i] == fromY ||
7450 castling[i] == toX && castlingRank[i] == toY
7451 ) castling[i] = -1; // revoke for moved or captured piece
7456 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7457 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7458 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7460 if (fromX == toX && fromY == toY) return;
7462 if (fromY == DROP_RANK) {
7464 piece = board[toY][toX] = (ChessSquare) fromX;
7466 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7467 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7468 if(gameInfo.variant == VariantKnightmate)
7469 king += (int) WhiteUnicorn - (int) WhiteKing;
7471 /* Code added by Tord: */
7472 /* FRC castling assumed when king captures friendly rook. */
7473 if (board[fromY][fromX] == WhiteKing &&
7474 board[toY][toX] == WhiteRook) {
7475 board[fromY][fromX] = EmptySquare;
7476 board[toY][toX] = EmptySquare;
7478 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7480 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7482 } else if (board[fromY][fromX] == BlackKing &&
7483 board[toY][toX] == BlackRook) {
7484 board[fromY][fromX] = EmptySquare;
7485 board[toY][toX] = EmptySquare;
7487 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7489 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7491 /* End of code added by Tord */
7493 } else if (board[fromY][fromX] == king
7494 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7495 && toY == fromY && toX > fromX+1) {
7496 board[fromY][fromX] = EmptySquare;
7497 board[toY][toX] = king;
7498 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7499 board[fromY][BOARD_RGHT-1] = EmptySquare;
7500 } else if (board[fromY][fromX] == king
7501 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7502 && toY == fromY && toX < fromX-1) {
7503 board[fromY][fromX] = EmptySquare;
7504 board[toY][toX] = king;
7505 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7506 board[fromY][BOARD_LEFT] = EmptySquare;
7507 } else if (board[fromY][fromX] == WhitePawn
7508 && toY >= BOARD_HEIGHT-promoRank
7509 && gameInfo.variant != VariantXiangqi
7511 /* white pawn promotion */
7512 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7513 if (board[toY][toX] == EmptySquare) {
7514 board[toY][toX] = WhiteQueen;
7516 if(gameInfo.variant==VariantBughouse ||
7517 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7518 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7519 board[fromY][fromX] = EmptySquare;
7520 } else if ((fromY == BOARD_HEIGHT-4)
7522 && gameInfo.variant != VariantXiangqi
7523 && gameInfo.variant != VariantBerolina
7524 && (board[fromY][fromX] == WhitePawn)
7525 && (board[toY][toX] == EmptySquare)) {
7526 board[fromY][fromX] = EmptySquare;
7527 board[toY][toX] = WhitePawn;
7528 captured = board[toY - 1][toX];
7529 board[toY - 1][toX] = EmptySquare;
7530 } else if ((fromY == BOARD_HEIGHT-4)
7532 && gameInfo.variant == VariantBerolina
7533 && (board[fromY][fromX] == WhitePawn)
7534 && (board[toY][toX] == EmptySquare)) {
7535 board[fromY][fromX] = EmptySquare;
7536 board[toY][toX] = WhitePawn;
7537 if(oldEP & EP_BEROLIN_A) {
7538 captured = board[fromY][fromX-1];
7539 board[fromY][fromX-1] = EmptySquare;
7540 }else{ captured = board[fromY][fromX+1];
7541 board[fromY][fromX+1] = EmptySquare;
7543 } else if (board[fromY][fromX] == king
7544 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7545 && toY == fromY && toX > fromX+1) {
7546 board[fromY][fromX] = EmptySquare;
7547 board[toY][toX] = king;
7548 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7549 board[fromY][BOARD_RGHT-1] = EmptySquare;
7550 } else if (board[fromY][fromX] == king
7551 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7552 && toY == fromY && toX < fromX-1) {
7553 board[fromY][fromX] = EmptySquare;
7554 board[toY][toX] = king;
7555 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7556 board[fromY][BOARD_LEFT] = EmptySquare;
7557 } else if (fromY == 7 && fromX == 3
7558 && board[fromY][fromX] == BlackKing
7559 && toY == 7 && toX == 5) {
7560 board[fromY][fromX] = EmptySquare;
7561 board[toY][toX] = BlackKing;
7562 board[fromY][7] = EmptySquare;
7563 board[toY][4] = BlackRook;
7564 } else if (fromY == 7 && fromX == 3
7565 && board[fromY][fromX] == BlackKing
7566 && toY == 7 && toX == 1) {
7567 board[fromY][fromX] = EmptySquare;
7568 board[toY][toX] = BlackKing;
7569 board[fromY][0] = EmptySquare;
7570 board[toY][2] = BlackRook;
7571 } else if (board[fromY][fromX] == BlackPawn
7573 && gameInfo.variant != VariantXiangqi
7575 /* black pawn promotion */
7576 board[toY][toX] = CharToPiece(ToLower(promoChar));
7577 if (board[toY][toX] == EmptySquare) {
7578 board[toY][toX] = BlackQueen;
7580 if(gameInfo.variant==VariantBughouse ||
7581 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7582 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7583 board[fromY][fromX] = EmptySquare;
7584 } else if ((fromY == 3)
7586 && gameInfo.variant != VariantXiangqi
7587 && gameInfo.variant != VariantBerolina
7588 && (board[fromY][fromX] == BlackPawn)
7589 && (board[toY][toX] == EmptySquare)) {
7590 board[fromY][fromX] = EmptySquare;
7591 board[toY][toX] = BlackPawn;
7592 captured = board[toY + 1][toX];
7593 board[toY + 1][toX] = EmptySquare;
7594 } else if ((fromY == 3)
7596 && gameInfo.variant == VariantBerolina
7597 && (board[fromY][fromX] == BlackPawn)
7598 && (board[toY][toX] == EmptySquare)) {
7599 board[fromY][fromX] = EmptySquare;
7600 board[toY][toX] = BlackPawn;
7601 if(oldEP & EP_BEROLIN_A) {
7602 captured = board[fromY][fromX-1];
7603 board[fromY][fromX-1] = EmptySquare;
7604 }else{ captured = board[fromY][fromX+1];
7605 board[fromY][fromX+1] = EmptySquare;
7608 board[toY][toX] = board[fromY][fromX];
7609 board[fromY][fromX] = EmptySquare;
7612 /* [HGM] now we promote for Shogi, if needed */
7613 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7614 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7617 if (gameInfo.holdingsWidth != 0) {
7619 /* !!A lot more code needs to be written to support holdings */
7620 /* [HGM] OK, so I have written it. Holdings are stored in the */
7621 /* penultimate board files, so they are automaticlly stored */
7622 /* in the game history. */
7623 if (fromY == DROP_RANK) {
7624 /* Delete from holdings, by decreasing count */
7625 /* and erasing image if necessary */
7627 if(p < (int) BlackPawn) { /* white drop */
7628 p -= (int)WhitePawn;
7629 p = PieceToNumber((ChessSquare)p);
7630 if(p >= gameInfo.holdingsSize) p = 0;
7631 if(--board[p][BOARD_WIDTH-2] <= 0)
7632 board[p][BOARD_WIDTH-1] = EmptySquare;
7633 if((int)board[p][BOARD_WIDTH-2] < 0)
7634 board[p][BOARD_WIDTH-2] = 0;
7635 } else { /* black drop */
7636 p -= (int)BlackPawn;
7637 p = PieceToNumber((ChessSquare)p);
7638 if(p >= gameInfo.holdingsSize) p = 0;
7639 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7640 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7641 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7642 board[BOARD_HEIGHT-1-p][1] = 0;
7645 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7646 && gameInfo.variant != VariantBughouse ) {
7647 /* [HGM] holdings: Add to holdings, if holdings exist */
7648 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7649 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7650 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7653 if (p >= (int) BlackPawn) {
7654 p -= (int)BlackPawn;
7655 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7656 /* in Shogi restore piece to its original first */
7657 captured = (ChessSquare) (DEMOTED captured);
7660 p = PieceToNumber((ChessSquare)p);
7661 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7662 board[p][BOARD_WIDTH-2]++;
7663 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7665 p -= (int)WhitePawn;
7666 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7667 captured = (ChessSquare) (DEMOTED captured);
7670 p = PieceToNumber((ChessSquare)p);
7671 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7672 board[BOARD_HEIGHT-1-p][1]++;
7673 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7676 } else if (gameInfo.variant == VariantAtomic) {
7677 if (captured != EmptySquare) {
7679 for (y = toY-1; y <= toY+1; y++) {
7680 for (x = toX-1; x <= toX+1; x++) {
7681 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7682 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7683 board[y][x] = EmptySquare;
7687 board[toY][toX] = EmptySquare;
7690 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7691 /* [HGM] Shogi promotions */
7692 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7695 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7696 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7697 // [HGM] superchess: take promotion piece out of holdings
7698 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7699 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7700 if(!--board[k][BOARD_WIDTH-2])
7701 board[k][BOARD_WIDTH-1] = EmptySquare;
7703 if(!--board[BOARD_HEIGHT-1-k][1])
7704 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7710 /* Updates forwardMostMove */
7712 MakeMove(fromX, fromY, toX, toY, promoChar)
7713 int fromX, fromY, toX, toY;
7716 // forwardMostMove++; // [HGM] bare: moved downstream
7718 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7719 int timeLeft; static int lastLoadFlag=0; int king, piece;
7720 piece = boards[forwardMostMove][fromY][fromX];
7721 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7722 if(gameInfo.variant == VariantKnightmate)
7723 king += (int) WhiteUnicorn - (int) WhiteKing;
7724 if(forwardMostMove == 0) {
7726 fprintf(serverMoves, "%s;", second.tidy);
7727 fprintf(serverMoves, "%s;", first.tidy);
7728 if(!blackPlaysFirst)
7729 fprintf(serverMoves, "%s;", second.tidy);
7730 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7731 lastLoadFlag = loadFlag;
7733 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7734 // print castling suffix
7735 if( toY == fromY && piece == king ) {
7737 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7739 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7742 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7743 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7744 boards[forwardMostMove][toY][toX] == EmptySquare
7746 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7748 if(promoChar != NULLCHAR)
7749 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7751 fprintf(serverMoves, "/%d/%d",
7752 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7753 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7754 else timeLeft = blackTimeRemaining/1000;
7755 fprintf(serverMoves, "/%d", timeLeft);
7757 fflush(serverMoves);
7760 if (forwardMostMove+1 >= MAX_MOVES) {
7761 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7765 if (commentList[forwardMostMove+1] != NULL) {
7766 free(commentList[forwardMostMove+1]);
7767 commentList[forwardMostMove+1] = NULL;
7769 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7770 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7771 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7772 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7773 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7774 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
7775 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7776 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7777 gameInfo.result = GameUnfinished;
7778 if (gameInfo.resultDetails != NULL) {
7779 free(gameInfo.resultDetails);
7780 gameInfo.resultDetails = NULL;
7782 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7783 moveList[forwardMostMove - 1]);
7784 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7785 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7786 fromY, fromX, toY, toX, promoChar,
7787 parseList[forwardMostMove - 1]);
7788 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7789 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7790 castlingRights[forwardMostMove]) ) {
7796 if(gameInfo.variant != VariantShogi)
7797 strcat(parseList[forwardMostMove - 1], "+");
7801 strcat(parseList[forwardMostMove - 1], "#");
7804 if (appData.debugMode) {
7805 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7810 /* Updates currentMove if not pausing */
7812 ShowMove(fromX, fromY, toX, toY)
7814 int instant = (gameMode == PlayFromGameFile) ?
7815 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7816 if(appData.noGUI) return;
7817 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7819 if (forwardMostMove == currentMove + 1) {
7820 AnimateMove(boards[forwardMostMove - 1],
7821 fromX, fromY, toX, toY);
7823 if (appData.highlightLastMove) {
7824 SetHighlights(fromX, fromY, toX, toY);
7827 currentMove = forwardMostMove;
7830 if (instant) return;
7832 DisplayMove(currentMove - 1);
7833 DrawPosition(FALSE, boards[currentMove]);
7834 DisplayBothClocks();
7835 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7838 void SendEgtPath(ChessProgramState *cps)
7839 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7840 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7842 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7845 char c, *q = name+1, *r, *s;
7847 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7848 while(*p && *p != ',') *q++ = *p++;
7850 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7851 strcmp(name, ",nalimov:") == 0 ) {
7852 // take nalimov path from the menu-changeable option first, if it is defined
7853 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7854 SendToProgram(buf,cps); // send egtbpath command for nalimov
7856 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7857 (s = StrStr(appData.egtFormats, name)) != NULL) {
7858 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7859 s = r = StrStr(s, ":") + 1; // beginning of path info
7860 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7861 c = *r; *r = 0; // temporarily null-terminate path info
7862 *--q = 0; // strip of trailig ':' from name
7863 sprintf(buf, "egtpath %s %s\n", name+1, s);
7865 SendToProgram(buf,cps); // send egtbpath command for this format
7867 if(*p == ',') p++; // read away comma to position for next format name
7872 InitChessProgram(cps, setup)
7873 ChessProgramState *cps;
7874 int setup; /* [HGM] needed to setup FRC opening position */
7876 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7877 if (appData.noChessProgram) return;
7878 hintRequested = FALSE;
7879 bookRequested = FALSE;
7881 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7882 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7883 if(cps->memSize) { /* [HGM] memory */
7884 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7885 SendToProgram(buf, cps);
7887 SendEgtPath(cps); /* [HGM] EGT */
7888 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7889 sprintf(buf, "cores %d\n", appData.smpCores);
7890 SendToProgram(buf, cps);
7893 SendToProgram(cps->initString, cps);
7894 if (gameInfo.variant != VariantNormal &&
7895 gameInfo.variant != VariantLoadable
7896 /* [HGM] also send variant if board size non-standard */
7897 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7899 char *v = VariantName(gameInfo.variant);
7900 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7901 /* [HGM] in protocol 1 we have to assume all variants valid */
7902 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7903 DisplayFatalError(buf, 0, 1);
7907 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7908 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7909 if( gameInfo.variant == VariantXiangqi )
7910 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7911 if( gameInfo.variant == VariantShogi )
7912 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7913 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7914 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7915 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7916 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7917 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7918 if( gameInfo.variant == VariantCourier )
7919 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7920 if( gameInfo.variant == VariantSuper )
7921 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7922 if( gameInfo.variant == VariantGreat )
7923 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7926 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7927 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7928 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7929 if(StrStr(cps->variants, b) == NULL) {
7930 // specific sized variant not known, check if general sizing allowed
7931 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7932 if(StrStr(cps->variants, "boardsize") == NULL) {
7933 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7934 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7935 DisplayFatalError(buf, 0, 1);
7938 /* [HGM] here we really should compare with the maximum supported board size */
7941 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7942 sprintf(buf, "variant %s\n", b);
7943 SendToProgram(buf, cps);
7945 currentlyInitializedVariant = gameInfo.variant;
7947 /* [HGM] send opening position in FRC to first engine */
7949 SendToProgram("force\n", cps);
7951 /* engine is now in force mode! Set flag to wake it up after first move. */
7952 setboardSpoiledMachineBlack = 1;
7956 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7957 SendToProgram(buf, cps);
7959 cps->maybeThinking = FALSE;
7960 cps->offeredDraw = 0;
7961 if (!appData.icsActive) {
7962 SendTimeControl(cps, movesPerSession, timeControl,
7963 timeIncrement, appData.searchDepth,
7966 if (appData.showThinking
7967 // [HGM] thinking: four options require thinking output to be sent
7968 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7970 SendToProgram("post\n", cps);
7972 SendToProgram("hard\n", cps);
7973 if (!appData.ponderNextMove) {
7974 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7975 it without being sure what state we are in first. "hard"
7976 is not a toggle, so that one is OK.
7978 SendToProgram("easy\n", cps);
7981 sprintf(buf, "ping %d\n", ++cps->lastPing);
7982 SendToProgram(buf, cps);
7984 cps->initDone = TRUE;
7989 StartChessProgram(cps)
7990 ChessProgramState *cps;
7995 if (appData.noChessProgram) return;
7996 cps->initDone = FALSE;
7998 if (strcmp(cps->host, "localhost") == 0) {
7999 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8000 } else if (*appData.remoteShell == NULLCHAR) {
8001 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8003 if (*appData.remoteUser == NULLCHAR) {
8004 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8007 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8008 cps->host, appData.remoteUser, cps->program);
8010 err = StartChildProcess(buf, "", &cps->pr);
8014 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8015 DisplayFatalError(buf, err, 1);
8021 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8022 if (cps->protocolVersion > 1) {
8023 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8024 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8025 cps->comboCnt = 0; // and values of combo boxes
8026 SendToProgram(buf, cps);
8028 SendToProgram("xboard\n", cps);
8034 TwoMachinesEventIfReady P((void))
8036 if (first.lastPing != first.lastPong) {
8037 DisplayMessage("", _("Waiting for first chess program"));
8038 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8041 if (second.lastPing != second.lastPong) {
8042 DisplayMessage("", _("Waiting for second chess program"));
8043 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8051 NextMatchGame P((void))
8053 int index; /* [HGM] autoinc: step load index during match */
8055 if (*appData.loadGameFile != NULLCHAR) {
8056 index = appData.loadGameIndex;
8057 if(index < 0) { // [HGM] autoinc
8058 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8059 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8061 LoadGameFromFile(appData.loadGameFile,
8063 appData.loadGameFile, FALSE);
8064 } else if (*appData.loadPositionFile != NULLCHAR) {
8065 index = appData.loadPositionIndex;
8066 if(index < 0) { // [HGM] autoinc
8067 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8068 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8070 LoadPositionFromFile(appData.loadPositionFile,
8072 appData.loadPositionFile);
8074 TwoMachinesEventIfReady();
8077 void UserAdjudicationEvent( int result )
8079 ChessMove gameResult = GameIsDrawn;
8082 gameResult = WhiteWins;
8084 else if( result < 0 ) {
8085 gameResult = BlackWins;
8088 if( gameMode == TwoMachinesPlay ) {
8089 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8094 // [HGM] save: calculate checksum of game to make games easily identifiable
8095 int StringCheckSum(char *s)
8098 if(s==NULL) return 0;
8099 while(*s) i = i*259 + *s++;
8106 for(i=backwardMostMove; i<forwardMostMove; i++) {
8107 sum += pvInfoList[i].depth;
8108 sum += StringCheckSum(parseList[i]);
8109 sum += StringCheckSum(commentList[i]);
8112 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8113 return sum + StringCheckSum(commentList[i]);
8114 } // end of save patch
8117 GameEnds(result, resultDetails, whosays)
8119 char *resultDetails;
8122 GameMode nextGameMode;
8126 if(endingGame) return; /* [HGM] crash: forbid recursion */
8129 if (appData.debugMode) {
8130 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8131 result, resultDetails ? resultDetails : "(null)", whosays);
8134 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8136 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8137 /* If we are playing on ICS, the server decides when the
8138 game is over, but the engine can offer to draw, claim
8142 if (appData.zippyPlay && first.initDone) {
8143 if (result == GameIsDrawn) {
8144 /* In case draw still needs to be claimed */
8145 SendToICS(ics_prefix);
8146 SendToICS("draw\n");
8147 } else if (StrCaseStr(resultDetails, "resign")) {
8148 SendToICS(ics_prefix);
8149 SendToICS("resign\n");
8153 endingGame = 0; /* [HGM] crash */
8157 /* If we're loading the game from a file, stop */
8158 if (whosays == GE_FILE) {
8159 (void) StopLoadGameTimer();
8163 /* Cancel draw offers */
8164 first.offeredDraw = second.offeredDraw = 0;
8166 /* If this is an ICS game, only ICS can really say it's done;
8167 if not, anyone can. */
8168 isIcsGame = (gameMode == IcsPlayingWhite ||
8169 gameMode == IcsPlayingBlack ||
8170 gameMode == IcsObserving ||
8171 gameMode == IcsExamining);
8173 if (!isIcsGame || whosays == GE_ICS) {
8174 /* OK -- not an ICS game, or ICS said it was done */
8176 if (!isIcsGame && !appData.noChessProgram)
8177 SetUserThinkingEnables();
8179 /* [HGM] if a machine claims the game end we verify this claim */
8180 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8181 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8183 ChessMove trueResult = (ChessMove) -1;
8185 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8186 first.twoMachinesColor[0] :
8187 second.twoMachinesColor[0] ;
8189 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8190 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8191 /* [HGM] verify: engine mate claims accepted if they were flagged */
8192 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8194 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8195 /* [HGM] verify: engine mate claims accepted if they were flagged */
8196 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8198 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8199 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8202 // now verify win claims, but not in drop games, as we don't understand those yet
8203 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8204 || gameInfo.variant == VariantGreat) &&
8205 (result == WhiteWins && claimer == 'w' ||
8206 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8207 if (appData.debugMode) {
8208 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8209 result, epStatus[forwardMostMove], forwardMostMove);
8211 if(result != trueResult) {
8212 sprintf(buf, "False win claim: '%s'", resultDetails);
8213 result = claimer == 'w' ? BlackWins : WhiteWins;
8214 resultDetails = buf;
8217 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8218 && (forwardMostMove <= backwardMostMove ||
8219 epStatus[forwardMostMove-1] > EP_DRAWS ||
8220 (claimer=='b')==(forwardMostMove&1))
8222 /* [HGM] verify: draws that were not flagged are false claims */
8223 sprintf(buf, "False draw claim: '%s'", resultDetails);
8224 result = claimer == 'w' ? BlackWins : WhiteWins;
8225 resultDetails = buf;
8227 /* (Claiming a loss is accepted no questions asked!) */
8229 /* [HGM] bare: don't allow bare King to win */
8230 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8231 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8232 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8233 && result != GameIsDrawn)
8234 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8235 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8236 int p = (int)boards[forwardMostMove][i][j] - color;
8237 if(p >= 0 && p <= (int)WhiteKing) k++;
8239 if (appData.debugMode) {
8240 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8241 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8244 result = GameIsDrawn;
8245 sprintf(buf, "%s but bare king", resultDetails);
8246 resultDetails = buf;
8252 if(serverMoves != NULL && !loadFlag) { char c = '=';
8253 if(result==WhiteWins) c = '+';
8254 if(result==BlackWins) c = '-';
8255 if(resultDetails != NULL)
8256 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8258 if (resultDetails != NULL) {
8259 gameInfo.result = result;
8260 gameInfo.resultDetails = StrSave(resultDetails);
8262 /* display last move only if game was not loaded from file */
8263 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8264 DisplayMove(currentMove - 1);
8266 if (forwardMostMove != 0) {
8267 if (gameMode != PlayFromGameFile && gameMode != EditGame
8268 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8270 if (*appData.saveGameFile != NULLCHAR) {
8271 SaveGameToFile(appData.saveGameFile, TRUE);
8272 } else if (appData.autoSaveGames) {
8275 if (*appData.savePositionFile != NULLCHAR) {
8276 SavePositionToFile(appData.savePositionFile);
8281 /* Tell program how game ended in case it is learning */
8282 /* [HGM] Moved this to after saving the PGN, just in case */
8283 /* engine died and we got here through time loss. In that */
8284 /* case we will get a fatal error writing the pipe, which */
8285 /* would otherwise lose us the PGN. */
8286 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8287 /* output during GameEnds should never be fatal anymore */
8288 if (gameMode == MachinePlaysWhite ||
8289 gameMode == MachinePlaysBlack ||
8290 gameMode == TwoMachinesPlay ||
8291 gameMode == IcsPlayingWhite ||
8292 gameMode == IcsPlayingBlack ||
8293 gameMode == BeginningOfGame) {
8295 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8297 if (first.pr != NoProc) {
8298 SendToProgram(buf, &first);
8300 if (second.pr != NoProc &&
8301 gameMode == TwoMachinesPlay) {
8302 SendToProgram(buf, &second);
8307 if (appData.icsActive) {
8308 if (appData.quietPlay &&
8309 (gameMode == IcsPlayingWhite ||
8310 gameMode == IcsPlayingBlack)) {
8311 SendToICS(ics_prefix);
8312 SendToICS("set shout 1\n");
8314 nextGameMode = IcsIdle;
8315 ics_user_moved = FALSE;
8316 /* clean up premove. It's ugly when the game has ended and the
8317 * premove highlights are still on the board.
8321 ClearPremoveHighlights();
8322 DrawPosition(FALSE, boards[currentMove]);
8324 if (whosays == GE_ICS) {
8327 if (gameMode == IcsPlayingWhite)
8329 else if(gameMode == IcsPlayingBlack)
8333 if (gameMode == IcsPlayingBlack)
8335 else if(gameMode == IcsPlayingWhite)
8342 PlayIcsUnfinishedSound();
8345 } else if (gameMode == EditGame ||
8346 gameMode == PlayFromGameFile ||
8347 gameMode == AnalyzeMode ||
8348 gameMode == AnalyzeFile) {
8349 nextGameMode = gameMode;
8351 nextGameMode = EndOfGame;
8356 nextGameMode = gameMode;
8359 if (appData.noChessProgram) {
8360 gameMode = nextGameMode;
8362 endingGame = 0; /* [HGM] crash */
8367 /* Put first chess program into idle state */
8368 if (first.pr != NoProc &&
8369 (gameMode == MachinePlaysWhite ||
8370 gameMode == MachinePlaysBlack ||
8371 gameMode == TwoMachinesPlay ||
8372 gameMode == IcsPlayingWhite ||
8373 gameMode == IcsPlayingBlack ||
8374 gameMode == BeginningOfGame)) {
8375 SendToProgram("force\n", &first);
8376 if (first.usePing) {
8378 sprintf(buf, "ping %d\n", ++first.lastPing);
8379 SendToProgram(buf, &first);
8382 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8383 /* Kill off first chess program */
8384 if (first.isr != NULL)
8385 RemoveInputSource(first.isr);
8388 if (first.pr != NoProc) {
8390 DoSleep( appData.delayBeforeQuit );
8391 SendToProgram("quit\n", &first);
8392 DoSleep( appData.delayAfterQuit );
8393 DestroyChildProcess(first.pr, first.useSigterm);
8398 /* Put second chess program into idle state */
8399 if (second.pr != NoProc &&
8400 gameMode == TwoMachinesPlay) {
8401 SendToProgram("force\n", &second);
8402 if (second.usePing) {
8404 sprintf(buf, "ping %d\n", ++second.lastPing);
8405 SendToProgram(buf, &second);
8408 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8409 /* Kill off second chess program */
8410 if (second.isr != NULL)
8411 RemoveInputSource(second.isr);
8414 if (second.pr != NoProc) {
8415 DoSleep( appData.delayBeforeQuit );
8416 SendToProgram("quit\n", &second);
8417 DoSleep( appData.delayAfterQuit );
8418 DestroyChildProcess(second.pr, second.useSigterm);
8423 if (matchMode && gameMode == TwoMachinesPlay) {
8426 if (first.twoMachinesColor[0] == 'w') {
8433 if (first.twoMachinesColor[0] == 'b') {
8442 if (matchGame < appData.matchGames) {
8444 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8445 tmp = first.twoMachinesColor;
8446 first.twoMachinesColor = second.twoMachinesColor;
8447 second.twoMachinesColor = tmp;
8449 gameMode = nextGameMode;
8451 if(appData.matchPause>10000 || appData.matchPause<10)
8452 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8453 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8454 endingGame = 0; /* [HGM] crash */
8458 gameMode = nextGameMode;
8459 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8460 first.tidy, second.tidy,
8461 first.matchWins, second.matchWins,
8462 appData.matchGames - (first.matchWins + second.matchWins));
8463 DisplayFatalError(buf, 0, 0);
8466 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8467 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8469 gameMode = nextGameMode;
8471 endingGame = 0; /* [HGM] crash */
8474 /* Assumes program was just initialized (initString sent).
8475 Leaves program in force mode. */
8477 FeedMovesToProgram(cps, upto)
8478 ChessProgramState *cps;
8483 if (appData.debugMode)
8484 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8485 startedFromSetupPosition ? "position and " : "",
8486 backwardMostMove, upto, cps->which);
8487 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8488 // [HGM] variantswitch: make engine aware of new variant
8489 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8490 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8491 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8492 SendToProgram(buf, cps);
8493 currentlyInitializedVariant = gameInfo.variant;
8495 SendToProgram("force\n", cps);
8496 if (startedFromSetupPosition) {
8497 SendBoard(cps, backwardMostMove);
8498 if (appData.debugMode) {
8499 fprintf(debugFP, "feedMoves\n");
8502 for (i = backwardMostMove; i < upto; i++) {
8503 SendMoveToProgram(i, cps);
8509 ResurrectChessProgram()
8511 /* The chess program may have exited.
8512 If so, restart it and feed it all the moves made so far. */
8514 if (appData.noChessProgram || first.pr != NoProc) return;
8516 StartChessProgram(&first);
8517 InitChessProgram(&first, FALSE);
8518 FeedMovesToProgram(&first, currentMove);
8520 if (!first.sendTime) {
8521 /* can't tell gnuchess what its clock should read,
8522 so we bow to its notion. */
8524 timeRemaining[0][currentMove] = whiteTimeRemaining;
8525 timeRemaining[1][currentMove] = blackTimeRemaining;
8528 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8529 appData.icsEngineAnalyze) && first.analysisSupport) {
8530 SendToProgram("analyze\n", &first);
8531 first.analyzing = TRUE;
8544 if (appData.debugMode) {
8545 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8546 redraw, init, gameMode);
8548 pausing = pauseExamInvalid = FALSE;
8549 startedFromSetupPosition = blackPlaysFirst = FALSE;
8551 whiteFlag = blackFlag = FALSE;
8552 userOfferedDraw = FALSE;
8553 hintRequested = bookRequested = FALSE;
8554 first.maybeThinking = FALSE;
8555 second.maybeThinking = FALSE;
8556 first.bookSuspend = FALSE; // [HGM] book
8557 second.bookSuspend = FALSE;
8558 thinkOutput[0] = NULLCHAR;
8559 lastHint[0] = NULLCHAR;
8560 ClearGameInfo(&gameInfo);
8561 gameInfo.variant = StringToVariant(appData.variant);
8562 ics_user_moved = ics_clock_paused = FALSE;
8563 ics_getting_history = H_FALSE;
8565 white_holding[0] = black_holding[0] = NULLCHAR;
8566 ClearProgramStats();
8567 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8571 flipView = appData.flipView;
8572 ClearPremoveHighlights();
8574 alarmSounded = FALSE;
8576 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8577 if(appData.serverMovesName != NULL) {
8578 /* [HGM] prepare to make moves file for broadcasting */
8579 clock_t t = clock();
8580 if(serverMoves != NULL) fclose(serverMoves);
8581 serverMoves = fopen(appData.serverMovesName, "r");
8582 if(serverMoves != NULL) {
8583 fclose(serverMoves);
8584 /* delay 15 sec before overwriting, so all clients can see end */
8585 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8587 serverMoves = fopen(appData.serverMovesName, "w");
8591 gameMode = BeginningOfGame;
8593 if(appData.icsActive) gameInfo.variant = VariantNormal;
8594 currentMove = forwardMostMove = backwardMostMove = 0;
8595 InitPosition(redraw);
8596 for (i = 0; i < MAX_MOVES; i++) {
8597 if (commentList[i] != NULL) {
8598 free(commentList[i]);
8599 commentList[i] = NULL;
8603 timeRemaining[0][0] = whiteTimeRemaining;
8604 timeRemaining[1][0] = blackTimeRemaining;
8605 if (first.pr == NULL) {
8606 StartChessProgram(&first);
8609 InitChessProgram(&first, startedFromSetupPosition);
8612 DisplayMessage("", "");
8613 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8614 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8621 if (!AutoPlayOneMove())
8623 if (matchMode || appData.timeDelay == 0)
8625 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8627 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8636 int fromX, fromY, toX, toY;
8638 if (appData.debugMode) {
8639 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8642 if (gameMode != PlayFromGameFile)
8645 if (currentMove >= forwardMostMove) {
8646 gameMode = EditGame;
8649 /* [AS] Clear current move marker at the end of a game */
8650 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8655 toX = moveList[currentMove][2] - AAA;
8656 toY = moveList[currentMove][3] - ONE;
8658 if (moveList[currentMove][1] == '@') {
8659 if (appData.highlightLastMove) {
8660 SetHighlights(-1, -1, toX, toY);
8663 fromX = moveList[currentMove][0] - AAA;
8664 fromY = moveList[currentMove][1] - ONE;
8666 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8668 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8670 if (appData.highlightLastMove) {
8671 SetHighlights(fromX, fromY, toX, toY);
8674 DisplayMove(currentMove);
8675 SendMoveToProgram(currentMove++, &first);
8676 DisplayBothClocks();
8677 DrawPosition(FALSE, boards[currentMove]);
8678 // [HGM] PV info: always display, routine tests if empty
8679 DisplayComment(currentMove - 1, commentList[currentMove]);
8685 LoadGameOneMove(readAhead)
8686 ChessMove readAhead;
8688 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8689 char promoChar = NULLCHAR;
8694 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8695 gameMode != AnalyzeMode && gameMode != Training) {
8700 yyboardindex = forwardMostMove;
8701 if (readAhead != (ChessMove)0) {
8702 moveType = readAhead;
8704 if (gameFileFP == NULL)
8706 moveType = (ChessMove) yylex();
8712 if (appData.debugMode)
8713 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8715 if (*p == '{' || *p == '[' || *p == '(') {
8716 p[strlen(p) - 1] = NULLCHAR;
8720 /* append the comment but don't display it */
8721 while (*p == '\n') p++;
8722 AppendComment(currentMove, p);
8725 case WhiteCapturesEnPassant:
8726 case BlackCapturesEnPassant:
8727 case WhitePromotionChancellor:
8728 case BlackPromotionChancellor:
8729 case WhitePromotionArchbishop:
8730 case BlackPromotionArchbishop:
8731 case WhitePromotionCentaur:
8732 case BlackPromotionCentaur:
8733 case WhitePromotionQueen:
8734 case BlackPromotionQueen:
8735 case WhitePromotionRook:
8736 case BlackPromotionRook:
8737 case WhitePromotionBishop:
8738 case BlackPromotionBishop:
8739 case WhitePromotionKnight:
8740 case BlackPromotionKnight:
8741 case WhitePromotionKing:
8742 case BlackPromotionKing:
8744 case WhiteKingSideCastle:
8745 case WhiteQueenSideCastle:
8746 case BlackKingSideCastle:
8747 case BlackQueenSideCastle:
8748 case WhiteKingSideCastleWild:
8749 case WhiteQueenSideCastleWild:
8750 case BlackKingSideCastleWild:
8751 case BlackQueenSideCastleWild:
8753 case WhiteHSideCastleFR:
8754 case WhiteASideCastleFR:
8755 case BlackHSideCastleFR:
8756 case BlackASideCastleFR:
8758 if (appData.debugMode)
8759 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8760 fromX = currentMoveString[0] - AAA;
8761 fromY = currentMoveString[1] - ONE;
8762 toX = currentMoveString[2] - AAA;
8763 toY = currentMoveString[3] - ONE;
8764 promoChar = currentMoveString[4];
8769 if (appData.debugMode)
8770 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8771 fromX = moveType == WhiteDrop ?
8772 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8773 (int) CharToPiece(ToLower(currentMoveString[0]));
8775 toX = currentMoveString[2] - AAA;
8776 toY = currentMoveString[3] - ONE;
8782 case GameUnfinished:
8783 if (appData.debugMode)
8784 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8785 p = strchr(yy_text, '{');
8786 if (p == NULL) p = strchr(yy_text, '(');
8789 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8791 q = strchr(p, *p == '{' ? '}' : ')');
8792 if (q != NULL) *q = NULLCHAR;
8795 GameEnds(moveType, p, GE_FILE);
8797 if (cmailMsgLoaded) {
8799 flipView = WhiteOnMove(currentMove);
8800 if (moveType == GameUnfinished) flipView = !flipView;
8801 if (appData.debugMode)
8802 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8806 case (ChessMove) 0: /* end of file */
8807 if (appData.debugMode)
8808 fprintf(debugFP, "Parser hit end of file\n");
8809 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8810 EP_UNKNOWN, castlingRights[currentMove]) ) {
8816 if (WhiteOnMove(currentMove)) {
8817 GameEnds(BlackWins, "Black mates", GE_FILE);
8819 GameEnds(WhiteWins, "White mates", GE_FILE);
8823 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8830 if (lastLoadGameStart == GNUChessGame) {
8831 /* GNUChessGames have numbers, but they aren't move numbers */
8832 if (appData.debugMode)
8833 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8834 yy_text, (int) moveType);
8835 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8837 /* else fall thru */
8842 /* Reached start of next game in file */
8843 if (appData.debugMode)
8844 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8845 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8846 EP_UNKNOWN, castlingRights[currentMove]) ) {
8852 if (WhiteOnMove(currentMove)) {
8853 GameEnds(BlackWins, "Black mates", GE_FILE);
8855 GameEnds(WhiteWins, "White mates", GE_FILE);
8859 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8865 case PositionDiagram: /* should not happen; ignore */
8866 case ElapsedTime: /* ignore */
8867 case NAG: /* ignore */
8868 if (appData.debugMode)
8869 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8870 yy_text, (int) moveType);
8871 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8874 if (appData.testLegality) {
8875 if (appData.debugMode)
8876 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8877 sprintf(move, _("Illegal move: %d.%s%s"),
8878 (forwardMostMove / 2) + 1,
8879 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8880 DisplayError(move, 0);
8883 if (appData.debugMode)
8884 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8885 yy_text, currentMoveString);
8886 fromX = currentMoveString[0] - AAA;
8887 fromY = currentMoveString[1] - ONE;
8888 toX = currentMoveString[2] - AAA;
8889 toY = currentMoveString[3] - ONE;
8890 promoChar = currentMoveString[4];
8895 if (appData.debugMode)
8896 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8897 sprintf(move, _("Ambiguous move: %d.%s%s"),
8898 (forwardMostMove / 2) + 1,
8899 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8900 DisplayError(move, 0);
8905 case ImpossibleMove:
8906 if (appData.debugMode)
8907 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8908 sprintf(move, _("Illegal move: %d.%s%s"),
8909 (forwardMostMove / 2) + 1,
8910 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8911 DisplayError(move, 0);
8917 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8918 DrawPosition(FALSE, boards[currentMove]);
8919 DisplayBothClocks();
8920 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8921 DisplayComment(currentMove - 1, commentList[currentMove]);
8923 (void) StopLoadGameTimer();
8925 cmailOldMove = forwardMostMove;
8928 /* currentMoveString is set as a side-effect of yylex */
8929 strcat(currentMoveString, "\n");
8930 strcpy(moveList[forwardMostMove], currentMoveString);
8932 thinkOutput[0] = NULLCHAR;
8933 MakeMove(fromX, fromY, toX, toY, promoChar);
8934 currentMove = forwardMostMove;
8939 /* Load the nth game from the given file */
8941 LoadGameFromFile(filename, n, title, useList)
8945 /*Boolean*/ int useList;
8950 if (strcmp(filename, "-") == 0) {
8954 f = fopen(filename, "rb");
8956 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8957 DisplayError(buf, errno);
8961 if (fseek(f, 0, 0) == -1) {
8962 /* f is not seekable; probably a pipe */
8965 if (useList && n == 0) {
8966 int error = GameListBuild(f);
8968 DisplayError(_("Cannot build game list"), error);
8969 } else if (!ListEmpty(&gameList) &&
8970 ((ListGame *) gameList.tailPred)->number > 1) {
8971 GameListPopUp(f, title);
8978 return LoadGame(f, n, title, FALSE);
8983 MakeRegisteredMove()
8985 int fromX, fromY, toX, toY;
8987 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8988 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8991 if (appData.debugMode)
8992 fprintf(debugFP, "Restoring %s for game %d\n",
8993 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8995 thinkOutput[0] = NULLCHAR;
8996 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8997 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8998 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8999 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9000 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9001 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9002 MakeMove(fromX, fromY, toX, toY, promoChar);
9003 ShowMove(fromX, fromY, toX, toY);
9005 switch (MateTest(boards[currentMove], PosFlags(currentMove),
9006 EP_UNKNOWN, castlingRights[currentMove]) ) {
9013 if (WhiteOnMove(currentMove)) {
9014 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9016 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9021 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9028 if (WhiteOnMove(currentMove)) {
9029 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9031 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9036 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9047 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9049 CmailLoadGame(f, gameNumber, title, useList)
9057 if (gameNumber > nCmailGames) {
9058 DisplayError(_("No more games in this message"), 0);
9061 if (f == lastLoadGameFP) {
9062 int offset = gameNumber - lastLoadGameNumber;
9064 cmailMsg[0] = NULLCHAR;
9065 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9066 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9067 nCmailMovesRegistered--;
9069 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9070 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9071 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9074 if (! RegisterMove()) return FALSE;
9078 retVal = LoadGame(f, gameNumber, title, useList);
9080 /* Make move registered during previous look at this game, if any */
9081 MakeRegisteredMove();
9083 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9084 commentList[currentMove]
9085 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9086 DisplayComment(currentMove - 1, commentList[currentMove]);
9092 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9097 int gameNumber = lastLoadGameNumber + offset;
9098 if (lastLoadGameFP == NULL) {
9099 DisplayError(_("No game has been loaded yet"), 0);
9102 if (gameNumber <= 0) {
9103 DisplayError(_("Can't back up any further"), 0);
9106 if (cmailMsgLoaded) {
9107 return CmailLoadGame(lastLoadGameFP, gameNumber,
9108 lastLoadGameTitle, lastLoadGameUseList);
9110 return LoadGame(lastLoadGameFP, gameNumber,
9111 lastLoadGameTitle, lastLoadGameUseList);
9117 /* Load the nth game from open file f */
9119 LoadGame(f, gameNumber, title, useList)
9127 int gn = gameNumber;
9128 ListGame *lg = NULL;
9131 GameMode oldGameMode;
9132 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9134 if (appData.debugMode)
9135 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9137 if (gameMode == Training )
9138 SetTrainingModeOff();
9140 oldGameMode = gameMode;
9141 if (gameMode != BeginningOfGame) {
9146 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9147 fclose(lastLoadGameFP);
9151 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9154 fseek(f, lg->offset, 0);
9155 GameListHighlight(gameNumber);
9159 DisplayError(_("Game number out of range"), 0);
9164 if (fseek(f, 0, 0) == -1) {
9165 if (f == lastLoadGameFP ?
9166 gameNumber == lastLoadGameNumber + 1 :
9170 DisplayError(_("Can't seek on game file"), 0);
9176 lastLoadGameNumber = gameNumber;
9177 strcpy(lastLoadGameTitle, title);
9178 lastLoadGameUseList = useList;
9182 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9183 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9184 lg->gameInfo.black);
9186 } else if (*title != NULLCHAR) {
9187 if (gameNumber > 1) {
9188 sprintf(buf, "%s %d", title, gameNumber);
9191 DisplayTitle(title);
9195 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9196 gameMode = PlayFromGameFile;
9200 currentMove = forwardMostMove = backwardMostMove = 0;
9201 CopyBoard(boards[0], initialPosition);
9205 * Skip the first gn-1 games in the file.
9206 * Also skip over anything that precedes an identifiable
9207 * start of game marker, to avoid being confused by
9208 * garbage at the start of the file. Currently
9209 * recognized start of game markers are the move number "1",
9210 * the pattern "gnuchess .* game", the pattern
9211 * "^[#;%] [^ ]* game file", and a PGN tag block.
9212 * A game that starts with one of the latter two patterns
9213 * will also have a move number 1, possibly
9214 * following a position diagram.
9215 * 5-4-02: Let's try being more lenient and allowing a game to
9216 * start with an unnumbered move. Does that break anything?
9218 cm = lastLoadGameStart = (ChessMove) 0;
9220 yyboardindex = forwardMostMove;
9221 cm = (ChessMove) yylex();
9224 if (cmailMsgLoaded) {
9225 nCmailGames = CMAIL_MAX_GAMES - gn;
9228 DisplayError(_("Game not found in file"), 0);
9235 lastLoadGameStart = cm;
9239 switch (lastLoadGameStart) {
9246 gn--; /* count this game */
9247 lastLoadGameStart = cm;
9256 switch (lastLoadGameStart) {
9261 gn--; /* count this game */
9262 lastLoadGameStart = cm;
9265 lastLoadGameStart = cm; /* game counted already */
9273 yyboardindex = forwardMostMove;
9274 cm = (ChessMove) yylex();
9275 } while (cm == PGNTag || cm == Comment);
9282 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9283 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9284 != CMAIL_OLD_RESULT) {
9286 cmailResult[ CMAIL_MAX_GAMES
9287 - gn - 1] = CMAIL_OLD_RESULT;
9293 /* Only a NormalMove can be at the start of a game
9294 * without a position diagram. */
9295 if (lastLoadGameStart == (ChessMove) 0) {
9297 lastLoadGameStart = MoveNumberOne;
9306 if (appData.debugMode)
9307 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9309 if (cm == XBoardGame) {
9310 /* Skip any header junk before position diagram and/or move 1 */
9312 yyboardindex = forwardMostMove;
9313 cm = (ChessMove) yylex();
9315 if (cm == (ChessMove) 0 ||
9316 cm == GNUChessGame || cm == XBoardGame) {
9317 /* Empty game; pretend end-of-file and handle later */
9322 if (cm == MoveNumberOne || cm == PositionDiagram ||
9323 cm == PGNTag || cm == Comment)
9326 } else if (cm == GNUChessGame) {
9327 if (gameInfo.event != NULL) {
9328 free(gameInfo.event);
9330 gameInfo.event = StrSave(yy_text);
9333 startedFromSetupPosition = FALSE;
9334 while (cm == PGNTag) {
9335 if (appData.debugMode)
9336 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9337 err = ParsePGNTag(yy_text, &gameInfo);
9338 if (!err) numPGNTags++;
9340 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9341 if(gameInfo.variant != oldVariant) {
9342 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9344 oldVariant = gameInfo.variant;
9345 if (appData.debugMode)
9346 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9350 if (gameInfo.fen != NULL) {
9351 Board initial_position;
9352 startedFromSetupPosition = TRUE;
9353 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9355 DisplayError(_("Bad FEN position in file"), 0);
9358 CopyBoard(boards[0], initial_position);
9359 if (blackPlaysFirst) {
9360 currentMove = forwardMostMove = backwardMostMove = 1;
9361 CopyBoard(boards[1], initial_position);
9362 strcpy(moveList[0], "");
9363 strcpy(parseList[0], "");
9364 timeRemaining[0][1] = whiteTimeRemaining;
9365 timeRemaining[1][1] = blackTimeRemaining;
9366 if (commentList[0] != NULL) {
9367 commentList[1] = commentList[0];
9368 commentList[0] = NULL;
9371 currentMove = forwardMostMove = backwardMostMove = 0;
9373 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9375 initialRulePlies = FENrulePlies;
9376 epStatus[forwardMostMove] = FENepStatus;
9377 for( i=0; i< nrCastlingRights; i++ )
9378 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9380 yyboardindex = forwardMostMove;
9382 gameInfo.fen = NULL;
9385 yyboardindex = forwardMostMove;
9386 cm = (ChessMove) yylex();
9388 /* Handle comments interspersed among the tags */
9389 while (cm == Comment) {
9391 if (appData.debugMode)
9392 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9394 if (*p == '{' || *p == '[' || *p == '(') {
9395 p[strlen(p) - 1] = NULLCHAR;
9398 while (*p == '\n') p++;
9399 AppendComment(currentMove, p);
9400 yyboardindex = forwardMostMove;
9401 cm = (ChessMove) yylex();
9405 /* don't rely on existence of Event tag since if game was
9406 * pasted from clipboard the Event tag may not exist
9408 if (numPGNTags > 0){
9410 if (gameInfo.variant == VariantNormal) {
9411 gameInfo.variant = StringToVariant(gameInfo.event);
9414 if( appData.autoDisplayTags ) {
9415 tags = PGNTags(&gameInfo);
9416 TagsPopUp(tags, CmailMsg());
9421 /* Make something up, but don't display it now */
9426 if (cm == PositionDiagram) {
9429 Board initial_position;
9431 if (appData.debugMode)
9432 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9434 if (!startedFromSetupPosition) {
9436 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9437 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9447 initial_position[i][j++] = CharToPiece(*p);
9450 while (*p == ' ' || *p == '\t' ||
9451 *p == '\n' || *p == '\r') p++;
9453 if (strncmp(p, "black", strlen("black"))==0)
9454 blackPlaysFirst = TRUE;
9456 blackPlaysFirst = FALSE;
9457 startedFromSetupPosition = TRUE;
9459 CopyBoard(boards[0], initial_position);
9460 if (blackPlaysFirst) {
9461 currentMove = forwardMostMove = backwardMostMove = 1;
9462 CopyBoard(boards[1], initial_position);
9463 strcpy(moveList[0], "");
9464 strcpy(parseList[0], "");
9465 timeRemaining[0][1] = whiteTimeRemaining;
9466 timeRemaining[1][1] = blackTimeRemaining;
9467 if (commentList[0] != NULL) {
9468 commentList[1] = commentList[0];
9469 commentList[0] = NULL;
9472 currentMove = forwardMostMove = backwardMostMove = 0;
9475 yyboardindex = forwardMostMove;
9476 cm = (ChessMove) yylex();
9479 if (first.pr == NoProc) {
9480 StartChessProgram(&first);
9482 InitChessProgram(&first, FALSE);
9483 SendToProgram("force\n", &first);
9484 if (startedFromSetupPosition) {
9485 SendBoard(&first, forwardMostMove);
9486 if (appData.debugMode) {
9487 fprintf(debugFP, "Load Game\n");
9489 DisplayBothClocks();
9492 /* [HGM] server: flag to write setup moves in broadcast file as one */
9493 loadFlag = appData.suppressLoadMoves;
9495 while (cm == Comment) {
9497 if (appData.debugMode)
9498 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9500 if (*p == '{' || *p == '[' || *p == '(') {
9501 p[strlen(p) - 1] = NULLCHAR;
9504 while (*p == '\n') p++;
9505 AppendComment(currentMove, p);
9506 yyboardindex = forwardMostMove;
9507 cm = (ChessMove) yylex();
9510 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9511 cm == WhiteWins || cm == BlackWins ||
9512 cm == GameIsDrawn || cm == GameUnfinished) {
9513 DisplayMessage("", _("No moves in game"));
9514 if (cmailMsgLoaded) {
9515 if (appData.debugMode)
9516 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9520 DrawPosition(FALSE, boards[currentMove]);
9521 DisplayBothClocks();
9522 gameMode = EditGame;
9529 // [HGM] PV info: routine tests if comment empty
9530 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9531 DisplayComment(currentMove - 1, commentList[currentMove]);
9533 if (!matchMode && appData.timeDelay != 0)
9534 DrawPosition(FALSE, boards[currentMove]);
9536 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9537 programStats.ok_to_send = 1;
9540 /* if the first token after the PGN tags is a move
9541 * and not move number 1, retrieve it from the parser
9543 if (cm != MoveNumberOne)
9544 LoadGameOneMove(cm);
9546 /* load the remaining moves from the file */
9547 while (LoadGameOneMove((ChessMove)0)) {
9548 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9549 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9552 /* rewind to the start of the game */
9553 currentMove = backwardMostMove;
9555 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9557 if (oldGameMode == AnalyzeFile ||
9558 oldGameMode == AnalyzeMode) {
9562 if (matchMode || appData.timeDelay == 0) {
9564 gameMode = EditGame;
9566 } else if (appData.timeDelay > 0) {
9570 if (appData.debugMode)
9571 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9573 loadFlag = 0; /* [HGM] true game starts */
9577 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9579 ReloadPosition(offset)
9582 int positionNumber = lastLoadPositionNumber + offset;
9583 if (lastLoadPositionFP == NULL) {
9584 DisplayError(_("No position has been loaded yet"), 0);
9587 if (positionNumber <= 0) {
9588 DisplayError(_("Can't back up any further"), 0);
9591 return LoadPosition(lastLoadPositionFP, positionNumber,
9592 lastLoadPositionTitle);
9595 /* Load the nth position from the given file */
9597 LoadPositionFromFile(filename, n, title)
9605 if (strcmp(filename, "-") == 0) {
9606 return LoadPosition(stdin, n, "stdin");
9608 f = fopen(filename, "rb");
9610 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9611 DisplayError(buf, errno);
9614 return LoadPosition(f, n, title);
9619 /* Load the nth position from the given open file, and close it */
9621 LoadPosition(f, positionNumber, title)
9626 char *p, line[MSG_SIZ];
9627 Board initial_position;
9628 int i, j, fenMode, pn;
9630 if (gameMode == Training )
9631 SetTrainingModeOff();
9633 if (gameMode != BeginningOfGame) {
9636 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9637 fclose(lastLoadPositionFP);
9639 if (positionNumber == 0) positionNumber = 1;
9640 lastLoadPositionFP = f;
9641 lastLoadPositionNumber = positionNumber;
9642 strcpy(lastLoadPositionTitle, title);
9643 if (first.pr == NoProc) {
9644 StartChessProgram(&first);
9645 InitChessProgram(&first, FALSE);
9647 pn = positionNumber;
9648 if (positionNumber < 0) {
9649 /* Negative position number means to seek to that byte offset */
9650 if (fseek(f, -positionNumber, 0) == -1) {
9651 DisplayError(_("Can't seek on position file"), 0);
9656 if (fseek(f, 0, 0) == -1) {
9657 if (f == lastLoadPositionFP ?
9658 positionNumber == lastLoadPositionNumber + 1 :
9659 positionNumber == 1) {
9662 DisplayError(_("Can't seek on position file"), 0);
9667 /* See if this file is FEN or old-style xboard */
9668 if (fgets(line, MSG_SIZ, f) == NULL) {
9669 DisplayError(_("Position not found in file"), 0);
9672 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9673 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9676 if (fenMode || line[0] == '#') pn--;
9678 /* skip positions before number pn */
9679 if (fgets(line, MSG_SIZ, f) == NULL) {
9681 DisplayError(_("Position not found in file"), 0);
9684 if (fenMode || line[0] == '#') pn--;
9689 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9690 DisplayError(_("Bad FEN position in file"), 0);
9694 (void) fgets(line, MSG_SIZ, f);
9695 (void) fgets(line, MSG_SIZ, f);
9697 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9698 (void) fgets(line, MSG_SIZ, f);
9699 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9702 initial_position[i][j++] = CharToPiece(*p);
9706 blackPlaysFirst = FALSE;
9708 (void) fgets(line, MSG_SIZ, f);
9709 if (strncmp(line, "black", strlen("black"))==0)
9710 blackPlaysFirst = TRUE;
9713 startedFromSetupPosition = TRUE;
9715 SendToProgram("force\n", &first);
9716 CopyBoard(boards[0], initial_position);
9717 if (blackPlaysFirst) {
9718 currentMove = forwardMostMove = backwardMostMove = 1;
9719 strcpy(moveList[0], "");
9720 strcpy(parseList[0], "");
9721 CopyBoard(boards[1], initial_position);
9722 DisplayMessage("", _("Black to play"));
9724 currentMove = forwardMostMove = backwardMostMove = 0;
9725 DisplayMessage("", _("White to play"));
9727 /* [HGM] copy FEN attributes as well */
9729 initialRulePlies = FENrulePlies;
9730 epStatus[forwardMostMove] = FENepStatus;
9731 for( i=0; i< nrCastlingRights; i++ )
9732 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9734 SendBoard(&first, forwardMostMove);
9735 if (appData.debugMode) {
9737 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9738 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9739 fprintf(debugFP, "Load Position\n");
9742 if (positionNumber > 1) {
9743 sprintf(line, "%s %d", title, positionNumber);
9746 DisplayTitle(title);
9748 gameMode = EditGame;
9751 timeRemaining[0][1] = whiteTimeRemaining;
9752 timeRemaining[1][1] = blackTimeRemaining;
9753 DrawPosition(FALSE, boards[currentMove]);
9760 CopyPlayerNameIntoFileName(dest, src)
9763 while (*src != NULLCHAR && *src != ',') {
9768 *(*dest)++ = *src++;
9773 char *DefaultFileName(ext)
9776 static char def[MSG_SIZ];
9779 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9781 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9783 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9792 /* Save the current game to the given file */
9794 SaveGameToFile(filename, append)
9801 if (strcmp(filename, "-") == 0) {
9802 return SaveGame(stdout, 0, NULL);
9804 f = fopen(filename, append ? "a" : "w");
9806 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9807 DisplayError(buf, errno);
9810 return SaveGame(f, 0, NULL);
9819 static char buf[MSG_SIZ];
9822 p = strchr(str, ' ');
9823 if (p == NULL) return str;
9824 strncpy(buf, str, p - str);
9825 buf[p - str] = NULLCHAR;
9829 #define PGN_MAX_LINE 75
9831 #define PGN_SIDE_WHITE 0
9832 #define PGN_SIDE_BLACK 1
9835 static int FindFirstMoveOutOfBook( int side )
9839 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9840 int index = backwardMostMove;
9841 int has_book_hit = 0;
9843 if( (index % 2) != side ) {
9847 while( index < forwardMostMove ) {
9848 /* Check to see if engine is in book */
9849 int depth = pvInfoList[index].depth;
9850 int score = pvInfoList[index].score;
9856 else if( score == 0 && depth == 63 ) {
9857 in_book = 1; /* Zappa */
9859 else if( score == 2 && depth == 99 ) {
9860 in_book = 1; /* Abrok */
9863 has_book_hit += in_book;
9879 void GetOutOfBookInfo( char * buf )
9883 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9885 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9886 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9890 if( oob[0] >= 0 || oob[1] >= 0 ) {
9891 for( i=0; i<2; i++ ) {
9895 if( i > 0 && oob[0] >= 0 ) {
9899 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9900 sprintf( buf+strlen(buf), "%s%.2f",
9901 pvInfoList[idx].score >= 0 ? "+" : "",
9902 pvInfoList[idx].score / 100.0 );
9908 /* Save game in PGN style and close the file */
9913 int i, offset, linelen, newblock;
9917 int movelen, numlen, blank;
9918 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9920 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9922 tm = time((time_t *) NULL);
9924 PrintPGNTags(f, &gameInfo);
9926 if (backwardMostMove > 0 || startedFromSetupPosition) {
9927 char *fen = PositionToFEN(backwardMostMove, NULL);
9928 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9929 fprintf(f, "\n{--------------\n");
9930 PrintPosition(f, backwardMostMove);
9931 fprintf(f, "--------------}\n");
9935 /* [AS] Out of book annotation */
9936 if( appData.saveOutOfBookInfo ) {
9939 GetOutOfBookInfo( buf );
9941 if( buf[0] != '\0' ) {
9942 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9949 i = backwardMostMove;
9953 while (i < forwardMostMove) {
9954 /* Print comments preceding this move */
9955 if (commentList[i] != NULL) {
9956 if (linelen > 0) fprintf(f, "\n");
9957 fprintf(f, "{\n%s}\n", commentList[i]);
9962 /* Format move number */
9964 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9967 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9969 numtext[0] = NULLCHAR;
9972 numlen = strlen(numtext);
9975 /* Print move number */
9976 blank = linelen > 0 && numlen > 0;
9977 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9986 fprintf(f, "%s", numtext);
9990 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9991 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9994 blank = linelen > 0 && movelen > 0;
9995 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10004 fprintf(f, "%s", move_buffer);
10005 linelen += movelen;
10007 /* [AS] Add PV info if present */
10008 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10009 /* [HGM] add time */
10010 char buf[MSG_SIZ]; int seconds;
10012 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10014 if( seconds <= 0) buf[0] = 0; else
10015 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10016 seconds = (seconds + 4)/10; // round to full seconds
10017 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10018 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10021 sprintf( move_buffer, "{%s%.2f/%d%s}",
10022 pvInfoList[i].score >= 0 ? "+" : "",
10023 pvInfoList[i].score / 100.0,
10024 pvInfoList[i].depth,
10027 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10029 /* Print score/depth */
10030 blank = linelen > 0 && movelen > 0;
10031 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10040 fprintf(f, "%s", move_buffer);
10041 linelen += movelen;
10047 /* Start a new line */
10048 if (linelen > 0) fprintf(f, "\n");
10050 /* Print comments after last move */
10051 if (commentList[i] != NULL) {
10052 fprintf(f, "{\n%s}\n", commentList[i]);
10056 if (gameInfo.resultDetails != NULL &&
10057 gameInfo.resultDetails[0] != NULLCHAR) {
10058 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10059 PGNResult(gameInfo.result));
10061 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10065 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10069 /* Save game in old style and close the file */
10071 SaveGameOldStyle(f)
10077 tm = time((time_t *) NULL);
10079 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10082 if (backwardMostMove > 0 || startedFromSetupPosition) {
10083 fprintf(f, "\n[--------------\n");
10084 PrintPosition(f, backwardMostMove);
10085 fprintf(f, "--------------]\n");
10090 i = backwardMostMove;
10091 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10093 while (i < forwardMostMove) {
10094 if (commentList[i] != NULL) {
10095 fprintf(f, "[%s]\n", commentList[i]);
10098 if ((i % 2) == 1) {
10099 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10102 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10104 if (commentList[i] != NULL) {
10108 if (i >= forwardMostMove) {
10112 fprintf(f, "%s\n", parseList[i]);
10117 if (commentList[i] != NULL) {
10118 fprintf(f, "[%s]\n", commentList[i]);
10121 /* This isn't really the old style, but it's close enough */
10122 if (gameInfo.resultDetails != NULL &&
10123 gameInfo.resultDetails[0] != NULLCHAR) {
10124 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10125 gameInfo.resultDetails);
10127 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10134 /* Save the current game to open file f and close the file */
10136 SaveGame(f, dummy, dummy2)
10141 if (gameMode == EditPosition) EditPositionDone(TRUE);
10142 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10143 if (appData.oldSaveStyle)
10144 return SaveGameOldStyle(f);
10146 return SaveGamePGN(f);
10149 /* Save the current position to the given file */
10151 SavePositionToFile(filename)
10157 if (strcmp(filename, "-") == 0) {
10158 return SavePosition(stdout, 0, NULL);
10160 f = fopen(filename, "a");
10162 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10163 DisplayError(buf, errno);
10166 SavePosition(f, 0, NULL);
10172 /* Save the current position to the given open file and close the file */
10174 SavePosition(f, dummy, dummy2)
10182 if (gameMode == EditPosition) EditPositionDone(TRUE);
10183 if (appData.oldSaveStyle) {
10184 tm = time((time_t *) NULL);
10186 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10188 fprintf(f, "[--------------\n");
10189 PrintPosition(f, currentMove);
10190 fprintf(f, "--------------]\n");
10192 fen = PositionToFEN(currentMove, NULL);
10193 fprintf(f, "%s\n", fen);
10201 ReloadCmailMsgEvent(unregister)
10205 static char *inFilename = NULL;
10206 static char *outFilename;
10208 struct stat inbuf, outbuf;
10211 /* Any registered moves are unregistered if unregister is set, */
10212 /* i.e. invoked by the signal handler */
10214 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10215 cmailMoveRegistered[i] = FALSE;
10216 if (cmailCommentList[i] != NULL) {
10217 free(cmailCommentList[i]);
10218 cmailCommentList[i] = NULL;
10221 nCmailMovesRegistered = 0;
10224 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10225 cmailResult[i] = CMAIL_NOT_RESULT;
10229 if (inFilename == NULL) {
10230 /* Because the filenames are static they only get malloced once */
10231 /* and they never get freed */
10232 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10233 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10235 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10236 sprintf(outFilename, "%s.out", appData.cmailGameName);
10239 status = stat(outFilename, &outbuf);
10241 cmailMailedMove = FALSE;
10243 status = stat(inFilename, &inbuf);
10244 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10247 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10248 counts the games, notes how each one terminated, etc.
10250 It would be nice to remove this kludge and instead gather all
10251 the information while building the game list. (And to keep it
10252 in the game list nodes instead of having a bunch of fixed-size
10253 parallel arrays.) Note this will require getting each game's
10254 termination from the PGN tags, as the game list builder does
10255 not process the game moves. --mann
10257 cmailMsgLoaded = TRUE;
10258 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10260 /* Load first game in the file or popup game menu */
10261 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10263 #endif /* !WIN32 */
10271 char string[MSG_SIZ];
10273 if ( cmailMailedMove
10274 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10275 return TRUE; /* Allow free viewing */
10278 /* Unregister move to ensure that we don't leave RegisterMove */
10279 /* with the move registered when the conditions for registering no */
10281 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10282 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10283 nCmailMovesRegistered --;
10285 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10287 free(cmailCommentList[lastLoadGameNumber - 1]);
10288 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10292 if (cmailOldMove == -1) {
10293 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10297 if (currentMove > cmailOldMove + 1) {
10298 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10302 if (currentMove < cmailOldMove) {
10303 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10307 if (forwardMostMove > currentMove) {
10308 /* Silently truncate extra moves */
10312 if ( (currentMove == cmailOldMove + 1)
10313 || ( (currentMove == cmailOldMove)
10314 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10315 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10316 if (gameInfo.result != GameUnfinished) {
10317 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10320 if (commentList[currentMove] != NULL) {
10321 cmailCommentList[lastLoadGameNumber - 1]
10322 = StrSave(commentList[currentMove]);
10324 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10326 if (appData.debugMode)
10327 fprintf(debugFP, "Saving %s for game %d\n",
10328 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10331 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10333 f = fopen(string, "w");
10334 if (appData.oldSaveStyle) {
10335 SaveGameOldStyle(f); /* also closes the file */
10337 sprintf(string, "%s.pos.out", appData.cmailGameName);
10338 f = fopen(string, "w");
10339 SavePosition(f, 0, NULL); /* also closes the file */
10341 fprintf(f, "{--------------\n");
10342 PrintPosition(f, currentMove);
10343 fprintf(f, "--------------}\n\n");
10345 SaveGame(f, 0, NULL); /* also closes the file*/
10348 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10349 nCmailMovesRegistered ++;
10350 } else if (nCmailGames == 1) {
10351 DisplayError(_("You have not made a move yet"), 0);
10362 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10363 FILE *commandOutput;
10364 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10365 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10371 if (! cmailMsgLoaded) {
10372 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10376 if (nCmailGames == nCmailResults) {
10377 DisplayError(_("No unfinished games"), 0);
10381 #if CMAIL_PROHIBIT_REMAIL
10382 if (cmailMailedMove) {
10383 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);
10384 DisplayError(msg, 0);
10389 if (! (cmailMailedMove || RegisterMove())) return;
10391 if ( cmailMailedMove
10392 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10393 sprintf(string, partCommandString,
10394 appData.debugMode ? " -v" : "", appData.cmailGameName);
10395 commandOutput = popen(string, "r");
10397 if (commandOutput == NULL) {
10398 DisplayError(_("Failed to invoke cmail"), 0);
10400 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10401 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10403 if (nBuffers > 1) {
10404 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10405 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10406 nBytes = MSG_SIZ - 1;
10408 (void) memcpy(msg, buffer, nBytes);
10410 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10412 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10413 cmailMailedMove = TRUE; /* Prevent >1 moves */
10416 for (i = 0; i < nCmailGames; i ++) {
10417 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10422 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10424 sprintf(buffer, "%s/%s.%s.archive",
10426 appData.cmailGameName,
10428 LoadGameFromFile(buffer, 1, buffer, FALSE);
10429 cmailMsgLoaded = FALSE;
10433 DisplayInformation(msg);
10434 pclose(commandOutput);
10437 if ((*cmailMsg) != '\0') {
10438 DisplayInformation(cmailMsg);
10443 #endif /* !WIN32 */
10452 int prependComma = 0;
10454 char string[MSG_SIZ]; /* Space for game-list */
10457 if (!cmailMsgLoaded) return "";
10459 if (cmailMailedMove) {
10460 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10462 /* Create a list of games left */
10463 sprintf(string, "[");
10464 for (i = 0; i < nCmailGames; i ++) {
10465 if (! ( cmailMoveRegistered[i]
10466 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10467 if (prependComma) {
10468 sprintf(number, ",%d", i + 1);
10470 sprintf(number, "%d", i + 1);
10474 strcat(string, number);
10477 strcat(string, "]");
10479 if (nCmailMovesRegistered + nCmailResults == 0) {
10480 switch (nCmailGames) {
10483 _("Still need to make move for game\n"));
10488 _("Still need to make moves for both games\n"));
10493 _("Still need to make moves for all %d games\n"),
10498 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10501 _("Still need to make a move for game %s\n"),
10506 if (nCmailResults == nCmailGames) {
10507 sprintf(cmailMsg, _("No unfinished games\n"));
10509 sprintf(cmailMsg, _("Ready to send mail\n"));
10515 _("Still need to make moves for games %s\n"),
10527 if (gameMode == Training)
10528 SetTrainingModeOff();
10531 cmailMsgLoaded = FALSE;
10532 if (appData.icsActive) {
10533 SendToICS(ics_prefix);
10534 SendToICS("refresh\n");
10544 /* Give up on clean exit */
10548 /* Keep trying for clean exit */
10552 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10554 if (telnetISR != NULL) {
10555 RemoveInputSource(telnetISR);
10557 if (icsPR != NoProc) {
10558 DestroyChildProcess(icsPR, TRUE);
10561 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10562 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10564 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10565 /* make sure this other one finishes before killing it! */
10566 if(endingGame) { int count = 0;
10567 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10568 while(endingGame && count++ < 10) DoSleep(1);
10569 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10572 /* Kill off chess programs */
10573 if (first.pr != NoProc) {
10576 DoSleep( appData.delayBeforeQuit );
10577 SendToProgram("quit\n", &first);
10578 DoSleep( appData.delayAfterQuit );
10579 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10581 if (second.pr != NoProc) {
10582 DoSleep( appData.delayBeforeQuit );
10583 SendToProgram("quit\n", &second);
10584 DoSleep( appData.delayAfterQuit );
10585 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10587 if (first.isr != NULL) {
10588 RemoveInputSource(first.isr);
10590 if (second.isr != NULL) {
10591 RemoveInputSource(second.isr);
10594 ShutDownFrontEnd();
10601 if (appData.debugMode)
10602 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10606 if (gameMode == MachinePlaysWhite ||
10607 gameMode == MachinePlaysBlack) {
10610 DisplayBothClocks();
10612 if (gameMode == PlayFromGameFile) {
10613 if (appData.timeDelay >= 0)
10614 AutoPlayGameLoop();
10615 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10616 Reset(FALSE, TRUE);
10617 SendToICS(ics_prefix);
10618 SendToICS("refresh\n");
10619 } else if (currentMove < forwardMostMove) {
10620 ForwardInner(forwardMostMove);
10622 pauseExamInvalid = FALSE;
10624 switch (gameMode) {
10628 pauseExamForwardMostMove = forwardMostMove;
10629 pauseExamInvalid = FALSE;
10632 case IcsPlayingWhite:
10633 case IcsPlayingBlack:
10637 case PlayFromGameFile:
10638 (void) StopLoadGameTimer();
10642 case BeginningOfGame:
10643 if (appData.icsActive) return;
10644 /* else fall through */
10645 case MachinePlaysWhite:
10646 case MachinePlaysBlack:
10647 case TwoMachinesPlay:
10648 if (forwardMostMove == 0)
10649 return; /* don't pause if no one has moved */
10650 if ((gameMode == MachinePlaysWhite &&
10651 !WhiteOnMove(forwardMostMove)) ||
10652 (gameMode == MachinePlaysBlack &&
10653 WhiteOnMove(forwardMostMove))) {
10666 char title[MSG_SIZ];
10668 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10669 strcpy(title, _("Edit comment"));
10671 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10672 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10673 parseList[currentMove - 1]);
10676 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10683 char *tags = PGNTags(&gameInfo);
10684 EditTagsPopUp(tags);
10691 if (appData.noChessProgram || gameMode == AnalyzeMode)
10694 if (gameMode != AnalyzeFile) {
10695 if (!appData.icsEngineAnalyze) {
10697 if (gameMode != EditGame) return;
10699 ResurrectChessProgram();
10700 SendToProgram("analyze\n", &first);
10701 first.analyzing = TRUE;
10702 /*first.maybeThinking = TRUE;*/
10703 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10704 EngineOutputPopUp();
10706 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10711 StartAnalysisClock();
10712 GetTimeMark(&lastNodeCountTime);
10719 if (appData.noChessProgram || gameMode == AnalyzeFile)
10722 if (gameMode != AnalyzeMode) {
10724 if (gameMode != EditGame) return;
10725 ResurrectChessProgram();
10726 SendToProgram("analyze\n", &first);
10727 first.analyzing = TRUE;
10728 /*first.maybeThinking = TRUE;*/
10729 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10730 EngineOutputPopUp();
10732 gameMode = AnalyzeFile;
10737 StartAnalysisClock();
10738 GetTimeMark(&lastNodeCountTime);
10743 MachineWhiteEvent()
10746 char *bookHit = NULL;
10748 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10752 if (gameMode == PlayFromGameFile ||
10753 gameMode == TwoMachinesPlay ||
10754 gameMode == Training ||
10755 gameMode == AnalyzeMode ||
10756 gameMode == EndOfGame)
10759 if (gameMode == EditPosition)
10760 EditPositionDone(TRUE);
10762 if (!WhiteOnMove(currentMove)) {
10763 DisplayError(_("It is not White's turn"), 0);
10767 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10770 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10771 gameMode == AnalyzeFile)
10774 ResurrectChessProgram(); /* in case it isn't running */
10775 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10776 gameMode = MachinePlaysWhite;
10779 gameMode = MachinePlaysWhite;
10783 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10785 if (first.sendName) {
10786 sprintf(buf, "name %s\n", gameInfo.black);
10787 SendToProgram(buf, &first);
10789 if (first.sendTime) {
10790 if (first.useColors) {
10791 SendToProgram("black\n", &first); /*gnu kludge*/
10793 SendTimeRemaining(&first, TRUE);
10795 if (first.useColors) {
10796 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10798 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10799 SetMachineThinkingEnables();
10800 first.maybeThinking = TRUE;
10804 if (appData.autoFlipView && !flipView) {
10805 flipView = !flipView;
10806 DrawPosition(FALSE, NULL);
10807 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10810 if(bookHit) { // [HGM] book: simulate book reply
10811 static char bookMove[MSG_SIZ]; // a bit generous?
10813 programStats.nodes = programStats.depth = programStats.time =
10814 programStats.score = programStats.got_only_move = 0;
10815 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10817 strcpy(bookMove, "move ");
10818 strcat(bookMove, bookHit);
10819 HandleMachineMove(bookMove, &first);
10824 MachineBlackEvent()
10827 char *bookHit = NULL;
10829 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10833 if (gameMode == PlayFromGameFile ||
10834 gameMode == TwoMachinesPlay ||
10835 gameMode == Training ||
10836 gameMode == AnalyzeMode ||
10837 gameMode == EndOfGame)
10840 if (gameMode == EditPosition)
10841 EditPositionDone(TRUE);
10843 if (WhiteOnMove(currentMove)) {
10844 DisplayError(_("It is not Black's turn"), 0);
10848 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10851 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10852 gameMode == AnalyzeFile)
10855 ResurrectChessProgram(); /* in case it isn't running */
10856 gameMode = MachinePlaysBlack;
10860 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10862 if (first.sendName) {
10863 sprintf(buf, "name %s\n", gameInfo.white);
10864 SendToProgram(buf, &first);
10866 if (first.sendTime) {
10867 if (first.useColors) {
10868 SendToProgram("white\n", &first); /*gnu kludge*/
10870 SendTimeRemaining(&first, FALSE);
10872 if (first.useColors) {
10873 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10875 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10876 SetMachineThinkingEnables();
10877 first.maybeThinking = TRUE;
10880 if (appData.autoFlipView && flipView) {
10881 flipView = !flipView;
10882 DrawPosition(FALSE, NULL);
10883 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10885 if(bookHit) { // [HGM] book: simulate book reply
10886 static char bookMove[MSG_SIZ]; // a bit generous?
10888 programStats.nodes = programStats.depth = programStats.time =
10889 programStats.score = programStats.got_only_move = 0;
10890 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10892 strcpy(bookMove, "move ");
10893 strcat(bookMove, bookHit);
10894 HandleMachineMove(bookMove, &first);
10900 DisplayTwoMachinesTitle()
10903 if (appData.matchGames > 0) {
10904 if (first.twoMachinesColor[0] == 'w') {
10905 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10906 gameInfo.white, gameInfo.black,
10907 first.matchWins, second.matchWins,
10908 matchGame - 1 - (first.matchWins + second.matchWins));
10910 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10911 gameInfo.white, gameInfo.black,
10912 second.matchWins, first.matchWins,
10913 matchGame - 1 - (first.matchWins + second.matchWins));
10916 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10922 TwoMachinesEvent P((void))
10926 ChessProgramState *onmove;
10927 char *bookHit = NULL;
10929 if (appData.noChessProgram) return;
10931 switch (gameMode) {
10932 case TwoMachinesPlay:
10934 case MachinePlaysWhite:
10935 case MachinePlaysBlack:
10936 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10937 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10941 case BeginningOfGame:
10942 case PlayFromGameFile:
10945 if (gameMode != EditGame) return;
10948 EditPositionDone(TRUE);
10959 forwardMostMove = currentMove;
10960 ResurrectChessProgram(); /* in case first program isn't running */
10962 if (second.pr == NULL) {
10963 StartChessProgram(&second);
10964 if (second.protocolVersion == 1) {
10965 TwoMachinesEventIfReady();
10967 /* kludge: allow timeout for initial "feature" command */
10969 DisplayMessage("", _("Starting second chess program"));
10970 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10974 DisplayMessage("", "");
10975 InitChessProgram(&second, FALSE);
10976 SendToProgram("force\n", &second);
10977 if (startedFromSetupPosition) {
10978 SendBoard(&second, backwardMostMove);
10979 if (appData.debugMode) {
10980 fprintf(debugFP, "Two Machines\n");
10983 for (i = backwardMostMove; i < forwardMostMove; i++) {
10984 SendMoveToProgram(i, &second);
10987 gameMode = TwoMachinesPlay;
10991 DisplayTwoMachinesTitle();
10993 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10999 SendToProgram(first.computerString, &first);
11000 if (first.sendName) {
11001 sprintf(buf, "name %s\n", second.tidy);
11002 SendToProgram(buf, &first);
11004 SendToProgram(second.computerString, &second);
11005 if (second.sendName) {
11006 sprintf(buf, "name %s\n", first.tidy);
11007 SendToProgram(buf, &second);
11011 if (!first.sendTime || !second.sendTime) {
11012 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11013 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11015 if (onmove->sendTime) {
11016 if (onmove->useColors) {
11017 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11019 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11021 if (onmove->useColors) {
11022 SendToProgram(onmove->twoMachinesColor, onmove);
11024 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11025 // SendToProgram("go\n", onmove);
11026 onmove->maybeThinking = TRUE;
11027 SetMachineThinkingEnables();
11031 if(bookHit) { // [HGM] book: simulate book reply
11032 static char bookMove[MSG_SIZ]; // a bit generous?
11034 programStats.nodes = programStats.depth = programStats.time =
11035 programStats.score = programStats.got_only_move = 0;
11036 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11038 strcpy(bookMove, "move ");
11039 strcat(bookMove, bookHit);
11040 savedMessage = bookMove; // args for deferred call
11041 savedState = onmove;
11042 ScheduleDelayedEvent(DeferredBookMove, 1);
11049 if (gameMode == Training) {
11050 SetTrainingModeOff();
11051 gameMode = PlayFromGameFile;
11052 DisplayMessage("", _("Training mode off"));
11054 gameMode = Training;
11055 animateTraining = appData.animate;
11057 /* make sure we are not already at the end of the game */
11058 if (currentMove < forwardMostMove) {
11059 SetTrainingModeOn();
11060 DisplayMessage("", _("Training mode on"));
11062 gameMode = PlayFromGameFile;
11063 DisplayError(_("Already at end of game"), 0);
11072 if (!appData.icsActive) return;
11073 switch (gameMode) {
11074 case IcsPlayingWhite:
11075 case IcsPlayingBlack:
11078 case BeginningOfGame:
11086 EditPositionDone(TRUE);
11099 gameMode = IcsIdle;
11110 switch (gameMode) {
11112 SetTrainingModeOff();
11114 case MachinePlaysWhite:
11115 case MachinePlaysBlack:
11116 case BeginningOfGame:
11117 SendToProgram("force\n", &first);
11118 SetUserThinkingEnables();
11120 case PlayFromGameFile:
11121 (void) StopLoadGameTimer();
11122 if (gameFileFP != NULL) {
11127 EditPositionDone(TRUE);
11132 SendToProgram("force\n", &first);
11134 case TwoMachinesPlay:
11135 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11136 ResurrectChessProgram();
11137 SetUserThinkingEnables();
11140 ResurrectChessProgram();
11142 case IcsPlayingBlack:
11143 case IcsPlayingWhite:
11144 DisplayError(_("Warning: You are still playing a game"), 0);
11147 DisplayError(_("Warning: You are still observing a game"), 0);
11150 DisplayError(_("Warning: You are still examining a game"), 0);
11161 first.offeredDraw = second.offeredDraw = 0;
11163 if (gameMode == PlayFromGameFile) {
11164 whiteTimeRemaining = timeRemaining[0][currentMove];
11165 blackTimeRemaining = timeRemaining[1][currentMove];
11169 if (gameMode == MachinePlaysWhite ||
11170 gameMode == MachinePlaysBlack ||
11171 gameMode == TwoMachinesPlay ||
11172 gameMode == EndOfGame) {
11173 i = forwardMostMove;
11174 while (i > currentMove) {
11175 SendToProgram("undo\n", &first);
11178 whiteTimeRemaining = timeRemaining[0][currentMove];
11179 blackTimeRemaining = timeRemaining[1][currentMove];
11180 DisplayBothClocks();
11181 if (whiteFlag || blackFlag) {
11182 whiteFlag = blackFlag = 0;
11187 gameMode = EditGame;
11194 EditPositionEvent()
11196 if (gameMode == EditPosition) {
11202 if (gameMode != EditGame) return;
11204 gameMode = EditPosition;
11207 if (currentMove > 0)
11208 CopyBoard(boards[0], boards[currentMove]);
11210 blackPlaysFirst = !WhiteOnMove(currentMove);
11212 currentMove = forwardMostMove = backwardMostMove = 0;
11213 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11220 /* [DM] icsEngineAnalyze - possible call from other functions */
11221 if (appData.icsEngineAnalyze) {
11222 appData.icsEngineAnalyze = FALSE;
11224 DisplayMessage("",_("Close ICS engine analyze..."));
11226 if (first.analysisSupport && first.analyzing) {
11227 SendToProgram("exit\n", &first);
11228 first.analyzing = FALSE;
11230 thinkOutput[0] = NULLCHAR;
11234 EditPositionDone(Boolean fakeRights)
11236 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11238 startedFromSetupPosition = TRUE;
11239 InitChessProgram(&first, FALSE);
11241 { /* don't do this if we just pasted FEN */
11242 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11243 if(boards[0][0][BOARD_WIDTH>>1] == king)
11245 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11246 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11249 castlingRights[0][2] = -1;
11250 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11252 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11253 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11256 castlingRights[0][5] = -1;
11258 SendToProgram("force\n", &first);
11259 if (blackPlaysFirst) {
11260 strcpy(moveList[0], "");
11261 strcpy(parseList[0], "");
11262 currentMove = forwardMostMove = backwardMostMove = 1;
11263 CopyBoard(boards[1], boards[0]);
11264 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11266 epStatus[1] = epStatus[0];
11267 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11270 currentMove = forwardMostMove = backwardMostMove = 0;
11272 SendBoard(&first, forwardMostMove);
11273 if (appData.debugMode) {
11274 fprintf(debugFP, "EditPosDone\n");
11277 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11278 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11279 gameMode = EditGame;
11281 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11282 ClearHighlights(); /* [AS] */
11285 /* Pause for `ms' milliseconds */
11286 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11296 } while (SubtractTimeMarks(&m2, &m1) < ms);
11299 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11301 SendMultiLineToICS(buf)
11304 char temp[MSG_SIZ+1], *p;
11311 strncpy(temp, buf, len);
11316 if (*p == '\n' || *p == '\r')
11321 strcat(temp, "\n");
11323 SendToPlayer(temp, strlen(temp));
11327 SetWhiteToPlayEvent()
11329 if (gameMode == EditPosition) {
11330 blackPlaysFirst = FALSE;
11331 DisplayBothClocks(); /* works because currentMove is 0 */
11332 } else if (gameMode == IcsExamining) {
11333 SendToICS(ics_prefix);
11334 SendToICS("tomove white\n");
11339 SetBlackToPlayEvent()
11341 if (gameMode == EditPosition) {
11342 blackPlaysFirst = TRUE;
11343 currentMove = 1; /* kludge */
11344 DisplayBothClocks();
11346 } else if (gameMode == IcsExamining) {
11347 SendToICS(ics_prefix);
11348 SendToICS("tomove black\n");
11353 EditPositionMenuEvent(selection, x, y)
11354 ChessSquare selection;
11358 ChessSquare piece = boards[0][y][x];
11360 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11362 switch (selection) {
11364 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11365 SendToICS(ics_prefix);
11366 SendToICS("bsetup clear\n");
11367 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11368 SendToICS(ics_prefix);
11369 SendToICS("clearboard\n");
11371 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11372 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11373 for (y = 0; y < BOARD_HEIGHT; y++) {
11374 if (gameMode == IcsExamining) {
11375 if (boards[currentMove][y][x] != EmptySquare) {
11376 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11381 boards[0][y][x] = p;
11386 if (gameMode == EditPosition) {
11387 DrawPosition(FALSE, boards[0]);
11392 SetWhiteToPlayEvent();
11396 SetBlackToPlayEvent();
11400 if (gameMode == IcsExamining) {
11401 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11404 boards[0][y][x] = EmptySquare;
11405 DrawPosition(FALSE, boards[0]);
11410 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11411 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11412 selection = (ChessSquare) (PROMOTED piece);
11413 } else if(piece == EmptySquare) selection = WhiteSilver;
11414 else selection = (ChessSquare)((int)piece - 1);
11418 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11419 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11420 selection = (ChessSquare) (DEMOTED piece);
11421 } else if(piece == EmptySquare) selection = BlackSilver;
11422 else selection = (ChessSquare)((int)piece + 1);
11427 if(gameInfo.variant == VariantShatranj ||
11428 gameInfo.variant == VariantXiangqi ||
11429 gameInfo.variant == VariantCourier ||
11430 gameInfo.variant == VariantMakruk )
11431 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11436 if(gameInfo.variant == VariantXiangqi)
11437 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11438 if(gameInfo.variant == VariantKnightmate)
11439 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11442 if (gameMode == IcsExamining) {
11443 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11444 PieceToChar(selection), AAA + x, ONE + y);
11447 boards[0][y][x] = selection;
11448 DrawPosition(FALSE, boards[0]);
11456 DropMenuEvent(selection, x, y)
11457 ChessSquare selection;
11460 ChessMove moveType;
11462 switch (gameMode) {
11463 case IcsPlayingWhite:
11464 case MachinePlaysBlack:
11465 if (!WhiteOnMove(currentMove)) {
11466 DisplayMoveError(_("It is Black's turn"));
11469 moveType = WhiteDrop;
11471 case IcsPlayingBlack:
11472 case MachinePlaysWhite:
11473 if (WhiteOnMove(currentMove)) {
11474 DisplayMoveError(_("It is White's turn"));
11477 moveType = BlackDrop;
11480 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11486 if (moveType == BlackDrop && selection < BlackPawn) {
11487 selection = (ChessSquare) ((int) selection
11488 + (int) BlackPawn - (int) WhitePawn);
11490 if (boards[currentMove][y][x] != EmptySquare) {
11491 DisplayMoveError(_("That square is occupied"));
11495 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11501 /* Accept a pending offer of any kind from opponent */
11503 if (appData.icsActive) {
11504 SendToICS(ics_prefix);
11505 SendToICS("accept\n");
11506 } else if (cmailMsgLoaded) {
11507 if (currentMove == cmailOldMove &&
11508 commentList[cmailOldMove] != NULL &&
11509 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11510 "Black offers a draw" : "White offers a draw")) {
11512 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11513 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11515 DisplayError(_("There is no pending offer on this move"), 0);
11516 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11519 /* Not used for offers from chess program */
11526 /* Decline a pending offer of any kind from opponent */
11528 if (appData.icsActive) {
11529 SendToICS(ics_prefix);
11530 SendToICS("decline\n");
11531 } else if (cmailMsgLoaded) {
11532 if (currentMove == cmailOldMove &&
11533 commentList[cmailOldMove] != NULL &&
11534 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11535 "Black offers a draw" : "White offers a draw")) {
11537 AppendComment(cmailOldMove, "Draw declined");
11538 DisplayComment(cmailOldMove - 1, "Draw declined");
11541 DisplayError(_("There is no pending offer on this move"), 0);
11544 /* Not used for offers from chess program */
11551 /* Issue ICS rematch command */
11552 if (appData.icsActive) {
11553 SendToICS(ics_prefix);
11554 SendToICS("rematch\n");
11561 /* Call your opponent's flag (claim a win on time) */
11562 if (appData.icsActive) {
11563 SendToICS(ics_prefix);
11564 SendToICS("flag\n");
11566 switch (gameMode) {
11569 case MachinePlaysWhite:
11572 GameEnds(GameIsDrawn, "Both players ran out of time",
11575 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11577 DisplayError(_("Your opponent is not out of time"), 0);
11580 case MachinePlaysBlack:
11583 GameEnds(GameIsDrawn, "Both players ran out of time",
11586 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11588 DisplayError(_("Your opponent is not out of time"), 0);
11598 /* Offer draw or accept pending draw offer from opponent */
11600 if (appData.icsActive) {
11601 /* Note: tournament rules require draw offers to be
11602 made after you make your move but before you punch
11603 your clock. Currently ICS doesn't let you do that;
11604 instead, you immediately punch your clock after making
11605 a move, but you can offer a draw at any time. */
11607 SendToICS(ics_prefix);
11608 SendToICS("draw\n");
11609 } else if (cmailMsgLoaded) {
11610 if (currentMove == cmailOldMove &&
11611 commentList[cmailOldMove] != NULL &&
11612 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11613 "Black offers a draw" : "White offers a draw")) {
11614 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11615 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11616 } else if (currentMove == cmailOldMove + 1) {
11617 char *offer = WhiteOnMove(cmailOldMove) ?
11618 "White offers a draw" : "Black offers a draw";
11619 AppendComment(currentMove, offer);
11620 DisplayComment(currentMove - 1, offer);
11621 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11623 DisplayError(_("You must make your move before offering a draw"), 0);
11624 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11626 } else if (first.offeredDraw) {
11627 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11629 if (first.sendDrawOffers) {
11630 SendToProgram("draw\n", &first);
11631 userOfferedDraw = TRUE;
11639 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11641 if (appData.icsActive) {
11642 SendToICS(ics_prefix);
11643 SendToICS("adjourn\n");
11645 /* Currently GNU Chess doesn't offer or accept Adjourns */
11653 /* Offer Abort or accept pending Abort offer from opponent */
11655 if (appData.icsActive) {
11656 SendToICS(ics_prefix);
11657 SendToICS("abort\n");
11659 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11666 /* Resign. You can do this even if it's not your turn. */
11668 if (appData.icsActive) {
11669 SendToICS(ics_prefix);
11670 SendToICS("resign\n");
11672 switch (gameMode) {
11673 case MachinePlaysWhite:
11674 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11676 case MachinePlaysBlack:
11677 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11680 if (cmailMsgLoaded) {
11682 if (WhiteOnMove(cmailOldMove)) {
11683 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11685 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11687 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11698 StopObservingEvent()
11700 /* Stop observing current games */
11701 SendToICS(ics_prefix);
11702 SendToICS("unobserve\n");
11706 StopExaminingEvent()
11708 /* Stop observing current game */
11709 SendToICS(ics_prefix);
11710 SendToICS("unexamine\n");
11714 ForwardInner(target)
11719 if (appData.debugMode)
11720 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11721 target, currentMove, forwardMostMove);
11723 if (gameMode == EditPosition)
11726 if (gameMode == PlayFromGameFile && !pausing)
11729 if (gameMode == IcsExamining && pausing)
11730 limit = pauseExamForwardMostMove;
11732 limit = forwardMostMove;
11734 if (target > limit) target = limit;
11736 if (target > 0 && moveList[target - 1][0]) {
11737 int fromX, fromY, toX, toY;
11738 toX = moveList[target - 1][2] - AAA;
11739 toY = moveList[target - 1][3] - ONE;
11740 if (moveList[target - 1][1] == '@') {
11741 if (appData.highlightLastMove) {
11742 SetHighlights(-1, -1, toX, toY);
11745 fromX = moveList[target - 1][0] - AAA;
11746 fromY = moveList[target - 1][1] - ONE;
11747 if (target == currentMove + 1) {
11748 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11750 if (appData.highlightLastMove) {
11751 SetHighlights(fromX, fromY, toX, toY);
11755 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11756 gameMode == Training || gameMode == PlayFromGameFile ||
11757 gameMode == AnalyzeFile) {
11758 while (currentMove < target) {
11759 SendMoveToProgram(currentMove++, &first);
11762 currentMove = target;
11765 if (gameMode == EditGame || gameMode == EndOfGame) {
11766 whiteTimeRemaining = timeRemaining[0][currentMove];
11767 blackTimeRemaining = timeRemaining[1][currentMove];
11769 DisplayBothClocks();
11770 DisplayMove(currentMove - 1);
11771 DrawPosition(FALSE, boards[currentMove]);
11772 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11773 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11774 DisplayComment(currentMove - 1, commentList[currentMove]);
11782 if (gameMode == IcsExamining && !pausing) {
11783 SendToICS(ics_prefix);
11784 SendToICS("forward\n");
11786 ForwardInner(currentMove + 1);
11793 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11794 /* to optimze, we temporarily turn off analysis mode while we feed
11795 * the remaining moves to the engine. Otherwise we get analysis output
11798 if (first.analysisSupport) {
11799 SendToProgram("exit\nforce\n", &first);
11800 first.analyzing = FALSE;
11804 if (gameMode == IcsExamining && !pausing) {
11805 SendToICS(ics_prefix);
11806 SendToICS("forward 999999\n");
11808 ForwardInner(forwardMostMove);
11811 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11812 /* we have fed all the moves, so reactivate analysis mode */
11813 SendToProgram("analyze\n", &first);
11814 first.analyzing = TRUE;
11815 /*first.maybeThinking = TRUE;*/
11816 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11821 BackwardInner(target)
11824 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11826 if (appData.debugMode)
11827 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11828 target, currentMove, forwardMostMove);
11830 if (gameMode == EditPosition) return;
11831 if (currentMove <= backwardMostMove) {
11833 DrawPosition(full_redraw, boards[currentMove]);
11836 if (gameMode == PlayFromGameFile && !pausing)
11839 if (moveList[target][0]) {
11840 int fromX, fromY, toX, toY;
11841 toX = moveList[target][2] - AAA;
11842 toY = moveList[target][3] - ONE;
11843 if (moveList[target][1] == '@') {
11844 if (appData.highlightLastMove) {
11845 SetHighlights(-1, -1, toX, toY);
11848 fromX = moveList[target][0] - AAA;
11849 fromY = moveList[target][1] - ONE;
11850 if (target == currentMove - 1) {
11851 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11853 if (appData.highlightLastMove) {
11854 SetHighlights(fromX, fromY, toX, toY);
11858 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11859 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11860 while (currentMove > target) {
11861 SendToProgram("undo\n", &first);
11865 currentMove = target;
11868 if (gameMode == EditGame || gameMode == EndOfGame) {
11869 whiteTimeRemaining = timeRemaining[0][currentMove];
11870 blackTimeRemaining = timeRemaining[1][currentMove];
11872 DisplayBothClocks();
11873 DisplayMove(currentMove - 1);
11874 DrawPosition(full_redraw, boards[currentMove]);
11875 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11876 // [HGM] PV info: routine tests if comment empty
11877 DisplayComment(currentMove - 1, commentList[currentMove]);
11883 if (gameMode == IcsExamining && !pausing) {
11884 SendToICS(ics_prefix);
11885 SendToICS("backward\n");
11887 BackwardInner(currentMove - 1);
11894 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11895 /* to optimize, we temporarily turn off analysis mode while we undo
11896 * all the moves. Otherwise we get analysis output after each undo.
11898 if (first.analysisSupport) {
11899 SendToProgram("exit\nforce\n", &first);
11900 first.analyzing = FALSE;
11904 if (gameMode == IcsExamining && !pausing) {
11905 SendToICS(ics_prefix);
11906 SendToICS("backward 999999\n");
11908 BackwardInner(backwardMostMove);
11911 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11912 /* we have fed all the moves, so reactivate analysis mode */
11913 SendToProgram("analyze\n", &first);
11914 first.analyzing = TRUE;
11915 /*first.maybeThinking = TRUE;*/
11916 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11923 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11924 if (to >= forwardMostMove) to = forwardMostMove;
11925 if (to <= backwardMostMove) to = backwardMostMove;
11926 if (to < currentMove) {
11936 if (gameMode != IcsExamining) {
11937 DisplayError(_("You are not examining a game"), 0);
11941 DisplayError(_("You can't revert while pausing"), 0);
11944 SendToICS(ics_prefix);
11945 SendToICS("revert\n");
11951 switch (gameMode) {
11952 case MachinePlaysWhite:
11953 case MachinePlaysBlack:
11954 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11955 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11958 if (forwardMostMove < 2) return;
11959 currentMove = forwardMostMove = forwardMostMove - 2;
11960 whiteTimeRemaining = timeRemaining[0][currentMove];
11961 blackTimeRemaining = timeRemaining[1][currentMove];
11962 DisplayBothClocks();
11963 DisplayMove(currentMove - 1);
11964 ClearHighlights();/*!! could figure this out*/
11965 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11966 SendToProgram("remove\n", &first);
11967 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11970 case BeginningOfGame:
11974 case IcsPlayingWhite:
11975 case IcsPlayingBlack:
11976 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11977 SendToICS(ics_prefix);
11978 SendToICS("takeback 2\n");
11980 SendToICS(ics_prefix);
11981 SendToICS("takeback 1\n");
11990 ChessProgramState *cps;
11992 switch (gameMode) {
11993 case MachinePlaysWhite:
11994 if (!WhiteOnMove(forwardMostMove)) {
11995 DisplayError(_("It is your turn"), 0);
12000 case MachinePlaysBlack:
12001 if (WhiteOnMove(forwardMostMove)) {
12002 DisplayError(_("It is your turn"), 0);
12007 case TwoMachinesPlay:
12008 if (WhiteOnMove(forwardMostMove) ==
12009 (first.twoMachinesColor[0] == 'w')) {
12015 case BeginningOfGame:
12019 SendToProgram("?\n", cps);
12023 TruncateGameEvent()
12026 if (gameMode != EditGame) return;
12033 if (forwardMostMove > currentMove) {
12034 if (gameInfo.resultDetails != NULL) {
12035 free(gameInfo.resultDetails);
12036 gameInfo.resultDetails = NULL;
12037 gameInfo.result = GameUnfinished;
12039 forwardMostMove = currentMove;
12040 HistorySet(parseList, backwardMostMove, forwardMostMove,
12048 if (appData.noChessProgram) return;
12049 switch (gameMode) {
12050 case MachinePlaysWhite:
12051 if (WhiteOnMove(forwardMostMove)) {
12052 DisplayError(_("Wait until your turn"), 0);
12056 case BeginningOfGame:
12057 case MachinePlaysBlack:
12058 if (!WhiteOnMove(forwardMostMove)) {
12059 DisplayError(_("Wait until your turn"), 0);
12064 DisplayError(_("No hint available"), 0);
12067 SendToProgram("hint\n", &first);
12068 hintRequested = TRUE;
12074 if (appData.noChessProgram) return;
12075 switch (gameMode) {
12076 case MachinePlaysWhite:
12077 if (WhiteOnMove(forwardMostMove)) {
12078 DisplayError(_("Wait until your turn"), 0);
12082 case BeginningOfGame:
12083 case MachinePlaysBlack:
12084 if (!WhiteOnMove(forwardMostMove)) {
12085 DisplayError(_("Wait until your turn"), 0);
12090 EditPositionDone(TRUE);
12092 case TwoMachinesPlay:
12097 SendToProgram("bk\n", &first);
12098 bookOutput[0] = NULLCHAR;
12099 bookRequested = TRUE;
12105 char *tags = PGNTags(&gameInfo);
12106 TagsPopUp(tags, CmailMsg());
12110 /* end button procedures */
12113 PrintPosition(fp, move)
12119 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12120 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12121 char c = PieceToChar(boards[move][i][j]);
12122 fputc(c == 'x' ? '.' : c, fp);
12123 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12126 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12127 fprintf(fp, "white to play\n");
12129 fprintf(fp, "black to play\n");
12136 if (gameInfo.white != NULL) {
12137 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12143 /* Find last component of program's own name, using some heuristics */
12145 TidyProgramName(prog, host, buf)
12146 char *prog, *host, buf[MSG_SIZ];
12149 int local = (strcmp(host, "localhost") == 0);
12150 while (!local && (p = strchr(prog, ';')) != NULL) {
12152 while (*p == ' ') p++;
12155 if (*prog == '"' || *prog == '\'') {
12156 q = strchr(prog + 1, *prog);
12158 q = strchr(prog, ' ');
12160 if (q == NULL) q = prog + strlen(prog);
12162 while (p >= prog && *p != '/' && *p != '\\') p--;
12164 if(p == prog && *p == '"') p++;
12165 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12166 memcpy(buf, p, q - p);
12167 buf[q - p] = NULLCHAR;
12175 TimeControlTagValue()
12178 if (!appData.clockMode) {
12180 } else if (movesPerSession > 0) {
12181 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12182 } else if (timeIncrement == 0) {
12183 sprintf(buf, "%ld", timeControl/1000);
12185 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12187 return StrSave(buf);
12193 /* This routine is used only for certain modes */
12194 VariantClass v = gameInfo.variant;
12195 ClearGameInfo(&gameInfo);
12196 gameInfo.variant = v;
12198 switch (gameMode) {
12199 case MachinePlaysWhite:
12200 gameInfo.event = StrSave( appData.pgnEventHeader );
12201 gameInfo.site = StrSave(HostName());
12202 gameInfo.date = PGNDate();
12203 gameInfo.round = StrSave("-");
12204 gameInfo.white = StrSave(first.tidy);
12205 gameInfo.black = StrSave(UserName());
12206 gameInfo.timeControl = TimeControlTagValue();
12209 case MachinePlaysBlack:
12210 gameInfo.event = StrSave( appData.pgnEventHeader );
12211 gameInfo.site = StrSave(HostName());
12212 gameInfo.date = PGNDate();
12213 gameInfo.round = StrSave("-");
12214 gameInfo.white = StrSave(UserName());
12215 gameInfo.black = StrSave(first.tidy);
12216 gameInfo.timeControl = TimeControlTagValue();
12219 case TwoMachinesPlay:
12220 gameInfo.event = StrSave( appData.pgnEventHeader );
12221 gameInfo.site = StrSave(HostName());
12222 gameInfo.date = PGNDate();
12223 if (matchGame > 0) {
12225 sprintf(buf, "%d", matchGame);
12226 gameInfo.round = StrSave(buf);
12228 gameInfo.round = StrSave("-");
12230 if (first.twoMachinesColor[0] == 'w') {
12231 gameInfo.white = StrSave(first.tidy);
12232 gameInfo.black = StrSave(second.tidy);
12234 gameInfo.white = StrSave(second.tidy);
12235 gameInfo.black = StrSave(first.tidy);
12237 gameInfo.timeControl = TimeControlTagValue();
12241 gameInfo.event = StrSave("Edited game");
12242 gameInfo.site = StrSave(HostName());
12243 gameInfo.date = PGNDate();
12244 gameInfo.round = StrSave("-");
12245 gameInfo.white = StrSave("-");
12246 gameInfo.black = StrSave("-");
12250 gameInfo.event = StrSave("Edited position");
12251 gameInfo.site = StrSave(HostName());
12252 gameInfo.date = PGNDate();
12253 gameInfo.round = StrSave("-");
12254 gameInfo.white = StrSave("-");
12255 gameInfo.black = StrSave("-");
12258 case IcsPlayingWhite:
12259 case IcsPlayingBlack:
12264 case PlayFromGameFile:
12265 gameInfo.event = StrSave("Game from non-PGN file");
12266 gameInfo.site = StrSave(HostName());
12267 gameInfo.date = PGNDate();
12268 gameInfo.round = StrSave("-");
12269 gameInfo.white = StrSave("?");
12270 gameInfo.black = StrSave("?");
12279 ReplaceComment(index, text)
12285 while (*text == '\n') text++;
12286 len = strlen(text);
12287 while (len > 0 && text[len - 1] == '\n') len--;
12289 if (commentList[index] != NULL)
12290 free(commentList[index]);
12293 commentList[index] = NULL;
12296 commentList[index] = (char *) malloc(len + 2);
12297 strncpy(commentList[index], text, len);
12298 commentList[index][len] = '\n';
12299 commentList[index][len + 1] = NULLCHAR;
12312 if (ch == '\r') continue;
12314 } while (ch != '\0');
12318 AppendComment(index, text)
12325 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12328 while (*text == '\n') text++;
12329 len = strlen(text);
12330 while (len > 0 && text[len - 1] == '\n') len--;
12332 if (len == 0) return;
12334 if (commentList[index] != NULL) {
12335 old = commentList[index];
12336 oldlen = strlen(old);
12337 commentList[index] = (char *) malloc(oldlen + len + 2);
12338 strcpy(commentList[index], old);
12340 strncpy(&commentList[index][oldlen], text, len);
12341 commentList[index][oldlen + len] = '\n';
12342 commentList[index][oldlen + len + 1] = NULLCHAR;
12344 commentList[index] = (char *) malloc(len + 2);
12345 strncpy(commentList[index], text, len);
12346 commentList[index][len] = '\n';
12347 commentList[index][len + 1] = NULLCHAR;
12351 static char * FindStr( char * text, char * sub_text )
12353 char * result = strstr( text, sub_text );
12355 if( result != NULL ) {
12356 result += strlen( sub_text );
12362 /* [AS] Try to extract PV info from PGN comment */
12363 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12364 char *GetInfoFromComment( int index, char * text )
12368 if( text != NULL && index > 0 ) {
12371 int time = -1, sec = 0, deci;
12372 char * s_eval = FindStr( text, "[%eval " );
12373 char * s_emt = FindStr( text, "[%emt " );
12375 if( s_eval != NULL || s_emt != NULL ) {
12379 if( s_eval != NULL ) {
12380 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12384 if( delim != ']' ) {
12389 if( s_emt != NULL ) {
12393 /* We expect something like: [+|-]nnn.nn/dd */
12396 sep = strchr( text, '/' );
12397 if( sep == NULL || sep < (text+4) ) {
12401 time = -1; sec = -1; deci = -1;
12402 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12403 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12404 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12405 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12409 if( score_lo < 0 || score_lo >= 100 ) {
12413 if(sec >= 0) time = 600*time + 10*sec; else
12414 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12416 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12418 /* [HGM] PV time: now locate end of PV info */
12419 while( *++sep >= '0' && *sep <= '9'); // strip depth
12421 while( *++sep >= '0' && *sep <= '9'); // strip time
12423 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12425 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12426 while(*sep == ' ') sep++;
12437 pvInfoList[index-1].depth = depth;
12438 pvInfoList[index-1].score = score;
12439 pvInfoList[index-1].time = 10*time; // centi-sec
12445 SendToProgram(message, cps)
12447 ChessProgramState *cps;
12449 int count, outCount, error;
12452 if (cps->pr == NULL) return;
12455 if (appData.debugMode) {
12458 fprintf(debugFP, "%ld >%-6s: %s",
12459 SubtractTimeMarks(&now, &programStartTime),
12460 cps->which, message);
12463 count = strlen(message);
12464 outCount = OutputToProcess(cps->pr, message, count, &error);
12465 if (outCount < count && !exiting
12466 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12467 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12468 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12469 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12470 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12471 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12473 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12475 gameInfo.resultDetails = StrSave(buf);
12477 DisplayFatalError(buf, error, 1);
12482 ReceiveFromProgram(isr, closure, message, count, error)
12483 InputSourceRef isr;
12491 ChessProgramState *cps = (ChessProgramState *)closure;
12493 if (isr != cps->isr) return; /* Killed intentionally */
12497 _("Error: %s chess program (%s) exited unexpectedly"),
12498 cps->which, cps->program);
12499 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12500 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12501 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12502 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12504 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12506 gameInfo.resultDetails = StrSave(buf);
12508 RemoveInputSource(cps->isr);
12509 DisplayFatalError(buf, 0, 1);
12512 _("Error reading from %s chess program (%s)"),
12513 cps->which, cps->program);
12514 RemoveInputSource(cps->isr);
12516 /* [AS] Program is misbehaving badly... kill it */
12517 if( count == -2 ) {
12518 DestroyChildProcess( cps->pr, 9 );
12522 DisplayFatalError(buf, error, 1);
12527 if ((end_str = strchr(message, '\r')) != NULL)
12528 *end_str = NULLCHAR;
12529 if ((end_str = strchr(message, '\n')) != NULL)
12530 *end_str = NULLCHAR;
12532 if (appData.debugMode) {
12533 TimeMark now; int print = 1;
12534 char *quote = ""; char c; int i;
12536 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12537 char start = message[0];
12538 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12539 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12540 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12541 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12542 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12543 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12544 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12545 sscanf(message, "pong %c", &c)!=1 && start != '#')
12546 { quote = "# "; print = (appData.engineComments == 2); }
12547 message[0] = start; // restore original message
12551 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12552 SubtractTimeMarks(&now, &programStartTime), cps->which,
12558 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12559 if (appData.icsEngineAnalyze) {
12560 if (strstr(message, "whisper") != NULL ||
12561 strstr(message, "kibitz") != NULL ||
12562 strstr(message, "tellics") != NULL) return;
12565 HandleMachineMove(message, cps);
12570 SendTimeControl(cps, mps, tc, inc, sd, st)
12571 ChessProgramState *cps;
12572 int mps, inc, sd, st;
12578 if( timeControl_2 > 0 ) {
12579 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12580 tc = timeControl_2;
12583 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12584 inc /= cps->timeOdds;
12585 st /= cps->timeOdds;
12587 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12590 /* Set exact time per move, normally using st command */
12591 if (cps->stKludge) {
12592 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12594 if (seconds == 0) {
12595 sprintf(buf, "level 1 %d\n", st/60);
12597 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12600 sprintf(buf, "st %d\n", st);
12603 /* Set conventional or incremental time control, using level command */
12604 if (seconds == 0) {
12605 /* Note old gnuchess bug -- minutes:seconds used to not work.
12606 Fixed in later versions, but still avoid :seconds
12607 when seconds is 0. */
12608 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12610 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12611 seconds, inc/1000);
12614 SendToProgram(buf, cps);
12616 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12617 /* Orthogonally, limit search to given depth */
12619 if (cps->sdKludge) {
12620 sprintf(buf, "depth\n%d\n", sd);
12622 sprintf(buf, "sd %d\n", sd);
12624 SendToProgram(buf, cps);
12627 if(cps->nps > 0) { /* [HGM] nps */
12628 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12630 sprintf(buf, "nps %d\n", cps->nps);
12631 SendToProgram(buf, cps);
12636 ChessProgramState *WhitePlayer()
12637 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12639 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12640 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12646 SendTimeRemaining(cps, machineWhite)
12647 ChessProgramState *cps;
12648 int /*boolean*/ machineWhite;
12650 char message[MSG_SIZ];
12653 /* Note: this routine must be called when the clocks are stopped
12654 or when they have *just* been set or switched; otherwise
12655 it will be off by the time since the current tick started.
12657 if (machineWhite) {
12658 time = whiteTimeRemaining / 10;
12659 otime = blackTimeRemaining / 10;
12661 time = blackTimeRemaining / 10;
12662 otime = whiteTimeRemaining / 10;
12664 /* [HGM] translate opponent's time by time-odds factor */
12665 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12666 if (appData.debugMode) {
12667 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12670 if (time <= 0) time = 1;
12671 if (otime <= 0) otime = 1;
12673 sprintf(message, "time %ld\n", time);
12674 SendToProgram(message, cps);
12676 sprintf(message, "otim %ld\n", otime);
12677 SendToProgram(message, cps);
12681 BoolFeature(p, name, loc, cps)
12685 ChessProgramState *cps;
12688 int len = strlen(name);
12690 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12692 sscanf(*p, "%d", &val);
12694 while (**p && **p != ' ') (*p)++;
12695 sprintf(buf, "accepted %s\n", name);
12696 SendToProgram(buf, cps);
12703 IntFeature(p, name, loc, cps)
12707 ChessProgramState *cps;
12710 int len = strlen(name);
12711 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12713 sscanf(*p, "%d", loc);
12714 while (**p && **p != ' ') (*p)++;
12715 sprintf(buf, "accepted %s\n", name);
12716 SendToProgram(buf, cps);
12723 StringFeature(p, name, loc, cps)
12727 ChessProgramState *cps;
12730 int len = strlen(name);
12731 if (strncmp((*p), name, len) == 0
12732 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12734 sscanf(*p, "%[^\"]", loc);
12735 while (**p && **p != '\"') (*p)++;
12736 if (**p == '\"') (*p)++;
12737 sprintf(buf, "accepted %s\n", name);
12738 SendToProgram(buf, cps);
12745 ParseOption(Option *opt, ChessProgramState *cps)
12746 // [HGM] options: process the string that defines an engine option, and determine
12747 // name, type, default value, and allowed value range
12749 char *p, *q, buf[MSG_SIZ];
12750 int n, min = (-1)<<31, max = 1<<31, def;
12752 if(p = strstr(opt->name, " -spin ")) {
12753 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12754 if(max < min) max = min; // enforce consistency
12755 if(def < min) def = min;
12756 if(def > max) def = max;
12761 } else if((p = strstr(opt->name, " -slider "))) {
12762 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12763 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12764 if(max < min) max = min; // enforce consistency
12765 if(def < min) def = min;
12766 if(def > max) def = max;
12770 opt->type = Spin; // Slider;
12771 } else if((p = strstr(opt->name, " -string "))) {
12772 opt->textValue = p+9;
12773 opt->type = TextBox;
12774 } else if((p = strstr(opt->name, " -file "))) {
12775 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12776 opt->textValue = p+7;
12777 opt->type = TextBox; // FileName;
12778 } else if((p = strstr(opt->name, " -path "))) {
12779 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12780 opt->textValue = p+7;
12781 opt->type = TextBox; // PathName;
12782 } else if(p = strstr(opt->name, " -check ")) {
12783 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12784 opt->value = (def != 0);
12785 opt->type = CheckBox;
12786 } else if(p = strstr(opt->name, " -combo ")) {
12787 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12788 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12789 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12790 opt->value = n = 0;
12791 while(q = StrStr(q, " /// ")) {
12792 n++; *q = 0; // count choices, and null-terminate each of them
12794 if(*q == '*') { // remember default, which is marked with * prefix
12798 cps->comboList[cps->comboCnt++] = q;
12800 cps->comboList[cps->comboCnt++] = NULL;
12802 opt->type = ComboBox;
12803 } else if(p = strstr(opt->name, " -button")) {
12804 opt->type = Button;
12805 } else if(p = strstr(opt->name, " -save")) {
12806 opt->type = SaveButton;
12807 } else return FALSE;
12808 *p = 0; // terminate option name
12809 // now look if the command-line options define a setting for this engine option.
12810 if(cps->optionSettings && cps->optionSettings[0])
12811 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12812 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12813 sprintf(buf, "option %s", p);
12814 if(p = strstr(buf, ",")) *p = 0;
12816 SendToProgram(buf, cps);
12822 FeatureDone(cps, val)
12823 ChessProgramState* cps;
12826 DelayedEventCallback cb = GetDelayedEvent();
12827 if ((cb == InitBackEnd3 && cps == &first) ||
12828 (cb == TwoMachinesEventIfReady && cps == &second)) {
12829 CancelDelayedEvent();
12830 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12832 cps->initDone = val;
12835 /* Parse feature command from engine */
12837 ParseFeatures(args, cps)
12839 ChessProgramState *cps;
12847 while (*p == ' ') p++;
12848 if (*p == NULLCHAR) return;
12850 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12851 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12852 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12853 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12854 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12855 if (BoolFeature(&p, "reuse", &val, cps)) {
12856 /* Engine can disable reuse, but can't enable it if user said no */
12857 if (!val) cps->reuse = FALSE;
12860 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12861 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12862 if (gameMode == TwoMachinesPlay) {
12863 DisplayTwoMachinesTitle();
12869 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12870 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12871 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12872 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12873 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12874 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12875 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12876 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12877 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12878 if (IntFeature(&p, "done", &val, cps)) {
12879 FeatureDone(cps, val);
12882 /* Added by Tord: */
12883 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12884 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12885 /* End of additions by Tord */
12887 /* [HGM] added features: */
12888 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12889 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12890 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12891 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12892 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12893 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12894 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12895 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12896 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12897 SendToProgram(buf, cps);
12900 if(cps->nrOptions >= MAX_OPTIONS) {
12902 sprintf(buf, "%s engine has too many options\n", cps->which);
12903 DisplayError(buf, 0);
12907 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12908 /* End of additions by HGM */
12910 /* unknown feature: complain and skip */
12912 while (*q && *q != '=') q++;
12913 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12914 SendToProgram(buf, cps);
12920 while (*p && *p != '\"') p++;
12921 if (*p == '\"') p++;
12923 while (*p && *p != ' ') p++;
12931 PeriodicUpdatesEvent(newState)
12934 if (newState == appData.periodicUpdates)
12937 appData.periodicUpdates=newState;
12939 /* Display type changes, so update it now */
12940 // DisplayAnalysis();
12942 /* Get the ball rolling again... */
12944 AnalysisPeriodicEvent(1);
12945 StartAnalysisClock();
12950 PonderNextMoveEvent(newState)
12953 if (newState == appData.ponderNextMove) return;
12954 if (gameMode == EditPosition) EditPositionDone(TRUE);
12956 SendToProgram("hard\n", &first);
12957 if (gameMode == TwoMachinesPlay) {
12958 SendToProgram("hard\n", &second);
12961 SendToProgram("easy\n", &first);
12962 thinkOutput[0] = NULLCHAR;
12963 if (gameMode == TwoMachinesPlay) {
12964 SendToProgram("easy\n", &second);
12967 appData.ponderNextMove = newState;
12971 NewSettingEvent(option, command, value)
12977 if (gameMode == EditPosition) EditPositionDone(TRUE);
12978 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12979 SendToProgram(buf, &first);
12980 if (gameMode == TwoMachinesPlay) {
12981 SendToProgram(buf, &second);
12986 ShowThinkingEvent()
12987 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12989 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12990 int newState = appData.showThinking
12991 // [HGM] thinking: other features now need thinking output as well
12992 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12994 if (oldState == newState) return;
12995 oldState = newState;
12996 if (gameMode == EditPosition) EditPositionDone(TRUE);
12998 SendToProgram("post\n", &first);
12999 if (gameMode == TwoMachinesPlay) {
13000 SendToProgram("post\n", &second);
13003 SendToProgram("nopost\n", &first);
13004 thinkOutput[0] = NULLCHAR;
13005 if (gameMode == TwoMachinesPlay) {
13006 SendToProgram("nopost\n", &second);
13009 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13013 AskQuestionEvent(title, question, replyPrefix, which)
13014 char *title; char *question; char *replyPrefix; char *which;
13016 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13017 if (pr == NoProc) return;
13018 AskQuestion(title, question, replyPrefix, pr);
13022 DisplayMove(moveNumber)
13025 char message[MSG_SIZ];
13027 char cpThinkOutput[MSG_SIZ];
13029 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13031 if (moveNumber == forwardMostMove - 1 ||
13032 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13034 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13036 if (strchr(cpThinkOutput, '\n')) {
13037 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13040 *cpThinkOutput = NULLCHAR;
13043 /* [AS] Hide thinking from human user */
13044 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13045 *cpThinkOutput = NULLCHAR;
13046 if( thinkOutput[0] != NULLCHAR ) {
13049 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13050 cpThinkOutput[i] = '.';
13052 cpThinkOutput[i] = NULLCHAR;
13053 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13057 if (moveNumber == forwardMostMove - 1 &&
13058 gameInfo.resultDetails != NULL) {
13059 if (gameInfo.resultDetails[0] == NULLCHAR) {
13060 sprintf(res, " %s", PGNResult(gameInfo.result));
13062 sprintf(res, " {%s} %s",
13063 gameInfo.resultDetails, PGNResult(gameInfo.result));
13069 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13070 DisplayMessage(res, cpThinkOutput);
13072 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13073 WhiteOnMove(moveNumber) ? " " : ".. ",
13074 parseList[moveNumber], res);
13075 DisplayMessage(message, cpThinkOutput);
13080 DisplayComment(moveNumber, text)
13084 char title[MSG_SIZ];
13085 char buf[8000]; // comment can be long!
13088 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13089 strcpy(title, "Comment");
13091 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13092 WhiteOnMove(moveNumber) ? " " : ".. ",
13093 parseList[moveNumber]);
13095 // [HGM] PV info: display PV info together with (or as) comment
13096 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13097 if(text == NULL) text = "";
13098 score = pvInfoList[moveNumber].score;
13099 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13100 depth, (pvInfoList[moveNumber].time+50)/100, text);
13103 if (text != NULL && (appData.autoDisplayComment || commentUp))
13104 CommentPopUp(title, text);
13107 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13108 * might be busy thinking or pondering. It can be omitted if your
13109 * gnuchess is configured to stop thinking immediately on any user
13110 * input. However, that gnuchess feature depends on the FIONREAD
13111 * ioctl, which does not work properly on some flavors of Unix.
13115 ChessProgramState *cps;
13118 if (!cps->useSigint) return;
13119 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13120 switch (gameMode) {
13121 case MachinePlaysWhite:
13122 case MachinePlaysBlack:
13123 case TwoMachinesPlay:
13124 case IcsPlayingWhite:
13125 case IcsPlayingBlack:
13128 /* Skip if we know it isn't thinking */
13129 if (!cps->maybeThinking) return;
13130 if (appData.debugMode)
13131 fprintf(debugFP, "Interrupting %s\n", cps->which);
13132 InterruptChildProcess(cps->pr);
13133 cps->maybeThinking = FALSE;
13138 #endif /*ATTENTION*/
13144 if (whiteTimeRemaining <= 0) {
13147 if (appData.icsActive) {
13148 if (appData.autoCallFlag &&
13149 gameMode == IcsPlayingBlack && !blackFlag) {
13150 SendToICS(ics_prefix);
13151 SendToICS("flag\n");
13155 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13157 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13158 if (appData.autoCallFlag) {
13159 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13166 if (blackTimeRemaining <= 0) {
13169 if (appData.icsActive) {
13170 if (appData.autoCallFlag &&
13171 gameMode == IcsPlayingWhite && !whiteFlag) {
13172 SendToICS(ics_prefix);
13173 SendToICS("flag\n");
13177 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13179 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13180 if (appData.autoCallFlag) {
13181 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13194 if (!appData.clockMode || appData.icsActive ||
13195 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13198 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13200 if ( !WhiteOnMove(forwardMostMove) )
13201 /* White made time control */
13202 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13203 /* [HGM] time odds: correct new time quota for time odds! */
13204 / WhitePlayer()->timeOdds;
13206 /* Black made time control */
13207 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13208 / WhitePlayer()->other->timeOdds;
13212 DisplayBothClocks()
13214 int wom = gameMode == EditPosition ?
13215 !blackPlaysFirst : WhiteOnMove(currentMove);
13216 DisplayWhiteClock(whiteTimeRemaining, wom);
13217 DisplayBlackClock(blackTimeRemaining, !wom);
13221 /* Timekeeping seems to be a portability nightmare. I think everyone
13222 has ftime(), but I'm really not sure, so I'm including some ifdefs
13223 to use other calls if you don't. Clocks will be less accurate if
13224 you have neither ftime nor gettimeofday.
13227 /* VS 2008 requires the #include outside of the function */
13228 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13229 #include <sys/timeb.h>
13232 /* Get the current time as a TimeMark */
13237 #if HAVE_GETTIMEOFDAY
13239 struct timeval timeVal;
13240 struct timezone timeZone;
13242 gettimeofday(&timeVal, &timeZone);
13243 tm->sec = (long) timeVal.tv_sec;
13244 tm->ms = (int) (timeVal.tv_usec / 1000L);
13246 #else /*!HAVE_GETTIMEOFDAY*/
13249 // include <sys/timeb.h> / moved to just above start of function
13250 struct timeb timeB;
13253 tm->sec = (long) timeB.time;
13254 tm->ms = (int) timeB.millitm;
13256 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13257 tm->sec = (long) time(NULL);
13263 /* Return the difference in milliseconds between two
13264 time marks. We assume the difference will fit in a long!
13267 SubtractTimeMarks(tm2, tm1)
13268 TimeMark *tm2, *tm1;
13270 return 1000L*(tm2->sec - tm1->sec) +
13271 (long) (tm2->ms - tm1->ms);
13276 * Code to manage the game clocks.
13278 * In tournament play, black starts the clock and then white makes a move.
13279 * We give the human user a slight advantage if he is playing white---the
13280 * clocks don't run until he makes his first move, so it takes zero time.
13281 * Also, we don't account for network lag, so we could get out of sync
13282 * with GNU Chess's clock -- but then, referees are always right.
13285 static TimeMark tickStartTM;
13286 static long intendedTickLength;
13289 NextTickLength(timeRemaining)
13290 long timeRemaining;
13292 long nominalTickLength, nextTickLength;
13294 if (timeRemaining > 0L && timeRemaining <= 10000L)
13295 nominalTickLength = 100L;
13297 nominalTickLength = 1000L;
13298 nextTickLength = timeRemaining % nominalTickLength;
13299 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13301 return nextTickLength;
13304 /* Adjust clock one minute up or down */
13306 AdjustClock(Boolean which, int dir)
13308 if(which) blackTimeRemaining += 60000*dir;
13309 else whiteTimeRemaining += 60000*dir;
13310 DisplayBothClocks();
13313 /* Stop clocks and reset to a fresh time control */
13317 (void) StopClockTimer();
13318 if (appData.icsActive) {
13319 whiteTimeRemaining = blackTimeRemaining = 0;
13320 } else { /* [HGM] correct new time quote for time odds */
13321 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13322 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13324 if (whiteFlag || blackFlag) {
13326 whiteFlag = blackFlag = FALSE;
13328 DisplayBothClocks();
13331 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13333 /* Decrement running clock by amount of time that has passed */
13337 long timeRemaining;
13338 long lastTickLength, fudge;
13341 if (!appData.clockMode) return;
13342 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13346 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13348 /* Fudge if we woke up a little too soon */
13349 fudge = intendedTickLength - lastTickLength;
13350 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13352 if (WhiteOnMove(forwardMostMove)) {
13353 if(whiteNPS >= 0) lastTickLength = 0;
13354 timeRemaining = whiteTimeRemaining -= lastTickLength;
13355 DisplayWhiteClock(whiteTimeRemaining - fudge,
13356 WhiteOnMove(currentMove));
13358 if(blackNPS >= 0) lastTickLength = 0;
13359 timeRemaining = blackTimeRemaining -= lastTickLength;
13360 DisplayBlackClock(blackTimeRemaining - fudge,
13361 !WhiteOnMove(currentMove));
13364 if (CheckFlags()) return;
13367 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13368 StartClockTimer(intendedTickLength);
13370 /* if the time remaining has fallen below the alarm threshold, sound the
13371 * alarm. if the alarm has sounded and (due to a takeback or time control
13372 * with increment) the time remaining has increased to a level above the
13373 * threshold, reset the alarm so it can sound again.
13376 if (appData.icsActive && appData.icsAlarm) {
13378 /* make sure we are dealing with the user's clock */
13379 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13380 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13383 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13384 alarmSounded = FALSE;
13385 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13387 alarmSounded = TRUE;
13393 /* A player has just moved, so stop the previously running
13394 clock and (if in clock mode) start the other one.
13395 We redisplay both clocks in case we're in ICS mode, because
13396 ICS gives us an update to both clocks after every move.
13397 Note that this routine is called *after* forwardMostMove
13398 is updated, so the last fractional tick must be subtracted
13399 from the color that is *not* on move now.
13402 SwitchClocks(int newMoveNr)
13404 long lastTickLength;
13406 int flagged = FALSE;
13410 if (StopClockTimer() && appData.clockMode) {
13411 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13412 if (!WhiteOnMove(forwardMostMove)) {
13413 if(blackNPS >= 0) lastTickLength = 0;
13414 blackTimeRemaining -= lastTickLength;
13415 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13416 // if(pvInfoList[forwardMostMove-1].time == -1)
13417 pvInfoList[forwardMostMove-1].time = // use GUI time
13418 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13420 if(whiteNPS >= 0) lastTickLength = 0;
13421 whiteTimeRemaining -= lastTickLength;
13422 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13423 // if(pvInfoList[forwardMostMove-1].time == -1)
13424 pvInfoList[forwardMostMove-1].time =
13425 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13427 flagged = CheckFlags();
13429 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13430 CheckTimeControl();
13432 if (flagged || !appData.clockMode) return;
13434 switch (gameMode) {
13435 case MachinePlaysBlack:
13436 case MachinePlaysWhite:
13437 case BeginningOfGame:
13438 if (pausing) return;
13442 case PlayFromGameFile:
13451 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13452 whiteTimeRemaining : blackTimeRemaining);
13453 StartClockTimer(intendedTickLength);
13457 /* Stop both clocks */
13461 long lastTickLength;
13464 if (!StopClockTimer()) return;
13465 if (!appData.clockMode) return;
13469 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13470 if (WhiteOnMove(forwardMostMove)) {
13471 if(whiteNPS >= 0) lastTickLength = 0;
13472 whiteTimeRemaining -= lastTickLength;
13473 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13475 if(blackNPS >= 0) lastTickLength = 0;
13476 blackTimeRemaining -= lastTickLength;
13477 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13482 /* Start clock of player on move. Time may have been reset, so
13483 if clock is already running, stop and restart it. */
13487 (void) StopClockTimer(); /* in case it was running already */
13488 DisplayBothClocks();
13489 if (CheckFlags()) return;
13491 if (!appData.clockMode) return;
13492 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13494 GetTimeMark(&tickStartTM);
13495 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13496 whiteTimeRemaining : blackTimeRemaining);
13498 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13499 whiteNPS = blackNPS = -1;
13500 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13501 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13502 whiteNPS = first.nps;
13503 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13504 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13505 blackNPS = first.nps;
13506 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13507 whiteNPS = second.nps;
13508 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13509 blackNPS = second.nps;
13510 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13512 StartClockTimer(intendedTickLength);
13519 long second, minute, hour, day;
13521 static char buf[32];
13523 if (ms > 0 && ms <= 9900) {
13524 /* convert milliseconds to tenths, rounding up */
13525 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13527 sprintf(buf, " %03.1f ", tenths/10.0);
13531 /* convert milliseconds to seconds, rounding up */
13532 /* use floating point to avoid strangeness of integer division
13533 with negative dividends on many machines */
13534 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13541 day = second / (60 * 60 * 24);
13542 second = second % (60 * 60 * 24);
13543 hour = second / (60 * 60);
13544 second = second % (60 * 60);
13545 minute = second / 60;
13546 second = second % 60;
13549 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13550 sign, day, hour, minute, second);
13552 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13554 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13561 * This is necessary because some C libraries aren't ANSI C compliant yet.
13564 StrStr(string, match)
13565 char *string, *match;
13569 length = strlen(match);
13571 for (i = strlen(string) - length; i >= 0; i--, string++)
13572 if (!strncmp(match, string, length))
13579 StrCaseStr(string, match)
13580 char *string, *match;
13584 length = strlen(match);
13586 for (i = strlen(string) - length; i >= 0; i--, string++) {
13587 for (j = 0; j < length; j++) {
13588 if (ToLower(match[j]) != ToLower(string[j]))
13591 if (j == length) return string;
13605 c1 = ToLower(*s1++);
13606 c2 = ToLower(*s2++);
13607 if (c1 > c2) return 1;
13608 if (c1 < c2) return -1;
13609 if (c1 == NULLCHAR) return 0;
13618 return isupper(c) ? tolower(c) : c;
13626 return islower(c) ? toupper(c) : c;
13628 #endif /* !_amigados */
13636 if ((ret = (char *) malloc(strlen(s) + 1))) {
13643 StrSavePtr(s, savePtr)
13644 char *s, **savePtr;
13649 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13650 strcpy(*savePtr, s);
13662 clock = time((time_t *)NULL);
13663 tm = localtime(&clock);
13664 sprintf(buf, "%04d.%02d.%02d",
13665 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13666 return StrSave(buf);
13671 PositionToFEN(move, overrideCastling)
13673 char *overrideCastling;
13675 int i, j, fromX, fromY, toX, toY;
13682 whiteToPlay = (gameMode == EditPosition) ?
13683 !blackPlaysFirst : (move % 2 == 0);
13686 /* Piece placement data */
13687 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13689 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13690 if (boards[move][i][j] == EmptySquare) {
13692 } else { ChessSquare piece = boards[move][i][j];
13693 if (emptycount > 0) {
13694 if(emptycount<10) /* [HGM] can be >= 10 */
13695 *p++ = '0' + emptycount;
13696 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13699 if(PieceToChar(piece) == '+') {
13700 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13702 piece = (ChessSquare)(DEMOTED piece);
13704 *p++ = PieceToChar(piece);
13706 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13707 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13712 if (emptycount > 0) {
13713 if(emptycount<10) /* [HGM] can be >= 10 */
13714 *p++ = '0' + emptycount;
13715 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13722 /* [HGM] print Crazyhouse or Shogi holdings */
13723 if( gameInfo.holdingsWidth ) {
13724 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13726 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13727 piece = boards[move][i][BOARD_WIDTH-1];
13728 if( piece != EmptySquare )
13729 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13730 *p++ = PieceToChar(piece);
13732 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13733 piece = boards[move][BOARD_HEIGHT-i-1][0];
13734 if( piece != EmptySquare )
13735 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13736 *p++ = PieceToChar(piece);
13739 if( q == p ) *p++ = '-';
13745 *p++ = whiteToPlay ? 'w' : 'b';
13748 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13749 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13751 if(nrCastlingRights) {
13753 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13754 /* [HGM] write directly from rights */
13755 if(castlingRights[move][2] >= 0 &&
13756 castlingRights[move][0] >= 0 )
13757 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13758 if(castlingRights[move][2] >= 0 &&
13759 castlingRights[move][1] >= 0 )
13760 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13761 if(castlingRights[move][5] >= 0 &&
13762 castlingRights[move][3] >= 0 )
13763 *p++ = castlingRights[move][3] + AAA;
13764 if(castlingRights[move][5] >= 0 &&
13765 castlingRights[move][4] >= 0 )
13766 *p++ = castlingRights[move][4] + AAA;
13769 /* [HGM] write true castling rights */
13770 if( nrCastlingRights == 6 ) {
13771 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13772 castlingRights[move][2] >= 0 ) *p++ = 'K';
13773 if(castlingRights[move][1] == BOARD_LEFT &&
13774 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13775 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13776 castlingRights[move][5] >= 0 ) *p++ = 'k';
13777 if(castlingRights[move][4] == BOARD_LEFT &&
13778 castlingRights[move][5] >= 0 ) *p++ = 'q';
13781 if (q == p) *p++ = '-'; /* No castling rights */
13785 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13786 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
13787 /* En passant target square */
13788 if (move > backwardMostMove) {
13789 fromX = moveList[move - 1][0] - AAA;
13790 fromY = moveList[move - 1][1] - ONE;
13791 toX = moveList[move - 1][2] - AAA;
13792 toY = moveList[move - 1][3] - ONE;
13793 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13794 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13795 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13797 /* 2-square pawn move just happened */
13799 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13803 } else if(move == backwardMostMove) {
13804 // [HGM] perhaps we should always do it like this, and forget the above?
13805 if(epStatus[move] >= 0) {
13806 *p++ = epStatus[move] + AAA;
13807 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13818 /* [HGM] find reversible plies */
13819 { int i = 0, j=move;
13821 if (appData.debugMode) { int k;
13822 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13823 for(k=backwardMostMove; k<=forwardMostMove; k++)
13824 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13828 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13829 if( j == backwardMostMove ) i += initialRulePlies;
13830 sprintf(p, "%d ", i);
13831 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13833 /* Fullmove number */
13834 sprintf(p, "%d", (move / 2) + 1);
13836 return StrSave(buf);
13840 ParseFEN(board, blackPlaysFirst, fen)
13842 int *blackPlaysFirst;
13852 /* [HGM] by default clear Crazyhouse holdings, if present */
13853 if(gameInfo.holdingsWidth) {
13854 for(i=0; i<BOARD_HEIGHT; i++) {
13855 board[i][0] = EmptySquare; /* black holdings */
13856 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13857 board[i][1] = (ChessSquare) 0; /* black counts */
13858 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13862 /* Piece placement data */
13863 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13866 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13867 if (*p == '/') p++;
13868 emptycount = gameInfo.boardWidth - j;
13869 while (emptycount--)
13870 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13872 #if(BOARD_SIZE >= 10)
13873 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13874 p++; emptycount=10;
13875 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13876 while (emptycount--)
13877 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13879 } else if (isdigit(*p)) {
13880 emptycount = *p++ - '0';
13881 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13882 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13883 while (emptycount--)
13884 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13885 } else if (*p == '+' || isalpha(*p)) {
13886 if (j >= gameInfo.boardWidth) return FALSE;
13888 piece = CharToPiece(*++p);
13889 if(piece == EmptySquare) return FALSE; /* unknown piece */
13890 piece = (ChessSquare) (PROMOTED piece ); p++;
13891 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13892 } else piece = CharToPiece(*p++);
13894 if(piece==EmptySquare) return FALSE; /* unknown piece */
13895 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13896 piece = (ChessSquare) (PROMOTED piece);
13897 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13900 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13906 while (*p == '/' || *p == ' ') p++;
13908 /* [HGM] look for Crazyhouse holdings here */
13909 while(*p==' ') p++;
13910 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13912 if(*p == '-' ) *p++; /* empty holdings */ else {
13913 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13914 /* if we would allow FEN reading to set board size, we would */
13915 /* have to add holdings and shift the board read so far here */
13916 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13918 if((int) piece >= (int) BlackPawn ) {
13919 i = (int)piece - (int)BlackPawn;
13920 i = PieceToNumber((ChessSquare)i);
13921 if( i >= gameInfo.holdingsSize ) return FALSE;
13922 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13923 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13925 i = (int)piece - (int)WhitePawn;
13926 i = PieceToNumber((ChessSquare)i);
13927 if( i >= gameInfo.holdingsSize ) return FALSE;
13928 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13929 board[i][BOARD_WIDTH-2]++; /* black holdings */
13933 if(*p == ']') *p++;
13936 while(*p == ' ') p++;
13941 *blackPlaysFirst = FALSE;
13944 *blackPlaysFirst = TRUE;
13950 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13951 /* return the extra info in global variiables */
13953 /* set defaults in case FEN is incomplete */
13954 FENepStatus = EP_UNKNOWN;
13955 for(i=0; i<nrCastlingRights; i++ ) {
13956 FENcastlingRights[i] =
13957 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13958 } /* assume possible unless obviously impossible */
13959 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13960 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13961 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13962 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13963 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13964 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13965 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13966 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13969 while(*p==' ') p++;
13970 if(nrCastlingRights) {
13971 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13972 /* castling indicator present, so default becomes no castlings */
13973 for(i=0; i<nrCastlingRights; i++ ) {
13974 FENcastlingRights[i] = -1;
13977 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13978 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13979 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13980 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13981 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13983 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13984 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13985 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13987 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13988 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13989 if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13990 && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13991 if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13992 && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13995 for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13996 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13997 FENcastlingRights[2] = whiteKingFile;
14000 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14001 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
14002 FENcastlingRights[2] = whiteKingFile;
14005 for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
14006 FENcastlingRights[3] = i != blackKingFile ? i : -1;
14007 FENcastlingRights[5] = blackKingFile;
14010 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14011 FENcastlingRights[4] = i != blackKingFile ? i : -1;
14012 FENcastlingRights[5] = blackKingFile;
14015 default: /* FRC castlings */
14016 if(c >= 'a') { /* black rights */
14017 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14018 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14019 if(i == BOARD_RGHT) break;
14020 FENcastlingRights[5] = i;
14022 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14023 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14025 FENcastlingRights[3] = c;
14027 FENcastlingRights[4] = c;
14028 } else { /* white rights */
14029 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14030 if(board[0][i] == WhiteKing) break;
14031 if(i == BOARD_RGHT) break;
14032 FENcastlingRights[2] = i;
14033 c -= AAA - 'a' + 'A';
14034 if(board[0][c] >= WhiteKing) break;
14036 FENcastlingRights[0] = c;
14038 FENcastlingRights[1] = c;
14042 for(i=0; i<nrCastlingRights; i++)
14043 if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14044 if (appData.debugMode) {
14045 fprintf(debugFP, "FEN castling rights:");
14046 for(i=0; i<nrCastlingRights; i++)
14047 fprintf(debugFP, " %d", FENcastlingRights[i]);
14048 fprintf(debugFP, "\n");
14051 while(*p==' ') p++;
14054 /* read e.p. field in games that know e.p. capture */
14055 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14056 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14058 p++; FENepStatus = EP_NONE;
14060 char c = *p++ - AAA;
14062 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14063 if(*p >= '0' && *p <='9') *p++;
14069 if(sscanf(p, "%d", &i) == 1) {
14070 FENrulePlies = i; /* 50-move ply counter */
14071 /* (The move number is still ignored) */
14078 EditPositionPasteFEN(char *fen)
14081 Board initial_position;
14083 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14084 DisplayError(_("Bad FEN position in clipboard"), 0);
14087 int savedBlackPlaysFirst = blackPlaysFirst;
14088 EditPositionEvent();
14089 blackPlaysFirst = savedBlackPlaysFirst;
14090 CopyBoard(boards[0], initial_position);
14091 /* [HGM] copy FEN attributes as well */
14093 initialRulePlies = FENrulePlies;
14094 epStatus[0] = FENepStatus;
14095 for( i=0; i<nrCastlingRights; i++ )
14096 castlingRights[0][i] = FENcastlingRights[i];
14098 EditPositionDone(FALSE);
14099 DisplayBothClocks();
14100 DrawPosition(FALSE, boards[currentMove]);
14105 static char cseq[12] = "\\ ";
14107 Boolean set_cont_sequence(char *new_seq)
14112 // handle bad attempts to set the sequence
14114 return 0; // acceptable error - no debug
14116 len = strlen(new_seq);
14117 ret = (len > 0) && (len < sizeof(cseq));
14119 strcpy(cseq, new_seq);
14120 else if (appData.debugMode)
14121 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14126 reformat a source message so words don't cross the width boundary. internal
14127 newlines are not removed. returns the wrapped size (no null character unless
14128 included in source message). If dest is NULL, only calculate the size required
14129 for the dest buffer. lp argument indicats line position upon entry, and it's
14130 passed back upon exit.
14132 int wrap(char *dest, char *src, int count, int width, int *lp)
14134 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14136 cseq_len = strlen(cseq);
14137 old_line = line = *lp;
14138 ansi = len = clen = 0;
14140 for (i=0; i < count; i++)
14142 if (src[i] == '\033')
14145 // if we hit the width, back up
14146 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14148 // store i & len in case the word is too long
14149 old_i = i, old_len = len;
14151 // find the end of the last word
14152 while (i && src[i] != ' ' && src[i] != '\n')
14158 // word too long? restore i & len before splitting it
14159 if ((old_i-i+clen) >= width)
14166 if (i && src[i-1] == ' ')
14169 if (src[i] != ' ' && src[i] != '\n')
14176 // now append the newline and continuation sequence
14181 strncpy(dest+len, cseq, cseq_len);
14189 dest[len] = src[i];
14193 if (src[i] == '\n')
14198 if (dest && appData.debugMode)
14200 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14201 count, width, line, len, *lp);
14202 show_bytes(debugFP, src, count);
14203 fprintf(debugFP, "\ndest: ");
14204 show_bytes(debugFP, dest, len);
14205 fprintf(debugFP, "\n");
14207 *lp = dest ? line : old_line;