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((void));
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:
358 flags &= ~F_ALL_CASTLE_OK;
366 FILE *gameFileFP, *debugFP;
369 [AS] Note: sometimes, the sscanf() function is used to parse the input
370 into a fixed-size buffer. Because of this, we must be prepared to
371 receive strings as long as the size of the input buffer, which is currently
372 set to 4K for Windows and 8K for the rest.
373 So, we must either allocate sufficiently large buffers here, or
374 reduce the size of the input buffer in the input reading part.
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
379 char thinkOutput1[MSG_SIZ*10];
381 ChessProgramState first, second;
383 /* premove variables */
386 int premoveFromX = 0;
387 int premoveFromY = 0;
388 int premovePromoChar = 0;
390 Boolean alarmSounded;
391 /* end premove variables */
393 char *ics_prefix = "$";
394 int ics_type = ICS_GENERIC;
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
397 int pauseExamForwardMostMove = 0;
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
404 int whiteFlag = FALSE, blackFlag = FALSE;
405 int userOfferedDraw = FALSE;
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
408 int cmailMoveType[CMAIL_MAX_GAMES];
409 long ics_clock_paused = 0;
410 ProcRef icsPR = NoProc, cmailPR = NoProc;
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
412 GameMode gameMode = BeginningOfGame;
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
416 int hiddenThinkOutputState = 0; /* [AS] */
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
418 int adjudicateLossPlies = 6;
419 char white_holding[64], black_holding[64];
420 TimeMark lastNodeCountTime;
421 long lastNodeCount=0;
422 int have_sent_ICS_logon = 0;
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
425 long timeControl_2; /* [AS] Allow separate time controls */
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
427 long timeRemaining[2][MAX_MOVES];
429 TimeMark programStartTime;
430 char ics_handle[MSG_SIZ];
431 int have_set_title = 0;
433 /* animateTraining preserves the state of appData.animate
434 * when Training mode is activated. This allows the
435 * response to be animated when appData.animate == TRUE and
436 * appData.animateDragging == TRUE.
438 Boolean animateTraining;
444 Board boards[MAX_MOVES];
445 /* [HGM] Following 7 needed for accurate legality tests: */
446 signed char epStatus[MAX_MOVES];
447 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
448 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
449 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
450 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
451 int initialRulePlies, FENrulePlies;
453 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int mute; // mute all sounds
458 ChessSquare FIDEArray[2][BOARD_SIZE] = {
459 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
460 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
461 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
462 BlackKing, BlackBishop, BlackKnight, BlackRook }
465 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
466 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
467 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
468 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
469 BlackKing, BlackKing, BlackKnight, BlackRook }
472 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
473 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
474 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
475 { BlackRook, BlackMan, BlackBishop, BlackQueen,
476 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
479 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
480 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
481 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
482 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
483 BlackKing, BlackBishop, BlackKnight, BlackRook }
486 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
487 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
488 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
489 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
490 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
495 ChessSquare ShogiArray[2][BOARD_SIZE] = {
496 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
497 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
498 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
499 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
502 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
504 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
506 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
510 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
511 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
513 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
516 ChessSquare GreatArray[2][BOARD_SIZE] = {
517 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
518 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
519 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
520 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
523 ChessSquare JanusArray[2][BOARD_SIZE] = {
524 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
525 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
526 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
527 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
531 ChessSquare GothicArray[2][BOARD_SIZE] = {
532 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
533 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
534 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
535 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
538 #define GothicArray CapablancaArray
542 ChessSquare FalconArray[2][BOARD_SIZE] = {
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
544 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
546 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
549 #define FalconArray CapablancaArray
552 #else // !(BOARD_SIZE>=10)
553 #define XiangqiPosition FIDEArray
554 #define CapablancaArray FIDEArray
555 #define GothicArray FIDEArray
556 #define GreatArray FIDEArray
557 #endif // !(BOARD_SIZE>=10)
560 ChessSquare CourierArray[2][BOARD_SIZE] = {
561 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
562 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
563 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
564 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
566 #else // !(BOARD_SIZE>=12)
567 #define CourierArray CapablancaArray
568 #endif // !(BOARD_SIZE>=12)
571 Board initialPosition;
574 /* Convert str to a rating. Checks for special cases of "----",
576 "++++", etc. Also strips ()'s */
578 string_to_rating(str)
581 while(*str && !isdigit(*str)) ++str;
583 return 0; /* One of the special "no rating" cases */
591 /* Init programStats */
592 programStats.movelist[0] = 0;
593 programStats.depth = 0;
594 programStats.nr_moves = 0;
595 programStats.moves_left = 0;
596 programStats.nodes = 0;
597 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
598 programStats.score = 0;
599 programStats.got_only_move = 0;
600 programStats.got_fail = 0;
601 programStats.line_is_book = 0;
607 int matched, min, sec;
609 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
611 GetTimeMark(&programStartTime);
612 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
615 programStats.ok_to_send = 1;
616 programStats.seen_stat = 0;
619 * Initialize game list
625 * Internet chess server status
627 if (appData.icsActive) {
628 appData.matchMode = FALSE;
629 appData.matchGames = 0;
631 appData.noChessProgram = !appData.zippyPlay;
633 appData.zippyPlay = FALSE;
634 appData.zippyTalk = FALSE;
635 appData.noChessProgram = TRUE;
637 if (*appData.icsHelper != NULLCHAR) {
638 appData.useTelnet = TRUE;
639 appData.telnetProgram = appData.icsHelper;
642 appData.zippyTalk = appData.zippyPlay = FALSE;
645 /* [AS] Initialize pv info list [HGM] and game state */
649 for( i=0; i<MAX_MOVES; i++ ) {
650 pvInfoList[i].depth = -1;
652 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
657 * Parse timeControl resource
659 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
660 appData.movesPerSession)) {
662 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
663 DisplayFatalError(buf, 0, 2);
667 * Parse searchTime resource
669 if (*appData.searchTime != NULLCHAR) {
670 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
672 searchTime = min * 60;
673 } else if (matched == 2) {
674 searchTime = min * 60 + sec;
677 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
678 DisplayFatalError(buf, 0, 2);
682 /* [AS] Adjudication threshold */
683 adjudicateLossThreshold = appData.adjudicateLossThreshold;
685 first.which = "first";
686 second.which = "second";
687 first.maybeThinking = second.maybeThinking = FALSE;
688 first.pr = second.pr = NoProc;
689 first.isr = second.isr = NULL;
690 first.sendTime = second.sendTime = 2;
691 first.sendDrawOffers = 1;
692 if (appData.firstPlaysBlack) {
693 first.twoMachinesColor = "black\n";
694 second.twoMachinesColor = "white\n";
696 first.twoMachinesColor = "white\n";
697 second.twoMachinesColor = "black\n";
699 first.program = appData.firstChessProgram;
700 second.program = appData.secondChessProgram;
701 first.host = appData.firstHost;
702 second.host = appData.secondHost;
703 first.dir = appData.firstDirectory;
704 second.dir = appData.secondDirectory;
705 first.other = &second;
706 second.other = &first;
707 first.initString = appData.initString;
708 second.initString = appData.secondInitString;
709 first.computerString = appData.firstComputerString;
710 second.computerString = appData.secondComputerString;
711 first.useSigint = second.useSigint = TRUE;
712 first.useSigterm = second.useSigterm = TRUE;
713 first.reuse = appData.reuseFirst;
714 second.reuse = appData.reuseSecond;
715 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
716 second.nps = appData.secondNPS;
717 first.useSetboard = second.useSetboard = FALSE;
718 first.useSAN = second.useSAN = FALSE;
719 first.usePing = second.usePing = FALSE;
720 first.lastPing = second.lastPing = 0;
721 first.lastPong = second.lastPong = 0;
722 first.usePlayother = second.usePlayother = FALSE;
723 first.useColors = second.useColors = TRUE;
724 first.useUsermove = second.useUsermove = FALSE;
725 first.sendICS = second.sendICS = FALSE;
726 first.sendName = second.sendName = appData.icsActive;
727 first.sdKludge = second.sdKludge = FALSE;
728 first.stKludge = second.stKludge = FALSE;
729 TidyProgramName(first.program, first.host, first.tidy);
730 TidyProgramName(second.program, second.host, second.tidy);
731 first.matchWins = second.matchWins = 0;
732 strcpy(first.variants, appData.variant);
733 strcpy(second.variants, appData.variant);
734 first.analysisSupport = second.analysisSupport = 2; /* detect */
735 first.analyzing = second.analyzing = FALSE;
736 first.initDone = second.initDone = FALSE;
738 /* New features added by Tord: */
739 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
740 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
741 /* End of new features added by Tord. */
742 first.fenOverride = appData.fenOverride1;
743 second.fenOverride = appData.fenOverride2;
745 /* [HGM] time odds: set factor for each machine */
746 first.timeOdds = appData.firstTimeOdds;
747 second.timeOdds = appData.secondTimeOdds;
749 if(appData.timeOddsMode) {
750 norm = first.timeOdds;
751 if(norm > second.timeOdds) norm = second.timeOdds;
753 first.timeOdds /= norm;
754 second.timeOdds /= norm;
757 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
758 first.accumulateTC = appData.firstAccumulateTC;
759 second.accumulateTC = appData.secondAccumulateTC;
760 first.maxNrOfSessions = second.maxNrOfSessions = 1;
763 first.debug = second.debug = FALSE;
764 first.supportsNPS = second.supportsNPS = UNKNOWN;
767 first.optionSettings = appData.firstOptions;
768 second.optionSettings = appData.secondOptions;
770 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
771 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
772 first.isUCI = appData.firstIsUCI; /* [AS] */
773 second.isUCI = appData.secondIsUCI; /* [AS] */
774 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
775 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
777 if (appData.firstProtocolVersion > PROTOVER ||
778 appData.firstProtocolVersion < 1) {
780 sprintf(buf, _("protocol version %d not supported"),
781 appData.firstProtocolVersion);
782 DisplayFatalError(buf, 0, 2);
784 first.protocolVersion = appData.firstProtocolVersion;
787 if (appData.secondProtocolVersion > PROTOVER ||
788 appData.secondProtocolVersion < 1) {
790 sprintf(buf, _("protocol version %d not supported"),
791 appData.secondProtocolVersion);
792 DisplayFatalError(buf, 0, 2);
794 second.protocolVersion = appData.secondProtocolVersion;
797 if (appData.icsActive) {
798 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
799 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
800 appData.clockMode = FALSE;
801 first.sendTime = second.sendTime = 0;
805 /* Override some settings from environment variables, for backward
806 compatibility. Unfortunately it's not feasible to have the env
807 vars just set defaults, at least in xboard. Ugh.
809 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
814 if (appData.noChessProgram) {
815 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
816 sprintf(programVersion, "%s", PACKAGE_STRING);
818 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
819 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
820 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
823 if (!appData.icsActive) {
825 /* Check for variants that are supported only in ICS mode,
826 or not at all. Some that are accepted here nevertheless
827 have bugs; see comments below.
829 VariantClass variant = StringToVariant(appData.variant);
831 case VariantBughouse: /* need four players and two boards */
832 case VariantKriegspiel: /* need to hide pieces and move details */
833 /* case VariantFischeRandom: (Fabien: moved below) */
834 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
835 DisplayFatalError(buf, 0, 2);
839 case VariantLoadable:
849 sprintf(buf, _("Unknown variant name %s"), appData.variant);
850 DisplayFatalError(buf, 0, 2);
853 case VariantXiangqi: /* [HGM] repetition rules not implemented */
854 case VariantFairy: /* [HGM] TestLegality definitely off! */
855 case VariantGothic: /* [HGM] should work */
856 case VariantCapablanca: /* [HGM] should work */
857 case VariantCourier: /* [HGM] initial forced moves not implemented */
858 case VariantShogi: /* [HGM] drops not tested for legality */
859 case VariantKnightmate: /* [HGM] should work */
860 case VariantCylinder: /* [HGM] untested */
861 case VariantFalcon: /* [HGM] untested */
862 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
863 offboard interposition not understood */
864 case VariantNormal: /* definitely works! */
865 case VariantWildCastle: /* pieces not automatically shuffled */
866 case VariantNoCastle: /* pieces not automatically shuffled */
867 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
868 case VariantLosers: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantSuicide: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantGiveaway: /* should work except for win condition,
873 and doesn't know captures are mandatory */
874 case VariantTwoKings: /* should work */
875 case VariantAtomic: /* should work except for win condition */
876 case Variant3Check: /* should work except for win condition */
877 case VariantShatranj: /* should work except for all win conditions */
878 case VariantBerolina: /* might work if TestLegality is off */
879 case VariantCapaRandom: /* should work */
880 case VariantJanus: /* should work */
881 case VariantSuper: /* experimental */
882 case VariantGreat: /* experimental, requires legality testing to be off */
887 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
888 InitEngineUCI( installDir, &second );
891 int NextIntegerFromString( char ** str, long * value )
896 while( *s == ' ' || *s == '\t' ) {
902 if( *s >= '0' && *s <= '9' ) {
903 while( *s >= '0' && *s <= '9' ) {
904 *value = *value * 10 + (*s - '0');
916 int NextTimeControlFromString( char ** str, long * value )
919 int result = NextIntegerFromString( str, &temp );
922 *value = temp * 60; /* Minutes */
925 result = NextIntegerFromString( str, &temp );
926 *value += temp; /* Seconds */
933 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
934 { /* [HGM] routine added to read '+moves/time' for secondary time control */
935 int result = -1; long temp, temp2;
937 if(**str != '+') return -1; // old params remain in force!
939 if( NextTimeControlFromString( str, &temp ) ) return -1;
942 /* time only: incremental or sudden-death time control */
943 if(**str == '+') { /* increment follows; read it */
945 if(result = NextIntegerFromString( str, &temp2)) return -1;
948 *moves = 0; *tc = temp * 1000;
950 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
952 (*str)++; /* classical time control */
953 result = NextTimeControlFromString( str, &temp2);
962 int GetTimeQuota(int movenr)
963 { /* [HGM] get time to add from the multi-session time-control string */
964 int moves=1; /* kludge to force reading of first session */
965 long time, increment;
966 char *s = fullTimeControlString;
968 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
970 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
971 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
972 if(movenr == -1) return time; /* last move before new session */
973 if(!moves) return increment; /* current session is incremental */
974 if(movenr >= 0) movenr -= moves; /* we already finished this session */
975 } while(movenr >= -1); /* try again for next session */
977 return 0; // no new time quota on this move
981 ParseTimeControl(tc, ti, mps)
990 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
993 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
994 else sprintf(buf, "+%s+%d", tc, ti);
997 sprintf(buf, "+%d/%s", mps, tc);
998 else sprintf(buf, "+%s", tc);
1000 fullTimeControlString = StrSave(buf);
1002 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1007 /* Parse second time control */
1010 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1018 timeControl_2 = tc2 * 1000;
1028 timeControl = tc1 * 1000;
1031 timeIncrement = ti * 1000; /* convert to ms */
1032 movesPerSession = 0;
1035 movesPerSession = mps;
1043 if (appData.debugMode) {
1044 fprintf(debugFP, "%s\n", programVersion);
1047 set_cont_sequence(appData.wrapContSeq);
1048 if (appData.matchGames > 0) {
1049 appData.matchMode = TRUE;
1050 } else if (appData.matchMode) {
1051 appData.matchGames = 1;
1053 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1054 appData.matchGames = appData.sameColorGames;
1055 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1056 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1057 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1060 if (appData.noChessProgram || first.protocolVersion == 1) {
1063 /* kludge: allow timeout for initial "feature" commands */
1065 DisplayMessage("", _("Starting chess program"));
1066 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1071 InitBackEnd3 P((void))
1073 GameMode initialMode;
1077 InitChessProgram(&first, startedFromSetupPosition);
1080 if (appData.icsActive) {
1082 /* [DM] Make a console window if needed [HGM] merged ifs */
1087 if (*appData.icsCommPort != NULLCHAR) {
1088 sprintf(buf, _("Could not open comm port %s"),
1089 appData.icsCommPort);
1091 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1092 appData.icsHost, appData.icsPort);
1094 DisplayFatalError(buf, err, 1);
1099 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1101 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1102 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1103 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1104 } else if (appData.noChessProgram) {
1110 if (*appData.cmailGameName != NULLCHAR) {
1112 OpenLoopback(&cmailPR);
1114 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1118 DisplayMessage("", "");
1119 if (StrCaseCmp(appData.initialMode, "") == 0) {
1120 initialMode = BeginningOfGame;
1121 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1122 initialMode = TwoMachinesPlay;
1123 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1124 initialMode = AnalyzeFile;
1125 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1126 initialMode = AnalyzeMode;
1127 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1128 initialMode = MachinePlaysWhite;
1129 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1130 initialMode = MachinePlaysBlack;
1131 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1132 initialMode = EditGame;
1133 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1134 initialMode = EditPosition;
1135 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1136 initialMode = Training;
1138 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1139 DisplayFatalError(buf, 0, 2);
1143 if (appData.matchMode) {
1144 /* Set up machine vs. machine match */
1145 if (appData.noChessProgram) {
1146 DisplayFatalError(_("Can't have a match with no chess programs"),
1152 if (*appData.loadGameFile != NULLCHAR) {
1153 int index = appData.loadGameIndex; // [HGM] autoinc
1154 if(index<0) lastIndex = index = 1;
1155 if (!LoadGameFromFile(appData.loadGameFile,
1157 appData.loadGameFile, FALSE)) {
1158 DisplayFatalError(_("Bad game file"), 0, 1);
1161 } else if (*appData.loadPositionFile != NULLCHAR) {
1162 int index = appData.loadPositionIndex; // [HGM] autoinc
1163 if(index<0) lastIndex = index = 1;
1164 if (!LoadPositionFromFile(appData.loadPositionFile,
1166 appData.loadPositionFile)) {
1167 DisplayFatalError(_("Bad position file"), 0, 1);
1172 } else if (*appData.cmailGameName != NULLCHAR) {
1173 /* Set up cmail mode */
1174 ReloadCmailMsgEvent(TRUE);
1176 /* Set up other modes */
1177 if (initialMode == AnalyzeFile) {
1178 if (*appData.loadGameFile == NULLCHAR) {
1179 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1183 if (*appData.loadGameFile != NULLCHAR) {
1184 (void) LoadGameFromFile(appData.loadGameFile,
1185 appData.loadGameIndex,
1186 appData.loadGameFile, TRUE);
1187 } else if (*appData.loadPositionFile != NULLCHAR) {
1188 (void) LoadPositionFromFile(appData.loadPositionFile,
1189 appData.loadPositionIndex,
1190 appData.loadPositionFile);
1191 /* [HGM] try to make self-starting even after FEN load */
1192 /* to allow automatic setup of fairy variants with wtm */
1193 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1194 gameMode = BeginningOfGame;
1195 setboardSpoiledMachineBlack = 1;
1197 /* [HGM] loadPos: make that every new game uses the setup */
1198 /* from file as long as we do not switch variant */
1199 if(!blackPlaysFirst) { int i;
1200 startedFromPositionFile = TRUE;
1201 CopyBoard(filePosition, boards[0]);
1202 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1205 if (initialMode == AnalyzeMode) {
1206 if (appData.noChessProgram) {
1207 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1210 if (appData.icsActive) {
1211 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1215 } else if (initialMode == AnalyzeFile) {
1216 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1217 ShowThinkingEvent();
1219 AnalysisPeriodicEvent(1);
1220 } else if (initialMode == MachinePlaysWhite) {
1221 if (appData.noChessProgram) {
1222 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1226 if (appData.icsActive) {
1227 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1231 MachineWhiteEvent();
1232 } else if (initialMode == MachinePlaysBlack) {
1233 if (appData.noChessProgram) {
1234 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1238 if (appData.icsActive) {
1239 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1243 MachineBlackEvent();
1244 } else if (initialMode == TwoMachinesPlay) {
1245 if (appData.noChessProgram) {
1246 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1250 if (appData.icsActive) {
1251 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1256 } else if (initialMode == EditGame) {
1258 } else if (initialMode == EditPosition) {
1259 EditPositionEvent();
1260 } else if (initialMode == Training) {
1261 if (*appData.loadGameFile == NULLCHAR) {
1262 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1271 * Establish will establish a contact to a remote host.port.
1272 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1273 * used to talk to the host.
1274 * Returns 0 if okay, error code if not.
1281 if (*appData.icsCommPort != NULLCHAR) {
1282 /* Talk to the host through a serial comm port */
1283 return OpenCommPort(appData.icsCommPort, &icsPR);
1285 } else if (*appData.gateway != NULLCHAR) {
1286 if (*appData.remoteShell == NULLCHAR) {
1287 /* Use the rcmd protocol to run telnet program on a gateway host */
1288 snprintf(buf, sizeof(buf), "%s %s %s",
1289 appData.telnetProgram, appData.icsHost, appData.icsPort);
1290 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1293 /* Use the rsh program to run telnet program on a gateway host */
1294 if (*appData.remoteUser == NULLCHAR) {
1295 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1296 appData.gateway, appData.telnetProgram,
1297 appData.icsHost, appData.icsPort);
1299 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1300 appData.remoteShell, appData.gateway,
1301 appData.remoteUser, appData.telnetProgram,
1302 appData.icsHost, appData.icsPort);
1304 return StartChildProcess(buf, "", &icsPR);
1307 } else if (appData.useTelnet) {
1308 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1311 /* TCP socket interface differs somewhat between
1312 Unix and NT; handle details in the front end.
1314 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1319 show_bytes(fp, buf, count)
1325 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1326 fprintf(fp, "\\%03o", *buf & 0xff);
1335 /* Returns an errno value */
1337 OutputMaybeTelnet(pr, message, count, outError)
1343 char buf[8192], *p, *q, *buflim;
1344 int left, newcount, outcount;
1346 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1347 *appData.gateway != NULLCHAR) {
1348 if (appData.debugMode) {
1349 fprintf(debugFP, ">ICS: ");
1350 show_bytes(debugFP, message, count);
1351 fprintf(debugFP, "\n");
1353 return OutputToProcess(pr, message, count, outError);
1356 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1363 if (appData.debugMode) {
1364 fprintf(debugFP, ">ICS: ");
1365 show_bytes(debugFP, buf, newcount);
1366 fprintf(debugFP, "\n");
1368 outcount = OutputToProcess(pr, buf, newcount, outError);
1369 if (outcount < newcount) return -1; /* to be sure */
1376 } else if (((unsigned char) *p) == TN_IAC) {
1377 *q++ = (char) TN_IAC;
1384 if (appData.debugMode) {
1385 fprintf(debugFP, ">ICS: ");
1386 show_bytes(debugFP, buf, newcount);
1387 fprintf(debugFP, "\n");
1389 outcount = OutputToProcess(pr, buf, newcount, outError);
1390 if (outcount < newcount) return -1; /* to be sure */
1395 read_from_player(isr, closure, message, count, error)
1402 int outError, outCount;
1403 static int gotEof = 0;
1405 /* Pass data read from player on to ICS */
1408 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1409 if (outCount < count) {
1410 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1412 } else if (count < 0) {
1413 RemoveInputSource(isr);
1414 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1415 } else if (gotEof++ > 0) {
1416 RemoveInputSource(isr);
1417 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1423 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1424 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1425 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1426 SendToICS("date\n");
1427 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1430 /* added routine for printf style output to ics */
1431 void ics_printf(char *format, ...)
1433 char buffer[MSG_SIZ];
1436 va_start(args, format);
1437 vsnprintf(buffer, sizeof(buffer), format, args);
1438 buffer[sizeof(buffer)-1] = '\0';
1447 int count, outCount, outError;
1449 if (icsPR == NULL) return;
1452 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1453 if (outCount < count) {
1454 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1458 /* This is used for sending logon scripts to the ICS. Sending
1459 without a delay causes problems when using timestamp on ICC
1460 (at least on my machine). */
1462 SendToICSDelayed(s,msdelay)
1466 int count, outCount, outError;
1468 if (icsPR == NULL) return;
1471 if (appData.debugMode) {
1472 fprintf(debugFP, ">ICS: ");
1473 show_bytes(debugFP, s, count);
1474 fprintf(debugFP, "\n");
1476 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1478 if (outCount < count) {
1479 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1484 /* Remove all highlighting escape sequences in s
1485 Also deletes any suffix starting with '('
1488 StripHighlightAndTitle(s)
1491 static char retbuf[MSG_SIZ];
1494 while (*s != NULLCHAR) {
1495 while (*s == '\033') {
1496 while (*s != NULLCHAR && !isalpha(*s)) s++;
1497 if (*s != NULLCHAR) s++;
1499 while (*s != NULLCHAR && *s != '\033') {
1500 if (*s == '(' || *s == '[') {
1511 /* Remove all highlighting escape sequences in s */
1516 static char retbuf[MSG_SIZ];
1519 while (*s != NULLCHAR) {
1520 while (*s == '\033') {
1521 while (*s != NULLCHAR && !isalpha(*s)) s++;
1522 if (*s != NULLCHAR) s++;
1524 while (*s != NULLCHAR && *s != '\033') {
1532 char *variantNames[] = VARIANT_NAMES;
1537 return variantNames[v];
1541 /* Identify a variant from the strings the chess servers use or the
1542 PGN Variant tag names we use. */
1549 VariantClass v = VariantNormal;
1550 int i, found = FALSE;
1555 /* [HGM] skip over optional board-size prefixes */
1556 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1557 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1558 while( *e++ != '_');
1561 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1565 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1566 if (StrCaseStr(e, variantNames[i])) {
1567 v = (VariantClass) i;
1574 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1575 || StrCaseStr(e, "wild/fr")
1576 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1577 v = VariantFischeRandom;
1578 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1579 (i = 1, p = StrCaseStr(e, "w"))) {
1581 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1588 case 0: /* FICS only, actually */
1590 /* Castling legal even if K starts on d-file */
1591 v = VariantWildCastle;
1596 /* Castling illegal even if K & R happen to start in
1597 normal positions. */
1598 v = VariantNoCastle;
1611 /* Castling legal iff K & R start in normal positions */
1617 /* Special wilds for position setup; unclear what to do here */
1618 v = VariantLoadable;
1621 /* Bizarre ICC game */
1622 v = VariantTwoKings;
1625 v = VariantKriegspiel;
1631 v = VariantFischeRandom;
1634 v = VariantCrazyhouse;
1637 v = VariantBughouse;
1643 /* Not quite the same as FICS suicide! */
1644 v = VariantGiveaway;
1650 v = VariantShatranj;
1653 /* Temporary names for future ICC types. The name *will* change in
1654 the next xboard/WinBoard release after ICC defines it. */
1692 v = VariantCapablanca;
1695 v = VariantKnightmate;
1701 v = VariantCylinder;
1707 v = VariantCapaRandom;
1710 v = VariantBerolina;
1722 /* Found "wild" or "w" in the string but no number;
1723 must assume it's normal chess. */
1727 sprintf(buf, _("Unknown wild type %d"), wnum);
1728 DisplayError(buf, 0);
1734 if (appData.debugMode) {
1735 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1736 e, wnum, VariantName(v));
1741 static int leftover_start = 0, leftover_len = 0;
1742 char star_match[STAR_MATCH_N][MSG_SIZ];
1744 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1745 advance *index beyond it, and set leftover_start to the new value of
1746 *index; else return FALSE. If pattern contains the character '*', it
1747 matches any sequence of characters not containing '\r', '\n', or the
1748 character following the '*' (if any), and the matched sequence(s) are
1749 copied into star_match.
1752 looking_at(buf, index, pattern)
1757 char *bufp = &buf[*index], *patternp = pattern;
1759 char *matchp = star_match[0];
1762 if (*patternp == NULLCHAR) {
1763 *index = leftover_start = bufp - buf;
1767 if (*bufp == NULLCHAR) return FALSE;
1768 if (*patternp == '*') {
1769 if (*bufp == *(patternp + 1)) {
1771 matchp = star_match[++star_count];
1775 } else if (*bufp == '\n' || *bufp == '\r') {
1777 if (*patternp == NULLCHAR)
1782 *matchp++ = *bufp++;
1786 if (*patternp != *bufp) return FALSE;
1793 SendToPlayer(data, length)
1797 int error, outCount;
1798 outCount = OutputToProcess(NoProc, data, length, &error);
1799 if (outCount < length) {
1800 DisplayFatalError(_("Error writing to display"), error, 1);
1805 PackHolding(packed, holding)
1817 switch (runlength) {
1828 sprintf(q, "%d", runlength);
1840 /* Telnet protocol requests from the front end */
1842 TelnetRequest(ddww, option)
1843 unsigned char ddww, option;
1845 unsigned char msg[3];
1846 int outCount, outError;
1848 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1850 if (appData.debugMode) {
1851 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1867 sprintf(buf1, "%d", ddww);
1876 sprintf(buf2, "%d", option);
1879 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1884 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1886 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1893 if (!appData.icsActive) return;
1894 TelnetRequest(TN_DO, TN_ECHO);
1900 if (!appData.icsActive) return;
1901 TelnetRequest(TN_DONT, TN_ECHO);
1905 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1907 /* put the holdings sent to us by the server on the board holdings area */
1908 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1912 if(gameInfo.holdingsWidth < 2) return;
1913 if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1914 return; // prevent overwriting by pre-board holdings
1916 if( (int)lowestPiece >= BlackPawn ) {
1919 holdingsStartRow = BOARD_HEIGHT-1;
1922 holdingsColumn = BOARD_WIDTH-1;
1923 countsColumn = BOARD_WIDTH-2;
1924 holdingsStartRow = 0;
1928 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1929 board[i][holdingsColumn] = EmptySquare;
1930 board[i][countsColumn] = (ChessSquare) 0;
1932 while( (p=*holdings++) != NULLCHAR ) {
1933 piece = CharToPiece( ToUpper(p) );
1934 if(piece == EmptySquare) continue;
1935 /*j = (int) piece - (int) WhitePawn;*/
1936 j = PieceToNumber(piece);
1937 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1938 if(j < 0) continue; /* should not happen */
1939 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1940 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1941 board[holdingsStartRow+j*direction][countsColumn]++;
1947 VariantSwitch(Board board, VariantClass newVariant)
1949 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1952 startedFromPositionFile = FALSE;
1953 if(gameInfo.variant == newVariant) return;
1955 /* [HGM] This routine is called each time an assignment is made to
1956 * gameInfo.variant during a game, to make sure the board sizes
1957 * are set to match the new variant. If that means adding or deleting
1958 * holdings, we shift the playing board accordingly
1959 * This kludge is needed because in ICS observe mode, we get boards
1960 * of an ongoing game without knowing the variant, and learn about the
1961 * latter only later. This can be because of the move list we requested,
1962 * in which case the game history is refilled from the beginning anyway,
1963 * but also when receiving holdings of a crazyhouse game. In the latter
1964 * case we want to add those holdings to the already received position.
1968 if (appData.debugMode) {
1969 fprintf(debugFP, "Switch board from %s to %s\n",
1970 VariantName(gameInfo.variant), VariantName(newVariant));
1971 setbuf(debugFP, NULL);
1973 shuffleOpenings = 0; /* [HGM] shuffle */
1974 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1978 newWidth = 9; newHeight = 9;
1979 gameInfo.holdingsSize = 7;
1980 case VariantBughouse:
1981 case VariantCrazyhouse:
1982 newHoldingsWidth = 2; break;
1986 newHoldingsWidth = 2;
1987 gameInfo.holdingsSize = 8;
1990 case VariantCapablanca:
1991 case VariantCapaRandom:
1994 newHoldingsWidth = gameInfo.holdingsSize = 0;
1997 if(newWidth != gameInfo.boardWidth ||
1998 newHeight != gameInfo.boardHeight ||
1999 newHoldingsWidth != gameInfo.holdingsWidth ) {
2001 /* shift position to new playing area, if needed */
2002 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2003 for(i=0; i<BOARD_HEIGHT; i++)
2004 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2005 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2007 for(i=0; i<newHeight; i++) {
2008 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2009 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2011 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2012 for(i=0; i<BOARD_HEIGHT; i++)
2013 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2014 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2017 gameInfo.boardWidth = newWidth;
2018 gameInfo.boardHeight = newHeight;
2019 gameInfo.holdingsWidth = newHoldingsWidth;
2020 gameInfo.variant = newVariant;
2021 InitDrawingSizes(-2, 0);
2022 } else gameInfo.variant = newVariant;
2023 CopyBoard(oldBoard, board); // remember correctly formatted board
2024 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2025 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2028 static int loggedOn = FALSE;
2030 /*-- Game start info cache: --*/
2032 char gs_kind[MSG_SIZ];
2033 static char player1Name[128] = "";
2034 static char player2Name[128] = "";
2035 static char cont_seq[] = "\n\\ ";
2036 static int player1Rating = -1;
2037 static int player2Rating = -1;
2038 /*----------------------------*/
2040 ColorClass curColor = ColorNormal;
2041 int suppressKibitz = 0;
2044 read_from_ics(isr, closure, data, count, error)
2051 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2052 #define STARTED_NONE 0
2053 #define STARTED_MOVES 1
2054 #define STARTED_BOARD 2
2055 #define STARTED_OBSERVE 3
2056 #define STARTED_HOLDINGS 4
2057 #define STARTED_CHATTER 5
2058 #define STARTED_COMMENT 6
2059 #define STARTED_MOVES_NOHIDE 7
2061 static int started = STARTED_NONE;
2062 static char parse[20000];
2063 static int parse_pos = 0;
2064 static char buf[BUF_SIZE + 1];
2065 static int firstTime = TRUE, intfSet = FALSE;
2066 static ColorClass prevColor = ColorNormal;
2067 static int savingComment = FALSE;
2068 static int cmatch = 0; // continuation sequence match
2075 int backup; /* [DM] For zippy color lines */
2077 char talker[MSG_SIZ]; // [HGM] chat
2080 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2082 if (appData.debugMode) {
2084 fprintf(debugFP, "<ICS: ");
2085 show_bytes(debugFP, data, count);
2086 fprintf(debugFP, "\n");
2090 if (appData.debugMode) { int f = forwardMostMove;
2091 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2092 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2095 /* If last read ended with a partial line that we couldn't parse,
2096 prepend it to the new read and try again. */
2097 if (leftover_len > 0) {
2098 for (i=0; i<leftover_len; i++)
2099 buf[i] = buf[leftover_start + i];
2102 /* copy new characters into the buffer */
2103 bp = buf + leftover_len;
2104 buf_len=leftover_len;
2105 for (i=0; i<count; i++)
2108 if (data[i] == '\r')
2111 // join lines split by ICS?
2112 if (!appData.noJoin)
2115 Joining just consists of finding matches against the
2116 continuation sequence, and discarding that sequence
2117 if found instead of copying it. So, until a match
2118 fails, there's nothing to do since it might be the
2119 complete sequence, and thus, something we don't want
2122 if (data[i] == cont_seq[cmatch])
2125 if (cmatch == strlen(cont_seq))
2127 cmatch = 0; // complete match. just reset the counter
2130 it's possible for the ICS to not include the space
2131 at the end of the last word, making our [correct]
2132 join operation fuse two separate words. the server
2133 does this when the space occurs at the width setting.
2135 if (!buf_len || buf[buf_len-1] != ' ')
2146 match failed, so we have to copy what matched before
2147 falling through and copying this character. In reality,
2148 this will only ever be just the newline character, but
2149 it doesn't hurt to be precise.
2151 strncpy(bp, cont_seq, cmatch);
2163 buf[buf_len] = NULLCHAR;
2164 next_out = leftover_len;
2168 while (i < buf_len) {
2169 /* Deal with part of the TELNET option negotiation
2170 protocol. We refuse to do anything beyond the
2171 defaults, except that we allow the WILL ECHO option,
2172 which ICS uses to turn off password echoing when we are
2173 directly connected to it. We reject this option
2174 if localLineEditing mode is on (always on in xboard)
2175 and we are talking to port 23, which might be a real
2176 telnet server that will try to keep WILL ECHO on permanently.
2178 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2179 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2180 unsigned char option;
2182 switch ((unsigned char) buf[++i]) {
2184 if (appData.debugMode)
2185 fprintf(debugFP, "\n<WILL ");
2186 switch (option = (unsigned char) buf[++i]) {
2188 if (appData.debugMode)
2189 fprintf(debugFP, "ECHO ");
2190 /* Reply only if this is a change, according
2191 to the protocol rules. */
2192 if (remoteEchoOption) break;
2193 if (appData.localLineEditing &&
2194 atoi(appData.icsPort) == TN_PORT) {
2195 TelnetRequest(TN_DONT, TN_ECHO);
2198 TelnetRequest(TN_DO, TN_ECHO);
2199 remoteEchoOption = TRUE;
2203 if (appData.debugMode)
2204 fprintf(debugFP, "%d ", option);
2205 /* Whatever this is, we don't want it. */
2206 TelnetRequest(TN_DONT, option);
2211 if (appData.debugMode)
2212 fprintf(debugFP, "\n<WONT ");
2213 switch (option = (unsigned char) buf[++i]) {
2215 if (appData.debugMode)
2216 fprintf(debugFP, "ECHO ");
2217 /* Reply only if this is a change, according
2218 to the protocol rules. */
2219 if (!remoteEchoOption) break;
2221 TelnetRequest(TN_DONT, TN_ECHO);
2222 remoteEchoOption = FALSE;
2225 if (appData.debugMode)
2226 fprintf(debugFP, "%d ", (unsigned char) option);
2227 /* Whatever this is, it must already be turned
2228 off, because we never agree to turn on
2229 anything non-default, so according to the
2230 protocol rules, we don't reply. */
2235 if (appData.debugMode)
2236 fprintf(debugFP, "\n<DO ");
2237 switch (option = (unsigned char) buf[++i]) {
2239 /* Whatever this is, we refuse to do it. */
2240 if (appData.debugMode)
2241 fprintf(debugFP, "%d ", option);
2242 TelnetRequest(TN_WONT, option);
2247 if (appData.debugMode)
2248 fprintf(debugFP, "\n<DONT ");
2249 switch (option = (unsigned char) buf[++i]) {
2251 if (appData.debugMode)
2252 fprintf(debugFP, "%d ", option);
2253 /* Whatever this is, we are already not doing
2254 it, because we never agree to do anything
2255 non-default, so according to the protocol
2256 rules, we don't reply. */
2261 if (appData.debugMode)
2262 fprintf(debugFP, "\n<IAC ");
2263 /* Doubled IAC; pass it through */
2267 if (appData.debugMode)
2268 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2269 /* Drop all other telnet commands on the floor */
2272 if (oldi > next_out)
2273 SendToPlayer(&buf[next_out], oldi - next_out);
2279 /* OK, this at least will *usually* work */
2280 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2284 if (loggedOn && !intfSet) {
2285 if (ics_type == ICS_ICC) {
2287 "/set-quietly interface %s\n/set-quietly style 12\n",
2289 } else if (ics_type == ICS_CHESSNET) {
2290 sprintf(str, "/style 12\n");
2292 strcpy(str, "alias $ @\n$set interface ");
2293 strcat(str, programVersion);
2294 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2296 strcat(str, "$iset nohighlight 1\n");
2298 strcat(str, "$iset lock 1\n$style 12\n");
2301 NotifyFrontendLogin();
2305 if (started == STARTED_COMMENT) {
2306 /* Accumulate characters in comment */
2307 parse[parse_pos++] = buf[i];
2308 if (buf[i] == '\n') {
2309 parse[parse_pos] = NULLCHAR;
2310 if(chattingPartner>=0) {
2312 sprintf(mess, "%s%s", talker, parse);
2313 OutputChatMessage(chattingPartner, mess);
2314 chattingPartner = -1;
2316 if(!suppressKibitz) // [HGM] kibitz
2317 AppendComment(forwardMostMove, StripHighlight(parse));
2318 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2319 int nrDigit = 0, nrAlph = 0, i;
2320 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2321 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2322 parse[parse_pos] = NULLCHAR;
2323 // try to be smart: if it does not look like search info, it should go to
2324 // ICS interaction window after all, not to engine-output window.
2325 for(i=0; i<parse_pos; i++) { // count letters and digits
2326 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2327 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2328 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2330 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2331 int depth=0; float score;
2332 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2333 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2334 pvInfoList[forwardMostMove-1].depth = depth;
2335 pvInfoList[forwardMostMove-1].score = 100*score;
2337 OutputKibitz(suppressKibitz, parse);
2340 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2341 SendToPlayer(tmp, strlen(tmp));
2344 started = STARTED_NONE;
2346 /* Don't match patterns against characters in chatter */
2351 if (started == STARTED_CHATTER) {
2352 if (buf[i] != '\n') {
2353 /* Don't match patterns against characters in chatter */
2357 started = STARTED_NONE;
2360 /* Kludge to deal with rcmd protocol */
2361 if (firstTime && looking_at(buf, &i, "\001*")) {
2362 DisplayFatalError(&buf[1], 0, 1);
2368 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2371 if (appData.debugMode)
2372 fprintf(debugFP, "ics_type %d\n", ics_type);
2375 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2376 ics_type = ICS_FICS;
2378 if (appData.debugMode)
2379 fprintf(debugFP, "ics_type %d\n", ics_type);
2382 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2383 ics_type = ICS_CHESSNET;
2385 if (appData.debugMode)
2386 fprintf(debugFP, "ics_type %d\n", ics_type);
2391 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2392 looking_at(buf, &i, "Logging you in as \"*\"") ||
2393 looking_at(buf, &i, "will be \"*\""))) {
2394 strcpy(ics_handle, star_match[0]);
2398 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2400 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2401 DisplayIcsInteractionTitle(buf);
2402 have_set_title = TRUE;
2405 /* skip finger notes */
2406 if (started == STARTED_NONE &&
2407 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2408 (buf[i] == '1' && buf[i+1] == '0')) &&
2409 buf[i+2] == ':' && buf[i+3] == ' ') {
2410 started = STARTED_CHATTER;
2415 /* skip formula vars */
2416 if (started == STARTED_NONE &&
2417 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2418 started = STARTED_CHATTER;
2424 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2425 if (appData.autoKibitz && started == STARTED_NONE &&
2426 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2427 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2428 if(looking_at(buf, &i, "* kibitzes: ") &&
2429 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2430 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2431 suppressKibitz = TRUE;
2432 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2433 && (gameMode == IcsPlayingWhite)) ||
2434 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2435 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2436 started = STARTED_CHATTER; // own kibitz we simply discard
2438 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2439 parse_pos = 0; parse[0] = NULLCHAR;
2440 savingComment = TRUE;
2441 suppressKibitz = gameMode != IcsObserving ? 2 :
2442 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2446 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2447 started = STARTED_CHATTER;
2448 suppressKibitz = TRUE;
2450 } // [HGM] kibitz: end of patch
2452 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2454 // [HGM] chat: intercept tells by users for which we have an open chat window
2456 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2457 looking_at(buf, &i, "* whispers:") ||
2458 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2459 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2461 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2462 chattingPartner = -1;
2464 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2465 for(p=0; p<MAX_CHAT; p++) {
2466 if(channel == atoi(chatPartner[p])) {
2467 talker[0] = '['; strcat(talker, "] ");
2468 chattingPartner = p; break;
2471 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2472 for(p=0; p<MAX_CHAT; p++) {
2473 if(!strcmp("WHISPER", chatPartner[p])) {
2474 talker[0] = '['; strcat(talker, "] ");
2475 chattingPartner = p; break;
2478 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2479 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2481 chattingPartner = p; break;
2483 if(chattingPartner<0) i = oldi; else {
2484 started = STARTED_COMMENT;
2485 parse_pos = 0; parse[0] = NULLCHAR;
2486 savingComment = 3 + chattingPartner; // counts as TRUE
2487 suppressKibitz = TRUE;
2489 } // [HGM] chat: end of patch
2491 if (appData.zippyTalk || appData.zippyPlay) {
2492 /* [DM] Backup address for color zippy lines */
2496 if (loggedOn == TRUE)
2497 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2498 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2500 if (ZippyControl(buf, &i) ||
2501 ZippyConverse(buf, &i) ||
2502 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2504 if (!appData.colorize) continue;
2508 } // [DM] 'else { ' deleted
2510 /* Regular tells and says */
2511 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2512 looking_at(buf, &i, "* (your partner) tells you: ") ||
2513 looking_at(buf, &i, "* says: ") ||
2514 /* Don't color "message" or "messages" output */
2515 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2516 looking_at(buf, &i, "*. * at *:*: ") ||
2517 looking_at(buf, &i, "--* (*:*): ") ||
2518 /* Message notifications (same color as tells) */
2519 looking_at(buf, &i, "* has left a message ") ||
2520 looking_at(buf, &i, "* just sent you a message:\n") ||
2521 /* Whispers and kibitzes */
2522 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2523 looking_at(buf, &i, "* kibitzes: ") ||
2525 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2527 if (tkind == 1 && strchr(star_match[0], ':')) {
2528 /* Avoid "tells you:" spoofs in channels */
2531 if (star_match[0][0] == NULLCHAR ||
2532 strchr(star_match[0], ' ') ||
2533 (tkind == 3 && strchr(star_match[1], ' '))) {
2534 /* Reject bogus matches */
2537 if (appData.colorize) {
2538 if (oldi > next_out) {
2539 SendToPlayer(&buf[next_out], oldi - next_out);
2544 Colorize(ColorTell, FALSE);
2545 curColor = ColorTell;
2548 Colorize(ColorKibitz, FALSE);
2549 curColor = ColorKibitz;
2552 p = strrchr(star_match[1], '(');
2559 Colorize(ColorChannel1, FALSE);
2560 curColor = ColorChannel1;
2562 Colorize(ColorChannel, FALSE);
2563 curColor = ColorChannel;
2567 curColor = ColorNormal;
2571 if (started == STARTED_NONE && appData.autoComment &&
2572 (gameMode == IcsObserving ||
2573 gameMode == IcsPlayingWhite ||
2574 gameMode == IcsPlayingBlack)) {
2575 parse_pos = i - oldi;
2576 memcpy(parse, &buf[oldi], parse_pos);
2577 parse[parse_pos] = NULLCHAR;
2578 started = STARTED_COMMENT;
2579 savingComment = TRUE;
2581 started = STARTED_CHATTER;
2582 savingComment = FALSE;
2589 if (looking_at(buf, &i, "* s-shouts: ") ||
2590 looking_at(buf, &i, "* c-shouts: ")) {
2591 if (appData.colorize) {
2592 if (oldi > next_out) {
2593 SendToPlayer(&buf[next_out], oldi - next_out);
2596 Colorize(ColorSShout, FALSE);
2597 curColor = ColorSShout;
2600 started = STARTED_CHATTER;
2604 if (looking_at(buf, &i, "--->")) {
2609 if (looking_at(buf, &i, "* shouts: ") ||
2610 looking_at(buf, &i, "--> ")) {
2611 if (appData.colorize) {
2612 if (oldi > next_out) {
2613 SendToPlayer(&buf[next_out], oldi - next_out);
2616 Colorize(ColorShout, FALSE);
2617 curColor = ColorShout;
2620 started = STARTED_CHATTER;
2624 if (looking_at( buf, &i, "Challenge:")) {
2625 if (appData.colorize) {
2626 if (oldi > next_out) {
2627 SendToPlayer(&buf[next_out], oldi - next_out);
2630 Colorize(ColorChallenge, FALSE);
2631 curColor = ColorChallenge;
2637 if (looking_at(buf, &i, "* offers you") ||
2638 looking_at(buf, &i, "* offers to be") ||
2639 looking_at(buf, &i, "* would like to") ||
2640 looking_at(buf, &i, "* requests to") ||
2641 looking_at(buf, &i, "Your opponent offers") ||
2642 looking_at(buf, &i, "Your opponent requests")) {
2644 if (appData.colorize) {
2645 if (oldi > next_out) {
2646 SendToPlayer(&buf[next_out], oldi - next_out);
2649 Colorize(ColorRequest, FALSE);
2650 curColor = ColorRequest;
2655 if (looking_at(buf, &i, "* (*) seeking")) {
2656 if (appData.colorize) {
2657 if (oldi > next_out) {
2658 SendToPlayer(&buf[next_out], oldi - next_out);
2661 Colorize(ColorSeek, FALSE);
2662 curColor = ColorSeek;
2667 if (looking_at(buf, &i, "\\ ")) {
2668 if (prevColor != ColorNormal) {
2669 if (oldi > next_out) {
2670 SendToPlayer(&buf[next_out], oldi - next_out);
2673 Colorize(prevColor, TRUE);
2674 curColor = prevColor;
2676 if (savingComment) {
2677 parse_pos = i - oldi;
2678 memcpy(parse, &buf[oldi], parse_pos);
2679 parse[parse_pos] = NULLCHAR;
2680 started = STARTED_COMMENT;
2681 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2682 chattingPartner = savingComment - 3; // kludge to remember the box
2684 started = STARTED_CHATTER;
2689 if (looking_at(buf, &i, "Black Strength :") ||
2690 looking_at(buf, &i, "<<< style 10 board >>>") ||
2691 looking_at(buf, &i, "<10>") ||
2692 looking_at(buf, &i, "#@#")) {
2693 /* Wrong board style */
2695 SendToICS(ics_prefix);
2696 SendToICS("set style 12\n");
2697 SendToICS(ics_prefix);
2698 SendToICS("refresh\n");
2702 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2704 have_sent_ICS_logon = 1;
2708 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2709 (looking_at(buf, &i, "\n<12> ") ||
2710 looking_at(buf, &i, "<12> "))) {
2712 if (oldi > next_out) {
2713 SendToPlayer(&buf[next_out], oldi - next_out);
2716 started = STARTED_BOARD;
2721 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2722 looking_at(buf, &i, "<b1> ")) {
2723 if (oldi > next_out) {
2724 SendToPlayer(&buf[next_out], oldi - next_out);
2727 started = STARTED_HOLDINGS;
2732 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2734 /* Header for a move list -- first line */
2736 switch (ics_getting_history) {
2740 case BeginningOfGame:
2741 /* User typed "moves" or "oldmoves" while we
2742 were idle. Pretend we asked for these
2743 moves and soak them up so user can step
2744 through them and/or save them.
2747 gameMode = IcsObserving;
2750 ics_getting_history = H_GOT_UNREQ_HEADER;
2752 case EditGame: /*?*/
2753 case EditPosition: /*?*/
2754 /* Should above feature work in these modes too? */
2755 /* For now it doesn't */
2756 ics_getting_history = H_GOT_UNWANTED_HEADER;
2759 ics_getting_history = H_GOT_UNWANTED_HEADER;
2764 /* Is this the right one? */
2765 if (gameInfo.white && gameInfo.black &&
2766 strcmp(gameInfo.white, star_match[0]) == 0 &&
2767 strcmp(gameInfo.black, star_match[2]) == 0) {
2769 ics_getting_history = H_GOT_REQ_HEADER;
2772 case H_GOT_REQ_HEADER:
2773 case H_GOT_UNREQ_HEADER:
2774 case H_GOT_UNWANTED_HEADER:
2775 case H_GETTING_MOVES:
2776 /* Should not happen */
2777 DisplayError(_("Error gathering move list: two headers"), 0);
2778 ics_getting_history = H_FALSE;
2782 /* Save player ratings into gameInfo if needed */
2783 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2784 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2785 (gameInfo.whiteRating == -1 ||
2786 gameInfo.blackRating == -1)) {
2788 gameInfo.whiteRating = string_to_rating(star_match[1]);
2789 gameInfo.blackRating = string_to_rating(star_match[3]);
2790 if (appData.debugMode)
2791 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2792 gameInfo.whiteRating, gameInfo.blackRating);
2797 if (looking_at(buf, &i,
2798 "* * match, initial time: * minute*, increment: * second")) {
2799 /* Header for a move list -- second line */
2800 /* Initial board will follow if this is a wild game */
2801 if (gameInfo.event != NULL) free(gameInfo.event);
2802 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2803 gameInfo.event = StrSave(str);
2804 /* [HGM] we switched variant. Translate boards if needed. */
2805 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2809 if (looking_at(buf, &i, "Move ")) {
2810 /* Beginning of a move list */
2811 switch (ics_getting_history) {
2813 /* Normally should not happen */
2814 /* Maybe user hit reset while we were parsing */
2817 /* Happens if we are ignoring a move list that is not
2818 * the one we just requested. Common if the user
2819 * tries to observe two games without turning off
2822 case H_GETTING_MOVES:
2823 /* Should not happen */
2824 DisplayError(_("Error gathering move list: nested"), 0);
2825 ics_getting_history = H_FALSE;
2827 case H_GOT_REQ_HEADER:
2828 ics_getting_history = H_GETTING_MOVES;
2829 started = STARTED_MOVES;
2831 if (oldi > next_out) {
2832 SendToPlayer(&buf[next_out], oldi - next_out);
2835 case H_GOT_UNREQ_HEADER:
2836 ics_getting_history = H_GETTING_MOVES;
2837 started = STARTED_MOVES_NOHIDE;
2840 case H_GOT_UNWANTED_HEADER:
2841 ics_getting_history = H_FALSE;
2847 if (looking_at(buf, &i, "% ") ||
2848 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2849 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2850 savingComment = FALSE;
2853 case STARTED_MOVES_NOHIDE:
2854 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2855 parse[parse_pos + i - oldi] = NULLCHAR;
2856 ParseGameHistory(parse);
2858 if (appData.zippyPlay && first.initDone) {
2859 FeedMovesToProgram(&first, forwardMostMove);
2860 if (gameMode == IcsPlayingWhite) {
2861 if (WhiteOnMove(forwardMostMove)) {
2862 if (first.sendTime) {
2863 if (first.useColors) {
2864 SendToProgram("black\n", &first);
2866 SendTimeRemaining(&first, TRUE);
2868 if (first.useColors) {
2869 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2871 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2872 first.maybeThinking = TRUE;
2874 if (first.usePlayother) {
2875 if (first.sendTime) {
2876 SendTimeRemaining(&first, TRUE);
2878 SendToProgram("playother\n", &first);
2884 } else if (gameMode == IcsPlayingBlack) {
2885 if (!WhiteOnMove(forwardMostMove)) {
2886 if (first.sendTime) {
2887 if (first.useColors) {
2888 SendToProgram("white\n", &first);
2890 SendTimeRemaining(&first, FALSE);
2892 if (first.useColors) {
2893 SendToProgram("black\n", &first);
2895 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2896 first.maybeThinking = TRUE;
2898 if (first.usePlayother) {
2899 if (first.sendTime) {
2900 SendTimeRemaining(&first, FALSE);
2902 SendToProgram("playother\n", &first);
2911 if (gameMode == IcsObserving && ics_gamenum == -1) {
2912 /* Moves came from oldmoves or moves command
2913 while we weren't doing anything else.
2915 currentMove = forwardMostMove;
2916 ClearHighlights();/*!!could figure this out*/
2917 flipView = appData.flipView;
2918 DrawPosition(TRUE, boards[currentMove]);
2919 DisplayBothClocks();
2920 sprintf(str, "%s vs. %s",
2921 gameInfo.white, gameInfo.black);
2925 /* Moves were history of an active game */
2926 if (gameInfo.resultDetails != NULL) {
2927 free(gameInfo.resultDetails);
2928 gameInfo.resultDetails = NULL;
2931 HistorySet(parseList, backwardMostMove,
2932 forwardMostMove, currentMove-1);
2933 DisplayMove(currentMove - 1);
2934 if (started == STARTED_MOVES) next_out = i;
2935 started = STARTED_NONE;
2936 ics_getting_history = H_FALSE;
2939 case STARTED_OBSERVE:
2940 started = STARTED_NONE;
2941 SendToICS(ics_prefix);
2942 SendToICS("refresh\n");
2948 if(bookHit) { // [HGM] book: simulate book reply
2949 static char bookMove[MSG_SIZ]; // a bit generous?
2951 programStats.nodes = programStats.depth = programStats.time =
2952 programStats.score = programStats.got_only_move = 0;
2953 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2955 strcpy(bookMove, "move ");
2956 strcat(bookMove, bookHit);
2957 HandleMachineMove(bookMove, &first);
2962 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2963 started == STARTED_HOLDINGS ||
2964 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2965 /* Accumulate characters in move list or board */
2966 parse[parse_pos++] = buf[i];
2969 /* Start of game messages. Mostly we detect start of game
2970 when the first board image arrives. On some versions
2971 of the ICS, though, we need to do a "refresh" after starting
2972 to observe in order to get the current board right away. */
2973 if (looking_at(buf, &i, "Adding game * to observation list")) {
2974 started = STARTED_OBSERVE;
2978 /* Handle auto-observe */
2979 if (appData.autoObserve &&
2980 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2981 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2983 /* Choose the player that was highlighted, if any. */
2984 if (star_match[0][0] == '\033' ||
2985 star_match[1][0] != '\033') {
2986 player = star_match[0];
2988 player = star_match[2];
2990 sprintf(str, "%sobserve %s\n",
2991 ics_prefix, StripHighlightAndTitle(player));
2994 /* Save ratings from notify string */
2995 strcpy(player1Name, star_match[0]);
2996 player1Rating = string_to_rating(star_match[1]);
2997 strcpy(player2Name, star_match[2]);
2998 player2Rating = string_to_rating(star_match[3]);
3000 if (appData.debugMode)
3002 "Ratings from 'Game notification:' %s %d, %s %d\n",
3003 player1Name, player1Rating,
3004 player2Name, player2Rating);
3009 /* Deal with automatic examine mode after a game,
3010 and with IcsObserving -> IcsExamining transition */
3011 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3012 looking_at(buf, &i, "has made you an examiner of game *")) {
3014 int gamenum = atoi(star_match[0]);
3015 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3016 gamenum == ics_gamenum) {
3017 /* We were already playing or observing this game;
3018 no need to refetch history */
3019 gameMode = IcsExamining;
3021 pauseExamForwardMostMove = forwardMostMove;
3022 } else if (currentMove < forwardMostMove) {
3023 ForwardInner(forwardMostMove);
3026 /* I don't think this case really can happen */
3027 SendToICS(ics_prefix);
3028 SendToICS("refresh\n");
3033 /* Error messages */
3034 // if (ics_user_moved) {
3035 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3036 if (looking_at(buf, &i, "Illegal move") ||
3037 looking_at(buf, &i, "Not a legal move") ||
3038 looking_at(buf, &i, "Your king is in check") ||
3039 looking_at(buf, &i, "It isn't your turn") ||
3040 looking_at(buf, &i, "It is not your move")) {
3042 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3043 currentMove = --forwardMostMove;
3044 DisplayMove(currentMove - 1); /* before DMError */
3045 DrawPosition(FALSE, boards[currentMove]);
3047 DisplayBothClocks();
3049 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3055 if (looking_at(buf, &i, "still have time") ||
3056 looking_at(buf, &i, "not out of time") ||
3057 looking_at(buf, &i, "either player is out of time") ||
3058 looking_at(buf, &i, "has timeseal; checking")) {
3059 /* We must have called his flag a little too soon */
3060 whiteFlag = blackFlag = FALSE;
3064 if (looking_at(buf, &i, "added * seconds to") ||
3065 looking_at(buf, &i, "seconds were added to")) {
3066 /* Update the clocks */
3067 SendToICS(ics_prefix);
3068 SendToICS("refresh\n");
3072 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3073 ics_clock_paused = TRUE;
3078 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3079 ics_clock_paused = FALSE;
3084 /* Grab player ratings from the Creating: message.
3085 Note we have to check for the special case when
3086 the ICS inserts things like [white] or [black]. */
3087 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3088 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3090 0 player 1 name (not necessarily white)
3092 2 empty, white, or black (IGNORED)
3093 3 player 2 name (not necessarily black)
3096 The names/ratings are sorted out when the game
3097 actually starts (below).
3099 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3100 player1Rating = string_to_rating(star_match[1]);
3101 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3102 player2Rating = string_to_rating(star_match[4]);
3104 if (appData.debugMode)
3106 "Ratings from 'Creating:' %s %d, %s %d\n",
3107 player1Name, player1Rating,
3108 player2Name, player2Rating);
3113 /* Improved generic start/end-of-game messages */
3114 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3115 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3116 /* If tkind == 0: */
3117 /* star_match[0] is the game number */
3118 /* [1] is the white player's name */
3119 /* [2] is the black player's name */
3120 /* For end-of-game: */
3121 /* [3] is the reason for the game end */
3122 /* [4] is a PGN end game-token, preceded by " " */
3123 /* For start-of-game: */
3124 /* [3] begins with "Creating" or "Continuing" */
3125 /* [4] is " *" or empty (don't care). */
3126 int gamenum = atoi(star_match[0]);
3127 char *whitename, *blackname, *why, *endtoken;
3128 ChessMove endtype = (ChessMove) 0;
3131 whitename = star_match[1];
3132 blackname = star_match[2];
3133 why = star_match[3];
3134 endtoken = star_match[4];
3136 whitename = star_match[1];
3137 blackname = star_match[3];
3138 why = star_match[5];
3139 endtoken = star_match[6];
3142 /* Game start messages */
3143 if (strncmp(why, "Creating ", 9) == 0 ||
3144 strncmp(why, "Continuing ", 11) == 0) {
3145 gs_gamenum = gamenum;
3146 strcpy(gs_kind, strchr(why, ' ') + 1);
3147 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3149 if (appData.zippyPlay) {
3150 ZippyGameStart(whitename, blackname);
3156 /* Game end messages */
3157 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3158 ics_gamenum != gamenum) {
3161 while (endtoken[0] == ' ') endtoken++;
3162 switch (endtoken[0]) {
3165 endtype = GameUnfinished;
3168 endtype = BlackWins;
3171 if (endtoken[1] == '/')
3172 endtype = GameIsDrawn;
3174 endtype = WhiteWins;
3177 GameEnds(endtype, why, GE_ICS);
3179 if (appData.zippyPlay && first.initDone) {
3180 ZippyGameEnd(endtype, why);
3181 if (first.pr == NULL) {
3182 /* Start the next process early so that we'll
3183 be ready for the next challenge */
3184 StartChessProgram(&first);
3186 /* Send "new" early, in case this command takes
3187 a long time to finish, so that we'll be ready
3188 for the next challenge. */
3189 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3196 if (looking_at(buf, &i, "Removing game * from observation") ||
3197 looking_at(buf, &i, "no longer observing game *") ||
3198 looking_at(buf, &i, "Game * (*) has no examiners")) {
3199 if (gameMode == IcsObserving &&
3200 atoi(star_match[0]) == ics_gamenum)
3202 /* icsEngineAnalyze */
3203 if (appData.icsEngineAnalyze) {
3210 ics_user_moved = FALSE;
3215 if (looking_at(buf, &i, "no longer examining game *")) {
3216 if (gameMode == IcsExamining &&
3217 atoi(star_match[0]) == ics_gamenum)
3221 ics_user_moved = FALSE;
3226 /* Advance leftover_start past any newlines we find,
3227 so only partial lines can get reparsed */
3228 if (looking_at(buf, &i, "\n")) {
3229 prevColor = curColor;
3230 if (curColor != ColorNormal) {
3231 if (oldi > next_out) {
3232 SendToPlayer(&buf[next_out], oldi - next_out);
3235 Colorize(ColorNormal, FALSE);
3236 curColor = ColorNormal;
3238 if (started == STARTED_BOARD) {
3239 started = STARTED_NONE;
3240 parse[parse_pos] = NULLCHAR;
3241 ParseBoard12(parse);
3244 /* Send premove here */
3245 if (appData.premove) {
3247 if (currentMove == 0 &&
3248 gameMode == IcsPlayingWhite &&
3249 appData.premoveWhite) {
3250 sprintf(str, "%s\n", appData.premoveWhiteText);
3251 if (appData.debugMode)
3252 fprintf(debugFP, "Sending premove:\n");
3254 } else if (currentMove == 1 &&
3255 gameMode == IcsPlayingBlack &&
3256 appData.premoveBlack) {
3257 sprintf(str, "%s\n", appData.premoveBlackText);
3258 if (appData.debugMode)
3259 fprintf(debugFP, "Sending premove:\n");
3261 } else if (gotPremove) {
3263 ClearPremoveHighlights();
3264 if (appData.debugMode)
3265 fprintf(debugFP, "Sending premove:\n");
3266 UserMoveEvent(premoveFromX, premoveFromY,
3267 premoveToX, premoveToY,
3272 /* Usually suppress following prompt */
3273 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3274 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3275 if (looking_at(buf, &i, "*% ")) {
3276 savingComment = FALSE;
3280 } else if (started == STARTED_HOLDINGS) {
3282 char new_piece[MSG_SIZ];
3283 started = STARTED_NONE;
3284 parse[parse_pos] = NULLCHAR;
3285 if (appData.debugMode)
3286 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3287 parse, currentMove);
3288 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3289 gamenum == ics_gamenum) {
3290 if (gameInfo.variant == VariantNormal) {
3291 /* [HGM] We seem to switch variant during a game!
3292 * Presumably no holdings were displayed, so we have
3293 * to move the position two files to the right to
3294 * create room for them!
3296 VariantClass newVariant;
3297 switch(gameInfo.boardWidth) { // base guess on board width
3298 case 9: newVariant = VariantShogi; break;
3299 case 10: newVariant = VariantGreat; break;
3300 default: newVariant = VariantCrazyhouse; break;
3302 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3303 /* Get a move list just to see the header, which
3304 will tell us whether this is really bug or zh */
3305 if (ics_getting_history == H_FALSE) {
3306 ics_getting_history = H_REQUESTED;
3307 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3311 new_piece[0] = NULLCHAR;
3312 sscanf(parse, "game %d white [%s black [%s <- %s",
3313 &gamenum, white_holding, black_holding,
3315 white_holding[strlen(white_holding)-1] = NULLCHAR;
3316 black_holding[strlen(black_holding)-1] = NULLCHAR;
3317 /* [HGM] copy holdings to board holdings area */
3318 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3319 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3320 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3322 if (appData.zippyPlay && first.initDone) {
3323 ZippyHoldings(white_holding, black_holding,
3327 if (tinyLayout || smallLayout) {
3328 char wh[16], bh[16];
3329 PackHolding(wh, white_holding);
3330 PackHolding(bh, black_holding);
3331 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3332 gameInfo.white, gameInfo.black);
3334 sprintf(str, "%s [%s] vs. %s [%s]",
3335 gameInfo.white, white_holding,
3336 gameInfo.black, black_holding);
3339 DrawPosition(FALSE, boards[currentMove]);
3342 /* Suppress following prompt */
3343 if (looking_at(buf, &i, "*% ")) {
3344 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3345 savingComment = FALSE;
3352 i++; /* skip unparsed character and loop back */
3355 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3356 started != STARTED_HOLDINGS && i > next_out) {
3357 SendToPlayer(&buf[next_out], i - next_out);
3360 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3362 leftover_len = buf_len - leftover_start;
3363 /* if buffer ends with something we couldn't parse,
3364 reparse it after appending the next read */
3366 } else if (count == 0) {
3367 RemoveInputSource(isr);
3368 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3370 DisplayFatalError(_("Error reading from ICS"), error, 1);
3375 /* Board style 12 looks like this:
3377 <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
3379 * The "<12> " is stripped before it gets to this routine. The two
3380 * trailing 0's (flip state and clock ticking) are later addition, and
3381 * some chess servers may not have them, or may have only the first.
3382 * Additional trailing fields may be added in the future.
3385 #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"
3387 #define RELATION_OBSERVING_PLAYED 0
3388 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3389 #define RELATION_PLAYING_MYMOVE 1
3390 #define RELATION_PLAYING_NOTMYMOVE -1
3391 #define RELATION_EXAMINING 2
3392 #define RELATION_ISOLATED_BOARD -3
3393 #define RELATION_STARTING_POSITION -4 /* FICS only */
3396 ParseBoard12(string)
3399 GameMode newGameMode;
3400 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3401 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3402 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3403 char to_play, board_chars[200];
3404 char move_str[500], str[500], elapsed_time[500];
3405 char black[32], white[32];
3407 int prevMove = currentMove;
3410 int fromX, fromY, toX, toY;
3412 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3413 char *bookHit = NULL; // [HGM] book
3414 Boolean weird = FALSE, reqFlag = FALSE;
3416 fromX = fromY = toX = toY = -1;
3420 if (appData.debugMode)
3421 fprintf(debugFP, _("Parsing board: %s\n"), string);
3423 move_str[0] = NULLCHAR;
3424 elapsed_time[0] = NULLCHAR;
3425 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3427 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3428 if(string[i] == ' ') { ranks++; files = 0; }
3430 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3433 for(j = 0; j <i; j++) board_chars[j] = string[j];
3434 board_chars[i] = '\0';
3437 n = sscanf(string, PATTERN, &to_play, &double_push,
3438 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3439 &gamenum, white, black, &relation, &basetime, &increment,
3440 &white_stren, &black_stren, &white_time, &black_time,
3441 &moveNum, str, elapsed_time, move_str, &ics_flip,
3445 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3446 DisplayError(str, 0);
3450 /* Convert the move number to internal form */
3451 moveNum = (moveNum - 1) * 2;
3452 if (to_play == 'B') moveNum++;
3453 if (moveNum >= MAX_MOVES) {
3454 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3460 case RELATION_OBSERVING_PLAYED:
3461 case RELATION_OBSERVING_STATIC:
3462 if (gamenum == -1) {
3463 /* Old ICC buglet */
3464 relation = RELATION_OBSERVING_STATIC;
3466 newGameMode = IcsObserving;
3468 case RELATION_PLAYING_MYMOVE:
3469 case RELATION_PLAYING_NOTMYMOVE:
3471 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3472 IcsPlayingWhite : IcsPlayingBlack;
3474 case RELATION_EXAMINING:
3475 newGameMode = IcsExamining;
3477 case RELATION_ISOLATED_BOARD:
3479 /* Just display this board. If user was doing something else,
3480 we will forget about it until the next board comes. */
3481 newGameMode = IcsIdle;
3483 case RELATION_STARTING_POSITION:
3484 newGameMode = gameMode;
3488 /* Modify behavior for initial board display on move listing
3491 switch (ics_getting_history) {
3495 case H_GOT_REQ_HEADER:
3496 case H_GOT_UNREQ_HEADER:
3497 /* This is the initial position of the current game */
3498 gamenum = ics_gamenum;
3499 moveNum = 0; /* old ICS bug workaround */
3500 if (to_play == 'B') {
3501 startedFromSetupPosition = TRUE;
3502 blackPlaysFirst = TRUE;
3504 if (forwardMostMove == 0) forwardMostMove = 1;
3505 if (backwardMostMove == 0) backwardMostMove = 1;
3506 if (currentMove == 0) currentMove = 1;
3508 newGameMode = gameMode;
3509 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3511 case H_GOT_UNWANTED_HEADER:
3512 /* This is an initial board that we don't want */
3514 case H_GETTING_MOVES:
3515 /* Should not happen */
3516 DisplayError(_("Error gathering move list: extra board"), 0);
3517 ics_getting_history = H_FALSE;
3521 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3522 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3523 /* [HGM] We seem to have switched variant unexpectedly
3524 * Try to guess new variant from board size
3526 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3527 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3528 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3529 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3530 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3531 if(!weird) newVariant = VariantNormal;
3532 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3533 /* Get a move list just to see the header, which
3534 will tell us whether this is really bug or zh */
3535 if (ics_getting_history == H_FALSE) {
3536 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3537 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3542 /* Take action if this is the first board of a new game, or of a
3543 different game than is currently being displayed. */
3544 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3545 relation == RELATION_ISOLATED_BOARD) {
3547 /* Forget the old game and get the history (if any) of the new one */
3548 if (gameMode != BeginningOfGame) {
3552 if (appData.autoRaiseBoard) BoardToTop();
3554 if (gamenum == -1) {
3555 newGameMode = IcsIdle;
3556 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3557 appData.getMoveList && !reqFlag) {
3558 /* Need to get game history */
3559 ics_getting_history = H_REQUESTED;
3560 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3564 /* Initially flip the board to have black on the bottom if playing
3565 black or if the ICS flip flag is set, but let the user change
3566 it with the Flip View button. */
3567 flipView = appData.autoFlipView ?
3568 (newGameMode == IcsPlayingBlack) || ics_flip :
3571 /* Done with values from previous mode; copy in new ones */
3572 gameMode = newGameMode;
3574 ics_gamenum = gamenum;
3575 if (gamenum == gs_gamenum) {
3576 int klen = strlen(gs_kind);
3577 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3578 sprintf(str, "ICS %s", gs_kind);
3579 gameInfo.event = StrSave(str);
3581 gameInfo.event = StrSave("ICS game");
3583 gameInfo.site = StrSave(appData.icsHost);
3584 gameInfo.date = PGNDate();
3585 gameInfo.round = StrSave("-");
3586 gameInfo.white = StrSave(white);
3587 gameInfo.black = StrSave(black);
3588 timeControl = basetime * 60 * 1000;
3590 timeIncrement = increment * 1000;
3591 movesPerSession = 0;
3592 gameInfo.timeControl = TimeControlTagValue();
3593 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3594 if (appData.debugMode) {
3595 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3596 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3597 setbuf(debugFP, NULL);
3600 gameInfo.outOfBook = NULL;
3602 /* Do we have the ratings? */
3603 if (strcmp(player1Name, white) == 0 &&
3604 strcmp(player2Name, black) == 0) {
3605 if (appData.debugMode)
3606 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3607 player1Rating, player2Rating);
3608 gameInfo.whiteRating = player1Rating;
3609 gameInfo.blackRating = player2Rating;
3610 } else if (strcmp(player2Name, white) == 0 &&
3611 strcmp(player1Name, black) == 0) {
3612 if (appData.debugMode)
3613 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3614 player2Rating, player1Rating);
3615 gameInfo.whiteRating = player2Rating;
3616 gameInfo.blackRating = player1Rating;
3618 player1Name[0] = player2Name[0] = NULLCHAR;
3620 /* Silence shouts if requested */
3621 if (appData.quietPlay &&
3622 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3623 SendToICS(ics_prefix);
3624 SendToICS("set shout 0\n");
3628 /* Deal with midgame name changes */
3630 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3631 if (gameInfo.white) free(gameInfo.white);
3632 gameInfo.white = StrSave(white);
3634 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3635 if (gameInfo.black) free(gameInfo.black);
3636 gameInfo.black = StrSave(black);
3640 /* Throw away game result if anything actually changes in examine mode */
3641 if (gameMode == IcsExamining && !newGame) {
3642 gameInfo.result = GameUnfinished;
3643 if (gameInfo.resultDetails != NULL) {
3644 free(gameInfo.resultDetails);
3645 gameInfo.resultDetails = NULL;
3649 /* In pausing && IcsExamining mode, we ignore boards coming
3650 in if they are in a different variation than we are. */
3651 if (pauseExamInvalid) return;
3652 if (pausing && gameMode == IcsExamining) {
3653 if (moveNum <= pauseExamForwardMostMove) {
3654 pauseExamInvalid = TRUE;
3655 forwardMostMove = pauseExamForwardMostMove;
3660 if (appData.debugMode) {
3661 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3663 /* Parse the board */
3664 for (k = 0; k < ranks; k++) {
3665 for (j = 0; j < files; j++)
3666 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3667 if(gameInfo.holdingsWidth > 1) {
3668 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3669 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3672 CopyBoard(boards[moveNum], board);
3673 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3675 startedFromSetupPosition =
3676 !CompareBoards(board, initialPosition);
3677 if(startedFromSetupPosition)
3678 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3681 /* [HGM] Set castling rights. Take the outermost Rooks,
3682 to make it also work for FRC opening positions. Note that board12
3683 is really defective for later FRC positions, as it has no way to
3684 indicate which Rook can castle if they are on the same side of King.
3685 For the initial position we grant rights to the outermost Rooks,
3686 and remember thos rights, and we then copy them on positions
3687 later in an FRC game. This means WB might not recognize castlings with
3688 Rooks that have moved back to their original position as illegal,
3689 but in ICS mode that is not its job anyway.
3691 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3692 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3694 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3695 if(board[0][i] == WhiteRook) j = i;
3696 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3697 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3698 if(board[0][i] == WhiteRook) j = i;
3699 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3700 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3701 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3702 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3703 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3704 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3705 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3707 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3708 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3709 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3710 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3711 if(board[BOARD_HEIGHT-1][k] == bKing)
3712 initialRights[5] = castlingRights[moveNum][5] = k;
3713 if(gameInfo.variant == VariantTwoKings) {
3714 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3715 if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3716 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3719 r = castlingRights[moveNum][0] = initialRights[0];
3720 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3721 r = castlingRights[moveNum][1] = initialRights[1];
3722 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3723 r = castlingRights[moveNum][3] = initialRights[3];
3724 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3725 r = castlingRights[moveNum][4] = initialRights[4];
3726 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3727 /* wildcastle kludge: always assume King has rights */
3728 r = castlingRights[moveNum][2] = initialRights[2];
3729 r = castlingRights[moveNum][5] = initialRights[5];
3731 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3732 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3735 if (ics_getting_history == H_GOT_REQ_HEADER ||
3736 ics_getting_history == H_GOT_UNREQ_HEADER) {
3737 /* This was an initial position from a move list, not
3738 the current position */
3742 /* Update currentMove and known move number limits */
3743 newMove = newGame || moveNum > forwardMostMove;
3746 forwardMostMove = backwardMostMove = currentMove = moveNum;
3747 if (gameMode == IcsExamining && moveNum == 0) {
3748 /* Workaround for ICS limitation: we are not told the wild
3749 type when starting to examine a game. But if we ask for
3750 the move list, the move list header will tell us */
3751 ics_getting_history = H_REQUESTED;
3752 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3755 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3756 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3758 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3759 /* [HGM] applied this also to an engine that is silently watching */
3760 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3761 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3762 gameInfo.variant == currentlyInitializedVariant) {
3763 takeback = forwardMostMove - moveNum;
3764 for (i = 0; i < takeback; i++) {
3765 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3766 SendToProgram("undo\n", &first);
3771 forwardMostMove = moveNum;
3772 if (!pausing || currentMove > forwardMostMove)
3773 currentMove = forwardMostMove;
3775 /* New part of history that is not contiguous with old part */
3776 if (pausing && gameMode == IcsExamining) {
3777 pauseExamInvalid = TRUE;
3778 forwardMostMove = pauseExamForwardMostMove;
3781 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3783 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3784 // [HGM] when we will receive the move list we now request, it will be
3785 // fed to the engine from the first move on. So if the engine is not
3786 // in the initial position now, bring it there.
3787 InitChessProgram(&first, 0);
3790 ics_getting_history = H_REQUESTED;
3791 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3794 forwardMostMove = backwardMostMove = currentMove = moveNum;
3797 /* Update the clocks */
3798 if (strchr(elapsed_time, '.')) {
3800 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3801 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3803 /* Time is in seconds */
3804 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3805 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3810 if (appData.zippyPlay && newGame &&
3811 gameMode != IcsObserving && gameMode != IcsIdle &&
3812 gameMode != IcsExamining)
3813 ZippyFirstBoard(moveNum, basetime, increment);
3816 /* Put the move on the move list, first converting
3817 to canonical algebraic form. */
3819 if (appData.debugMode) {
3820 if (appData.debugMode) { int f = forwardMostMove;
3821 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3822 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3824 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3825 fprintf(debugFP, "moveNum = %d\n", moveNum);
3826 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3827 setbuf(debugFP, NULL);
3829 if (moveNum <= backwardMostMove) {
3830 /* We don't know what the board looked like before
3832 strcpy(parseList[moveNum - 1], move_str);
3833 strcat(parseList[moveNum - 1], " ");
3834 strcat(parseList[moveNum - 1], elapsed_time);
3835 moveList[moveNum - 1][0] = NULLCHAR;
3836 } else if (strcmp(move_str, "none") == 0) {
3837 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3838 /* Again, we don't know what the board looked like;
3839 this is really the start of the game. */
3840 parseList[moveNum - 1][0] = NULLCHAR;
3841 moveList[moveNum - 1][0] = NULLCHAR;
3842 backwardMostMove = moveNum;
3843 startedFromSetupPosition = TRUE;
3844 fromX = fromY = toX = toY = -1;
3846 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3847 // So we parse the long-algebraic move string in stead of the SAN move
3848 int valid; char buf[MSG_SIZ], *prom;
3850 // str looks something like "Q/a1-a2"; kill the slash
3852 sprintf(buf, "%c%s", str[0], str+2);
3853 else strcpy(buf, str); // might be castling
3854 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3855 strcat(buf, prom); // long move lacks promo specification!
3856 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3857 if(appData.debugMode)
3858 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3859 strcpy(move_str, buf);
3861 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3862 &fromX, &fromY, &toX, &toY, &promoChar)
3863 || ParseOneMove(buf, moveNum - 1, &moveType,
3864 &fromX, &fromY, &toX, &toY, &promoChar);
3865 // end of long SAN patch
3867 (void) CoordsToAlgebraic(boards[moveNum - 1],
3868 PosFlags(moveNum - 1), EP_UNKNOWN,
3869 fromY, fromX, toY, toX, promoChar,
3870 parseList[moveNum-1]);
3871 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3872 castlingRights[moveNum]) ) {
3878 if(gameInfo.variant != VariantShogi)
3879 strcat(parseList[moveNum - 1], "+");
3882 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3883 strcat(parseList[moveNum - 1], "#");
3886 strcat(parseList[moveNum - 1], " ");
3887 strcat(parseList[moveNum - 1], elapsed_time);
3888 /* currentMoveString is set as a side-effect of ParseOneMove */
3889 strcpy(moveList[moveNum - 1], currentMoveString);
3890 strcat(moveList[moveNum - 1], "\n");
3892 /* Move from ICS was illegal!? Punt. */
3893 if (appData.debugMode) {
3894 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3895 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3897 strcpy(parseList[moveNum - 1], move_str);
3898 strcat(parseList[moveNum - 1], " ");
3899 strcat(parseList[moveNum - 1], elapsed_time);
3900 moveList[moveNum - 1][0] = NULLCHAR;
3901 fromX = fromY = toX = toY = -1;
3904 if (appData.debugMode) {
3905 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3906 setbuf(debugFP, NULL);
3910 /* Send move to chess program (BEFORE animating it). */
3911 if (appData.zippyPlay && !newGame && newMove &&
3912 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3914 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3915 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3916 if (moveList[moveNum - 1][0] == NULLCHAR) {
3917 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3919 DisplayError(str, 0);
3921 if (first.sendTime) {
3922 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3924 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3925 if (firstMove && !bookHit) {
3927 if (first.useColors) {
3928 SendToProgram(gameMode == IcsPlayingWhite ?
3930 "black\ngo\n", &first);
3932 SendToProgram("go\n", &first);
3934 first.maybeThinking = TRUE;
3937 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3938 if (moveList[moveNum - 1][0] == NULLCHAR) {
3939 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3940 DisplayError(str, 0);
3942 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3943 SendMoveToProgram(moveNum - 1, &first);
3950 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3951 /* If move comes from a remote source, animate it. If it
3952 isn't remote, it will have already been animated. */
3953 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3954 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3956 if (!pausing && appData.highlightLastMove) {
3957 SetHighlights(fromX, fromY, toX, toY);
3961 /* Start the clocks */
3962 whiteFlag = blackFlag = FALSE;
3963 appData.clockMode = !(basetime == 0 && increment == 0);
3965 ics_clock_paused = TRUE;
3967 } else if (ticking == 1) {
3968 ics_clock_paused = FALSE;
3970 if (gameMode == IcsIdle ||
3971 relation == RELATION_OBSERVING_STATIC ||
3972 relation == RELATION_EXAMINING ||
3974 DisplayBothClocks();
3978 /* Display opponents and material strengths */
3979 if (gameInfo.variant != VariantBughouse &&
3980 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3981 if (tinyLayout || smallLayout) {
3982 if(gameInfo.variant == VariantNormal)
3983 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3984 gameInfo.white, white_stren, gameInfo.black, black_stren,
3985 basetime, increment);
3987 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3988 gameInfo.white, white_stren, gameInfo.black, black_stren,
3989 basetime, increment, (int) gameInfo.variant);
3991 if(gameInfo.variant == VariantNormal)
3992 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3993 gameInfo.white, white_stren, gameInfo.black, black_stren,
3994 basetime, increment);
3996 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3997 gameInfo.white, white_stren, gameInfo.black, black_stren,
3998 basetime, increment, VariantName(gameInfo.variant));
4001 if (appData.debugMode) {
4002 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4007 /* Display the board */
4008 if (!pausing && !appData.noGUI) {
4010 if (appData.premove)
4012 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4013 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4014 ClearPremoveHighlights();
4016 DrawPosition(FALSE, boards[currentMove]);
4017 DisplayMove(moveNum - 1);
4018 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4019 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4020 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4021 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4025 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4027 if(bookHit) { // [HGM] book: simulate book reply
4028 static char bookMove[MSG_SIZ]; // a bit generous?
4030 programStats.nodes = programStats.depth = programStats.time =
4031 programStats.score = programStats.got_only_move = 0;
4032 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4034 strcpy(bookMove, "move ");
4035 strcat(bookMove, bookHit);
4036 HandleMachineMove(bookMove, &first);
4045 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4046 ics_getting_history = H_REQUESTED;
4047 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4053 AnalysisPeriodicEvent(force)
4056 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4057 && !force) || !appData.periodicUpdates)
4060 /* Send . command to Crafty to collect stats */
4061 SendToProgram(".\n", &first);
4063 /* Don't send another until we get a response (this makes
4064 us stop sending to old Crafty's which don't understand
4065 the "." command (sending illegal cmds resets node count & time,
4066 which looks bad)) */
4067 programStats.ok_to_send = 0;
4070 void ics_update_width(new_width)
4073 ics_printf("set width %d\n", new_width);
4077 SendMoveToProgram(moveNum, cps)
4079 ChessProgramState *cps;
4083 if (cps->useUsermove) {
4084 SendToProgram("usermove ", cps);
4088 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4089 int len = space - parseList[moveNum];
4090 memcpy(buf, parseList[moveNum], len);
4092 buf[len] = NULLCHAR;
4094 sprintf(buf, "%s\n", parseList[moveNum]);
4096 SendToProgram(buf, cps);
4098 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4099 AlphaRank(moveList[moveNum], 4);
4100 SendToProgram(moveList[moveNum], cps);
4101 AlphaRank(moveList[moveNum], 4); // and back
4103 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4104 * the engine. It would be nice to have a better way to identify castle
4106 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4107 && cps->useOOCastle) {
4108 int fromX = moveList[moveNum][0] - AAA;
4109 int fromY = moveList[moveNum][1] - ONE;
4110 int toX = moveList[moveNum][2] - AAA;
4111 int toY = moveList[moveNum][3] - ONE;
4112 if((boards[moveNum][fromY][fromX] == WhiteKing
4113 && boards[moveNum][toY][toX] == WhiteRook)
4114 || (boards[moveNum][fromY][fromX] == BlackKing
4115 && boards[moveNum][toY][toX] == BlackRook)) {
4116 if(toX > fromX) SendToProgram("O-O\n", cps);
4117 else SendToProgram("O-O-O\n", cps);
4119 else SendToProgram(moveList[moveNum], cps);
4121 else SendToProgram(moveList[moveNum], cps);
4122 /* End of additions by Tord */
4125 /* [HGM] setting up the opening has brought engine in force mode! */
4126 /* Send 'go' if we are in a mode where machine should play. */
4127 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4128 (gameMode == TwoMachinesPlay ||
4130 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4132 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4133 SendToProgram("go\n", cps);
4134 if (appData.debugMode) {
4135 fprintf(debugFP, "(extra)\n");
4138 setboardSpoiledMachineBlack = 0;
4142 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4144 int fromX, fromY, toX, toY;
4146 char user_move[MSG_SIZ];
4150 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4151 (int)moveType, fromX, fromY, toX, toY);
4152 DisplayError(user_move + strlen("say "), 0);
4154 case WhiteKingSideCastle:
4155 case BlackKingSideCastle:
4156 case WhiteQueenSideCastleWild:
4157 case BlackQueenSideCastleWild:
4159 case WhiteHSideCastleFR:
4160 case BlackHSideCastleFR:
4162 sprintf(user_move, "o-o\n");
4164 case WhiteQueenSideCastle:
4165 case BlackQueenSideCastle:
4166 case WhiteKingSideCastleWild:
4167 case BlackKingSideCastleWild:
4169 case WhiteASideCastleFR:
4170 case BlackASideCastleFR:
4172 sprintf(user_move, "o-o-o\n");
4174 case WhitePromotionQueen:
4175 case BlackPromotionQueen:
4176 case WhitePromotionRook:
4177 case BlackPromotionRook:
4178 case WhitePromotionBishop:
4179 case BlackPromotionBishop:
4180 case WhitePromotionKnight:
4181 case BlackPromotionKnight:
4182 case WhitePromotionKing:
4183 case BlackPromotionKing:
4184 case WhitePromotionChancellor:
4185 case BlackPromotionChancellor:
4186 case WhitePromotionArchbishop:
4187 case BlackPromotionArchbishop:
4188 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4189 sprintf(user_move, "%c%c%c%c=%c\n",
4190 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191 PieceToChar(WhiteFerz));
4192 else if(gameInfo.variant == VariantGreat)
4193 sprintf(user_move, "%c%c%c%c=%c\n",
4194 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195 PieceToChar(WhiteMan));
4197 sprintf(user_move, "%c%c%c%c=%c\n",
4198 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4199 PieceToChar(PromoPiece(moveType)));
4203 sprintf(user_move, "%c@%c%c\n",
4204 ToUpper(PieceToChar((ChessSquare) fromX)),
4205 AAA + toX, ONE + toY);
4208 case WhiteCapturesEnPassant:
4209 case BlackCapturesEnPassant:
4210 case IllegalMove: /* could be a variant we don't quite understand */
4211 sprintf(user_move, "%c%c%c%c\n",
4212 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4215 SendToICS(user_move);
4216 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4217 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4221 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4226 if (rf == DROP_RANK) {
4227 sprintf(move, "%c@%c%c\n",
4228 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4230 if (promoChar == 'x' || promoChar == NULLCHAR) {
4231 sprintf(move, "%c%c%c%c\n",
4232 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4234 sprintf(move, "%c%c%c%c%c\n",
4235 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4241 ProcessICSInitScript(f)
4246 while (fgets(buf, MSG_SIZ, f)) {
4247 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4254 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4256 AlphaRank(char *move, int n)
4258 // char *p = move, c; int x, y;
4260 if (appData.debugMode) {
4261 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4265 move[2]>='0' && move[2]<='9' &&
4266 move[3]>='a' && move[3]<='x' ) {
4268 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4269 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4271 if(move[0]>='0' && move[0]<='9' &&
4272 move[1]>='a' && move[1]<='x' &&
4273 move[2]>='0' && move[2]<='9' &&
4274 move[3]>='a' && move[3]<='x' ) {
4275 /* input move, Shogi -> normal */
4276 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4277 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4278 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4279 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4282 move[3]>='0' && move[3]<='9' &&
4283 move[2]>='a' && move[2]<='x' ) {
4285 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4286 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4289 move[0]>='a' && move[0]<='x' &&
4290 move[3]>='0' && move[3]<='9' &&
4291 move[2]>='a' && move[2]<='x' ) {
4292 /* output move, normal -> Shogi */
4293 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4294 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4295 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4296 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4297 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4299 if (appData.debugMode) {
4300 fprintf(debugFP, " out = '%s'\n", move);
4304 /* Parser for moves from gnuchess, ICS, or user typein box */
4306 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4309 ChessMove *moveType;
4310 int *fromX, *fromY, *toX, *toY;
4313 if (appData.debugMode) {
4314 fprintf(debugFP, "move to parse: %s\n", move);
4316 *moveType = yylexstr(moveNum, move);
4318 switch (*moveType) {
4319 case WhitePromotionChancellor:
4320 case BlackPromotionChancellor:
4321 case WhitePromotionArchbishop:
4322 case BlackPromotionArchbishop:
4323 case WhitePromotionQueen:
4324 case BlackPromotionQueen:
4325 case WhitePromotionRook:
4326 case BlackPromotionRook:
4327 case WhitePromotionBishop:
4328 case BlackPromotionBishop:
4329 case WhitePromotionKnight:
4330 case BlackPromotionKnight:
4331 case WhitePromotionKing:
4332 case BlackPromotionKing:
4334 case WhiteCapturesEnPassant:
4335 case BlackCapturesEnPassant:
4336 case WhiteKingSideCastle:
4337 case WhiteQueenSideCastle:
4338 case BlackKingSideCastle:
4339 case BlackQueenSideCastle:
4340 case WhiteKingSideCastleWild:
4341 case WhiteQueenSideCastleWild:
4342 case BlackKingSideCastleWild:
4343 case BlackQueenSideCastleWild:
4344 /* Code added by Tord: */
4345 case WhiteHSideCastleFR:
4346 case WhiteASideCastleFR:
4347 case BlackHSideCastleFR:
4348 case BlackASideCastleFR:
4349 /* End of code added by Tord */
4350 case IllegalMove: /* bug or odd chess variant */
4351 *fromX = currentMoveString[0] - AAA;
4352 *fromY = currentMoveString[1] - ONE;
4353 *toX = currentMoveString[2] - AAA;
4354 *toY = currentMoveString[3] - ONE;
4355 *promoChar = currentMoveString[4];
4356 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4357 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4358 if (appData.debugMode) {
4359 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4361 *fromX = *fromY = *toX = *toY = 0;
4364 if (appData.testLegality) {
4365 return (*moveType != IllegalMove);
4367 return !(*fromX == *toX && *fromY == *toY);
4372 *fromX = *moveType == WhiteDrop ?
4373 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4374 (int) CharToPiece(ToLower(currentMoveString[0]));
4376 *toX = currentMoveString[2] - AAA;
4377 *toY = currentMoveString[3] - ONE;
4378 *promoChar = NULLCHAR;
4382 case ImpossibleMove:
4383 case (ChessMove) 0: /* end of file */
4392 if (appData.debugMode) {
4393 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4396 *fromX = *fromY = *toX = *toY = 0;
4397 *promoChar = NULLCHAR;
4402 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4403 // All positions will have equal probability, but the current method will not provide a unique
4404 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4410 int piecesLeft[(int)BlackPawn];
4411 int seed, nrOfShuffles;
4413 void GetPositionNumber()
4414 { // sets global variable seed
4417 seed = appData.defaultFrcPosition;
4418 if(seed < 0) { // randomize based on time for negative FRC position numbers
4419 for(i=0; i<50; i++) seed += random();
4420 seed = random() ^ random() >> 8 ^ random() << 8;
4421 if(seed<0) seed = -seed;
4425 int put(Board board, int pieceType, int rank, int n, int shade)
4426 // put the piece on the (n-1)-th empty squares of the given shade
4430 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4431 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4432 board[rank][i] = (ChessSquare) pieceType;
4433 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4435 piecesLeft[pieceType]--;
4443 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4444 // calculate where the next piece goes, (any empty square), and put it there
4448 i = seed % squaresLeft[shade];
4449 nrOfShuffles *= squaresLeft[shade];
4450 seed /= squaresLeft[shade];
4451 put(board, pieceType, rank, i, shade);
4454 void AddTwoPieces(Board board, int pieceType, int rank)
4455 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4457 int i, n=squaresLeft[ANY], j=n-1, k;
4459 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4460 i = seed % k; // pick one
4463 while(i >= j) i -= j--;
4464 j = n - 1 - j; i += j;
4465 put(board, pieceType, rank, j, ANY);
4466 put(board, pieceType, rank, i, ANY);
4469 void SetUpShuffle(Board board, int number)
4473 GetPositionNumber(); nrOfShuffles = 1;
4475 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4476 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4477 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4479 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4481 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4482 p = (int) board[0][i];
4483 if(p < (int) BlackPawn) piecesLeft[p] ++;
4484 board[0][i] = EmptySquare;
4487 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4488 // shuffles restricted to allow normal castling put KRR first
4489 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4490 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4491 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4492 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4493 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4494 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4495 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4496 put(board, WhiteRook, 0, 0, ANY);
4497 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4500 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4501 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4502 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4503 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4504 while(piecesLeft[p] >= 2) {
4505 AddOnePiece(board, p, 0, LITE);
4506 AddOnePiece(board, p, 0, DARK);
4508 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4511 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4512 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4513 // but we leave King and Rooks for last, to possibly obey FRC restriction
4514 if(p == (int)WhiteRook) continue;
4515 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4516 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4519 // now everything is placed, except perhaps King (Unicorn) and Rooks
4521 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4522 // Last King gets castling rights
4523 while(piecesLeft[(int)WhiteUnicorn]) {
4524 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4525 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4528 while(piecesLeft[(int)WhiteKing]) {
4529 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4530 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4535 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4536 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4539 // Only Rooks can be left; simply place them all
4540 while(piecesLeft[(int)WhiteRook]) {
4541 i = put(board, WhiteRook, 0, 0, ANY);
4542 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4545 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4547 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4550 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4551 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4554 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4557 int SetCharTable( char *table, const char * map )
4558 /* [HGM] moved here from winboard.c because of its general usefulness */
4559 /* Basically a safe strcpy that uses the last character as King */
4561 int result = FALSE; int NrPieces;
4563 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4564 && NrPieces >= 12 && !(NrPieces&1)) {
4565 int i; /* [HGM] Accept even length from 12 to 34 */
4567 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4568 for( i=0; i<NrPieces/2-1; i++ ) {
4570 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4572 table[(int) WhiteKing] = map[NrPieces/2-1];
4573 table[(int) BlackKing] = map[NrPieces-1];
4581 void Prelude(Board board)
4582 { // [HGM] superchess: random selection of exo-pieces
4583 int i, j, k; ChessSquare p;
4584 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4586 GetPositionNumber(); // use FRC position number
4588 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4589 SetCharTable(pieceToChar, appData.pieceToCharTable);
4590 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4591 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4594 j = seed%4; seed /= 4;
4595 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4596 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4597 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4598 j = seed%3 + (seed%3 >= j); seed /= 3;
4599 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4600 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4601 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4602 j = seed%3; seed /= 3;
4603 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4604 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4605 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4606 j = seed%2 + (seed%2 >= j); seed /= 2;
4607 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4608 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4609 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4610 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4611 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4612 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4613 put(board, exoPieces[0], 0, 0, ANY);
4614 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4618 InitPosition(redraw)
4621 ChessSquare (* pieces)[BOARD_SIZE];
4622 int i, j, pawnRow, overrule,
4623 oldx = gameInfo.boardWidth,
4624 oldy = gameInfo.boardHeight,
4625 oldh = gameInfo.holdingsWidth,
4626 oldv = gameInfo.variant;
4628 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4630 /* [AS] Initialize pv info list [HGM] and game status */
4632 for( i=0; i<MAX_MOVES; i++ ) {
4633 pvInfoList[i].depth = 0;
4634 epStatus[i]=EP_NONE;
4635 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4638 initialRulePlies = 0; /* 50-move counter start */
4640 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4641 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4645 /* [HGM] logic here is completely changed. In stead of full positions */
4646 /* the initialized data only consist of the two backranks. The switch */
4647 /* selects which one we will use, which is than copied to the Board */
4648 /* initialPosition, which for the rest is initialized by Pawns and */
4649 /* empty squares. This initial position is then copied to boards[0], */
4650 /* possibly after shuffling, so that it remains available. */
4652 gameInfo.holdingsWidth = 0; /* default board sizes */
4653 gameInfo.boardWidth = 8;
4654 gameInfo.boardHeight = 8;
4655 gameInfo.holdingsSize = 0;
4656 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4657 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4658 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4660 switch (gameInfo.variant) {
4661 case VariantFischeRandom:
4662 shuffleOpenings = TRUE;
4666 case VariantShatranj:
4667 pieces = ShatranjArray;
4668 nrCastlingRights = 0;
4669 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4671 case VariantTwoKings:
4672 pieces = twoKingsArray;
4674 case VariantCapaRandom:
4675 shuffleOpenings = TRUE;
4676 case VariantCapablanca:
4677 pieces = CapablancaArray;
4678 gameInfo.boardWidth = 10;
4679 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4682 pieces = GothicArray;
4683 gameInfo.boardWidth = 10;
4684 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4687 pieces = JanusArray;
4688 gameInfo.boardWidth = 10;
4689 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4690 nrCastlingRights = 6;
4691 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4692 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4693 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4694 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4695 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4696 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4699 pieces = FalconArray;
4700 gameInfo.boardWidth = 10;
4701 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4703 case VariantXiangqi:
4704 pieces = XiangqiArray;
4705 gameInfo.boardWidth = 9;
4706 gameInfo.boardHeight = 10;
4707 nrCastlingRights = 0;
4708 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4711 pieces = ShogiArray;
4712 gameInfo.boardWidth = 9;
4713 gameInfo.boardHeight = 9;
4714 gameInfo.holdingsSize = 7;
4715 nrCastlingRights = 0;
4716 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4718 case VariantCourier:
4719 pieces = CourierArray;
4720 gameInfo.boardWidth = 12;
4721 nrCastlingRights = 0;
4722 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4723 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4725 case VariantKnightmate:
4726 pieces = KnightmateArray;
4727 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4730 pieces = fairyArray;
4731 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4734 pieces = GreatArray;
4735 gameInfo.boardWidth = 10;
4736 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4737 gameInfo.holdingsSize = 8;
4741 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4742 gameInfo.holdingsSize = 8;
4743 startedFromSetupPosition = TRUE;
4745 case VariantCrazyhouse:
4746 case VariantBughouse:
4748 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4749 gameInfo.holdingsSize = 5;
4751 case VariantWildCastle:
4753 /* !!?shuffle with kings guaranteed to be on d or e file */
4754 shuffleOpenings = 1;
4756 case VariantNoCastle:
4758 nrCastlingRights = 0;
4759 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4760 /* !!?unconstrained back-rank shuffle */
4761 shuffleOpenings = 1;
4766 if(appData.NrFiles >= 0) {
4767 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4768 gameInfo.boardWidth = appData.NrFiles;
4770 if(appData.NrRanks >= 0) {
4771 gameInfo.boardHeight = appData.NrRanks;
4773 if(appData.holdingsSize >= 0) {
4774 i = appData.holdingsSize;
4775 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4776 gameInfo.holdingsSize = i;
4778 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4779 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4780 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4782 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4783 if(pawnRow < 1) pawnRow = 1;
4785 /* User pieceToChar list overrules defaults */
4786 if(appData.pieceToCharTable != NULL)
4787 SetCharTable(pieceToChar, appData.pieceToCharTable);
4789 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4791 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4792 s = (ChessSquare) 0; /* account holding counts in guard band */
4793 for( i=0; i<BOARD_HEIGHT; i++ )
4794 initialPosition[i][j] = s;
4796 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4797 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4798 initialPosition[pawnRow][j] = WhitePawn;
4799 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4800 if(gameInfo.variant == VariantXiangqi) {
4802 initialPosition[pawnRow][j] =
4803 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4804 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4805 initialPosition[2][j] = WhiteCannon;
4806 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4810 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4812 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4815 initialPosition[1][j] = WhiteBishop;
4816 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4818 initialPosition[1][j] = WhiteRook;
4819 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4822 if( nrCastlingRights == -1) {
4823 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4824 /* This sets default castling rights from none to normal corners */
4825 /* Variants with other castling rights must set them themselves above */
4826 nrCastlingRights = 6;
4828 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4829 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4830 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4831 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4832 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4833 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4836 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4837 if(gameInfo.variant == VariantGreat) { // promotion commoners
4838 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4839 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4840 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4841 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4843 if (appData.debugMode) {
4844 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4846 if(shuffleOpenings) {
4847 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4848 startedFromSetupPosition = TRUE;
4850 if(startedFromPositionFile) {
4851 /* [HGM] loadPos: use PositionFile for every new game */
4852 CopyBoard(initialPosition, filePosition);
4853 for(i=0; i<nrCastlingRights; i++)
4854 castlingRights[0][i] = initialRights[i] = fileRights[i];
4855 startedFromSetupPosition = TRUE;
4858 CopyBoard(boards[0], initialPosition);
4860 if(oldx != gameInfo.boardWidth ||
4861 oldy != gameInfo.boardHeight ||
4862 oldh != gameInfo.holdingsWidth
4864 || oldv == VariantGothic || // For licensing popups
4865 gameInfo.variant == VariantGothic
4868 || oldv == VariantFalcon ||
4869 gameInfo.variant == VariantFalcon
4872 InitDrawingSizes(-2 ,0);
4875 DrawPosition(TRUE, boards[currentMove]);
4879 SendBoard(cps, moveNum)
4880 ChessProgramState *cps;
4883 char message[MSG_SIZ];
4885 if (cps->useSetboard) {
4886 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4887 sprintf(message, "setboard %s\n", fen);
4888 SendToProgram(message, cps);
4894 /* Kludge to set black to move, avoiding the troublesome and now
4895 * deprecated "black" command.
4897 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4899 SendToProgram("edit\n", cps);
4900 SendToProgram("#\n", cps);
4901 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4902 bp = &boards[moveNum][i][BOARD_LEFT];
4903 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4904 if ((int) *bp < (int) BlackPawn) {
4905 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4907 if(message[0] == '+' || message[0] == '~') {
4908 sprintf(message, "%c%c%c+\n",
4909 PieceToChar((ChessSquare)(DEMOTED *bp)),
4912 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4913 message[1] = BOARD_RGHT - 1 - j + '1';
4914 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4916 SendToProgram(message, cps);
4921 SendToProgram("c\n", cps);
4922 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4923 bp = &boards[moveNum][i][BOARD_LEFT];
4924 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4925 if (((int) *bp != (int) EmptySquare)
4926 && ((int) *bp >= (int) BlackPawn)) {
4927 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4929 if(message[0] == '+' || message[0] == '~') {
4930 sprintf(message, "%c%c%c+\n",
4931 PieceToChar((ChessSquare)(DEMOTED *bp)),
4934 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4935 message[1] = BOARD_RGHT - 1 - j + '1';
4936 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4938 SendToProgram(message, cps);
4943 SendToProgram(".\n", cps);
4945 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4949 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4951 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4952 /* [HGM] add Shogi promotions */
4953 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4958 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4959 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4961 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4962 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4965 piece = boards[currentMove][fromY][fromX];
4966 if(gameInfo.variant == VariantShogi) {
4967 promotionZoneSize = 3;
4968 highestPromotingPiece = (int)WhiteFerz;
4971 // next weed out all moves that do not touch the promotion zone at all
4972 if((int)piece >= BlackPawn) {
4973 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4975 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4977 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4978 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4981 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4983 // weed out mandatory Shogi promotions
4984 if(gameInfo.variant == VariantShogi) {
4985 if(piece >= BlackPawn) {
4986 if(toY == 0 && piece == BlackPawn ||
4987 toY == 0 && piece == BlackQueen ||
4988 toY <= 1 && piece == BlackKnight) {
4993 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4994 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4995 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5002 // weed out obviously illegal Pawn moves
5003 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5004 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5005 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5006 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5007 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5008 // note we are not allowed to test for valid (non-)capture, due to premove
5011 // we either have a choice what to promote to, or (in Shogi) whether to promote
5012 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5013 *promoChoice = PieceToChar(BlackFerz); // no choice
5016 if(appData.alwaysPromoteToQueen) { // predetermined
5017 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5018 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5019 else *promoChoice = PieceToChar(BlackQueen);
5023 // suppress promotion popup on illegal moves that are not premoves
5024 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5025 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5026 if(appData.testLegality && !premove) {
5027 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5028 epStatus[currentMove], castlingRights[currentMove],
5029 fromY, fromX, toY, toX, NULLCHAR);
5030 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5031 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5039 InPalace(row, column)
5041 { /* [HGM] for Xiangqi */
5042 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5043 column < (BOARD_WIDTH + 4)/2 &&
5044 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5049 PieceForSquare (x, y)
5053 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5056 return boards[currentMove][y][x];
5060 OKToStartUserMove(x, y)
5063 ChessSquare from_piece;
5066 if (matchMode) return FALSE;
5067 if (gameMode == EditPosition) return TRUE;
5069 if (x >= 0 && y >= 0)
5070 from_piece = boards[currentMove][y][x];
5072 from_piece = EmptySquare;
5074 if (from_piece == EmptySquare) return FALSE;
5076 white_piece = (int)from_piece >= (int)WhitePawn &&
5077 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5080 case PlayFromGameFile:
5082 case TwoMachinesPlay:
5090 case MachinePlaysWhite:
5091 case IcsPlayingBlack:
5092 if (appData.zippyPlay) return FALSE;
5094 DisplayMoveError(_("You are playing Black"));
5099 case MachinePlaysBlack:
5100 case IcsPlayingWhite:
5101 if (appData.zippyPlay) return FALSE;
5103 DisplayMoveError(_("You are playing White"));
5109 if (!white_piece && WhiteOnMove(currentMove)) {
5110 DisplayMoveError(_("It is White's turn"));
5113 if (white_piece && !WhiteOnMove(currentMove)) {
5114 DisplayMoveError(_("It is Black's turn"));
5117 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5118 /* Editing correspondence game history */
5119 /* Could disallow this or prompt for confirmation */
5122 if (currentMove < forwardMostMove) {
5123 /* Discarding moves */
5124 /* Could prompt for confirmation here,
5125 but I don't think that's such a good idea */
5126 forwardMostMove = currentMove;
5130 case BeginningOfGame:
5131 if (appData.icsActive) return FALSE;
5132 if (!appData.noChessProgram) {
5134 DisplayMoveError(_("You are playing White"));
5141 if (!white_piece && WhiteOnMove(currentMove)) {
5142 DisplayMoveError(_("It is White's turn"));
5145 if (white_piece && !WhiteOnMove(currentMove)) {
5146 DisplayMoveError(_("It is Black's turn"));
5155 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5156 && gameMode != AnalyzeFile && gameMode != Training) {
5157 DisplayMoveError(_("Displayed position is not current"));
5163 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5164 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5165 int lastLoadGameUseList = FALSE;
5166 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5167 ChessMove lastLoadGameStart = (ChessMove) 0;
5170 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5171 int fromX, fromY, toX, toY;
5176 ChessSquare pdown, pup;
5178 /* Check if the user is playing in turn. This is complicated because we
5179 let the user "pick up" a piece before it is his turn. So the piece he
5180 tried to pick up may have been captured by the time he puts it down!
5181 Therefore we use the color the user is supposed to be playing in this
5182 test, not the color of the piece that is currently on the starting
5183 square---except in EditGame mode, where the user is playing both
5184 sides; fortunately there the capture race can't happen. (It can
5185 now happen in IcsExamining mode, but that's just too bad. The user
5186 will get a somewhat confusing message in that case.)
5190 case PlayFromGameFile:
5192 case TwoMachinesPlay:
5196 /* We switched into a game mode where moves are not accepted,
5197 perhaps while the mouse button was down. */
5198 return ImpossibleMove;
5200 case MachinePlaysWhite:
5201 /* User is moving for Black */
5202 if (WhiteOnMove(currentMove)) {
5203 DisplayMoveError(_("It is White's turn"));
5204 return ImpossibleMove;
5208 case MachinePlaysBlack:
5209 /* User is moving for White */
5210 if (!WhiteOnMove(currentMove)) {
5211 DisplayMoveError(_("It is Black's turn"));
5212 return ImpossibleMove;
5218 case BeginningOfGame:
5221 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5222 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5223 /* User is moving for Black */
5224 if (WhiteOnMove(currentMove)) {
5225 DisplayMoveError(_("It is White's turn"));
5226 return ImpossibleMove;
5229 /* User is moving for White */
5230 if (!WhiteOnMove(currentMove)) {
5231 DisplayMoveError(_("It is Black's turn"));
5232 return ImpossibleMove;
5237 case IcsPlayingBlack:
5238 /* User is moving for Black */
5239 if (WhiteOnMove(currentMove)) {
5240 if (!appData.premove) {
5241 DisplayMoveError(_("It is White's turn"));
5242 } else if (toX >= 0 && toY >= 0) {
5245 premoveFromX = fromX;
5246 premoveFromY = fromY;
5247 premovePromoChar = promoChar;
5249 if (appData.debugMode)
5250 fprintf(debugFP, "Got premove: fromX %d,"
5251 "fromY %d, toX %d, toY %d\n",
5252 fromX, fromY, toX, toY);
5254 return ImpossibleMove;
5258 case IcsPlayingWhite:
5259 /* User is moving for White */
5260 if (!WhiteOnMove(currentMove)) {
5261 if (!appData.premove) {
5262 DisplayMoveError(_("It is Black's turn"));
5263 } else if (toX >= 0 && toY >= 0) {
5266 premoveFromX = fromX;
5267 premoveFromY = fromY;
5268 premovePromoChar = promoChar;
5270 if (appData.debugMode)
5271 fprintf(debugFP, "Got premove: fromX %d,"
5272 "fromY %d, toX %d, toY %d\n",
5273 fromX, fromY, toX, toY);
5275 return ImpossibleMove;
5283 /* EditPosition, empty square, or different color piece;
5284 click-click move is possible */
5285 if (toX == -2 || toY == -2) {
5286 boards[0][fromY][fromX] = EmptySquare;
5287 return AmbiguousMove;
5288 } else if (toX >= 0 && toY >= 0) {
5289 boards[0][toY][toX] = boards[0][fromY][fromX];
5290 boards[0][fromY][fromX] = EmptySquare;
5291 return AmbiguousMove;
5293 return ImpossibleMove;
5296 if(toX < 0 || toY < 0) return ImpossibleMove;
5297 pdown = boards[currentMove][fromY][fromX];
5298 pup = boards[currentMove][toY][toX];
5300 /* [HGM] If move started in holdings, it means a drop */
5301 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5302 if( pup != EmptySquare ) return ImpossibleMove;
5303 if(appData.testLegality) {
5304 /* it would be more logical if LegalityTest() also figured out
5305 * which drops are legal. For now we forbid pawns on back rank.
5306 * Shogi is on its own here...
5308 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5309 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5310 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5312 return WhiteDrop; /* Not needed to specify white or black yet */
5315 userOfferedDraw = FALSE;
5317 /* [HGM] always test for legality, to get promotion info */
5318 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5319 epStatus[currentMove], castlingRights[currentMove],
5320 fromY, fromX, toY, toX, promoChar);
5321 /* [HGM] but possibly ignore an IllegalMove result */
5322 if (appData.testLegality) {
5323 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5324 DisplayMoveError(_("Illegal move"));
5325 return ImpossibleMove;
5330 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5331 function is made into one that returns an OK move type if FinishMove
5332 should be called. This to give the calling driver routine the
5333 opportunity to finish the userMove input with a promotion popup,
5334 without bothering the user with this for invalid or illegal moves */
5336 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5339 /* Common tail of UserMoveEvent and DropMenuEvent */
5341 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5343 int fromX, fromY, toX, toY;
5344 /*char*/int promoChar;
5348 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5349 // [HGM] superchess: suppress promotions to non-available piece
5350 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5351 if(WhiteOnMove(currentMove)) {
5352 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5354 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5358 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5359 move type in caller when we know the move is a legal promotion */
5360 if(moveType == NormalMove && promoChar)
5361 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5363 /* [HGM] convert drag-and-drop piece drops to standard form */
5364 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5365 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5366 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5367 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5368 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5369 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5370 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5371 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5375 /* [HGM] <popupFix> The following if has been moved here from
5376 UserMoveEvent(). Because it seemed to belong here (why not allow
5377 piece drops in training games?), and because it can only be
5378 performed after it is known to what we promote. */
5379 if (gameMode == Training) {
5380 /* compare the move played on the board to the next move in the
5381 * game. If they match, display the move and the opponent's response.
5382 * If they don't match, display an error message.
5385 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5386 CopyBoard(testBoard, boards[currentMove]);
5387 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5389 if (CompareBoards(testBoard, boards[currentMove+1])) {
5390 ForwardInner(currentMove+1);
5392 /* Autoplay the opponent's response.
5393 * if appData.animate was TRUE when Training mode was entered,
5394 * the response will be animated.
5396 saveAnimate = appData.animate;
5397 appData.animate = animateTraining;
5398 ForwardInner(currentMove+1);
5399 appData.animate = saveAnimate;
5401 /* check for the end of the game */
5402 if (currentMove >= forwardMostMove) {
5403 gameMode = PlayFromGameFile;
5405 SetTrainingModeOff();
5406 DisplayInformation(_("End of game"));
5409 DisplayError(_("Incorrect move"), 0);
5414 /* Ok, now we know that the move is good, so we can kill
5415 the previous line in Analysis Mode */
5416 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5417 forwardMostMove = currentMove;
5420 /* If we need the chess program but it's dead, restart it */
5421 ResurrectChessProgram();
5423 /* A user move restarts a paused game*/
5427 thinkOutput[0] = NULLCHAR;
5429 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5431 if (gameMode == BeginningOfGame) {
5432 if (appData.noChessProgram) {
5433 gameMode = EditGame;
5437 gameMode = MachinePlaysBlack;
5440 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5442 if (first.sendName) {
5443 sprintf(buf, "name %s\n", gameInfo.white);
5444 SendToProgram(buf, &first);
5451 /* Relay move to ICS or chess engine */
5452 if (appData.icsActive) {
5453 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5454 gameMode == IcsExamining) {
5455 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5459 if (first.sendTime && (gameMode == BeginningOfGame ||
5460 gameMode == MachinePlaysWhite ||
5461 gameMode == MachinePlaysBlack)) {
5462 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5464 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5465 // [HGM] book: if program might be playing, let it use book
5466 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5467 first.maybeThinking = TRUE;
5468 } else SendMoveToProgram(forwardMostMove-1, &first);
5469 if (currentMove == cmailOldMove + 1) {
5470 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5474 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5478 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5479 EP_UNKNOWN, castlingRights[currentMove]) ) {
5485 if (WhiteOnMove(currentMove)) {
5486 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5488 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5492 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5497 case MachinePlaysBlack:
5498 case MachinePlaysWhite:
5499 /* disable certain menu options while machine is thinking */
5500 SetMachineThinkingEnables();
5507 if(bookHit) { // [HGM] book: simulate book reply
5508 static char bookMove[MSG_SIZ]; // a bit generous?
5510 programStats.nodes = programStats.depth = programStats.time =
5511 programStats.score = programStats.got_only_move = 0;
5512 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5514 strcpy(bookMove, "move ");
5515 strcat(bookMove, bookHit);
5516 HandleMachineMove(bookMove, &first);
5522 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5523 int fromX, fromY, toX, toY;
5526 /* [HGM] This routine was added to allow calling of its two logical
5527 parts from other modules in the old way. Before, UserMoveEvent()
5528 automatically called FinishMove() if the move was OK, and returned
5529 otherwise. I separated the two, in order to make it possible to
5530 slip a promotion popup in between. But that it always needs two
5531 calls, to the first part, (now called UserMoveTest() ), and to
5532 FinishMove if the first part succeeded. Calls that do not need
5533 to do anything in between, can call this routine the old way.
5535 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5536 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5537 if(moveType == AmbiguousMove)
5538 DrawPosition(FALSE, boards[currentMove]);
5539 else if(moveType != ImpossibleMove && moveType != Comment)
5540 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5543 void LeftClick(ClickType clickType, int xPix, int yPix)
5546 Boolean saveAnimate;
5547 static int second = 0, promotionChoice = 0;
5548 char promoChoice = NULLCHAR;
5550 if (clickType == Press) ErrorPopDown();
5552 x = EventToSquare(xPix, BOARD_WIDTH);
5553 y = EventToSquare(yPix, BOARD_HEIGHT);
5554 if (!flipView && y >= 0) {
5555 y = BOARD_HEIGHT - 1 - y;
5557 if (flipView && x >= 0) {
5558 x = BOARD_WIDTH - 1 - x;
5561 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5562 if(clickType == Release) return; // ignore upclick of click-click destination
5563 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5564 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5565 if(gameInfo.holdingsWidth &&
5566 (WhiteOnMove(currentMove)
5567 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5568 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5569 // click in right holdings, for determining promotion piece
5570 ChessSquare p = boards[currentMove][y][x];
5571 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5572 if(p != EmptySquare) {
5573 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5578 DrawPosition(FALSE, boards[currentMove]);
5582 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5583 if(clickType == Press
5584 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5585 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5586 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5590 if (clickType == Press) {
5592 if (OKToStartUserMove(x, y)) {
5596 DragPieceBegin(xPix, yPix);
5597 if (appData.highlightDragging) {
5598 SetHighlights(x, y, -1, -1);
5606 if (clickType == Press && gameMode != EditPosition) {
5611 // ignore off-board to clicks
5612 if(y < 0 || x < 0) return;
5614 /* Check if clicking again on the same color piece */
5615 fromP = boards[currentMove][fromY][fromX];
5616 toP = boards[currentMove][y][x];
5617 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5618 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5619 WhitePawn <= toP && toP <= WhiteKing &&
5620 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5621 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5622 (BlackPawn <= fromP && fromP <= BlackKing &&
5623 BlackPawn <= toP && toP <= BlackKing &&
5624 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5625 !(fromP == BlackKing && toP == BlackRook && frc))) {
5626 /* Clicked again on same color piece -- changed his mind */
5627 second = (x == fromX && y == fromY);
5628 if (appData.highlightDragging) {
5629 SetHighlights(x, y, -1, -1);
5633 if (OKToStartUserMove(x, y)) {
5636 DragPieceBegin(xPix, yPix);
5640 // ignore clicks on holdings
5641 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5644 if (clickType == Release && x == fromX && y == fromY) {
5645 DragPieceEnd(xPix, yPix);
5646 if (appData.animateDragging) {
5647 /* Undo animation damage if any */
5648 DrawPosition(FALSE, NULL);
5651 /* Second up/down in same square; just abort move */
5656 ClearPremoveHighlights();
5658 /* First upclick in same square; start click-click mode */
5659 SetHighlights(x, y, -1, -1);
5664 /* we now have a different from- and (possibly off-board) to-square */
5665 /* Completed move */
5668 saveAnimate = appData.animate;
5669 if (clickType == Press) {
5670 /* Finish clickclick move */
5671 if (appData.animate || appData.highlightLastMove) {
5672 SetHighlights(fromX, fromY, toX, toY);
5677 /* Finish drag move */
5678 if (appData.highlightLastMove) {
5679 SetHighlights(fromX, fromY, toX, toY);
5683 DragPieceEnd(xPix, yPix);
5684 /* Don't animate move and drag both */
5685 appData.animate = FALSE;
5688 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5689 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5692 DrawPosition(TRUE, NULL);
5696 // off-board moves should not be highlighted
5697 if(x < 0 || x < 0) ClearHighlights();
5699 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5700 SetHighlights(fromX, fromY, toX, toY);
5701 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5702 // [HGM] super: promotion to captured piece selected from holdings
5703 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5704 promotionChoice = TRUE;
5705 // kludge follows to temporarily execute move on display, without promoting yet
5706 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5707 boards[currentMove][toY][toX] = p;
5708 DrawPosition(FALSE, boards[currentMove]);
5709 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5710 boards[currentMove][toY][toX] = q;
5711 DisplayMessage("Click in holdings to choose piece", "");
5716 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5717 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5718 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5721 appData.animate = saveAnimate;
5722 if (appData.animate || appData.animateDragging) {
5723 /* Undo animation damage if needed */
5724 DrawPosition(FALSE, NULL);
5728 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5730 // char * hint = lastHint;
5731 FrontEndProgramStats stats;
5733 stats.which = cps == &first ? 0 : 1;
5734 stats.depth = cpstats->depth;
5735 stats.nodes = cpstats->nodes;
5736 stats.score = cpstats->score;
5737 stats.time = cpstats->time;
5738 stats.pv = cpstats->movelist;
5739 stats.hint = lastHint;
5740 stats.an_move_index = 0;
5741 stats.an_move_count = 0;
5743 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5744 stats.hint = cpstats->move_name;
5745 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5746 stats.an_move_count = cpstats->nr_moves;
5749 SetProgramStats( &stats );
5752 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5753 { // [HGM] book: this routine intercepts moves to simulate book replies
5754 char *bookHit = NULL;
5756 //first determine if the incoming move brings opponent into his book
5757 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5758 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5759 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5760 if(bookHit != NULL && !cps->bookSuspend) {
5761 // make sure opponent is not going to reply after receiving move to book position
5762 SendToProgram("force\n", cps);
5763 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5765 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5766 // now arrange restart after book miss
5768 // after a book hit we never send 'go', and the code after the call to this routine
5769 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5771 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5772 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5773 SendToProgram(buf, cps);
5774 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5775 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5776 SendToProgram("go\n", cps);
5777 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5778 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5779 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5780 SendToProgram("go\n", cps);
5781 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5783 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5787 ChessProgramState *savedState;
5788 void DeferredBookMove(void)
5790 if(savedState->lastPing != savedState->lastPong)
5791 ScheduleDelayedEvent(DeferredBookMove, 10);
5793 HandleMachineMove(savedMessage, savedState);
5797 HandleMachineMove(message, cps)
5799 ChessProgramState *cps;
5801 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5802 char realname[MSG_SIZ];
5803 int fromX, fromY, toX, toY;
5810 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5812 * Kludge to ignore BEL characters
5814 while (*message == '\007') message++;
5817 * [HGM] engine debug message: ignore lines starting with '#' character
5819 if(cps->debug && *message == '#') return;
5822 * Look for book output
5824 if (cps == &first && bookRequested) {
5825 if (message[0] == '\t' || message[0] == ' ') {
5826 /* Part of the book output is here; append it */
5827 strcat(bookOutput, message);
5828 strcat(bookOutput, " \n");
5830 } else if (bookOutput[0] != NULLCHAR) {
5831 /* All of book output has arrived; display it */
5832 char *p = bookOutput;
5833 while (*p != NULLCHAR) {
5834 if (*p == '\t') *p = ' ';
5837 DisplayInformation(bookOutput);
5838 bookRequested = FALSE;
5839 /* Fall through to parse the current output */
5844 * Look for machine move.
5846 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5847 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5849 /* This method is only useful on engines that support ping */
5850 if (cps->lastPing != cps->lastPong) {
5851 if (gameMode == BeginningOfGame) {
5852 /* Extra move from before last new; ignore */
5853 if (appData.debugMode) {
5854 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5857 if (appData.debugMode) {
5858 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5859 cps->which, gameMode);
5862 SendToProgram("undo\n", cps);
5868 case BeginningOfGame:
5869 /* Extra move from before last reset; ignore */
5870 if (appData.debugMode) {
5871 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5878 /* Extra move after we tried to stop. The mode test is
5879 not a reliable way of detecting this problem, but it's
5880 the best we can do on engines that don't support ping.
5882 if (appData.debugMode) {
5883 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5884 cps->which, gameMode);
5886 SendToProgram("undo\n", cps);
5889 case MachinePlaysWhite:
5890 case IcsPlayingWhite:
5891 machineWhite = TRUE;
5894 case MachinePlaysBlack:
5895 case IcsPlayingBlack:
5896 machineWhite = FALSE;
5899 case TwoMachinesPlay:
5900 machineWhite = (cps->twoMachinesColor[0] == 'w');
5903 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5904 if (appData.debugMode) {
5906 "Ignoring move out of turn by %s, gameMode %d"
5907 ", forwardMost %d\n",
5908 cps->which, gameMode, forwardMostMove);
5913 if (appData.debugMode) { int f = forwardMostMove;
5914 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5915 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5917 if(cps->alphaRank) AlphaRank(machineMove, 4);
5918 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5919 &fromX, &fromY, &toX, &toY, &promoChar)) {
5920 /* Machine move could not be parsed; ignore it. */
5921 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5922 machineMove, cps->which);
5923 DisplayError(buf1, 0);
5924 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5925 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5926 if (gameMode == TwoMachinesPlay) {
5927 GameEnds(machineWhite ? BlackWins : WhiteWins,
5933 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5934 /* So we have to redo legality test with true e.p. status here, */
5935 /* to make sure an illegal e.p. capture does not slip through, */
5936 /* to cause a forfeit on a justified illegal-move complaint */
5937 /* of the opponent. */
5938 if( gameMode==TwoMachinesPlay && appData.testLegality
5939 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5942 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5943 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5944 fromY, fromX, toY, toX, promoChar);
5945 if (appData.debugMode) {
5947 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5948 castlingRights[forwardMostMove][i], castlingRank[i]);
5949 fprintf(debugFP, "castling rights\n");
5951 if(moveType == IllegalMove) {
5952 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5953 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5954 GameEnds(machineWhite ? BlackWins : WhiteWins,
5957 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5958 /* [HGM] Kludge to handle engines that send FRC-style castling
5959 when they shouldn't (like TSCP-Gothic) */
5961 case WhiteASideCastleFR:
5962 case BlackASideCastleFR:
5964 currentMoveString[2]++;
5966 case WhiteHSideCastleFR:
5967 case BlackHSideCastleFR:
5969 currentMoveString[2]--;
5971 default: ; // nothing to do, but suppresses warning of pedantic compilers
5974 hintRequested = FALSE;
5975 lastHint[0] = NULLCHAR;
5976 bookRequested = FALSE;
5977 /* Program may be pondering now */
5978 cps->maybeThinking = TRUE;
5979 if (cps->sendTime == 2) cps->sendTime = 1;
5980 if (cps->offeredDraw) cps->offeredDraw--;
5983 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5985 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5987 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5988 char buf[3*MSG_SIZ];
5990 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5991 programStats.score / 100.,
5993 programStats.time / 100.,
5994 (unsigned int)programStats.nodes,
5995 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5996 programStats.movelist);
5998 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6002 /* currentMoveString is set as a side-effect of ParseOneMove */
6003 strcpy(machineMove, currentMoveString);
6004 strcat(machineMove, "\n");
6005 strcpy(moveList[forwardMostMove], machineMove);
6007 /* [AS] Save move info and clear stats for next move */
6008 pvInfoList[ forwardMostMove ].score = programStats.score;
6009 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6010 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6011 ClearProgramStats();
6012 thinkOutput[0] = NULLCHAR;
6013 hiddenThinkOutputState = 0;
6015 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6017 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6018 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6021 while( count < adjudicateLossPlies ) {
6022 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6025 score = -score; /* Flip score for winning side */
6028 if( score > adjudicateLossThreshold ) {
6035 if( count >= adjudicateLossPlies ) {
6036 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6038 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6039 "Xboard adjudication",
6046 if( gameMode == TwoMachinesPlay ) {
6047 // [HGM] some adjudications useful with buggy engines
6048 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6049 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6052 if( appData.testLegality )
6053 { /* [HGM] Some more adjudications for obstinate engines */
6054 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6055 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6056 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6057 static int moveCount = 6;
6059 char *reason = NULL;
6061 /* Count what is on board. */
6062 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6063 { ChessSquare p = boards[forwardMostMove][i][j];
6067 { /* count B,N,R and other of each side */
6070 NrK++; break; // [HGM] atomic: count Kings
6074 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6075 bishopsColor |= 1 << ((i^j)&1);
6080 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6081 bishopsColor |= 1 << ((i^j)&1);
6096 PawnAdvance += m; NrPawns++;
6098 NrPieces += (p != EmptySquare);
6099 NrW += ((int)p < (int)BlackPawn);
6100 if(gameInfo.variant == VariantXiangqi &&
6101 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6102 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6103 NrW -= ((int)p < (int)BlackPawn);
6107 /* Some material-based adjudications that have to be made before stalemate test */
6108 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6109 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6110 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6111 if(appData.checkMates) {
6112 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6113 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6114 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6115 "Xboard adjudication: King destroyed", GE_XBOARD );
6120 /* Bare King in Shatranj (loses) or Losers (wins) */
6121 if( NrW == 1 || NrPieces - NrW == 1) {
6122 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6123 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6124 if(appData.checkMates) {
6125 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6126 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6127 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6128 "Xboard adjudication: Bare king", GE_XBOARD );
6132 if( gameInfo.variant == VariantShatranj && --bare < 0)
6134 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6135 if(appData.checkMates) {
6136 /* but only adjudicate if adjudication enabled */
6137 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6138 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6139 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6140 "Xboard adjudication: Bare king", GE_XBOARD );
6147 // don't wait for engine to announce game end if we can judge ourselves
6148 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6149 castlingRights[forwardMostMove]) ) {
6151 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6152 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6153 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6154 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6157 reason = "Xboard adjudication: 3rd check";
6158 epStatus[forwardMostMove] = EP_CHECKMATE;
6168 reason = "Xboard adjudication: Stalemate";
6169 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6170 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6171 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6172 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6173 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6174 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6175 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6176 EP_CHECKMATE : EP_WINS);
6177 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6178 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6182 reason = "Xboard adjudication: Checkmate";
6183 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6187 switch(i = epStatus[forwardMostMove]) {
6189 result = GameIsDrawn; break;
6191 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6193 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6195 result = (ChessMove) 0;
6197 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6198 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6199 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6200 GameEnds( result, reason, GE_XBOARD );
6204 /* Next absolutely insufficient mating material. */
6205 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6206 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6207 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6208 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6209 { /* KBK, KNK, KK of KBKB with like Bishops */
6211 /* always flag draws, for judging claims */
6212 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6214 if(appData.materialDraws) {
6215 /* but only adjudicate them if adjudication enabled */
6216 SendToProgram("force\n", cps->other); // suppress reply
6217 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6218 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6219 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6224 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6226 ( NrWR == 1 && NrBR == 1 /* KRKR */
6227 || NrWQ==1 && NrBQ==1 /* KQKQ */
6228 || NrWN==2 || NrBN==2 /* KNNK */
6229 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6231 if(--moveCount < 0 && appData.trivialDraws)
6232 { /* if the first 3 moves do not show a tactical win, declare draw */
6233 SendToProgram("force\n", cps->other); // suppress reply
6234 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6235 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6236 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6239 } else moveCount = 6;
6243 /* Check for rep-draws */
6245 for(k = forwardMostMove-2;
6246 k>=backwardMostMove && k>=forwardMostMove-100 &&
6247 epStatus[k] < EP_UNKNOWN &&
6248 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6251 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6252 /* compare castling rights */
6253 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6254 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6255 rights++; /* King lost rights, while rook still had them */
6256 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6257 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6258 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6259 rights++; /* but at least one rook lost them */
6261 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6262 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6264 if( castlingRights[forwardMostMove][5] >= 0 ) {
6265 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6266 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6269 if( rights == 0 && ++count > appData.drawRepeats-2
6270 && appData.drawRepeats > 1) {
6271 /* adjudicate after user-specified nr of repeats */
6272 SendToProgram("force\n", cps->other); // suppress reply
6273 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6274 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6275 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6276 // [HGM] xiangqi: check for forbidden perpetuals
6277 int m, ourPerpetual = 1, hisPerpetual = 1;
6278 for(m=forwardMostMove; m>k; m-=2) {
6279 if(MateTest(boards[m], PosFlags(m),
6280 EP_NONE, castlingRights[m]) != MT_CHECK)
6281 ourPerpetual = 0; // the current mover did not always check
6282 if(MateTest(boards[m-1], PosFlags(m-1),
6283 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6284 hisPerpetual = 0; // the opponent did not always check
6286 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6287 ourPerpetual, hisPerpetual);
6288 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6289 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6290 "Xboard adjudication: perpetual checking", GE_XBOARD );
6293 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6294 break; // (or we would have caught him before). Abort repetition-checking loop.
6295 // Now check for perpetual chases
6296 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6297 hisPerpetual = PerpetualChase(k, forwardMostMove);
6298 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6299 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6300 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6301 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6304 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6305 break; // Abort repetition-checking loop.
6307 // if neither of us is checking or chasing all the time, or both are, it is draw
6309 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6312 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6313 epStatus[forwardMostMove] = EP_REP_DRAW;
6317 /* Now we test for 50-move draws. Determine ply count */
6318 count = forwardMostMove;
6319 /* look for last irreversble move */
6320 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6322 /* if we hit starting position, add initial plies */
6323 if( count == backwardMostMove )
6324 count -= initialRulePlies;
6325 count = forwardMostMove - count;
6327 epStatus[forwardMostMove] = EP_RULE_DRAW;
6328 /* this is used to judge if draw claims are legal */
6329 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6330 SendToProgram("force\n", cps->other); // suppress reply
6331 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6332 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6333 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6337 /* if draw offer is pending, treat it as a draw claim
6338 * when draw condition present, to allow engines a way to
6339 * claim draws before making their move to avoid a race
6340 * condition occurring after their move
6342 if( cps->other->offeredDraw || cps->offeredDraw ) {
6344 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6345 p = "Draw claim: 50-move rule";
6346 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6347 p = "Draw claim: 3-fold repetition";
6348 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6349 p = "Draw claim: insufficient mating material";
6351 SendToProgram("force\n", cps->other); // suppress reply
6352 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6353 GameEnds( GameIsDrawn, p, GE_XBOARD );
6354 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6360 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6361 SendToProgram("force\n", cps->other); // suppress reply
6362 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6363 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6365 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6372 if (gameMode == TwoMachinesPlay) {
6373 /* [HGM] relaying draw offers moved to after reception of move */
6374 /* and interpreting offer as claim if it brings draw condition */
6375 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6376 SendToProgram("draw\n", cps->other);
6378 if (cps->other->sendTime) {
6379 SendTimeRemaining(cps->other,
6380 cps->other->twoMachinesColor[0] == 'w');
6382 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6383 if (firstMove && !bookHit) {
6385 if (cps->other->useColors) {
6386 SendToProgram(cps->other->twoMachinesColor, cps->other);
6388 SendToProgram("go\n", cps->other);
6390 cps->other->maybeThinking = TRUE;
6393 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6395 if (!pausing && appData.ringBellAfterMoves) {
6400 * Reenable menu items that were disabled while
6401 * machine was thinking
6403 if (gameMode != TwoMachinesPlay)
6404 SetUserThinkingEnables();
6406 // [HGM] book: after book hit opponent has received move and is now in force mode
6407 // force the book reply into it, and then fake that it outputted this move by jumping
6408 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6410 static char bookMove[MSG_SIZ]; // a bit generous?
6412 strcpy(bookMove, "move ");
6413 strcat(bookMove, bookHit);
6416 programStats.nodes = programStats.depth = programStats.time =
6417 programStats.score = programStats.got_only_move = 0;
6418 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6420 if(cps->lastPing != cps->lastPong) {
6421 savedMessage = message; // args for deferred call
6423 ScheduleDelayedEvent(DeferredBookMove, 10);
6432 /* Set special modes for chess engines. Later something general
6433 * could be added here; for now there is just one kludge feature,
6434 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6435 * when "xboard" is given as an interactive command.
6437 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6438 cps->useSigint = FALSE;
6439 cps->useSigterm = FALSE;
6441 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6442 ParseFeatures(message+8, cps);
6443 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6446 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6447 * want this, I was asked to put it in, and obliged.
6449 if (!strncmp(message, "setboard ", 9)) {
6450 Board initial_position; int i;
6452 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6454 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6455 DisplayError(_("Bad FEN received from engine"), 0);
6459 CopyBoard(boards[0], initial_position);
6460 initialRulePlies = FENrulePlies;
6461 epStatus[0] = FENepStatus;
6462 for( i=0; i<nrCastlingRights; i++ )
6463 castlingRights[0][i] = FENcastlingRights[i];
6464 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6465 else gameMode = MachinePlaysBlack;
6466 DrawPosition(FALSE, boards[currentMove]);
6472 * Look for communication commands
6474 if (!strncmp(message, "telluser ", 9)) {
6475 DisplayNote(message + 9);
6478 if (!strncmp(message, "tellusererror ", 14)) {
6479 DisplayError(message + 14, 0);
6482 if (!strncmp(message, "tellopponent ", 13)) {
6483 if (appData.icsActive) {
6485 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6489 DisplayNote(message + 13);
6493 if (!strncmp(message, "tellothers ", 11)) {
6494 if (appData.icsActive) {
6496 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6502 if (!strncmp(message, "tellall ", 8)) {
6503 if (appData.icsActive) {
6505 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6509 DisplayNote(message + 8);
6513 if (strncmp(message, "warning", 7) == 0) {
6514 /* Undocumented feature, use tellusererror in new code */
6515 DisplayError(message, 0);
6518 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6519 strcpy(realname, cps->tidy);
6520 strcat(realname, " query");
6521 AskQuestion(realname, buf2, buf1, cps->pr);
6524 /* Commands from the engine directly to ICS. We don't allow these to be
6525 * sent until we are logged on. Crafty kibitzes have been known to
6526 * interfere with the login process.
6529 if (!strncmp(message, "tellics ", 8)) {
6530 SendToICS(message + 8);
6534 if (!strncmp(message, "tellicsnoalias ", 15)) {
6535 SendToICS(ics_prefix);
6536 SendToICS(message + 15);
6540 /* The following are for backward compatibility only */
6541 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6542 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6543 SendToICS(ics_prefix);
6549 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6553 * If the move is illegal, cancel it and redraw the board.
6554 * Also deal with other error cases. Matching is rather loose
6555 * here to accommodate engines written before the spec.
6557 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6558 strncmp(message, "Error", 5) == 0) {
6559 if (StrStr(message, "name") ||
6560 StrStr(message, "rating") || StrStr(message, "?") ||
6561 StrStr(message, "result") || StrStr(message, "board") ||
6562 StrStr(message, "bk") || StrStr(message, "computer") ||
6563 StrStr(message, "variant") || StrStr(message, "hint") ||
6564 StrStr(message, "random") || StrStr(message, "depth") ||
6565 StrStr(message, "accepted")) {
6568 if (StrStr(message, "protover")) {
6569 /* Program is responding to input, so it's apparently done
6570 initializing, and this error message indicates it is
6571 protocol version 1. So we don't need to wait any longer
6572 for it to initialize and send feature commands. */
6573 FeatureDone(cps, 1);
6574 cps->protocolVersion = 1;
6577 cps->maybeThinking = FALSE;
6579 if (StrStr(message, "draw")) {
6580 /* Program doesn't have "draw" command */
6581 cps->sendDrawOffers = 0;
6584 if (cps->sendTime != 1 &&
6585 (StrStr(message, "time") || StrStr(message, "otim"))) {
6586 /* Program apparently doesn't have "time" or "otim" command */
6590 if (StrStr(message, "analyze")) {
6591 cps->analysisSupport = FALSE;
6592 cps->analyzing = FALSE;
6594 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6595 DisplayError(buf2, 0);
6598 if (StrStr(message, "(no matching move)st")) {
6599 /* Special kludge for GNU Chess 4 only */
6600 cps->stKludge = TRUE;
6601 SendTimeControl(cps, movesPerSession, timeControl,
6602 timeIncrement, appData.searchDepth,
6606 if (StrStr(message, "(no matching move)sd")) {
6607 /* Special kludge for GNU Chess 4 only */
6608 cps->sdKludge = TRUE;
6609 SendTimeControl(cps, movesPerSession, timeControl,
6610 timeIncrement, appData.searchDepth,
6614 if (!StrStr(message, "llegal")) {
6617 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6618 gameMode == IcsIdle) return;
6619 if (forwardMostMove <= backwardMostMove) return;
6620 if (pausing) PauseEvent();
6621 if(appData.forceIllegal) {
6622 // [HGM] illegal: machine refused move; force position after move into it
6623 SendToProgram("force\n", cps);
6624 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6625 // we have a real problem now, as SendBoard will use the a2a3 kludge
6626 // when black is to move, while there might be nothing on a2 or black
6627 // might already have the move. So send the board as if white has the move.
6628 // But first we must change the stm of the engine, as it refused the last move
6629 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6630 if(WhiteOnMove(forwardMostMove)) {
6631 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6632 SendBoard(cps, forwardMostMove); // kludgeless board
6634 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6635 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6636 SendBoard(cps, forwardMostMove+1); // kludgeless board
6638 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6639 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6640 gameMode == TwoMachinesPlay)
6641 SendToProgram("go\n", cps);
6644 if (gameMode == PlayFromGameFile) {
6645 /* Stop reading this game file */
6646 gameMode = EditGame;
6649 currentMove = --forwardMostMove;
6650 DisplayMove(currentMove-1); /* before DisplayMoveError */
6652 DisplayBothClocks();
6653 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6654 parseList[currentMove], cps->which);
6655 DisplayMoveError(buf1);
6656 DrawPosition(FALSE, boards[currentMove]);
6658 /* [HGM] illegal-move claim should forfeit game when Xboard */
6659 /* only passes fully legal moves */
6660 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6661 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6662 "False illegal-move claim", GE_XBOARD );
6666 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6667 /* Program has a broken "time" command that
6668 outputs a string not ending in newline.
6674 * If chess program startup fails, exit with an error message.
6675 * Attempts to recover here are futile.
6677 if ((StrStr(message, "unknown host") != NULL)
6678 || (StrStr(message, "No remote directory") != NULL)
6679 || (StrStr(message, "not found") != NULL)
6680 || (StrStr(message, "No such file") != NULL)
6681 || (StrStr(message, "can't alloc") != NULL)
6682 || (StrStr(message, "Permission denied") != NULL)) {
6684 cps->maybeThinking = FALSE;
6685 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6686 cps->which, cps->program, cps->host, message);
6687 RemoveInputSource(cps->isr);
6688 DisplayFatalError(buf1, 0, 1);
6693 * Look for hint output
6695 if (sscanf(message, "Hint: %s", buf1) == 1) {
6696 if (cps == &first && hintRequested) {
6697 hintRequested = FALSE;
6698 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6699 &fromX, &fromY, &toX, &toY, &promoChar)) {
6700 (void) CoordsToAlgebraic(boards[forwardMostMove],
6701 PosFlags(forwardMostMove), EP_UNKNOWN,
6702 fromY, fromX, toY, toX, promoChar, buf1);
6703 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6704 DisplayInformation(buf2);
6706 /* Hint move could not be parsed!? */
6707 snprintf(buf2, sizeof(buf2),
6708 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6710 DisplayError(buf2, 0);
6713 strcpy(lastHint, buf1);
6719 * Ignore other messages if game is not in progress
6721 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6722 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6725 * look for win, lose, draw, or draw offer
6727 if (strncmp(message, "1-0", 3) == 0) {
6728 char *p, *q, *r = "";
6729 p = strchr(message, '{');
6737 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6739 } else if (strncmp(message, "0-1", 3) == 0) {
6740 char *p, *q, *r = "";
6741 p = strchr(message, '{');
6749 /* Kludge for Arasan 4.1 bug */
6750 if (strcmp(r, "Black resigns") == 0) {
6751 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6754 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6756 } else if (strncmp(message, "1/2", 3) == 0) {
6757 char *p, *q, *r = "";
6758 p = strchr(message, '{');
6767 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6770 } else if (strncmp(message, "White resign", 12) == 0) {
6771 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6773 } else if (strncmp(message, "Black resign", 12) == 0) {
6774 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6776 } else if (strncmp(message, "White matches", 13) == 0 ||
6777 strncmp(message, "Black matches", 13) == 0 ) {
6778 /* [HGM] ignore GNUShogi noises */
6780 } else if (strncmp(message, "White", 5) == 0 &&
6781 message[5] != '(' &&
6782 StrStr(message, "Black") == NULL) {
6783 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6785 } else if (strncmp(message, "Black", 5) == 0 &&
6786 message[5] != '(') {
6787 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6789 } else if (strcmp(message, "resign") == 0 ||
6790 strcmp(message, "computer resigns") == 0) {
6792 case MachinePlaysBlack:
6793 case IcsPlayingBlack:
6794 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6796 case MachinePlaysWhite:
6797 case IcsPlayingWhite:
6798 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6800 case TwoMachinesPlay:
6801 if (cps->twoMachinesColor[0] == 'w')
6802 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6804 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6811 } else if (strncmp(message, "opponent mates", 14) == 0) {
6813 case MachinePlaysBlack:
6814 case IcsPlayingBlack:
6815 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6817 case MachinePlaysWhite:
6818 case IcsPlayingWhite:
6819 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6821 case TwoMachinesPlay:
6822 if (cps->twoMachinesColor[0] == 'w')
6823 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6825 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6832 } else if (strncmp(message, "computer mates", 14) == 0) {
6834 case MachinePlaysBlack:
6835 case IcsPlayingBlack:
6836 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6838 case MachinePlaysWhite:
6839 case IcsPlayingWhite:
6840 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6842 case TwoMachinesPlay:
6843 if (cps->twoMachinesColor[0] == 'w')
6844 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6846 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6853 } else if (strncmp(message, "checkmate", 9) == 0) {
6854 if (WhiteOnMove(forwardMostMove)) {
6855 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6857 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6860 } else if (strstr(message, "Draw") != NULL ||
6861 strstr(message, "game is a draw") != NULL) {
6862 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6864 } else if (strstr(message, "offer") != NULL &&
6865 strstr(message, "draw") != NULL) {
6867 if (appData.zippyPlay && first.initDone) {
6868 /* Relay offer to ICS */
6869 SendToICS(ics_prefix);
6870 SendToICS("draw\n");
6873 cps->offeredDraw = 2; /* valid until this engine moves twice */
6874 if (gameMode == TwoMachinesPlay) {
6875 if (cps->other->offeredDraw) {
6876 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6877 /* [HGM] in two-machine mode we delay relaying draw offer */
6878 /* until after we also have move, to see if it is really claim */
6880 } else if (gameMode == MachinePlaysWhite ||
6881 gameMode == MachinePlaysBlack) {
6882 if (userOfferedDraw) {
6883 DisplayInformation(_("Machine accepts your draw offer"));
6884 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6886 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6893 * Look for thinking output
6895 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6896 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6898 int plylev, mvleft, mvtot, curscore, time;
6899 char mvname[MOVE_LEN];
6903 int prefixHint = FALSE;
6904 mvname[0] = NULLCHAR;
6907 case MachinePlaysBlack:
6908 case IcsPlayingBlack:
6909 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6911 case MachinePlaysWhite:
6912 case IcsPlayingWhite:
6913 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6918 case IcsObserving: /* [DM] icsEngineAnalyze */
6919 if (!appData.icsEngineAnalyze) ignore = TRUE;
6921 case TwoMachinesPlay:
6922 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6933 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6934 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6936 if (plyext != ' ' && plyext != '\t') {
6940 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6941 if( cps->scoreIsAbsolute &&
6942 ( gameMode == MachinePlaysBlack ||
6943 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6944 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6945 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6946 !WhiteOnMove(currentMove)
6949 curscore = -curscore;
6953 programStats.depth = plylev;
6954 programStats.nodes = nodes;
6955 programStats.time = time;
6956 programStats.score = curscore;
6957 programStats.got_only_move = 0;
6959 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6962 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6963 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6964 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6965 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6966 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6967 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6968 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6969 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6972 /* Buffer overflow protection */
6973 if (buf1[0] != NULLCHAR) {
6974 if (strlen(buf1) >= sizeof(programStats.movelist)
6975 && appData.debugMode) {
6977 "PV is too long; using the first %u bytes.\n",
6978 (unsigned) sizeof(programStats.movelist) - 1);
6981 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6983 sprintf(programStats.movelist, " no PV\n");
6986 if (programStats.seen_stat) {
6987 programStats.ok_to_send = 1;
6990 if (strchr(programStats.movelist, '(') != NULL) {
6991 programStats.line_is_book = 1;
6992 programStats.nr_moves = 0;
6993 programStats.moves_left = 0;
6995 programStats.line_is_book = 0;
6998 SendProgramStatsToFrontend( cps, &programStats );
7001 [AS] Protect the thinkOutput buffer from overflow... this
7002 is only useful if buf1 hasn't overflowed first!
7004 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7006 (gameMode == TwoMachinesPlay ?
7007 ToUpper(cps->twoMachinesColor[0]) : ' '),
7008 ((double) curscore) / 100.0,
7009 prefixHint ? lastHint : "",
7010 prefixHint ? " " : "" );
7012 if( buf1[0] != NULLCHAR ) {
7013 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7015 if( strlen(buf1) > max_len ) {
7016 if( appData.debugMode) {
7017 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7019 buf1[max_len+1] = '\0';
7022 strcat( thinkOutput, buf1 );
7025 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7026 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7027 DisplayMove(currentMove - 1);
7031 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7032 /* crafty (9.25+) says "(only move) <move>"
7033 * if there is only 1 legal move
7035 sscanf(p, "(only move) %s", buf1);
7036 sprintf(thinkOutput, "%s (only move)", buf1);
7037 sprintf(programStats.movelist, "%s (only move)", buf1);
7038 programStats.depth = 1;
7039 programStats.nr_moves = 1;
7040 programStats.moves_left = 1;
7041 programStats.nodes = 1;
7042 programStats.time = 1;
7043 programStats.got_only_move = 1;
7045 /* Not really, but we also use this member to
7046 mean "line isn't going to change" (Crafty
7047 isn't searching, so stats won't change) */
7048 programStats.line_is_book = 1;
7050 SendProgramStatsToFrontend( cps, &programStats );
7052 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7053 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7054 DisplayMove(currentMove - 1);
7057 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7058 &time, &nodes, &plylev, &mvleft,
7059 &mvtot, mvname) >= 5) {
7060 /* The stat01: line is from Crafty (9.29+) in response
7061 to the "." command */
7062 programStats.seen_stat = 1;
7063 cps->maybeThinking = TRUE;
7065 if (programStats.got_only_move || !appData.periodicUpdates)
7068 programStats.depth = plylev;
7069 programStats.time = time;
7070 programStats.nodes = nodes;
7071 programStats.moves_left = mvleft;
7072 programStats.nr_moves = mvtot;
7073 strcpy(programStats.move_name, mvname);
7074 programStats.ok_to_send = 1;
7075 programStats.movelist[0] = '\0';
7077 SendProgramStatsToFrontend( cps, &programStats );
7081 } else if (strncmp(message,"++",2) == 0) {
7082 /* Crafty 9.29+ outputs this */
7083 programStats.got_fail = 2;
7086 } else if (strncmp(message,"--",2) == 0) {
7087 /* Crafty 9.29+ outputs this */
7088 programStats.got_fail = 1;
7091 } else if (thinkOutput[0] != NULLCHAR &&
7092 strncmp(message, " ", 4) == 0) {
7093 unsigned message_len;
7096 while (*p && *p == ' ') p++;
7098 message_len = strlen( p );
7100 /* [AS] Avoid buffer overflow */
7101 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7102 strcat(thinkOutput, " ");
7103 strcat(thinkOutput, p);
7106 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7107 strcat(programStats.movelist, " ");
7108 strcat(programStats.movelist, p);
7111 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7112 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7113 DisplayMove(currentMove - 1);
7121 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7122 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7124 ChessProgramStats cpstats;
7126 if (plyext != ' ' && plyext != '\t') {
7130 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7131 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7132 curscore = -curscore;
7135 cpstats.depth = plylev;
7136 cpstats.nodes = nodes;
7137 cpstats.time = time;
7138 cpstats.score = curscore;
7139 cpstats.got_only_move = 0;
7140 cpstats.movelist[0] = '\0';
7142 if (buf1[0] != NULLCHAR) {
7143 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7146 cpstats.ok_to_send = 0;
7147 cpstats.line_is_book = 0;
7148 cpstats.nr_moves = 0;
7149 cpstats.moves_left = 0;
7151 SendProgramStatsToFrontend( cps, &cpstats );
7158 /* Parse a game score from the character string "game", and
7159 record it as the history of the current game. The game
7160 score is NOT assumed to start from the standard position.
7161 The display is not updated in any way.
7164 ParseGameHistory(game)
7168 int fromX, fromY, toX, toY, boardIndex;
7173 if (appData.debugMode)
7174 fprintf(debugFP, "Parsing game history: %s\n", game);
7176 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7177 gameInfo.site = StrSave(appData.icsHost);
7178 gameInfo.date = PGNDate();
7179 gameInfo.round = StrSave("-");
7181 /* Parse out names of players */
7182 while (*game == ' ') game++;
7184 while (*game != ' ') *p++ = *game++;
7186 gameInfo.white = StrSave(buf);
7187 while (*game == ' ') game++;
7189 while (*game != ' ' && *game != '\n') *p++ = *game++;
7191 gameInfo.black = StrSave(buf);
7194 boardIndex = blackPlaysFirst ? 1 : 0;
7197 yyboardindex = boardIndex;
7198 moveType = (ChessMove) yylex();
7200 case IllegalMove: /* maybe suicide chess, etc. */
7201 if (appData.debugMode) {
7202 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7203 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7204 setbuf(debugFP, NULL);
7206 case WhitePromotionChancellor:
7207 case BlackPromotionChancellor:
7208 case WhitePromotionArchbishop:
7209 case BlackPromotionArchbishop:
7210 case WhitePromotionQueen:
7211 case BlackPromotionQueen:
7212 case WhitePromotionRook:
7213 case BlackPromotionRook:
7214 case WhitePromotionBishop:
7215 case BlackPromotionBishop:
7216 case WhitePromotionKnight:
7217 case BlackPromotionKnight:
7218 case WhitePromotionKing:
7219 case BlackPromotionKing:
7221 case WhiteCapturesEnPassant:
7222 case BlackCapturesEnPassant:
7223 case WhiteKingSideCastle:
7224 case WhiteQueenSideCastle:
7225 case BlackKingSideCastle:
7226 case BlackQueenSideCastle:
7227 case WhiteKingSideCastleWild:
7228 case WhiteQueenSideCastleWild:
7229 case BlackKingSideCastleWild:
7230 case BlackQueenSideCastleWild:
7232 case WhiteHSideCastleFR:
7233 case WhiteASideCastleFR:
7234 case BlackHSideCastleFR:
7235 case BlackASideCastleFR:
7237 fromX = currentMoveString[0] - AAA;
7238 fromY = currentMoveString[1] - ONE;
7239 toX = currentMoveString[2] - AAA;
7240 toY = currentMoveString[3] - ONE;
7241 promoChar = currentMoveString[4];
7245 fromX = moveType == WhiteDrop ?
7246 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7247 (int) CharToPiece(ToLower(currentMoveString[0]));
7249 toX = currentMoveString[2] - AAA;
7250 toY = currentMoveString[3] - ONE;
7251 promoChar = NULLCHAR;
7255 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7256 if (appData.debugMode) {
7257 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7258 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7259 setbuf(debugFP, NULL);
7261 DisplayError(buf, 0);
7263 case ImpossibleMove:
7265 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7266 if (appData.debugMode) {
7267 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7268 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7269 setbuf(debugFP, NULL);
7271 DisplayError(buf, 0);
7273 case (ChessMove) 0: /* end of file */
7274 if (boardIndex < backwardMostMove) {
7275 /* Oops, gap. How did that happen? */
7276 DisplayError(_("Gap in move list"), 0);
7279 backwardMostMove = blackPlaysFirst ? 1 : 0;
7280 if (boardIndex > forwardMostMove) {
7281 forwardMostMove = boardIndex;
7285 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7286 strcat(parseList[boardIndex-1], " ");
7287 strcat(parseList[boardIndex-1], yy_text);
7299 case GameUnfinished:
7300 if (gameMode == IcsExamining) {
7301 if (boardIndex < backwardMostMove) {
7302 /* Oops, gap. How did that happen? */
7305 backwardMostMove = blackPlaysFirst ? 1 : 0;
7308 gameInfo.result = moveType;
7309 p = strchr(yy_text, '{');
7310 if (p == NULL) p = strchr(yy_text, '(');
7313 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7315 q = strchr(p, *p == '{' ? '}' : ')');
7316 if (q != NULL) *q = NULLCHAR;
7319 gameInfo.resultDetails = StrSave(p);
7322 if (boardIndex >= forwardMostMove &&
7323 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7324 backwardMostMove = blackPlaysFirst ? 1 : 0;
7327 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7328 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7329 parseList[boardIndex]);
7330 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7331 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7332 /* currentMoveString is set as a side-effect of yylex */
7333 strcpy(moveList[boardIndex], currentMoveString);
7334 strcat(moveList[boardIndex], "\n");
7336 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7337 castlingRights[boardIndex], &epStatus[boardIndex]);
7338 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7339 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7345 if(gameInfo.variant != VariantShogi)
7346 strcat(parseList[boardIndex - 1], "+");
7350 strcat(parseList[boardIndex - 1], "#");
7357 /* Apply a move to the given board */
7359 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7360 int fromX, fromY, toX, toY;
7366 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7368 /* [HGM] compute & store e.p. status and castling rights for new position */
7369 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7372 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7376 if( board[toY][toX] != EmptySquare )
7379 if( board[fromY][fromX] == WhitePawn ) {
7380 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7383 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7384 gameInfo.variant != VariantBerolina || toX < fromX)
7385 *ep = toX | berolina;
7386 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7387 gameInfo.variant != VariantBerolina || toX > fromX)
7391 if( board[fromY][fromX] == BlackPawn ) {
7392 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7394 if( toY-fromY== -2) {
7395 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7396 gameInfo.variant != VariantBerolina || toX < fromX)
7397 *ep = toX | berolina;
7398 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7399 gameInfo.variant != VariantBerolina || toX > fromX)
7404 for(i=0; i<nrCastlingRights; i++) {
7405 if(castling[i] == fromX && castlingRank[i] == fromY ||
7406 castling[i] == toX && castlingRank[i] == toY
7407 ) castling[i] = -1; // revoke for moved or captured piece
7412 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7413 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7414 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7416 if (fromX == toX && fromY == toY) return;
7418 if (fromY == DROP_RANK) {
7420 piece = board[toY][toX] = (ChessSquare) fromX;
7422 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7423 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7424 if(gameInfo.variant == VariantKnightmate)
7425 king += (int) WhiteUnicorn - (int) WhiteKing;
7427 /* Code added by Tord: */
7428 /* FRC castling assumed when king captures friendly rook. */
7429 if (board[fromY][fromX] == WhiteKing &&
7430 board[toY][toX] == WhiteRook) {
7431 board[fromY][fromX] = EmptySquare;
7432 board[toY][toX] = EmptySquare;
7434 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7436 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7438 } else if (board[fromY][fromX] == BlackKing &&
7439 board[toY][toX] == BlackRook) {
7440 board[fromY][fromX] = EmptySquare;
7441 board[toY][toX] = EmptySquare;
7443 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7445 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7447 /* End of code added by Tord */
7449 } else if (board[fromY][fromX] == king
7450 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7451 && toY == fromY && toX > fromX+1) {
7452 board[fromY][fromX] = EmptySquare;
7453 board[toY][toX] = king;
7454 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7455 board[fromY][BOARD_RGHT-1] = EmptySquare;
7456 } else if (board[fromY][fromX] == king
7457 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7458 && toY == fromY && toX < fromX-1) {
7459 board[fromY][fromX] = EmptySquare;
7460 board[toY][toX] = king;
7461 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7462 board[fromY][BOARD_LEFT] = EmptySquare;
7463 } else if (board[fromY][fromX] == WhitePawn
7464 && toY == BOARD_HEIGHT-1
7465 && gameInfo.variant != VariantXiangqi
7467 /* white pawn promotion */
7468 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7469 if (board[toY][toX] == EmptySquare) {
7470 board[toY][toX] = WhiteQueen;
7472 if(gameInfo.variant==VariantBughouse ||
7473 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7474 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7475 board[fromY][fromX] = EmptySquare;
7476 } else if ((fromY == BOARD_HEIGHT-4)
7478 && gameInfo.variant != VariantXiangqi
7479 && gameInfo.variant != VariantBerolina
7480 && (board[fromY][fromX] == WhitePawn)
7481 && (board[toY][toX] == EmptySquare)) {
7482 board[fromY][fromX] = EmptySquare;
7483 board[toY][toX] = WhitePawn;
7484 captured = board[toY - 1][toX];
7485 board[toY - 1][toX] = EmptySquare;
7486 } else if ((fromY == BOARD_HEIGHT-4)
7488 && gameInfo.variant == VariantBerolina
7489 && (board[fromY][fromX] == WhitePawn)
7490 && (board[toY][toX] == EmptySquare)) {
7491 board[fromY][fromX] = EmptySquare;
7492 board[toY][toX] = WhitePawn;
7493 if(oldEP & EP_BEROLIN_A) {
7494 captured = board[fromY][fromX-1];
7495 board[fromY][fromX-1] = EmptySquare;
7496 }else{ captured = board[fromY][fromX+1];
7497 board[fromY][fromX+1] = EmptySquare;
7499 } else if (board[fromY][fromX] == king
7500 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7501 && toY == fromY && toX > fromX+1) {
7502 board[fromY][fromX] = EmptySquare;
7503 board[toY][toX] = king;
7504 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7505 board[fromY][BOARD_RGHT-1] = EmptySquare;
7506 } else if (board[fromY][fromX] == king
7507 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7508 && toY == fromY && toX < fromX-1) {
7509 board[fromY][fromX] = EmptySquare;
7510 board[toY][toX] = king;
7511 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7512 board[fromY][BOARD_LEFT] = EmptySquare;
7513 } else if (fromY == 7 && fromX == 3
7514 && board[fromY][fromX] == BlackKing
7515 && toY == 7 && toX == 5) {
7516 board[fromY][fromX] = EmptySquare;
7517 board[toY][toX] = BlackKing;
7518 board[fromY][7] = EmptySquare;
7519 board[toY][4] = BlackRook;
7520 } else if (fromY == 7 && fromX == 3
7521 && board[fromY][fromX] == BlackKing
7522 && toY == 7 && toX == 1) {
7523 board[fromY][fromX] = EmptySquare;
7524 board[toY][toX] = BlackKing;
7525 board[fromY][0] = EmptySquare;
7526 board[toY][2] = BlackRook;
7527 } else if (board[fromY][fromX] == BlackPawn
7529 && gameInfo.variant != VariantXiangqi
7531 /* black pawn promotion */
7532 board[0][toX] = CharToPiece(ToLower(promoChar));
7533 if (board[0][toX] == EmptySquare) {
7534 board[0][toX] = BlackQueen;
7536 if(gameInfo.variant==VariantBughouse ||
7537 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7538 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7539 board[fromY][fromX] = EmptySquare;
7540 } else if ((fromY == 3)
7542 && gameInfo.variant != VariantXiangqi
7543 && gameInfo.variant != VariantBerolina
7544 && (board[fromY][fromX] == BlackPawn)
7545 && (board[toY][toX] == EmptySquare)) {
7546 board[fromY][fromX] = EmptySquare;
7547 board[toY][toX] = BlackPawn;
7548 captured = board[toY + 1][toX];
7549 board[toY + 1][toX] = EmptySquare;
7550 } else if ((fromY == 3)
7552 && gameInfo.variant == VariantBerolina
7553 && (board[fromY][fromX] == BlackPawn)
7554 && (board[toY][toX] == EmptySquare)) {
7555 board[fromY][fromX] = EmptySquare;
7556 board[toY][toX] = BlackPawn;
7557 if(oldEP & EP_BEROLIN_A) {
7558 captured = board[fromY][fromX-1];
7559 board[fromY][fromX-1] = EmptySquare;
7560 }else{ captured = board[fromY][fromX+1];
7561 board[fromY][fromX+1] = EmptySquare;
7564 board[toY][toX] = board[fromY][fromX];
7565 board[fromY][fromX] = EmptySquare;
7568 /* [HGM] now we promote for Shogi, if needed */
7569 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7570 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7573 if (gameInfo.holdingsWidth != 0) {
7575 /* !!A lot more code needs to be written to support holdings */
7576 /* [HGM] OK, so I have written it. Holdings are stored in the */
7577 /* penultimate board files, so they are automaticlly stored */
7578 /* in the game history. */
7579 if (fromY == DROP_RANK) {
7580 /* Delete from holdings, by decreasing count */
7581 /* and erasing image if necessary */
7583 if(p < (int) BlackPawn) { /* white drop */
7584 p -= (int)WhitePawn;
7585 p = PieceToNumber((ChessSquare)p);
7586 if(p >= gameInfo.holdingsSize) p = 0;
7587 if(--board[p][BOARD_WIDTH-2] <= 0)
7588 board[p][BOARD_WIDTH-1] = EmptySquare;
7589 if((int)board[p][BOARD_WIDTH-2] < 0)
7590 board[p][BOARD_WIDTH-2] = 0;
7591 } else { /* black drop */
7592 p -= (int)BlackPawn;
7593 p = PieceToNumber((ChessSquare)p);
7594 if(p >= gameInfo.holdingsSize) p = 0;
7595 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7596 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7597 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7598 board[BOARD_HEIGHT-1-p][1] = 0;
7601 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7602 && gameInfo.variant != VariantBughouse ) {
7603 /* [HGM] holdings: Add to holdings, if holdings exist */
7604 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7605 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7606 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7609 if (p >= (int) BlackPawn) {
7610 p -= (int)BlackPawn;
7611 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7612 /* in Shogi restore piece to its original first */
7613 captured = (ChessSquare) (DEMOTED captured);
7616 p = PieceToNumber((ChessSquare)p);
7617 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7618 board[p][BOARD_WIDTH-2]++;
7619 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7621 p -= (int)WhitePawn;
7622 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7623 captured = (ChessSquare) (DEMOTED captured);
7626 p = PieceToNumber((ChessSquare)p);
7627 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7628 board[BOARD_HEIGHT-1-p][1]++;
7629 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7632 } else if (gameInfo.variant == VariantAtomic) {
7633 if (captured != EmptySquare) {
7635 for (y = toY-1; y <= toY+1; y++) {
7636 for (x = toX-1; x <= toX+1; x++) {
7637 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7638 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7639 board[y][x] = EmptySquare;
7643 board[toY][toX] = EmptySquare;
7646 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7647 /* [HGM] Shogi promotions */
7648 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7651 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7652 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7653 // [HGM] superchess: take promotion piece out of holdings
7654 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7655 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7656 if(!--board[k][BOARD_WIDTH-2])
7657 board[k][BOARD_WIDTH-1] = EmptySquare;
7659 if(!--board[BOARD_HEIGHT-1-k][1])
7660 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7666 /* Updates forwardMostMove */
7668 MakeMove(fromX, fromY, toX, toY, promoChar)
7669 int fromX, fromY, toX, toY;
7672 // forwardMostMove++; // [HGM] bare: moved downstream
7674 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7675 int timeLeft; static int lastLoadFlag=0; int king, piece;
7676 piece = boards[forwardMostMove][fromY][fromX];
7677 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7678 if(gameInfo.variant == VariantKnightmate)
7679 king += (int) WhiteUnicorn - (int) WhiteKing;
7680 if(forwardMostMove == 0) {
7682 fprintf(serverMoves, "%s;", second.tidy);
7683 fprintf(serverMoves, "%s;", first.tidy);
7684 if(!blackPlaysFirst)
7685 fprintf(serverMoves, "%s;", second.tidy);
7686 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7687 lastLoadFlag = loadFlag;
7689 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7690 // print castling suffix
7691 if( toY == fromY && piece == king ) {
7693 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7695 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7698 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7699 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7700 boards[forwardMostMove][toY][toX] == EmptySquare
7702 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7704 if(promoChar != NULLCHAR)
7705 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7707 fprintf(serverMoves, "/%d/%d",
7708 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7709 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7710 else timeLeft = blackTimeRemaining/1000;
7711 fprintf(serverMoves, "/%d", timeLeft);
7713 fflush(serverMoves);
7716 if (forwardMostMove+1 >= MAX_MOVES) {
7717 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7721 if (commentList[forwardMostMove+1] != NULL) {
7722 free(commentList[forwardMostMove+1]);
7723 commentList[forwardMostMove+1] = NULL;
7725 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7726 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7727 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7728 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7729 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7730 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7731 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7732 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7733 gameInfo.result = GameUnfinished;
7734 if (gameInfo.resultDetails != NULL) {
7735 free(gameInfo.resultDetails);
7736 gameInfo.resultDetails = NULL;
7738 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7739 moveList[forwardMostMove - 1]);
7740 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7741 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7742 fromY, fromX, toY, toX, promoChar,
7743 parseList[forwardMostMove - 1]);
7744 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7745 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7746 castlingRights[forwardMostMove]) ) {
7752 if(gameInfo.variant != VariantShogi)
7753 strcat(parseList[forwardMostMove - 1], "+");
7757 strcat(parseList[forwardMostMove - 1], "#");
7760 if (appData.debugMode) {
7761 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7766 /* Updates currentMove if not pausing */
7768 ShowMove(fromX, fromY, toX, toY)
7770 int instant = (gameMode == PlayFromGameFile) ?
7771 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7772 if(appData.noGUI) return;
7773 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7775 if (forwardMostMove == currentMove + 1) {
7776 AnimateMove(boards[forwardMostMove - 1],
7777 fromX, fromY, toX, toY);
7779 if (appData.highlightLastMove) {
7780 SetHighlights(fromX, fromY, toX, toY);
7783 currentMove = forwardMostMove;
7786 if (instant) return;
7788 DisplayMove(currentMove - 1);
7789 DrawPosition(FALSE, boards[currentMove]);
7790 DisplayBothClocks();
7791 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7794 void SendEgtPath(ChessProgramState *cps)
7795 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7796 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7798 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7801 char c, *q = name+1, *r, *s;
7803 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7804 while(*p && *p != ',') *q++ = *p++;
7806 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7807 strcmp(name, ",nalimov:") == 0 ) {
7808 // take nalimov path from the menu-changeable option first, if it is defined
7809 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7810 SendToProgram(buf,cps); // send egtbpath command for nalimov
7812 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7813 (s = StrStr(appData.egtFormats, name)) != NULL) {
7814 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7815 s = r = StrStr(s, ":") + 1; // beginning of path info
7816 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7817 c = *r; *r = 0; // temporarily null-terminate path info
7818 *--q = 0; // strip of trailig ':' from name
7819 sprintf(buf, "egtpath %s %s\n", name+1, s);
7821 SendToProgram(buf,cps); // send egtbpath command for this format
7823 if(*p == ',') p++; // read away comma to position for next format name
7828 InitChessProgram(cps, setup)
7829 ChessProgramState *cps;
7830 int setup; /* [HGM] needed to setup FRC opening position */
7832 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7833 if (appData.noChessProgram) return;
7834 hintRequested = FALSE;
7835 bookRequested = FALSE;
7837 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7838 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7839 if(cps->memSize) { /* [HGM] memory */
7840 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7841 SendToProgram(buf, cps);
7843 SendEgtPath(cps); /* [HGM] EGT */
7844 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7845 sprintf(buf, "cores %d\n", appData.smpCores);
7846 SendToProgram(buf, cps);
7849 SendToProgram(cps->initString, cps);
7850 if (gameInfo.variant != VariantNormal &&
7851 gameInfo.variant != VariantLoadable
7852 /* [HGM] also send variant if board size non-standard */
7853 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7855 char *v = VariantName(gameInfo.variant);
7856 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7857 /* [HGM] in protocol 1 we have to assume all variants valid */
7858 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7859 DisplayFatalError(buf, 0, 1);
7863 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7864 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7865 if( gameInfo.variant == VariantXiangqi )
7866 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7867 if( gameInfo.variant == VariantShogi )
7868 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7869 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7870 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7871 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7872 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7873 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7874 if( gameInfo.variant == VariantCourier )
7875 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7876 if( gameInfo.variant == VariantSuper )
7877 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7878 if( gameInfo.variant == VariantGreat )
7879 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7882 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7883 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7884 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7885 if(StrStr(cps->variants, b) == NULL) {
7886 // specific sized variant not known, check if general sizing allowed
7887 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7888 if(StrStr(cps->variants, "boardsize") == NULL) {
7889 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7890 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7891 DisplayFatalError(buf, 0, 1);
7894 /* [HGM] here we really should compare with the maximum supported board size */
7897 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7898 sprintf(buf, "variant %s\n", b);
7899 SendToProgram(buf, cps);
7901 currentlyInitializedVariant = gameInfo.variant;
7903 /* [HGM] send opening position in FRC to first engine */
7905 SendToProgram("force\n", cps);
7907 /* engine is now in force mode! Set flag to wake it up after first move. */
7908 setboardSpoiledMachineBlack = 1;
7912 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7913 SendToProgram(buf, cps);
7915 cps->maybeThinking = FALSE;
7916 cps->offeredDraw = 0;
7917 if (!appData.icsActive) {
7918 SendTimeControl(cps, movesPerSession, timeControl,
7919 timeIncrement, appData.searchDepth,
7922 if (appData.showThinking
7923 // [HGM] thinking: four options require thinking output to be sent
7924 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7926 SendToProgram("post\n", cps);
7928 SendToProgram("hard\n", cps);
7929 if (!appData.ponderNextMove) {
7930 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7931 it without being sure what state we are in first. "hard"
7932 is not a toggle, so that one is OK.
7934 SendToProgram("easy\n", cps);
7937 sprintf(buf, "ping %d\n", ++cps->lastPing);
7938 SendToProgram(buf, cps);
7940 cps->initDone = TRUE;
7945 StartChessProgram(cps)
7946 ChessProgramState *cps;
7951 if (appData.noChessProgram) return;
7952 cps->initDone = FALSE;
7954 if (strcmp(cps->host, "localhost") == 0) {
7955 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7956 } else if (*appData.remoteShell == NULLCHAR) {
7957 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7959 if (*appData.remoteUser == NULLCHAR) {
7960 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7963 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7964 cps->host, appData.remoteUser, cps->program);
7966 err = StartChildProcess(buf, "", &cps->pr);
7970 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7971 DisplayFatalError(buf, err, 1);
7977 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7978 if (cps->protocolVersion > 1) {
7979 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7980 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7981 cps->comboCnt = 0; // and values of combo boxes
7982 SendToProgram(buf, cps);
7984 SendToProgram("xboard\n", cps);
7990 TwoMachinesEventIfReady P((void))
7992 if (first.lastPing != first.lastPong) {
7993 DisplayMessage("", _("Waiting for first chess program"));
7994 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7997 if (second.lastPing != second.lastPong) {
7998 DisplayMessage("", _("Waiting for second chess program"));
7999 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8007 NextMatchGame P((void))
8009 int index; /* [HGM] autoinc: step load index during match */
8011 if (*appData.loadGameFile != NULLCHAR) {
8012 index = appData.loadGameIndex;
8013 if(index < 0) { // [HGM] autoinc
8014 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8015 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8017 LoadGameFromFile(appData.loadGameFile,
8019 appData.loadGameFile, FALSE);
8020 } else if (*appData.loadPositionFile != NULLCHAR) {
8021 index = appData.loadPositionIndex;
8022 if(index < 0) { // [HGM] autoinc
8023 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8024 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8026 LoadPositionFromFile(appData.loadPositionFile,
8028 appData.loadPositionFile);
8030 TwoMachinesEventIfReady();
8033 void UserAdjudicationEvent( int result )
8035 ChessMove gameResult = GameIsDrawn;
8038 gameResult = WhiteWins;
8040 else if( result < 0 ) {
8041 gameResult = BlackWins;
8044 if( gameMode == TwoMachinesPlay ) {
8045 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8050 // [HGM] save: calculate checksum of game to make games easily identifiable
8051 int StringCheckSum(char *s)
8054 if(s==NULL) return 0;
8055 while(*s) i = i*259 + *s++;
8062 for(i=backwardMostMove; i<forwardMostMove; i++) {
8063 sum += pvInfoList[i].depth;
8064 sum += StringCheckSum(parseList[i]);
8065 sum += StringCheckSum(commentList[i]);
8068 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8069 return sum + StringCheckSum(commentList[i]);
8070 } // end of save patch
8073 GameEnds(result, resultDetails, whosays)
8075 char *resultDetails;
8078 GameMode nextGameMode;
8082 if(endingGame) return; /* [HGM] crash: forbid recursion */
8085 if (appData.debugMode) {
8086 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8087 result, resultDetails ? resultDetails : "(null)", whosays);
8090 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8091 /* If we are playing on ICS, the server decides when the
8092 game is over, but the engine can offer to draw, claim
8096 if (appData.zippyPlay && first.initDone) {
8097 if (result == GameIsDrawn) {
8098 /* In case draw still needs to be claimed */
8099 SendToICS(ics_prefix);
8100 SendToICS("draw\n");
8101 } else if (StrCaseStr(resultDetails, "resign")) {
8102 SendToICS(ics_prefix);
8103 SendToICS("resign\n");
8107 endingGame = 0; /* [HGM] crash */
8111 /* If we're loading the game from a file, stop */
8112 if (whosays == GE_FILE) {
8113 (void) StopLoadGameTimer();
8117 /* Cancel draw offers */
8118 first.offeredDraw = second.offeredDraw = 0;
8120 /* If this is an ICS game, only ICS can really say it's done;
8121 if not, anyone can. */
8122 isIcsGame = (gameMode == IcsPlayingWhite ||
8123 gameMode == IcsPlayingBlack ||
8124 gameMode == IcsObserving ||
8125 gameMode == IcsExamining);
8127 if (!isIcsGame || whosays == GE_ICS) {
8128 /* OK -- not an ICS game, or ICS said it was done */
8130 if (!isIcsGame && !appData.noChessProgram)
8131 SetUserThinkingEnables();
8133 /* [HGM] if a machine claims the game end we verify this claim */
8134 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8135 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8137 ChessMove trueResult = (ChessMove) -1;
8139 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8140 first.twoMachinesColor[0] :
8141 second.twoMachinesColor[0] ;
8143 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8144 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8145 /* [HGM] verify: engine mate claims accepted if they were flagged */
8146 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8148 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8149 /* [HGM] verify: engine mate claims accepted if they were flagged */
8150 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8152 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8153 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8156 // now verify win claims, but not in drop games, as we don't understand those yet
8157 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8158 || gameInfo.variant == VariantGreat) &&
8159 (result == WhiteWins && claimer == 'w' ||
8160 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8161 if (appData.debugMode) {
8162 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8163 result, epStatus[forwardMostMove], forwardMostMove);
8165 if(result != trueResult) {
8166 sprintf(buf, "False win claim: '%s'", resultDetails);
8167 result = claimer == 'w' ? BlackWins : WhiteWins;
8168 resultDetails = buf;
8171 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8172 && (forwardMostMove <= backwardMostMove ||
8173 epStatus[forwardMostMove-1] > EP_DRAWS ||
8174 (claimer=='b')==(forwardMostMove&1))
8176 /* [HGM] verify: draws that were not flagged are false claims */
8177 sprintf(buf, "False draw claim: '%s'", resultDetails);
8178 result = claimer == 'w' ? BlackWins : WhiteWins;
8179 resultDetails = buf;
8181 /* (Claiming a loss is accepted no questions asked!) */
8183 /* [HGM] bare: don't allow bare King to win */
8184 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8185 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8186 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8187 && result != GameIsDrawn)
8188 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8189 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8190 int p = (int)boards[forwardMostMove][i][j] - color;
8191 if(p >= 0 && p <= (int)WhiteKing) k++;
8193 if (appData.debugMode) {
8194 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8195 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8198 result = GameIsDrawn;
8199 sprintf(buf, "%s but bare king", resultDetails);
8200 resultDetails = buf;
8206 if(serverMoves != NULL && !loadFlag) { char c = '=';
8207 if(result==WhiteWins) c = '+';
8208 if(result==BlackWins) c = '-';
8209 if(resultDetails != NULL)
8210 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8212 if (resultDetails != NULL) {
8213 gameInfo.result = result;
8214 gameInfo.resultDetails = StrSave(resultDetails);
8216 /* display last move only if game was not loaded from file */
8217 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8218 DisplayMove(currentMove - 1);
8220 if (forwardMostMove != 0) {
8221 if (gameMode != PlayFromGameFile && gameMode != EditGame
8222 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8224 if (*appData.saveGameFile != NULLCHAR) {
8225 SaveGameToFile(appData.saveGameFile, TRUE);
8226 } else if (appData.autoSaveGames) {
8229 if (*appData.savePositionFile != NULLCHAR) {
8230 SavePositionToFile(appData.savePositionFile);
8235 /* Tell program how game ended in case it is learning */
8236 /* [HGM] Moved this to after saving the PGN, just in case */
8237 /* engine died and we got here through time loss. In that */
8238 /* case we will get a fatal error writing the pipe, which */
8239 /* would otherwise lose us the PGN. */
8240 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8241 /* output during GameEnds should never be fatal anymore */
8242 if (gameMode == MachinePlaysWhite ||
8243 gameMode == MachinePlaysBlack ||
8244 gameMode == TwoMachinesPlay ||
8245 gameMode == IcsPlayingWhite ||
8246 gameMode == IcsPlayingBlack ||
8247 gameMode == BeginningOfGame) {
8249 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8251 if (first.pr != NoProc) {
8252 SendToProgram(buf, &first);
8254 if (second.pr != NoProc &&
8255 gameMode == TwoMachinesPlay) {
8256 SendToProgram(buf, &second);
8261 if (appData.icsActive) {
8262 if (appData.quietPlay &&
8263 (gameMode == IcsPlayingWhite ||
8264 gameMode == IcsPlayingBlack)) {
8265 SendToICS(ics_prefix);
8266 SendToICS("set shout 1\n");
8268 nextGameMode = IcsIdle;
8269 ics_user_moved = FALSE;
8270 /* clean up premove. It's ugly when the game has ended and the
8271 * premove highlights are still on the board.
8275 ClearPremoveHighlights();
8276 DrawPosition(FALSE, boards[currentMove]);
8278 if (whosays == GE_ICS) {
8281 if (gameMode == IcsPlayingWhite)
8283 else if(gameMode == IcsPlayingBlack)
8287 if (gameMode == IcsPlayingBlack)
8289 else if(gameMode == IcsPlayingWhite)
8296 PlayIcsUnfinishedSound();
8299 } else if (gameMode == EditGame ||
8300 gameMode == PlayFromGameFile ||
8301 gameMode == AnalyzeMode ||
8302 gameMode == AnalyzeFile) {
8303 nextGameMode = gameMode;
8305 nextGameMode = EndOfGame;
8310 nextGameMode = gameMode;
8313 if (appData.noChessProgram) {
8314 gameMode = nextGameMode;
8316 endingGame = 0; /* [HGM] crash */
8321 /* Put first chess program into idle state */
8322 if (first.pr != NoProc &&
8323 (gameMode == MachinePlaysWhite ||
8324 gameMode == MachinePlaysBlack ||
8325 gameMode == TwoMachinesPlay ||
8326 gameMode == IcsPlayingWhite ||
8327 gameMode == IcsPlayingBlack ||
8328 gameMode == BeginningOfGame)) {
8329 SendToProgram("force\n", &first);
8330 if (first.usePing) {
8332 sprintf(buf, "ping %d\n", ++first.lastPing);
8333 SendToProgram(buf, &first);
8336 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8337 /* Kill off first chess program */
8338 if (first.isr != NULL)
8339 RemoveInputSource(first.isr);
8342 if (first.pr != NoProc) {
8344 DoSleep( appData.delayBeforeQuit );
8345 SendToProgram("quit\n", &first);
8346 DoSleep( appData.delayAfterQuit );
8347 DestroyChildProcess(first.pr, first.useSigterm);
8352 /* Put second chess program into idle state */
8353 if (second.pr != NoProc &&
8354 gameMode == TwoMachinesPlay) {
8355 SendToProgram("force\n", &second);
8356 if (second.usePing) {
8358 sprintf(buf, "ping %d\n", ++second.lastPing);
8359 SendToProgram(buf, &second);
8362 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8363 /* Kill off second chess program */
8364 if (second.isr != NULL)
8365 RemoveInputSource(second.isr);
8368 if (second.pr != NoProc) {
8369 DoSleep( appData.delayBeforeQuit );
8370 SendToProgram("quit\n", &second);
8371 DoSleep( appData.delayAfterQuit );
8372 DestroyChildProcess(second.pr, second.useSigterm);
8377 if (matchMode && gameMode == TwoMachinesPlay) {
8380 if (first.twoMachinesColor[0] == 'w') {
8387 if (first.twoMachinesColor[0] == 'b') {
8396 if (matchGame < appData.matchGames) {
8398 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8399 tmp = first.twoMachinesColor;
8400 first.twoMachinesColor = second.twoMachinesColor;
8401 second.twoMachinesColor = tmp;
8403 gameMode = nextGameMode;
8405 if(appData.matchPause>10000 || appData.matchPause<10)
8406 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8407 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8408 endingGame = 0; /* [HGM] crash */
8412 gameMode = nextGameMode;
8413 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8414 first.tidy, second.tidy,
8415 first.matchWins, second.matchWins,
8416 appData.matchGames - (first.matchWins + second.matchWins));
8417 DisplayFatalError(buf, 0, 0);
8420 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8421 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8423 gameMode = nextGameMode;
8425 endingGame = 0; /* [HGM] crash */
8428 /* Assumes program was just initialized (initString sent).
8429 Leaves program in force mode. */
8431 FeedMovesToProgram(cps, upto)
8432 ChessProgramState *cps;
8437 if (appData.debugMode)
8438 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8439 startedFromSetupPosition ? "position and " : "",
8440 backwardMostMove, upto, cps->which);
8441 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8442 // [HGM] variantswitch: make engine aware of new variant
8443 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8444 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8445 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8446 SendToProgram(buf, cps);
8447 currentlyInitializedVariant = gameInfo.variant;
8449 SendToProgram("force\n", cps);
8450 if (startedFromSetupPosition) {
8451 SendBoard(cps, backwardMostMove);
8452 if (appData.debugMode) {
8453 fprintf(debugFP, "feedMoves\n");
8456 for (i = backwardMostMove; i < upto; i++) {
8457 SendMoveToProgram(i, cps);
8463 ResurrectChessProgram()
8465 /* The chess program may have exited.
8466 If so, restart it and feed it all the moves made so far. */
8468 if (appData.noChessProgram || first.pr != NoProc) return;
8470 StartChessProgram(&first);
8471 InitChessProgram(&first, FALSE);
8472 FeedMovesToProgram(&first, currentMove);
8474 if (!first.sendTime) {
8475 /* can't tell gnuchess what its clock should read,
8476 so we bow to its notion. */
8478 timeRemaining[0][currentMove] = whiteTimeRemaining;
8479 timeRemaining[1][currentMove] = blackTimeRemaining;
8482 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8483 appData.icsEngineAnalyze) && first.analysisSupport) {
8484 SendToProgram("analyze\n", &first);
8485 first.analyzing = TRUE;
8498 if (appData.debugMode) {
8499 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8500 redraw, init, gameMode);
8502 pausing = pauseExamInvalid = FALSE;
8503 startedFromSetupPosition = blackPlaysFirst = FALSE;
8505 whiteFlag = blackFlag = FALSE;
8506 userOfferedDraw = FALSE;
8507 hintRequested = bookRequested = FALSE;
8508 first.maybeThinking = FALSE;
8509 second.maybeThinking = FALSE;
8510 first.bookSuspend = FALSE; // [HGM] book
8511 second.bookSuspend = FALSE;
8512 thinkOutput[0] = NULLCHAR;
8513 lastHint[0] = NULLCHAR;
8514 ClearGameInfo(&gameInfo);
8515 gameInfo.variant = StringToVariant(appData.variant);
8516 ics_user_moved = ics_clock_paused = FALSE;
8517 ics_getting_history = H_FALSE;
8519 white_holding[0] = black_holding[0] = NULLCHAR;
8520 ClearProgramStats();
8521 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8525 flipView = appData.flipView;
8526 ClearPremoveHighlights();
8528 alarmSounded = FALSE;
8530 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8531 if(appData.serverMovesName != NULL) {
8532 /* [HGM] prepare to make moves file for broadcasting */
8533 clock_t t = clock();
8534 if(serverMoves != NULL) fclose(serverMoves);
8535 serverMoves = fopen(appData.serverMovesName, "r");
8536 if(serverMoves != NULL) {
8537 fclose(serverMoves);
8538 /* delay 15 sec before overwriting, so all clients can see end */
8539 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8541 serverMoves = fopen(appData.serverMovesName, "w");
8545 gameMode = BeginningOfGame;
8547 if(appData.icsActive) gameInfo.variant = VariantNormal;
8548 currentMove = forwardMostMove = backwardMostMove = 0;
8549 InitPosition(redraw);
8550 for (i = 0; i < MAX_MOVES; i++) {
8551 if (commentList[i] != NULL) {
8552 free(commentList[i]);
8553 commentList[i] = NULL;
8557 timeRemaining[0][0] = whiteTimeRemaining;
8558 timeRemaining[1][0] = blackTimeRemaining;
8559 if (first.pr == NULL) {
8560 StartChessProgram(&first);
8563 InitChessProgram(&first, startedFromSetupPosition);
8566 DisplayMessage("", "");
8567 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8568 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8575 if (!AutoPlayOneMove())
8577 if (matchMode || appData.timeDelay == 0)
8579 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8581 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8590 int fromX, fromY, toX, toY;
8592 if (appData.debugMode) {
8593 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8596 if (gameMode != PlayFromGameFile)
8599 if (currentMove >= forwardMostMove) {
8600 gameMode = EditGame;
8603 /* [AS] Clear current move marker at the end of a game */
8604 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8609 toX = moveList[currentMove][2] - AAA;
8610 toY = moveList[currentMove][3] - ONE;
8612 if (moveList[currentMove][1] == '@') {
8613 if (appData.highlightLastMove) {
8614 SetHighlights(-1, -1, toX, toY);
8617 fromX = moveList[currentMove][0] - AAA;
8618 fromY = moveList[currentMove][1] - ONE;
8620 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8622 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8624 if (appData.highlightLastMove) {
8625 SetHighlights(fromX, fromY, toX, toY);
8628 DisplayMove(currentMove);
8629 SendMoveToProgram(currentMove++, &first);
8630 DisplayBothClocks();
8631 DrawPosition(FALSE, boards[currentMove]);
8632 // [HGM] PV info: always display, routine tests if empty
8633 DisplayComment(currentMove - 1, commentList[currentMove]);
8639 LoadGameOneMove(readAhead)
8640 ChessMove readAhead;
8642 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8643 char promoChar = NULLCHAR;
8648 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8649 gameMode != AnalyzeMode && gameMode != Training) {
8654 yyboardindex = forwardMostMove;
8655 if (readAhead != (ChessMove)0) {
8656 moveType = readAhead;
8658 if (gameFileFP == NULL)
8660 moveType = (ChessMove) yylex();
8666 if (appData.debugMode)
8667 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8669 if (*p == '{' || *p == '[' || *p == '(') {
8670 p[strlen(p) - 1] = NULLCHAR;
8674 /* append the comment but don't display it */
8675 while (*p == '\n') p++;
8676 AppendComment(currentMove, p);
8679 case WhiteCapturesEnPassant:
8680 case BlackCapturesEnPassant:
8681 case WhitePromotionChancellor:
8682 case BlackPromotionChancellor:
8683 case WhitePromotionArchbishop:
8684 case BlackPromotionArchbishop:
8685 case WhitePromotionCentaur:
8686 case BlackPromotionCentaur:
8687 case WhitePromotionQueen:
8688 case BlackPromotionQueen:
8689 case WhitePromotionRook:
8690 case BlackPromotionRook:
8691 case WhitePromotionBishop:
8692 case BlackPromotionBishop:
8693 case WhitePromotionKnight:
8694 case BlackPromotionKnight:
8695 case WhitePromotionKing:
8696 case BlackPromotionKing:
8698 case WhiteKingSideCastle:
8699 case WhiteQueenSideCastle:
8700 case BlackKingSideCastle:
8701 case BlackQueenSideCastle:
8702 case WhiteKingSideCastleWild:
8703 case WhiteQueenSideCastleWild:
8704 case BlackKingSideCastleWild:
8705 case BlackQueenSideCastleWild:
8707 case WhiteHSideCastleFR:
8708 case WhiteASideCastleFR:
8709 case BlackHSideCastleFR:
8710 case BlackASideCastleFR:
8712 if (appData.debugMode)
8713 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8714 fromX = currentMoveString[0] - AAA;
8715 fromY = currentMoveString[1] - ONE;
8716 toX = currentMoveString[2] - AAA;
8717 toY = currentMoveString[3] - ONE;
8718 promoChar = currentMoveString[4];
8723 if (appData.debugMode)
8724 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8725 fromX = moveType == WhiteDrop ?
8726 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8727 (int) CharToPiece(ToLower(currentMoveString[0]));
8729 toX = currentMoveString[2] - AAA;
8730 toY = currentMoveString[3] - ONE;
8736 case GameUnfinished:
8737 if (appData.debugMode)
8738 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8739 p = strchr(yy_text, '{');
8740 if (p == NULL) p = strchr(yy_text, '(');
8743 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8745 q = strchr(p, *p == '{' ? '}' : ')');
8746 if (q != NULL) *q = NULLCHAR;
8749 GameEnds(moveType, p, GE_FILE);
8751 if (cmailMsgLoaded) {
8753 flipView = WhiteOnMove(currentMove);
8754 if (moveType == GameUnfinished) flipView = !flipView;
8755 if (appData.debugMode)
8756 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8760 case (ChessMove) 0: /* end of file */
8761 if (appData.debugMode)
8762 fprintf(debugFP, "Parser hit end of file\n");
8763 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8764 EP_UNKNOWN, castlingRights[currentMove]) ) {
8770 if (WhiteOnMove(currentMove)) {
8771 GameEnds(BlackWins, "Black mates", GE_FILE);
8773 GameEnds(WhiteWins, "White mates", GE_FILE);
8777 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8784 if (lastLoadGameStart == GNUChessGame) {
8785 /* GNUChessGames have numbers, but they aren't move numbers */
8786 if (appData.debugMode)
8787 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8788 yy_text, (int) moveType);
8789 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8791 /* else fall thru */
8796 /* Reached start of next game in file */
8797 if (appData.debugMode)
8798 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8799 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8800 EP_UNKNOWN, castlingRights[currentMove]) ) {
8806 if (WhiteOnMove(currentMove)) {
8807 GameEnds(BlackWins, "Black mates", GE_FILE);
8809 GameEnds(WhiteWins, "White mates", GE_FILE);
8813 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8819 case PositionDiagram: /* should not happen; ignore */
8820 case ElapsedTime: /* ignore */
8821 case NAG: /* ignore */
8822 if (appData.debugMode)
8823 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8824 yy_text, (int) moveType);
8825 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8828 if (appData.testLegality) {
8829 if (appData.debugMode)
8830 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8831 sprintf(move, _("Illegal move: %d.%s%s"),
8832 (forwardMostMove / 2) + 1,
8833 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8834 DisplayError(move, 0);
8837 if (appData.debugMode)
8838 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8839 yy_text, currentMoveString);
8840 fromX = currentMoveString[0] - AAA;
8841 fromY = currentMoveString[1] - ONE;
8842 toX = currentMoveString[2] - AAA;
8843 toY = currentMoveString[3] - ONE;
8844 promoChar = currentMoveString[4];
8849 if (appData.debugMode)
8850 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8851 sprintf(move, _("Ambiguous move: %d.%s%s"),
8852 (forwardMostMove / 2) + 1,
8853 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8854 DisplayError(move, 0);
8859 case ImpossibleMove:
8860 if (appData.debugMode)
8861 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8862 sprintf(move, _("Illegal move: %d.%s%s"),
8863 (forwardMostMove / 2) + 1,
8864 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8865 DisplayError(move, 0);
8871 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8872 DrawPosition(FALSE, boards[currentMove]);
8873 DisplayBothClocks();
8874 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8875 DisplayComment(currentMove - 1, commentList[currentMove]);
8877 (void) StopLoadGameTimer();
8879 cmailOldMove = forwardMostMove;
8882 /* currentMoveString is set as a side-effect of yylex */
8883 strcat(currentMoveString, "\n");
8884 strcpy(moveList[forwardMostMove], currentMoveString);
8886 thinkOutput[0] = NULLCHAR;
8887 MakeMove(fromX, fromY, toX, toY, promoChar);
8888 currentMove = forwardMostMove;
8893 /* Load the nth game from the given file */
8895 LoadGameFromFile(filename, n, title, useList)
8899 /*Boolean*/ int useList;
8904 if (strcmp(filename, "-") == 0) {
8908 f = fopen(filename, "rb");
8910 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8911 DisplayError(buf, errno);
8915 if (fseek(f, 0, 0) == -1) {
8916 /* f is not seekable; probably a pipe */
8919 if (useList && n == 0) {
8920 int error = GameListBuild(f);
8922 DisplayError(_("Cannot build game list"), error);
8923 } else if (!ListEmpty(&gameList) &&
8924 ((ListGame *) gameList.tailPred)->number > 1) {
8925 GameListPopUp(f, title);
8932 return LoadGame(f, n, title, FALSE);
8937 MakeRegisteredMove()
8939 int fromX, fromY, toX, toY;
8941 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8942 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8945 if (appData.debugMode)
8946 fprintf(debugFP, "Restoring %s for game %d\n",
8947 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8949 thinkOutput[0] = NULLCHAR;
8950 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8951 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8952 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8953 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8954 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8955 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8956 MakeMove(fromX, fromY, toX, toY, promoChar);
8957 ShowMove(fromX, fromY, toX, toY);
8959 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8960 EP_UNKNOWN, castlingRights[currentMove]) ) {
8967 if (WhiteOnMove(currentMove)) {
8968 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8970 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8975 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8982 if (WhiteOnMove(currentMove)) {
8983 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8985 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8990 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9001 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9003 CmailLoadGame(f, gameNumber, title, useList)
9011 if (gameNumber > nCmailGames) {
9012 DisplayError(_("No more games in this message"), 0);
9015 if (f == lastLoadGameFP) {
9016 int offset = gameNumber - lastLoadGameNumber;
9018 cmailMsg[0] = NULLCHAR;
9019 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9020 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9021 nCmailMovesRegistered--;
9023 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9024 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9025 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9028 if (! RegisterMove()) return FALSE;
9032 retVal = LoadGame(f, gameNumber, title, useList);
9034 /* Make move registered during previous look at this game, if any */
9035 MakeRegisteredMove();
9037 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9038 commentList[currentMove]
9039 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9040 DisplayComment(currentMove - 1, commentList[currentMove]);
9046 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9051 int gameNumber = lastLoadGameNumber + offset;
9052 if (lastLoadGameFP == NULL) {
9053 DisplayError(_("No game has been loaded yet"), 0);
9056 if (gameNumber <= 0) {
9057 DisplayError(_("Can't back up any further"), 0);
9060 if (cmailMsgLoaded) {
9061 return CmailLoadGame(lastLoadGameFP, gameNumber,
9062 lastLoadGameTitle, lastLoadGameUseList);
9064 return LoadGame(lastLoadGameFP, gameNumber,
9065 lastLoadGameTitle, lastLoadGameUseList);
9071 /* Load the nth game from open file f */
9073 LoadGame(f, gameNumber, title, useList)
9081 int gn = gameNumber;
9082 ListGame *lg = NULL;
9085 GameMode oldGameMode;
9086 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9088 if (appData.debugMode)
9089 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9091 if (gameMode == Training )
9092 SetTrainingModeOff();
9094 oldGameMode = gameMode;
9095 if (gameMode != BeginningOfGame) {
9100 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9101 fclose(lastLoadGameFP);
9105 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9108 fseek(f, lg->offset, 0);
9109 GameListHighlight(gameNumber);
9113 DisplayError(_("Game number out of range"), 0);
9118 if (fseek(f, 0, 0) == -1) {
9119 if (f == lastLoadGameFP ?
9120 gameNumber == lastLoadGameNumber + 1 :
9124 DisplayError(_("Can't seek on game file"), 0);
9130 lastLoadGameNumber = gameNumber;
9131 strcpy(lastLoadGameTitle, title);
9132 lastLoadGameUseList = useList;
9136 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9137 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9138 lg->gameInfo.black);
9140 } else if (*title != NULLCHAR) {
9141 if (gameNumber > 1) {
9142 sprintf(buf, "%s %d", title, gameNumber);
9145 DisplayTitle(title);
9149 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9150 gameMode = PlayFromGameFile;
9154 currentMove = forwardMostMove = backwardMostMove = 0;
9155 CopyBoard(boards[0], initialPosition);
9159 * Skip the first gn-1 games in the file.
9160 * Also skip over anything that precedes an identifiable
9161 * start of game marker, to avoid being confused by
9162 * garbage at the start of the file. Currently
9163 * recognized start of game markers are the move number "1",
9164 * the pattern "gnuchess .* game", the pattern
9165 * "^[#;%] [^ ]* game file", and a PGN tag block.
9166 * A game that starts with one of the latter two patterns
9167 * will also have a move number 1, possibly
9168 * following a position diagram.
9169 * 5-4-02: Let's try being more lenient and allowing a game to
9170 * start with an unnumbered move. Does that break anything?
9172 cm = lastLoadGameStart = (ChessMove) 0;
9174 yyboardindex = forwardMostMove;
9175 cm = (ChessMove) yylex();
9178 if (cmailMsgLoaded) {
9179 nCmailGames = CMAIL_MAX_GAMES - gn;
9182 DisplayError(_("Game not found in file"), 0);
9189 lastLoadGameStart = cm;
9193 switch (lastLoadGameStart) {
9200 gn--; /* count this game */
9201 lastLoadGameStart = cm;
9210 switch (lastLoadGameStart) {
9215 gn--; /* count this game */
9216 lastLoadGameStart = cm;
9219 lastLoadGameStart = cm; /* game counted already */
9227 yyboardindex = forwardMostMove;
9228 cm = (ChessMove) yylex();
9229 } while (cm == PGNTag || cm == Comment);
9236 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9237 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9238 != CMAIL_OLD_RESULT) {
9240 cmailResult[ CMAIL_MAX_GAMES
9241 - gn - 1] = CMAIL_OLD_RESULT;
9247 /* Only a NormalMove can be at the start of a game
9248 * without a position diagram. */
9249 if (lastLoadGameStart == (ChessMove) 0) {
9251 lastLoadGameStart = MoveNumberOne;
9260 if (appData.debugMode)
9261 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9263 if (cm == XBoardGame) {
9264 /* Skip any header junk before position diagram and/or move 1 */
9266 yyboardindex = forwardMostMove;
9267 cm = (ChessMove) yylex();
9269 if (cm == (ChessMove) 0 ||
9270 cm == GNUChessGame || cm == XBoardGame) {
9271 /* Empty game; pretend end-of-file and handle later */
9276 if (cm == MoveNumberOne || cm == PositionDiagram ||
9277 cm == PGNTag || cm == Comment)
9280 } else if (cm == GNUChessGame) {
9281 if (gameInfo.event != NULL) {
9282 free(gameInfo.event);
9284 gameInfo.event = StrSave(yy_text);
9287 startedFromSetupPosition = FALSE;
9288 while (cm == PGNTag) {
9289 if (appData.debugMode)
9290 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9291 err = ParsePGNTag(yy_text, &gameInfo);
9292 if (!err) numPGNTags++;
9294 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9295 if(gameInfo.variant != oldVariant) {
9296 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9298 oldVariant = gameInfo.variant;
9299 if (appData.debugMode)
9300 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9304 if (gameInfo.fen != NULL) {
9305 Board initial_position;
9306 startedFromSetupPosition = TRUE;
9307 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9309 DisplayError(_("Bad FEN position in file"), 0);
9312 CopyBoard(boards[0], initial_position);
9313 if (blackPlaysFirst) {
9314 currentMove = forwardMostMove = backwardMostMove = 1;
9315 CopyBoard(boards[1], initial_position);
9316 strcpy(moveList[0], "");
9317 strcpy(parseList[0], "");
9318 timeRemaining[0][1] = whiteTimeRemaining;
9319 timeRemaining[1][1] = blackTimeRemaining;
9320 if (commentList[0] != NULL) {
9321 commentList[1] = commentList[0];
9322 commentList[0] = NULL;
9325 currentMove = forwardMostMove = backwardMostMove = 0;
9327 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9329 initialRulePlies = FENrulePlies;
9330 epStatus[forwardMostMove] = FENepStatus;
9331 for( i=0; i< nrCastlingRights; i++ )
9332 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9334 yyboardindex = forwardMostMove;
9336 gameInfo.fen = NULL;
9339 yyboardindex = forwardMostMove;
9340 cm = (ChessMove) yylex();
9342 /* Handle comments interspersed among the tags */
9343 while (cm == Comment) {
9345 if (appData.debugMode)
9346 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9348 if (*p == '{' || *p == '[' || *p == '(') {
9349 p[strlen(p) - 1] = NULLCHAR;
9352 while (*p == '\n') p++;
9353 AppendComment(currentMove, p);
9354 yyboardindex = forwardMostMove;
9355 cm = (ChessMove) yylex();
9359 /* don't rely on existence of Event tag since if game was
9360 * pasted from clipboard the Event tag may not exist
9362 if (numPGNTags > 0){
9364 if (gameInfo.variant == VariantNormal) {
9365 gameInfo.variant = StringToVariant(gameInfo.event);
9368 if( appData.autoDisplayTags ) {
9369 tags = PGNTags(&gameInfo);
9370 TagsPopUp(tags, CmailMsg());
9375 /* Make something up, but don't display it now */
9380 if (cm == PositionDiagram) {
9383 Board initial_position;
9385 if (appData.debugMode)
9386 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9388 if (!startedFromSetupPosition) {
9390 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9391 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9401 initial_position[i][j++] = CharToPiece(*p);
9404 while (*p == ' ' || *p == '\t' ||
9405 *p == '\n' || *p == '\r') p++;
9407 if (strncmp(p, "black", strlen("black"))==0)
9408 blackPlaysFirst = TRUE;
9410 blackPlaysFirst = FALSE;
9411 startedFromSetupPosition = TRUE;
9413 CopyBoard(boards[0], initial_position);
9414 if (blackPlaysFirst) {
9415 currentMove = forwardMostMove = backwardMostMove = 1;
9416 CopyBoard(boards[1], initial_position);
9417 strcpy(moveList[0], "");
9418 strcpy(parseList[0], "");
9419 timeRemaining[0][1] = whiteTimeRemaining;
9420 timeRemaining[1][1] = blackTimeRemaining;
9421 if (commentList[0] != NULL) {
9422 commentList[1] = commentList[0];
9423 commentList[0] = NULL;
9426 currentMove = forwardMostMove = backwardMostMove = 0;
9429 yyboardindex = forwardMostMove;
9430 cm = (ChessMove) yylex();
9433 if (first.pr == NoProc) {
9434 StartChessProgram(&first);
9436 InitChessProgram(&first, FALSE);
9437 SendToProgram("force\n", &first);
9438 if (startedFromSetupPosition) {
9439 SendBoard(&first, forwardMostMove);
9440 if (appData.debugMode) {
9441 fprintf(debugFP, "Load Game\n");
9443 DisplayBothClocks();
9446 /* [HGM] server: flag to write setup moves in broadcast file as one */
9447 loadFlag = appData.suppressLoadMoves;
9449 while (cm == Comment) {
9451 if (appData.debugMode)
9452 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9454 if (*p == '{' || *p == '[' || *p == '(') {
9455 p[strlen(p) - 1] = NULLCHAR;
9458 while (*p == '\n') p++;
9459 AppendComment(currentMove, p);
9460 yyboardindex = forwardMostMove;
9461 cm = (ChessMove) yylex();
9464 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9465 cm == WhiteWins || cm == BlackWins ||
9466 cm == GameIsDrawn || cm == GameUnfinished) {
9467 DisplayMessage("", _("No moves in game"));
9468 if (cmailMsgLoaded) {
9469 if (appData.debugMode)
9470 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9474 DrawPosition(FALSE, boards[currentMove]);
9475 DisplayBothClocks();
9476 gameMode = EditGame;
9483 // [HGM] PV info: routine tests if comment empty
9484 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9485 DisplayComment(currentMove - 1, commentList[currentMove]);
9487 if (!matchMode && appData.timeDelay != 0)
9488 DrawPosition(FALSE, boards[currentMove]);
9490 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9491 programStats.ok_to_send = 1;
9494 /* if the first token after the PGN tags is a move
9495 * and not move number 1, retrieve it from the parser
9497 if (cm != MoveNumberOne)
9498 LoadGameOneMove(cm);
9500 /* load the remaining moves from the file */
9501 while (LoadGameOneMove((ChessMove)0)) {
9502 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9503 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9506 /* rewind to the start of the game */
9507 currentMove = backwardMostMove;
9509 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9511 if (oldGameMode == AnalyzeFile ||
9512 oldGameMode == AnalyzeMode) {
9516 if (matchMode || appData.timeDelay == 0) {
9518 gameMode = EditGame;
9520 } else if (appData.timeDelay > 0) {
9524 if (appData.debugMode)
9525 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9527 loadFlag = 0; /* [HGM] true game starts */
9531 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9533 ReloadPosition(offset)
9536 int positionNumber = lastLoadPositionNumber + offset;
9537 if (lastLoadPositionFP == NULL) {
9538 DisplayError(_("No position has been loaded yet"), 0);
9541 if (positionNumber <= 0) {
9542 DisplayError(_("Can't back up any further"), 0);
9545 return LoadPosition(lastLoadPositionFP, positionNumber,
9546 lastLoadPositionTitle);
9549 /* Load the nth position from the given file */
9551 LoadPositionFromFile(filename, n, title)
9559 if (strcmp(filename, "-") == 0) {
9560 return LoadPosition(stdin, n, "stdin");
9562 f = fopen(filename, "rb");
9564 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9565 DisplayError(buf, errno);
9568 return LoadPosition(f, n, title);
9573 /* Load the nth position from the given open file, and close it */
9575 LoadPosition(f, positionNumber, title)
9580 char *p, line[MSG_SIZ];
9581 Board initial_position;
9582 int i, j, fenMode, pn;
9584 if (gameMode == Training )
9585 SetTrainingModeOff();
9587 if (gameMode != BeginningOfGame) {
9590 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9591 fclose(lastLoadPositionFP);
9593 if (positionNumber == 0) positionNumber = 1;
9594 lastLoadPositionFP = f;
9595 lastLoadPositionNumber = positionNumber;
9596 strcpy(lastLoadPositionTitle, title);
9597 if (first.pr == NoProc) {
9598 StartChessProgram(&first);
9599 InitChessProgram(&first, FALSE);
9601 pn = positionNumber;
9602 if (positionNumber < 0) {
9603 /* Negative position number means to seek to that byte offset */
9604 if (fseek(f, -positionNumber, 0) == -1) {
9605 DisplayError(_("Can't seek on position file"), 0);
9610 if (fseek(f, 0, 0) == -1) {
9611 if (f == lastLoadPositionFP ?
9612 positionNumber == lastLoadPositionNumber + 1 :
9613 positionNumber == 1) {
9616 DisplayError(_("Can't seek on position file"), 0);
9621 /* See if this file is FEN or old-style xboard */
9622 if (fgets(line, MSG_SIZ, f) == NULL) {
9623 DisplayError(_("Position not found in file"), 0);
9626 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9627 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9630 if (fenMode || line[0] == '#') pn--;
9632 /* skip positions before number pn */
9633 if (fgets(line, MSG_SIZ, f) == NULL) {
9635 DisplayError(_("Position not found in file"), 0);
9638 if (fenMode || line[0] == '#') pn--;
9643 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9644 DisplayError(_("Bad FEN position in file"), 0);
9648 (void) fgets(line, MSG_SIZ, f);
9649 (void) fgets(line, MSG_SIZ, f);
9651 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9652 (void) fgets(line, MSG_SIZ, f);
9653 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9656 initial_position[i][j++] = CharToPiece(*p);
9660 blackPlaysFirst = FALSE;
9662 (void) fgets(line, MSG_SIZ, f);
9663 if (strncmp(line, "black", strlen("black"))==0)
9664 blackPlaysFirst = TRUE;
9667 startedFromSetupPosition = TRUE;
9669 SendToProgram("force\n", &first);
9670 CopyBoard(boards[0], initial_position);
9671 if (blackPlaysFirst) {
9672 currentMove = forwardMostMove = backwardMostMove = 1;
9673 strcpy(moveList[0], "");
9674 strcpy(parseList[0], "");
9675 CopyBoard(boards[1], initial_position);
9676 DisplayMessage("", _("Black to play"));
9678 currentMove = forwardMostMove = backwardMostMove = 0;
9679 DisplayMessage("", _("White to play"));
9681 /* [HGM] copy FEN attributes as well */
9683 initialRulePlies = FENrulePlies;
9684 epStatus[forwardMostMove] = FENepStatus;
9685 for( i=0; i< nrCastlingRights; i++ )
9686 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9688 SendBoard(&first, forwardMostMove);
9689 if (appData.debugMode) {
9691 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9692 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9693 fprintf(debugFP, "Load Position\n");
9696 if (positionNumber > 1) {
9697 sprintf(line, "%s %d", title, positionNumber);
9700 DisplayTitle(title);
9702 gameMode = EditGame;
9705 timeRemaining[0][1] = whiteTimeRemaining;
9706 timeRemaining[1][1] = blackTimeRemaining;
9707 DrawPosition(FALSE, boards[currentMove]);
9714 CopyPlayerNameIntoFileName(dest, src)
9717 while (*src != NULLCHAR && *src != ',') {
9722 *(*dest)++ = *src++;
9727 char *DefaultFileName(ext)
9730 static char def[MSG_SIZ];
9733 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9735 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9737 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9746 /* Save the current game to the given file */
9748 SaveGameToFile(filename, append)
9755 if (strcmp(filename, "-") == 0) {
9756 return SaveGame(stdout, 0, NULL);
9758 f = fopen(filename, append ? "a" : "w");
9760 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9761 DisplayError(buf, errno);
9764 return SaveGame(f, 0, NULL);
9773 static char buf[MSG_SIZ];
9776 p = strchr(str, ' ');
9777 if (p == NULL) return str;
9778 strncpy(buf, str, p - str);
9779 buf[p - str] = NULLCHAR;
9783 #define PGN_MAX_LINE 75
9785 #define PGN_SIDE_WHITE 0
9786 #define PGN_SIDE_BLACK 1
9789 static int FindFirstMoveOutOfBook( int side )
9793 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9794 int index = backwardMostMove;
9795 int has_book_hit = 0;
9797 if( (index % 2) != side ) {
9801 while( index < forwardMostMove ) {
9802 /* Check to see if engine is in book */
9803 int depth = pvInfoList[index].depth;
9804 int score = pvInfoList[index].score;
9810 else if( score == 0 && depth == 63 ) {
9811 in_book = 1; /* Zappa */
9813 else if( score == 2 && depth == 99 ) {
9814 in_book = 1; /* Abrok */
9817 has_book_hit += in_book;
9833 void GetOutOfBookInfo( char * buf )
9837 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9839 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9840 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9844 if( oob[0] >= 0 || oob[1] >= 0 ) {
9845 for( i=0; i<2; i++ ) {
9849 if( i > 0 && oob[0] >= 0 ) {
9853 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9854 sprintf( buf+strlen(buf), "%s%.2f",
9855 pvInfoList[idx].score >= 0 ? "+" : "",
9856 pvInfoList[idx].score / 100.0 );
9862 /* Save game in PGN style and close the file */
9867 int i, offset, linelen, newblock;
9871 int movelen, numlen, blank;
9872 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9874 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9876 tm = time((time_t *) NULL);
9878 PrintPGNTags(f, &gameInfo);
9880 if (backwardMostMove > 0 || startedFromSetupPosition) {
9881 char *fen = PositionToFEN(backwardMostMove, NULL);
9882 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9883 fprintf(f, "\n{--------------\n");
9884 PrintPosition(f, backwardMostMove);
9885 fprintf(f, "--------------}\n");
9889 /* [AS] Out of book annotation */
9890 if( appData.saveOutOfBookInfo ) {
9893 GetOutOfBookInfo( buf );
9895 if( buf[0] != '\0' ) {
9896 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9903 i = backwardMostMove;
9907 while (i < forwardMostMove) {
9908 /* Print comments preceding this move */
9909 if (commentList[i] != NULL) {
9910 if (linelen > 0) fprintf(f, "\n");
9911 fprintf(f, "{\n%s}\n", commentList[i]);
9916 /* Format move number */
9918 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9921 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9923 numtext[0] = NULLCHAR;
9926 numlen = strlen(numtext);
9929 /* Print move number */
9930 blank = linelen > 0 && numlen > 0;
9931 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9940 fprintf(f, "%s", numtext);
9944 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9945 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9948 blank = linelen > 0 && movelen > 0;
9949 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9958 fprintf(f, "%s", move_buffer);
9961 /* [AS] Add PV info if present */
9962 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9963 /* [HGM] add time */
9964 char buf[MSG_SIZ]; int seconds;
9966 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9968 if( seconds <= 0) buf[0] = 0; else
9969 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9970 seconds = (seconds + 4)/10; // round to full seconds
9971 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9972 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9975 sprintf( move_buffer, "{%s%.2f/%d%s}",
9976 pvInfoList[i].score >= 0 ? "+" : "",
9977 pvInfoList[i].score / 100.0,
9978 pvInfoList[i].depth,
9981 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9983 /* Print score/depth */
9984 blank = linelen > 0 && movelen > 0;
9985 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9994 fprintf(f, "%s", move_buffer);
10001 /* Start a new line */
10002 if (linelen > 0) fprintf(f, "\n");
10004 /* Print comments after last move */
10005 if (commentList[i] != NULL) {
10006 fprintf(f, "{\n%s}\n", commentList[i]);
10010 if (gameInfo.resultDetails != NULL &&
10011 gameInfo.resultDetails[0] != NULLCHAR) {
10012 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10013 PGNResult(gameInfo.result));
10015 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10019 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10023 /* Save game in old style and close the file */
10025 SaveGameOldStyle(f)
10031 tm = time((time_t *) NULL);
10033 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10036 if (backwardMostMove > 0 || startedFromSetupPosition) {
10037 fprintf(f, "\n[--------------\n");
10038 PrintPosition(f, backwardMostMove);
10039 fprintf(f, "--------------]\n");
10044 i = backwardMostMove;
10045 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10047 while (i < forwardMostMove) {
10048 if (commentList[i] != NULL) {
10049 fprintf(f, "[%s]\n", commentList[i]);
10052 if ((i % 2) == 1) {
10053 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10056 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10058 if (commentList[i] != NULL) {
10062 if (i >= forwardMostMove) {
10066 fprintf(f, "%s\n", parseList[i]);
10071 if (commentList[i] != NULL) {
10072 fprintf(f, "[%s]\n", commentList[i]);
10075 /* This isn't really the old style, but it's close enough */
10076 if (gameInfo.resultDetails != NULL &&
10077 gameInfo.resultDetails[0] != NULLCHAR) {
10078 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10079 gameInfo.resultDetails);
10081 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10088 /* Save the current game to open file f and close the file */
10090 SaveGame(f, dummy, dummy2)
10095 if (gameMode == EditPosition) EditPositionDone(TRUE);
10096 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10097 if (appData.oldSaveStyle)
10098 return SaveGameOldStyle(f);
10100 return SaveGamePGN(f);
10103 /* Save the current position to the given file */
10105 SavePositionToFile(filename)
10111 if (strcmp(filename, "-") == 0) {
10112 return SavePosition(stdout, 0, NULL);
10114 f = fopen(filename, "a");
10116 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10117 DisplayError(buf, errno);
10120 SavePosition(f, 0, NULL);
10126 /* Save the current position to the given open file and close the file */
10128 SavePosition(f, dummy, dummy2)
10136 if (gameMode == EditPosition) EditPositionDone(TRUE);
10137 if (appData.oldSaveStyle) {
10138 tm = time((time_t *) NULL);
10140 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10142 fprintf(f, "[--------------\n");
10143 PrintPosition(f, currentMove);
10144 fprintf(f, "--------------]\n");
10146 fen = PositionToFEN(currentMove, NULL);
10147 fprintf(f, "%s\n", fen);
10155 ReloadCmailMsgEvent(unregister)
10159 static char *inFilename = NULL;
10160 static char *outFilename;
10162 struct stat inbuf, outbuf;
10165 /* Any registered moves are unregistered if unregister is set, */
10166 /* i.e. invoked by the signal handler */
10168 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10169 cmailMoveRegistered[i] = FALSE;
10170 if (cmailCommentList[i] != NULL) {
10171 free(cmailCommentList[i]);
10172 cmailCommentList[i] = NULL;
10175 nCmailMovesRegistered = 0;
10178 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10179 cmailResult[i] = CMAIL_NOT_RESULT;
10183 if (inFilename == NULL) {
10184 /* Because the filenames are static they only get malloced once */
10185 /* and they never get freed */
10186 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10187 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10189 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10190 sprintf(outFilename, "%s.out", appData.cmailGameName);
10193 status = stat(outFilename, &outbuf);
10195 cmailMailedMove = FALSE;
10197 status = stat(inFilename, &inbuf);
10198 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10201 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10202 counts the games, notes how each one terminated, etc.
10204 It would be nice to remove this kludge and instead gather all
10205 the information while building the game list. (And to keep it
10206 in the game list nodes instead of having a bunch of fixed-size
10207 parallel arrays.) Note this will require getting each game's
10208 termination from the PGN tags, as the game list builder does
10209 not process the game moves. --mann
10211 cmailMsgLoaded = TRUE;
10212 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10214 /* Load first game in the file or popup game menu */
10215 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10217 #endif /* !WIN32 */
10225 char string[MSG_SIZ];
10227 if ( cmailMailedMove
10228 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10229 return TRUE; /* Allow free viewing */
10232 /* Unregister move to ensure that we don't leave RegisterMove */
10233 /* with the move registered when the conditions for registering no */
10235 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10236 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10237 nCmailMovesRegistered --;
10239 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10241 free(cmailCommentList[lastLoadGameNumber - 1]);
10242 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10246 if (cmailOldMove == -1) {
10247 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10251 if (currentMove > cmailOldMove + 1) {
10252 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10256 if (currentMove < cmailOldMove) {
10257 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10261 if (forwardMostMove > currentMove) {
10262 /* Silently truncate extra moves */
10266 if ( (currentMove == cmailOldMove + 1)
10267 || ( (currentMove == cmailOldMove)
10268 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10269 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10270 if (gameInfo.result != GameUnfinished) {
10271 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10274 if (commentList[currentMove] != NULL) {
10275 cmailCommentList[lastLoadGameNumber - 1]
10276 = StrSave(commentList[currentMove]);
10278 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10280 if (appData.debugMode)
10281 fprintf(debugFP, "Saving %s for game %d\n",
10282 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10285 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10287 f = fopen(string, "w");
10288 if (appData.oldSaveStyle) {
10289 SaveGameOldStyle(f); /* also closes the file */
10291 sprintf(string, "%s.pos.out", appData.cmailGameName);
10292 f = fopen(string, "w");
10293 SavePosition(f, 0, NULL); /* also closes the file */
10295 fprintf(f, "{--------------\n");
10296 PrintPosition(f, currentMove);
10297 fprintf(f, "--------------}\n\n");
10299 SaveGame(f, 0, NULL); /* also closes the file*/
10302 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10303 nCmailMovesRegistered ++;
10304 } else if (nCmailGames == 1) {
10305 DisplayError(_("You have not made a move yet"), 0);
10316 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10317 FILE *commandOutput;
10318 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10319 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10325 if (! cmailMsgLoaded) {
10326 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10330 if (nCmailGames == nCmailResults) {
10331 DisplayError(_("No unfinished games"), 0);
10335 #if CMAIL_PROHIBIT_REMAIL
10336 if (cmailMailedMove) {
10337 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);
10338 DisplayError(msg, 0);
10343 if (! (cmailMailedMove || RegisterMove())) return;
10345 if ( cmailMailedMove
10346 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10347 sprintf(string, partCommandString,
10348 appData.debugMode ? " -v" : "", appData.cmailGameName);
10349 commandOutput = popen(string, "r");
10351 if (commandOutput == NULL) {
10352 DisplayError(_("Failed to invoke cmail"), 0);
10354 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10355 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10357 if (nBuffers > 1) {
10358 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10359 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10360 nBytes = MSG_SIZ - 1;
10362 (void) memcpy(msg, buffer, nBytes);
10364 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10366 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10367 cmailMailedMove = TRUE; /* Prevent >1 moves */
10370 for (i = 0; i < nCmailGames; i ++) {
10371 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10376 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10378 sprintf(buffer, "%s/%s.%s.archive",
10380 appData.cmailGameName,
10382 LoadGameFromFile(buffer, 1, buffer, FALSE);
10383 cmailMsgLoaded = FALSE;
10387 DisplayInformation(msg);
10388 pclose(commandOutput);
10391 if ((*cmailMsg) != '\0') {
10392 DisplayInformation(cmailMsg);
10397 #endif /* !WIN32 */
10406 int prependComma = 0;
10408 char string[MSG_SIZ]; /* Space for game-list */
10411 if (!cmailMsgLoaded) return "";
10413 if (cmailMailedMove) {
10414 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10416 /* Create a list of games left */
10417 sprintf(string, "[");
10418 for (i = 0; i < nCmailGames; i ++) {
10419 if (! ( cmailMoveRegistered[i]
10420 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10421 if (prependComma) {
10422 sprintf(number, ",%d", i + 1);
10424 sprintf(number, "%d", i + 1);
10428 strcat(string, number);
10431 strcat(string, "]");
10433 if (nCmailMovesRegistered + nCmailResults == 0) {
10434 switch (nCmailGames) {
10437 _("Still need to make move for game\n"));
10442 _("Still need to make moves for both games\n"));
10447 _("Still need to make moves for all %d games\n"),
10452 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10455 _("Still need to make a move for game %s\n"),
10460 if (nCmailResults == nCmailGames) {
10461 sprintf(cmailMsg, _("No unfinished games\n"));
10463 sprintf(cmailMsg, _("Ready to send mail\n"));
10469 _("Still need to make moves for games %s\n"),
10481 if (gameMode == Training)
10482 SetTrainingModeOff();
10485 cmailMsgLoaded = FALSE;
10486 if (appData.icsActive) {
10487 SendToICS(ics_prefix);
10488 SendToICS("refresh\n");
10498 /* Give up on clean exit */
10502 /* Keep trying for clean exit */
10506 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10508 if (telnetISR != NULL) {
10509 RemoveInputSource(telnetISR);
10511 if (icsPR != NoProc) {
10512 DestroyChildProcess(icsPR, TRUE);
10515 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10516 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10518 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10519 /* make sure this other one finishes before killing it! */
10520 if(endingGame) { int count = 0;
10521 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10522 while(endingGame && count++ < 10) DoSleep(1);
10523 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10526 /* Kill off chess programs */
10527 if (first.pr != NoProc) {
10530 DoSleep( appData.delayBeforeQuit );
10531 SendToProgram("quit\n", &first);
10532 DoSleep( appData.delayAfterQuit );
10533 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10535 if (second.pr != NoProc) {
10536 DoSleep( appData.delayBeforeQuit );
10537 SendToProgram("quit\n", &second);
10538 DoSleep( appData.delayAfterQuit );
10539 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10541 if (first.isr != NULL) {
10542 RemoveInputSource(first.isr);
10544 if (second.isr != NULL) {
10545 RemoveInputSource(second.isr);
10548 ShutDownFrontEnd();
10555 if (appData.debugMode)
10556 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10560 if (gameMode == MachinePlaysWhite ||
10561 gameMode == MachinePlaysBlack) {
10564 DisplayBothClocks();
10566 if (gameMode == PlayFromGameFile) {
10567 if (appData.timeDelay >= 0)
10568 AutoPlayGameLoop();
10569 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10570 Reset(FALSE, TRUE);
10571 SendToICS(ics_prefix);
10572 SendToICS("refresh\n");
10573 } else if (currentMove < forwardMostMove) {
10574 ForwardInner(forwardMostMove);
10576 pauseExamInvalid = FALSE;
10578 switch (gameMode) {
10582 pauseExamForwardMostMove = forwardMostMove;
10583 pauseExamInvalid = FALSE;
10586 case IcsPlayingWhite:
10587 case IcsPlayingBlack:
10591 case PlayFromGameFile:
10592 (void) StopLoadGameTimer();
10596 case BeginningOfGame:
10597 if (appData.icsActive) return;
10598 /* else fall through */
10599 case MachinePlaysWhite:
10600 case MachinePlaysBlack:
10601 case TwoMachinesPlay:
10602 if (forwardMostMove == 0)
10603 return; /* don't pause if no one has moved */
10604 if ((gameMode == MachinePlaysWhite &&
10605 !WhiteOnMove(forwardMostMove)) ||
10606 (gameMode == MachinePlaysBlack &&
10607 WhiteOnMove(forwardMostMove))) {
10620 char title[MSG_SIZ];
10622 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10623 strcpy(title, _("Edit comment"));
10625 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10626 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10627 parseList[currentMove - 1]);
10630 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10637 char *tags = PGNTags(&gameInfo);
10638 EditTagsPopUp(tags);
10645 if (appData.noChessProgram || gameMode == AnalyzeMode)
10648 if (gameMode != AnalyzeFile) {
10649 if (!appData.icsEngineAnalyze) {
10651 if (gameMode != EditGame) return;
10653 ResurrectChessProgram();
10654 SendToProgram("analyze\n", &first);
10655 first.analyzing = TRUE;
10656 /*first.maybeThinking = TRUE;*/
10657 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10658 EngineOutputPopUp();
10660 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10665 StartAnalysisClock();
10666 GetTimeMark(&lastNodeCountTime);
10673 if (appData.noChessProgram || gameMode == AnalyzeFile)
10676 if (gameMode != AnalyzeMode) {
10678 if (gameMode != EditGame) return;
10679 ResurrectChessProgram();
10680 SendToProgram("analyze\n", &first);
10681 first.analyzing = TRUE;
10682 /*first.maybeThinking = TRUE;*/
10683 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10684 EngineOutputPopUp();
10686 gameMode = AnalyzeFile;
10691 StartAnalysisClock();
10692 GetTimeMark(&lastNodeCountTime);
10697 MachineWhiteEvent()
10700 char *bookHit = NULL;
10702 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10706 if (gameMode == PlayFromGameFile ||
10707 gameMode == TwoMachinesPlay ||
10708 gameMode == Training ||
10709 gameMode == AnalyzeMode ||
10710 gameMode == EndOfGame)
10713 if (gameMode == EditPosition)
10714 EditPositionDone(TRUE);
10716 if (!WhiteOnMove(currentMove)) {
10717 DisplayError(_("It is not White's turn"), 0);
10721 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10724 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10725 gameMode == AnalyzeFile)
10728 ResurrectChessProgram(); /* in case it isn't running */
10729 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10730 gameMode = MachinePlaysWhite;
10733 gameMode = MachinePlaysWhite;
10737 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10739 if (first.sendName) {
10740 sprintf(buf, "name %s\n", gameInfo.black);
10741 SendToProgram(buf, &first);
10743 if (first.sendTime) {
10744 if (first.useColors) {
10745 SendToProgram("black\n", &first); /*gnu kludge*/
10747 SendTimeRemaining(&first, TRUE);
10749 if (first.useColors) {
10750 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10752 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10753 SetMachineThinkingEnables();
10754 first.maybeThinking = TRUE;
10758 if (appData.autoFlipView && !flipView) {
10759 flipView = !flipView;
10760 DrawPosition(FALSE, NULL);
10761 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10764 if(bookHit) { // [HGM] book: simulate book reply
10765 static char bookMove[MSG_SIZ]; // a bit generous?
10767 programStats.nodes = programStats.depth = programStats.time =
10768 programStats.score = programStats.got_only_move = 0;
10769 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10771 strcpy(bookMove, "move ");
10772 strcat(bookMove, bookHit);
10773 HandleMachineMove(bookMove, &first);
10778 MachineBlackEvent()
10781 char *bookHit = NULL;
10783 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10787 if (gameMode == PlayFromGameFile ||
10788 gameMode == TwoMachinesPlay ||
10789 gameMode == Training ||
10790 gameMode == AnalyzeMode ||
10791 gameMode == EndOfGame)
10794 if (gameMode == EditPosition)
10795 EditPositionDone(TRUE);
10797 if (WhiteOnMove(currentMove)) {
10798 DisplayError(_("It is not Black's turn"), 0);
10802 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10805 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10806 gameMode == AnalyzeFile)
10809 ResurrectChessProgram(); /* in case it isn't running */
10810 gameMode = MachinePlaysBlack;
10814 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10816 if (first.sendName) {
10817 sprintf(buf, "name %s\n", gameInfo.white);
10818 SendToProgram(buf, &first);
10820 if (first.sendTime) {
10821 if (first.useColors) {
10822 SendToProgram("white\n", &first); /*gnu kludge*/
10824 SendTimeRemaining(&first, FALSE);
10826 if (first.useColors) {
10827 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10829 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10830 SetMachineThinkingEnables();
10831 first.maybeThinking = TRUE;
10834 if (appData.autoFlipView && flipView) {
10835 flipView = !flipView;
10836 DrawPosition(FALSE, NULL);
10837 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10839 if(bookHit) { // [HGM] book: simulate book reply
10840 static char bookMove[MSG_SIZ]; // a bit generous?
10842 programStats.nodes = programStats.depth = programStats.time =
10843 programStats.score = programStats.got_only_move = 0;
10844 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10846 strcpy(bookMove, "move ");
10847 strcat(bookMove, bookHit);
10848 HandleMachineMove(bookMove, &first);
10854 DisplayTwoMachinesTitle()
10857 if (appData.matchGames > 0) {
10858 if (first.twoMachinesColor[0] == 'w') {
10859 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10860 gameInfo.white, gameInfo.black,
10861 first.matchWins, second.matchWins,
10862 matchGame - 1 - (first.matchWins + second.matchWins));
10864 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10865 gameInfo.white, gameInfo.black,
10866 second.matchWins, first.matchWins,
10867 matchGame - 1 - (first.matchWins + second.matchWins));
10870 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10876 TwoMachinesEvent P((void))
10880 ChessProgramState *onmove;
10881 char *bookHit = NULL;
10883 if (appData.noChessProgram) return;
10885 switch (gameMode) {
10886 case TwoMachinesPlay:
10888 case MachinePlaysWhite:
10889 case MachinePlaysBlack:
10890 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10891 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10895 case BeginningOfGame:
10896 case PlayFromGameFile:
10899 if (gameMode != EditGame) return;
10902 EditPositionDone(TRUE);
10913 forwardMostMove = currentMove;
10914 ResurrectChessProgram(); /* in case first program isn't running */
10916 if (second.pr == NULL) {
10917 StartChessProgram(&second);
10918 if (second.protocolVersion == 1) {
10919 TwoMachinesEventIfReady();
10921 /* kludge: allow timeout for initial "feature" command */
10923 DisplayMessage("", _("Starting second chess program"));
10924 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10928 DisplayMessage("", "");
10929 InitChessProgram(&second, FALSE);
10930 SendToProgram("force\n", &second);
10931 if (startedFromSetupPosition) {
10932 SendBoard(&second, backwardMostMove);
10933 if (appData.debugMode) {
10934 fprintf(debugFP, "Two Machines\n");
10937 for (i = backwardMostMove; i < forwardMostMove; i++) {
10938 SendMoveToProgram(i, &second);
10941 gameMode = TwoMachinesPlay;
10945 DisplayTwoMachinesTitle();
10947 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10953 SendToProgram(first.computerString, &first);
10954 if (first.sendName) {
10955 sprintf(buf, "name %s\n", second.tidy);
10956 SendToProgram(buf, &first);
10958 SendToProgram(second.computerString, &second);
10959 if (second.sendName) {
10960 sprintf(buf, "name %s\n", first.tidy);
10961 SendToProgram(buf, &second);
10965 if (!first.sendTime || !second.sendTime) {
10966 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10967 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10969 if (onmove->sendTime) {
10970 if (onmove->useColors) {
10971 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10973 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10975 if (onmove->useColors) {
10976 SendToProgram(onmove->twoMachinesColor, onmove);
10978 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10979 // SendToProgram("go\n", onmove);
10980 onmove->maybeThinking = TRUE;
10981 SetMachineThinkingEnables();
10985 if(bookHit) { // [HGM] book: simulate book reply
10986 static char bookMove[MSG_SIZ]; // a bit generous?
10988 programStats.nodes = programStats.depth = programStats.time =
10989 programStats.score = programStats.got_only_move = 0;
10990 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10992 strcpy(bookMove, "move ");
10993 strcat(bookMove, bookHit);
10994 savedMessage = bookMove; // args for deferred call
10995 savedState = onmove;
10996 ScheduleDelayedEvent(DeferredBookMove, 1);
11003 if (gameMode == Training) {
11004 SetTrainingModeOff();
11005 gameMode = PlayFromGameFile;
11006 DisplayMessage("", _("Training mode off"));
11008 gameMode = Training;
11009 animateTraining = appData.animate;
11011 /* make sure we are not already at the end of the game */
11012 if (currentMove < forwardMostMove) {
11013 SetTrainingModeOn();
11014 DisplayMessage("", _("Training mode on"));
11016 gameMode = PlayFromGameFile;
11017 DisplayError(_("Already at end of game"), 0);
11026 if (!appData.icsActive) return;
11027 switch (gameMode) {
11028 case IcsPlayingWhite:
11029 case IcsPlayingBlack:
11032 case BeginningOfGame:
11040 EditPositionDone(TRUE);
11053 gameMode = IcsIdle;
11064 switch (gameMode) {
11066 SetTrainingModeOff();
11068 case MachinePlaysWhite:
11069 case MachinePlaysBlack:
11070 case BeginningOfGame:
11071 SendToProgram("force\n", &first);
11072 SetUserThinkingEnables();
11074 case PlayFromGameFile:
11075 (void) StopLoadGameTimer();
11076 if (gameFileFP != NULL) {
11081 EditPositionDone(TRUE);
11086 SendToProgram("force\n", &first);
11088 case TwoMachinesPlay:
11089 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11090 ResurrectChessProgram();
11091 SetUserThinkingEnables();
11094 ResurrectChessProgram();
11096 case IcsPlayingBlack:
11097 case IcsPlayingWhite:
11098 DisplayError(_("Warning: You are still playing a game"), 0);
11101 DisplayError(_("Warning: You are still observing a game"), 0);
11104 DisplayError(_("Warning: You are still examining a game"), 0);
11115 first.offeredDraw = second.offeredDraw = 0;
11117 if (gameMode == PlayFromGameFile) {
11118 whiteTimeRemaining = timeRemaining[0][currentMove];
11119 blackTimeRemaining = timeRemaining[1][currentMove];
11123 if (gameMode == MachinePlaysWhite ||
11124 gameMode == MachinePlaysBlack ||
11125 gameMode == TwoMachinesPlay ||
11126 gameMode == EndOfGame) {
11127 i = forwardMostMove;
11128 while (i > currentMove) {
11129 SendToProgram("undo\n", &first);
11132 whiteTimeRemaining = timeRemaining[0][currentMove];
11133 blackTimeRemaining = timeRemaining[1][currentMove];
11134 DisplayBothClocks();
11135 if (whiteFlag || blackFlag) {
11136 whiteFlag = blackFlag = 0;
11141 gameMode = EditGame;
11148 EditPositionEvent()
11150 if (gameMode == EditPosition) {
11156 if (gameMode != EditGame) return;
11158 gameMode = EditPosition;
11161 if (currentMove > 0)
11162 CopyBoard(boards[0], boards[currentMove]);
11164 blackPlaysFirst = !WhiteOnMove(currentMove);
11166 currentMove = forwardMostMove = backwardMostMove = 0;
11167 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11174 /* [DM] icsEngineAnalyze - possible call from other functions */
11175 if (appData.icsEngineAnalyze) {
11176 appData.icsEngineAnalyze = FALSE;
11178 DisplayMessage("",_("Close ICS engine analyze..."));
11180 if (first.analysisSupport && first.analyzing) {
11181 SendToProgram("exit\n", &first);
11182 first.analyzing = FALSE;
11184 thinkOutput[0] = NULLCHAR;
11188 EditPositionDone(Boolean fakeRights)
11190 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11192 startedFromSetupPosition = TRUE;
11193 InitChessProgram(&first, FALSE);
11195 { /* don't do this if we just pasted FEN */
11196 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11197 if(boards[0][0][BOARD_WIDTH>>1] == king)
11199 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11200 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11203 castlingRights[0][2] = -1;
11204 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11206 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11207 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11210 castlingRights[0][5] = -1;
11212 SendToProgram("force\n", &first);
11213 if (blackPlaysFirst) {
11214 strcpy(moveList[0], "");
11215 strcpy(parseList[0], "");
11216 currentMove = forwardMostMove = backwardMostMove = 1;
11217 CopyBoard(boards[1], boards[0]);
11218 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11220 epStatus[1] = epStatus[0];
11221 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11224 currentMove = forwardMostMove = backwardMostMove = 0;
11226 SendBoard(&first, forwardMostMove);
11227 if (appData.debugMode) {
11228 fprintf(debugFP, "EditPosDone\n");
11231 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11232 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11233 gameMode = EditGame;
11235 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11236 ClearHighlights(); /* [AS] */
11239 /* Pause for `ms' milliseconds */
11240 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11250 } while (SubtractTimeMarks(&m2, &m1) < ms);
11253 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11255 SendMultiLineToICS(buf)
11258 char temp[MSG_SIZ+1], *p;
11265 strncpy(temp, buf, len);
11270 if (*p == '\n' || *p == '\r')
11275 strcat(temp, "\n");
11277 SendToPlayer(temp, strlen(temp));
11281 SetWhiteToPlayEvent()
11283 if (gameMode == EditPosition) {
11284 blackPlaysFirst = FALSE;
11285 DisplayBothClocks(); /* works because currentMove is 0 */
11286 } else if (gameMode == IcsExamining) {
11287 SendToICS(ics_prefix);
11288 SendToICS("tomove white\n");
11293 SetBlackToPlayEvent()
11295 if (gameMode == EditPosition) {
11296 blackPlaysFirst = TRUE;
11297 currentMove = 1; /* kludge */
11298 DisplayBothClocks();
11300 } else if (gameMode == IcsExamining) {
11301 SendToICS(ics_prefix);
11302 SendToICS("tomove black\n");
11307 EditPositionMenuEvent(selection, x, y)
11308 ChessSquare selection;
11312 ChessSquare piece = boards[0][y][x];
11314 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11316 switch (selection) {
11318 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11319 SendToICS(ics_prefix);
11320 SendToICS("bsetup clear\n");
11321 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11322 SendToICS(ics_prefix);
11323 SendToICS("clearboard\n");
11325 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11326 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11327 for (y = 0; y < BOARD_HEIGHT; y++) {
11328 if (gameMode == IcsExamining) {
11329 if (boards[currentMove][y][x] != EmptySquare) {
11330 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11335 boards[0][y][x] = p;
11340 if (gameMode == EditPosition) {
11341 DrawPosition(FALSE, boards[0]);
11346 SetWhiteToPlayEvent();
11350 SetBlackToPlayEvent();
11354 if (gameMode == IcsExamining) {
11355 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11358 boards[0][y][x] = EmptySquare;
11359 DrawPosition(FALSE, boards[0]);
11364 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11365 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11366 selection = (ChessSquare) (PROMOTED piece);
11367 } else if(piece == EmptySquare) selection = WhiteSilver;
11368 else selection = (ChessSquare)((int)piece - 1);
11372 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11373 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11374 selection = (ChessSquare) (DEMOTED piece);
11375 } else if(piece == EmptySquare) selection = BlackSilver;
11376 else selection = (ChessSquare)((int)piece + 1);
11381 if(gameInfo.variant == VariantShatranj ||
11382 gameInfo.variant == VariantXiangqi ||
11383 gameInfo.variant == VariantCourier )
11384 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11389 if(gameInfo.variant == VariantXiangqi)
11390 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11391 if(gameInfo.variant == VariantKnightmate)
11392 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11395 if (gameMode == IcsExamining) {
11396 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11397 PieceToChar(selection), AAA + x, ONE + y);
11400 boards[0][y][x] = selection;
11401 DrawPosition(FALSE, boards[0]);
11409 DropMenuEvent(selection, x, y)
11410 ChessSquare selection;
11413 ChessMove moveType;
11415 switch (gameMode) {
11416 case IcsPlayingWhite:
11417 case MachinePlaysBlack:
11418 if (!WhiteOnMove(currentMove)) {
11419 DisplayMoveError(_("It is Black's turn"));
11422 moveType = WhiteDrop;
11424 case IcsPlayingBlack:
11425 case MachinePlaysWhite:
11426 if (WhiteOnMove(currentMove)) {
11427 DisplayMoveError(_("It is White's turn"));
11430 moveType = BlackDrop;
11433 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11439 if (moveType == BlackDrop && selection < BlackPawn) {
11440 selection = (ChessSquare) ((int) selection
11441 + (int) BlackPawn - (int) WhitePawn);
11443 if (boards[currentMove][y][x] != EmptySquare) {
11444 DisplayMoveError(_("That square is occupied"));
11448 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11454 /* Accept a pending offer of any kind from opponent */
11456 if (appData.icsActive) {
11457 SendToICS(ics_prefix);
11458 SendToICS("accept\n");
11459 } else if (cmailMsgLoaded) {
11460 if (currentMove == cmailOldMove &&
11461 commentList[cmailOldMove] != NULL &&
11462 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11463 "Black offers a draw" : "White offers a draw")) {
11465 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11466 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11468 DisplayError(_("There is no pending offer on this move"), 0);
11469 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11472 /* Not used for offers from chess program */
11479 /* Decline a pending offer of any kind from opponent */
11481 if (appData.icsActive) {
11482 SendToICS(ics_prefix);
11483 SendToICS("decline\n");
11484 } else if (cmailMsgLoaded) {
11485 if (currentMove == cmailOldMove &&
11486 commentList[cmailOldMove] != NULL &&
11487 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11488 "Black offers a draw" : "White offers a draw")) {
11490 AppendComment(cmailOldMove, "Draw declined");
11491 DisplayComment(cmailOldMove - 1, "Draw declined");
11494 DisplayError(_("There is no pending offer on this move"), 0);
11497 /* Not used for offers from chess program */
11504 /* Issue ICS rematch command */
11505 if (appData.icsActive) {
11506 SendToICS(ics_prefix);
11507 SendToICS("rematch\n");
11514 /* Call your opponent's flag (claim a win on time) */
11515 if (appData.icsActive) {
11516 SendToICS(ics_prefix);
11517 SendToICS("flag\n");
11519 switch (gameMode) {
11522 case MachinePlaysWhite:
11525 GameEnds(GameIsDrawn, "Both players ran out of time",
11528 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11530 DisplayError(_("Your opponent is not out of time"), 0);
11533 case MachinePlaysBlack:
11536 GameEnds(GameIsDrawn, "Both players ran out of time",
11539 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11541 DisplayError(_("Your opponent is not out of time"), 0);
11551 /* Offer draw or accept pending draw offer from opponent */
11553 if (appData.icsActive) {
11554 /* Note: tournament rules require draw offers to be
11555 made after you make your move but before you punch
11556 your clock. Currently ICS doesn't let you do that;
11557 instead, you immediately punch your clock after making
11558 a move, but you can offer a draw at any time. */
11560 SendToICS(ics_prefix);
11561 SendToICS("draw\n");
11562 } else if (cmailMsgLoaded) {
11563 if (currentMove == cmailOldMove &&
11564 commentList[cmailOldMove] != NULL &&
11565 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11566 "Black offers a draw" : "White offers a draw")) {
11567 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11568 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11569 } else if (currentMove == cmailOldMove + 1) {
11570 char *offer = WhiteOnMove(cmailOldMove) ?
11571 "White offers a draw" : "Black offers a draw";
11572 AppendComment(currentMove, offer);
11573 DisplayComment(currentMove - 1, offer);
11574 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11576 DisplayError(_("You must make your move before offering a draw"), 0);
11577 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11579 } else if (first.offeredDraw) {
11580 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11582 if (first.sendDrawOffers) {
11583 SendToProgram("draw\n", &first);
11584 userOfferedDraw = TRUE;
11592 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11594 if (appData.icsActive) {
11595 SendToICS(ics_prefix);
11596 SendToICS("adjourn\n");
11598 /* Currently GNU Chess doesn't offer or accept Adjourns */
11606 /* Offer Abort or accept pending Abort offer from opponent */
11608 if (appData.icsActive) {
11609 SendToICS(ics_prefix);
11610 SendToICS("abort\n");
11612 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11619 /* Resign. You can do this even if it's not your turn. */
11621 if (appData.icsActive) {
11622 SendToICS(ics_prefix);
11623 SendToICS("resign\n");
11625 switch (gameMode) {
11626 case MachinePlaysWhite:
11627 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11629 case MachinePlaysBlack:
11630 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11633 if (cmailMsgLoaded) {
11635 if (WhiteOnMove(cmailOldMove)) {
11636 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11638 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11640 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11651 StopObservingEvent()
11653 /* Stop observing current games */
11654 SendToICS(ics_prefix);
11655 SendToICS("unobserve\n");
11659 StopExaminingEvent()
11661 /* Stop observing current game */
11662 SendToICS(ics_prefix);
11663 SendToICS("unexamine\n");
11667 ForwardInner(target)
11672 if (appData.debugMode)
11673 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11674 target, currentMove, forwardMostMove);
11676 if (gameMode == EditPosition)
11679 if (gameMode == PlayFromGameFile && !pausing)
11682 if (gameMode == IcsExamining && pausing)
11683 limit = pauseExamForwardMostMove;
11685 limit = forwardMostMove;
11687 if (target > limit) target = limit;
11689 if (target > 0 && moveList[target - 1][0]) {
11690 int fromX, fromY, toX, toY;
11691 toX = moveList[target - 1][2] - AAA;
11692 toY = moveList[target - 1][3] - ONE;
11693 if (moveList[target - 1][1] == '@') {
11694 if (appData.highlightLastMove) {
11695 SetHighlights(-1, -1, toX, toY);
11698 fromX = moveList[target - 1][0] - AAA;
11699 fromY = moveList[target - 1][1] - ONE;
11700 if (target == currentMove + 1) {
11701 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11703 if (appData.highlightLastMove) {
11704 SetHighlights(fromX, fromY, toX, toY);
11708 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11709 gameMode == Training || gameMode == PlayFromGameFile ||
11710 gameMode == AnalyzeFile) {
11711 while (currentMove < target) {
11712 SendMoveToProgram(currentMove++, &first);
11715 currentMove = target;
11718 if (gameMode == EditGame || gameMode == EndOfGame) {
11719 whiteTimeRemaining = timeRemaining[0][currentMove];
11720 blackTimeRemaining = timeRemaining[1][currentMove];
11722 DisplayBothClocks();
11723 DisplayMove(currentMove - 1);
11724 DrawPosition(FALSE, boards[currentMove]);
11725 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11726 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11727 DisplayComment(currentMove - 1, commentList[currentMove]);
11735 if (gameMode == IcsExamining && !pausing) {
11736 SendToICS(ics_prefix);
11737 SendToICS("forward\n");
11739 ForwardInner(currentMove + 1);
11746 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11747 /* to optimze, we temporarily turn off analysis mode while we feed
11748 * the remaining moves to the engine. Otherwise we get analysis output
11751 if (first.analysisSupport) {
11752 SendToProgram("exit\nforce\n", &first);
11753 first.analyzing = FALSE;
11757 if (gameMode == IcsExamining && !pausing) {
11758 SendToICS(ics_prefix);
11759 SendToICS("forward 999999\n");
11761 ForwardInner(forwardMostMove);
11764 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11765 /* we have fed all the moves, so reactivate analysis mode */
11766 SendToProgram("analyze\n", &first);
11767 first.analyzing = TRUE;
11768 /*first.maybeThinking = TRUE;*/
11769 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11774 BackwardInner(target)
11777 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11779 if (appData.debugMode)
11780 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11781 target, currentMove, forwardMostMove);
11783 if (gameMode == EditPosition) return;
11784 if (currentMove <= backwardMostMove) {
11786 DrawPosition(full_redraw, boards[currentMove]);
11789 if (gameMode == PlayFromGameFile && !pausing)
11792 if (moveList[target][0]) {
11793 int fromX, fromY, toX, toY;
11794 toX = moveList[target][2] - AAA;
11795 toY = moveList[target][3] - ONE;
11796 if (moveList[target][1] == '@') {
11797 if (appData.highlightLastMove) {
11798 SetHighlights(-1, -1, toX, toY);
11801 fromX = moveList[target][0] - AAA;
11802 fromY = moveList[target][1] - ONE;
11803 if (target == currentMove - 1) {
11804 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11806 if (appData.highlightLastMove) {
11807 SetHighlights(fromX, fromY, toX, toY);
11811 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11812 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11813 while (currentMove > target) {
11814 SendToProgram("undo\n", &first);
11818 currentMove = target;
11821 if (gameMode == EditGame || gameMode == EndOfGame) {
11822 whiteTimeRemaining = timeRemaining[0][currentMove];
11823 blackTimeRemaining = timeRemaining[1][currentMove];
11825 DisplayBothClocks();
11826 DisplayMove(currentMove - 1);
11827 DrawPosition(full_redraw, boards[currentMove]);
11828 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11829 // [HGM] PV info: routine tests if comment empty
11830 DisplayComment(currentMove - 1, commentList[currentMove]);
11836 if (gameMode == IcsExamining && !pausing) {
11837 SendToICS(ics_prefix);
11838 SendToICS("backward\n");
11840 BackwardInner(currentMove - 1);
11847 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11848 /* to optimize, we temporarily turn off analysis mode while we undo
11849 * all the moves. Otherwise we get analysis output after each undo.
11851 if (first.analysisSupport) {
11852 SendToProgram("exit\nforce\n", &first);
11853 first.analyzing = FALSE;
11857 if (gameMode == IcsExamining && !pausing) {
11858 SendToICS(ics_prefix);
11859 SendToICS("backward 999999\n");
11861 BackwardInner(backwardMostMove);
11864 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11865 /* we have fed all the moves, so reactivate analysis mode */
11866 SendToProgram("analyze\n", &first);
11867 first.analyzing = TRUE;
11868 /*first.maybeThinking = TRUE;*/
11869 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11876 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11877 if (to >= forwardMostMove) to = forwardMostMove;
11878 if (to <= backwardMostMove) to = backwardMostMove;
11879 if (to < currentMove) {
11889 if (gameMode != IcsExamining) {
11890 DisplayError(_("You are not examining a game"), 0);
11894 DisplayError(_("You can't revert while pausing"), 0);
11897 SendToICS(ics_prefix);
11898 SendToICS("revert\n");
11904 switch (gameMode) {
11905 case MachinePlaysWhite:
11906 case MachinePlaysBlack:
11907 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11908 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11911 if (forwardMostMove < 2) return;
11912 currentMove = forwardMostMove = forwardMostMove - 2;
11913 whiteTimeRemaining = timeRemaining[0][currentMove];
11914 blackTimeRemaining = timeRemaining[1][currentMove];
11915 DisplayBothClocks();
11916 DisplayMove(currentMove - 1);
11917 ClearHighlights();/*!! could figure this out*/
11918 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11919 SendToProgram("remove\n", &first);
11920 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11923 case BeginningOfGame:
11927 case IcsPlayingWhite:
11928 case IcsPlayingBlack:
11929 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11930 SendToICS(ics_prefix);
11931 SendToICS("takeback 2\n");
11933 SendToICS(ics_prefix);
11934 SendToICS("takeback 1\n");
11943 ChessProgramState *cps;
11945 switch (gameMode) {
11946 case MachinePlaysWhite:
11947 if (!WhiteOnMove(forwardMostMove)) {
11948 DisplayError(_("It is your turn"), 0);
11953 case MachinePlaysBlack:
11954 if (WhiteOnMove(forwardMostMove)) {
11955 DisplayError(_("It is your turn"), 0);
11960 case TwoMachinesPlay:
11961 if (WhiteOnMove(forwardMostMove) ==
11962 (first.twoMachinesColor[0] == 'w')) {
11968 case BeginningOfGame:
11972 SendToProgram("?\n", cps);
11976 TruncateGameEvent()
11979 if (gameMode != EditGame) return;
11986 if (forwardMostMove > currentMove) {
11987 if (gameInfo.resultDetails != NULL) {
11988 free(gameInfo.resultDetails);
11989 gameInfo.resultDetails = NULL;
11990 gameInfo.result = GameUnfinished;
11992 forwardMostMove = currentMove;
11993 HistorySet(parseList, backwardMostMove, forwardMostMove,
12001 if (appData.noChessProgram) return;
12002 switch (gameMode) {
12003 case MachinePlaysWhite:
12004 if (WhiteOnMove(forwardMostMove)) {
12005 DisplayError(_("Wait until your turn"), 0);
12009 case BeginningOfGame:
12010 case MachinePlaysBlack:
12011 if (!WhiteOnMove(forwardMostMove)) {
12012 DisplayError(_("Wait until your turn"), 0);
12017 DisplayError(_("No hint available"), 0);
12020 SendToProgram("hint\n", &first);
12021 hintRequested = TRUE;
12027 if (appData.noChessProgram) return;
12028 switch (gameMode) {
12029 case MachinePlaysWhite:
12030 if (WhiteOnMove(forwardMostMove)) {
12031 DisplayError(_("Wait until your turn"), 0);
12035 case BeginningOfGame:
12036 case MachinePlaysBlack:
12037 if (!WhiteOnMove(forwardMostMove)) {
12038 DisplayError(_("Wait until your turn"), 0);
12043 EditPositionDone(TRUE);
12045 case TwoMachinesPlay:
12050 SendToProgram("bk\n", &first);
12051 bookOutput[0] = NULLCHAR;
12052 bookRequested = TRUE;
12058 char *tags = PGNTags(&gameInfo);
12059 TagsPopUp(tags, CmailMsg());
12063 /* end button procedures */
12066 PrintPosition(fp, move)
12072 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12073 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12074 char c = PieceToChar(boards[move][i][j]);
12075 fputc(c == 'x' ? '.' : c, fp);
12076 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12079 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12080 fprintf(fp, "white to play\n");
12082 fprintf(fp, "black to play\n");
12089 if (gameInfo.white != NULL) {
12090 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12096 /* Find last component of program's own name, using some heuristics */
12098 TidyProgramName(prog, host, buf)
12099 char *prog, *host, buf[MSG_SIZ];
12102 int local = (strcmp(host, "localhost") == 0);
12103 while (!local && (p = strchr(prog, ';')) != NULL) {
12105 while (*p == ' ') p++;
12108 if (*prog == '"' || *prog == '\'') {
12109 q = strchr(prog + 1, *prog);
12111 q = strchr(prog, ' ');
12113 if (q == NULL) q = prog + strlen(prog);
12115 while (p >= prog && *p != '/' && *p != '\\') p--;
12117 if(p == prog && *p == '"') p++;
12118 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12119 memcpy(buf, p, q - p);
12120 buf[q - p] = NULLCHAR;
12128 TimeControlTagValue()
12131 if (!appData.clockMode) {
12133 } else if (movesPerSession > 0) {
12134 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12135 } else if (timeIncrement == 0) {
12136 sprintf(buf, "%ld", timeControl/1000);
12138 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12140 return StrSave(buf);
12146 /* This routine is used only for certain modes */
12147 VariantClass v = gameInfo.variant;
12148 ClearGameInfo(&gameInfo);
12149 gameInfo.variant = v;
12151 switch (gameMode) {
12152 case MachinePlaysWhite:
12153 gameInfo.event = StrSave( appData.pgnEventHeader );
12154 gameInfo.site = StrSave(HostName());
12155 gameInfo.date = PGNDate();
12156 gameInfo.round = StrSave("-");
12157 gameInfo.white = StrSave(first.tidy);
12158 gameInfo.black = StrSave(UserName());
12159 gameInfo.timeControl = TimeControlTagValue();
12162 case MachinePlaysBlack:
12163 gameInfo.event = StrSave( appData.pgnEventHeader );
12164 gameInfo.site = StrSave(HostName());
12165 gameInfo.date = PGNDate();
12166 gameInfo.round = StrSave("-");
12167 gameInfo.white = StrSave(UserName());
12168 gameInfo.black = StrSave(first.tidy);
12169 gameInfo.timeControl = TimeControlTagValue();
12172 case TwoMachinesPlay:
12173 gameInfo.event = StrSave( appData.pgnEventHeader );
12174 gameInfo.site = StrSave(HostName());
12175 gameInfo.date = PGNDate();
12176 if (matchGame > 0) {
12178 sprintf(buf, "%d", matchGame);
12179 gameInfo.round = StrSave(buf);
12181 gameInfo.round = StrSave("-");
12183 if (first.twoMachinesColor[0] == 'w') {
12184 gameInfo.white = StrSave(first.tidy);
12185 gameInfo.black = StrSave(second.tidy);
12187 gameInfo.white = StrSave(second.tidy);
12188 gameInfo.black = StrSave(first.tidy);
12190 gameInfo.timeControl = TimeControlTagValue();
12194 gameInfo.event = StrSave("Edited game");
12195 gameInfo.site = StrSave(HostName());
12196 gameInfo.date = PGNDate();
12197 gameInfo.round = StrSave("-");
12198 gameInfo.white = StrSave("-");
12199 gameInfo.black = StrSave("-");
12203 gameInfo.event = StrSave("Edited position");
12204 gameInfo.site = StrSave(HostName());
12205 gameInfo.date = PGNDate();
12206 gameInfo.round = StrSave("-");
12207 gameInfo.white = StrSave("-");
12208 gameInfo.black = StrSave("-");
12211 case IcsPlayingWhite:
12212 case IcsPlayingBlack:
12217 case PlayFromGameFile:
12218 gameInfo.event = StrSave("Game from non-PGN file");
12219 gameInfo.site = StrSave(HostName());
12220 gameInfo.date = PGNDate();
12221 gameInfo.round = StrSave("-");
12222 gameInfo.white = StrSave("?");
12223 gameInfo.black = StrSave("?");
12232 ReplaceComment(index, text)
12238 while (*text == '\n') text++;
12239 len = strlen(text);
12240 while (len > 0 && text[len - 1] == '\n') len--;
12242 if (commentList[index] != NULL)
12243 free(commentList[index]);
12246 commentList[index] = NULL;
12249 commentList[index] = (char *) malloc(len + 2);
12250 strncpy(commentList[index], text, len);
12251 commentList[index][len] = '\n';
12252 commentList[index][len + 1] = NULLCHAR;
12265 if (ch == '\r') continue;
12267 } while (ch != '\0');
12271 AppendComment(index, text)
12278 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12281 while (*text == '\n') text++;
12282 len = strlen(text);
12283 while (len > 0 && text[len - 1] == '\n') len--;
12285 if (len == 0) return;
12287 if (commentList[index] != NULL) {
12288 old = commentList[index];
12289 oldlen = strlen(old);
12290 commentList[index] = (char *) malloc(oldlen + len + 2);
12291 strcpy(commentList[index], old);
12293 strncpy(&commentList[index][oldlen], text, len);
12294 commentList[index][oldlen + len] = '\n';
12295 commentList[index][oldlen + len + 1] = NULLCHAR;
12297 commentList[index] = (char *) malloc(len + 2);
12298 strncpy(commentList[index], text, len);
12299 commentList[index][len] = '\n';
12300 commentList[index][len + 1] = NULLCHAR;
12304 static char * FindStr( char * text, char * sub_text )
12306 char * result = strstr( text, sub_text );
12308 if( result != NULL ) {
12309 result += strlen( sub_text );
12315 /* [AS] Try to extract PV info from PGN comment */
12316 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12317 char *GetInfoFromComment( int index, char * text )
12321 if( text != NULL && index > 0 ) {
12324 int time = -1, sec = 0, deci;
12325 char * s_eval = FindStr( text, "[%eval " );
12326 char * s_emt = FindStr( text, "[%emt " );
12328 if( s_eval != NULL || s_emt != NULL ) {
12332 if( s_eval != NULL ) {
12333 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12337 if( delim != ']' ) {
12342 if( s_emt != NULL ) {
12346 /* We expect something like: [+|-]nnn.nn/dd */
12349 sep = strchr( text, '/' );
12350 if( sep == NULL || sep < (text+4) ) {
12354 time = -1; sec = -1; deci = -1;
12355 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12356 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12357 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12358 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12362 if( score_lo < 0 || score_lo >= 100 ) {
12366 if(sec >= 0) time = 600*time + 10*sec; else
12367 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12369 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12371 /* [HGM] PV time: now locate end of PV info */
12372 while( *++sep >= '0' && *sep <= '9'); // strip depth
12374 while( *++sep >= '0' && *sep <= '9'); // strip time
12376 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12378 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12379 while(*sep == ' ') sep++;
12390 pvInfoList[index-1].depth = depth;
12391 pvInfoList[index-1].score = score;
12392 pvInfoList[index-1].time = 10*time; // centi-sec
12398 SendToProgram(message, cps)
12400 ChessProgramState *cps;
12402 int count, outCount, error;
12405 if (cps->pr == NULL) return;
12408 if (appData.debugMode) {
12411 fprintf(debugFP, "%ld >%-6s: %s",
12412 SubtractTimeMarks(&now, &programStartTime),
12413 cps->which, message);
12416 count = strlen(message);
12417 outCount = OutputToProcess(cps->pr, message, count, &error);
12418 if (outCount < count && !exiting
12419 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12420 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12421 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12422 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12423 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12424 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12426 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12428 gameInfo.resultDetails = StrSave(buf);
12430 DisplayFatalError(buf, error, 1);
12435 ReceiveFromProgram(isr, closure, message, count, error)
12436 InputSourceRef isr;
12444 ChessProgramState *cps = (ChessProgramState *)closure;
12446 if (isr != cps->isr) return; /* Killed intentionally */
12450 _("Error: %s chess program (%s) exited unexpectedly"),
12451 cps->which, cps->program);
12452 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12453 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12454 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12455 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12457 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12459 gameInfo.resultDetails = StrSave(buf);
12461 RemoveInputSource(cps->isr);
12462 DisplayFatalError(buf, 0, 1);
12465 _("Error reading from %s chess program (%s)"),
12466 cps->which, cps->program);
12467 RemoveInputSource(cps->isr);
12469 /* [AS] Program is misbehaving badly... kill it */
12470 if( count == -2 ) {
12471 DestroyChildProcess( cps->pr, 9 );
12475 DisplayFatalError(buf, error, 1);
12480 if ((end_str = strchr(message, '\r')) != NULL)
12481 *end_str = NULLCHAR;
12482 if ((end_str = strchr(message, '\n')) != NULL)
12483 *end_str = NULLCHAR;
12485 if (appData.debugMode) {
12486 TimeMark now; int print = 1;
12487 char *quote = ""; char c; int i;
12489 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12490 char start = message[0];
12491 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12492 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12493 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12494 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12495 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12496 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12497 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12498 sscanf(message, "pong %c", &c)!=1 && start != '#')
12499 { quote = "# "; print = (appData.engineComments == 2); }
12500 message[0] = start; // restore original message
12504 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12505 SubtractTimeMarks(&now, &programStartTime), cps->which,
12511 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12512 if (appData.icsEngineAnalyze) {
12513 if (strstr(message, "whisper") != NULL ||
12514 strstr(message, "kibitz") != NULL ||
12515 strstr(message, "tellics") != NULL) return;
12518 HandleMachineMove(message, cps);
12523 SendTimeControl(cps, mps, tc, inc, sd, st)
12524 ChessProgramState *cps;
12525 int mps, inc, sd, st;
12531 if( timeControl_2 > 0 ) {
12532 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12533 tc = timeControl_2;
12536 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12537 inc /= cps->timeOdds;
12538 st /= cps->timeOdds;
12540 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12543 /* Set exact time per move, normally using st command */
12544 if (cps->stKludge) {
12545 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12547 if (seconds == 0) {
12548 sprintf(buf, "level 1 %d\n", st/60);
12550 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12553 sprintf(buf, "st %d\n", st);
12556 /* Set conventional or incremental time control, using level command */
12557 if (seconds == 0) {
12558 /* Note old gnuchess bug -- minutes:seconds used to not work.
12559 Fixed in later versions, but still avoid :seconds
12560 when seconds is 0. */
12561 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12563 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12564 seconds, inc/1000);
12567 SendToProgram(buf, cps);
12569 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12570 /* Orthogonally, limit search to given depth */
12572 if (cps->sdKludge) {
12573 sprintf(buf, "depth\n%d\n", sd);
12575 sprintf(buf, "sd %d\n", sd);
12577 SendToProgram(buf, cps);
12580 if(cps->nps > 0) { /* [HGM] nps */
12581 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12583 sprintf(buf, "nps %d\n", cps->nps);
12584 SendToProgram(buf, cps);
12589 ChessProgramState *WhitePlayer()
12590 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12592 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12593 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12599 SendTimeRemaining(cps, machineWhite)
12600 ChessProgramState *cps;
12601 int /*boolean*/ machineWhite;
12603 char message[MSG_SIZ];
12606 /* Note: this routine must be called when the clocks are stopped
12607 or when they have *just* been set or switched; otherwise
12608 it will be off by the time since the current tick started.
12610 if (machineWhite) {
12611 time = whiteTimeRemaining / 10;
12612 otime = blackTimeRemaining / 10;
12614 time = blackTimeRemaining / 10;
12615 otime = whiteTimeRemaining / 10;
12617 /* [HGM] translate opponent's time by time-odds factor */
12618 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12619 if (appData.debugMode) {
12620 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12623 if (time <= 0) time = 1;
12624 if (otime <= 0) otime = 1;
12626 sprintf(message, "time %ld\n", time);
12627 SendToProgram(message, cps);
12629 sprintf(message, "otim %ld\n", otime);
12630 SendToProgram(message, cps);
12634 BoolFeature(p, name, loc, cps)
12638 ChessProgramState *cps;
12641 int len = strlen(name);
12643 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12645 sscanf(*p, "%d", &val);
12647 while (**p && **p != ' ') (*p)++;
12648 sprintf(buf, "accepted %s\n", name);
12649 SendToProgram(buf, cps);
12656 IntFeature(p, name, loc, cps)
12660 ChessProgramState *cps;
12663 int len = strlen(name);
12664 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12666 sscanf(*p, "%d", loc);
12667 while (**p && **p != ' ') (*p)++;
12668 sprintf(buf, "accepted %s\n", name);
12669 SendToProgram(buf, cps);
12676 StringFeature(p, name, loc, cps)
12680 ChessProgramState *cps;
12683 int len = strlen(name);
12684 if (strncmp((*p), name, len) == 0
12685 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12687 sscanf(*p, "%[^\"]", loc);
12688 while (**p && **p != '\"') (*p)++;
12689 if (**p == '\"') (*p)++;
12690 sprintf(buf, "accepted %s\n", name);
12691 SendToProgram(buf, cps);
12698 ParseOption(Option *opt, ChessProgramState *cps)
12699 // [HGM] options: process the string that defines an engine option, and determine
12700 // name, type, default value, and allowed value range
12702 char *p, *q, buf[MSG_SIZ];
12703 int n, min = (-1)<<31, max = 1<<31, def;
12705 if(p = strstr(opt->name, " -spin ")) {
12706 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12707 if(max < min) max = min; // enforce consistency
12708 if(def < min) def = min;
12709 if(def > max) def = max;
12714 } else if((p = strstr(opt->name, " -slider "))) {
12715 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12716 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12717 if(max < min) max = min; // enforce consistency
12718 if(def < min) def = min;
12719 if(def > max) def = max;
12723 opt->type = Spin; // Slider;
12724 } else if((p = strstr(opt->name, " -string "))) {
12725 opt->textValue = p+9;
12726 opt->type = TextBox;
12727 } else if((p = strstr(opt->name, " -file "))) {
12728 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12729 opt->textValue = p+7;
12730 opt->type = TextBox; // FileName;
12731 } else if((p = strstr(opt->name, " -path "))) {
12732 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12733 opt->textValue = p+7;
12734 opt->type = TextBox; // PathName;
12735 } else if(p = strstr(opt->name, " -check ")) {
12736 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12737 opt->value = (def != 0);
12738 opt->type = CheckBox;
12739 } else if(p = strstr(opt->name, " -combo ")) {
12740 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12741 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12742 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12743 opt->value = n = 0;
12744 while(q = StrStr(q, " /// ")) {
12745 n++; *q = 0; // count choices, and null-terminate each of them
12747 if(*q == '*') { // remember default, which is marked with * prefix
12751 cps->comboList[cps->comboCnt++] = q;
12753 cps->comboList[cps->comboCnt++] = NULL;
12755 opt->type = ComboBox;
12756 } else if(p = strstr(opt->name, " -button")) {
12757 opt->type = Button;
12758 } else if(p = strstr(opt->name, " -save")) {
12759 opt->type = SaveButton;
12760 } else return FALSE;
12761 *p = 0; // terminate option name
12762 // now look if the command-line options define a setting for this engine option.
12763 if(cps->optionSettings && cps->optionSettings[0])
12764 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12765 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12766 sprintf(buf, "option %s", p);
12767 if(p = strstr(buf, ",")) *p = 0;
12769 SendToProgram(buf, cps);
12775 FeatureDone(cps, val)
12776 ChessProgramState* cps;
12779 DelayedEventCallback cb = GetDelayedEvent();
12780 if ((cb == InitBackEnd3 && cps == &first) ||
12781 (cb == TwoMachinesEventIfReady && cps == &second)) {
12782 CancelDelayedEvent();
12783 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12785 cps->initDone = val;
12788 /* Parse feature command from engine */
12790 ParseFeatures(args, cps)
12792 ChessProgramState *cps;
12800 while (*p == ' ') p++;
12801 if (*p == NULLCHAR) return;
12803 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12804 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12805 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12806 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12807 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12808 if (BoolFeature(&p, "reuse", &val, cps)) {
12809 /* Engine can disable reuse, but can't enable it if user said no */
12810 if (!val) cps->reuse = FALSE;
12813 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12814 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12815 if (gameMode == TwoMachinesPlay) {
12816 DisplayTwoMachinesTitle();
12822 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12823 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12824 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12825 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12826 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12827 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12828 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12829 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12830 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12831 if (IntFeature(&p, "done", &val, cps)) {
12832 FeatureDone(cps, val);
12835 /* Added by Tord: */
12836 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12837 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12838 /* End of additions by Tord */
12840 /* [HGM] added features: */
12841 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12842 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12843 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12844 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12845 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12846 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12847 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12848 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12849 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12850 SendToProgram(buf, cps);
12853 if(cps->nrOptions >= MAX_OPTIONS) {
12855 sprintf(buf, "%s engine has too many options\n", cps->which);
12856 DisplayError(buf, 0);
12860 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12861 /* End of additions by HGM */
12863 /* unknown feature: complain and skip */
12865 while (*q && *q != '=') q++;
12866 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12867 SendToProgram(buf, cps);
12873 while (*p && *p != '\"') p++;
12874 if (*p == '\"') p++;
12876 while (*p && *p != ' ') p++;
12884 PeriodicUpdatesEvent(newState)
12887 if (newState == appData.periodicUpdates)
12890 appData.periodicUpdates=newState;
12892 /* Display type changes, so update it now */
12893 // DisplayAnalysis();
12895 /* Get the ball rolling again... */
12897 AnalysisPeriodicEvent(1);
12898 StartAnalysisClock();
12903 PonderNextMoveEvent(newState)
12906 if (newState == appData.ponderNextMove) return;
12907 if (gameMode == EditPosition) EditPositionDone(TRUE);
12909 SendToProgram("hard\n", &first);
12910 if (gameMode == TwoMachinesPlay) {
12911 SendToProgram("hard\n", &second);
12914 SendToProgram("easy\n", &first);
12915 thinkOutput[0] = NULLCHAR;
12916 if (gameMode == TwoMachinesPlay) {
12917 SendToProgram("easy\n", &second);
12920 appData.ponderNextMove = newState;
12924 NewSettingEvent(option, command, value)
12930 if (gameMode == EditPosition) EditPositionDone(TRUE);
12931 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12932 SendToProgram(buf, &first);
12933 if (gameMode == TwoMachinesPlay) {
12934 SendToProgram(buf, &second);
12939 ShowThinkingEvent()
12940 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12942 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12943 int newState = appData.showThinking
12944 // [HGM] thinking: other features now need thinking output as well
12945 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12947 if (oldState == newState) return;
12948 oldState = newState;
12949 if (gameMode == EditPosition) EditPositionDone(TRUE);
12951 SendToProgram("post\n", &first);
12952 if (gameMode == TwoMachinesPlay) {
12953 SendToProgram("post\n", &second);
12956 SendToProgram("nopost\n", &first);
12957 thinkOutput[0] = NULLCHAR;
12958 if (gameMode == TwoMachinesPlay) {
12959 SendToProgram("nopost\n", &second);
12962 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12966 AskQuestionEvent(title, question, replyPrefix, which)
12967 char *title; char *question; char *replyPrefix; char *which;
12969 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12970 if (pr == NoProc) return;
12971 AskQuestion(title, question, replyPrefix, pr);
12975 DisplayMove(moveNumber)
12978 char message[MSG_SIZ];
12980 char cpThinkOutput[MSG_SIZ];
12982 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12984 if (moveNumber == forwardMostMove - 1 ||
12985 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12987 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12989 if (strchr(cpThinkOutput, '\n')) {
12990 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12993 *cpThinkOutput = NULLCHAR;
12996 /* [AS] Hide thinking from human user */
12997 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12998 *cpThinkOutput = NULLCHAR;
12999 if( thinkOutput[0] != NULLCHAR ) {
13002 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13003 cpThinkOutput[i] = '.';
13005 cpThinkOutput[i] = NULLCHAR;
13006 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13010 if (moveNumber == forwardMostMove - 1 &&
13011 gameInfo.resultDetails != NULL) {
13012 if (gameInfo.resultDetails[0] == NULLCHAR) {
13013 sprintf(res, " %s", PGNResult(gameInfo.result));
13015 sprintf(res, " {%s} %s",
13016 gameInfo.resultDetails, PGNResult(gameInfo.result));
13022 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13023 DisplayMessage(res, cpThinkOutput);
13025 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13026 WhiteOnMove(moveNumber) ? " " : ".. ",
13027 parseList[moveNumber], res);
13028 DisplayMessage(message, cpThinkOutput);
13033 DisplayComment(moveNumber, text)
13037 char title[MSG_SIZ];
13038 char buf[8000]; // comment can be long!
13041 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13042 strcpy(title, "Comment");
13044 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13045 WhiteOnMove(moveNumber) ? " " : ".. ",
13046 parseList[moveNumber]);
13048 // [HGM] PV info: display PV info together with (or as) comment
13049 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13050 if(text == NULL) text = "";
13051 score = pvInfoList[moveNumber].score;
13052 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13053 depth, (pvInfoList[moveNumber].time+50)/100, text);
13056 if (text != NULL && (appData.autoDisplayComment || commentUp))
13057 CommentPopUp(title, text);
13060 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13061 * might be busy thinking or pondering. It can be omitted if your
13062 * gnuchess is configured to stop thinking immediately on any user
13063 * input. However, that gnuchess feature depends on the FIONREAD
13064 * ioctl, which does not work properly on some flavors of Unix.
13068 ChessProgramState *cps;
13071 if (!cps->useSigint) return;
13072 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13073 switch (gameMode) {
13074 case MachinePlaysWhite:
13075 case MachinePlaysBlack:
13076 case TwoMachinesPlay:
13077 case IcsPlayingWhite:
13078 case IcsPlayingBlack:
13081 /* Skip if we know it isn't thinking */
13082 if (!cps->maybeThinking) return;
13083 if (appData.debugMode)
13084 fprintf(debugFP, "Interrupting %s\n", cps->which);
13085 InterruptChildProcess(cps->pr);
13086 cps->maybeThinking = FALSE;
13091 #endif /*ATTENTION*/
13097 if (whiteTimeRemaining <= 0) {
13100 if (appData.icsActive) {
13101 if (appData.autoCallFlag &&
13102 gameMode == IcsPlayingBlack && !blackFlag) {
13103 SendToICS(ics_prefix);
13104 SendToICS("flag\n");
13108 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13110 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13111 if (appData.autoCallFlag) {
13112 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13119 if (blackTimeRemaining <= 0) {
13122 if (appData.icsActive) {
13123 if (appData.autoCallFlag &&
13124 gameMode == IcsPlayingWhite && !whiteFlag) {
13125 SendToICS(ics_prefix);
13126 SendToICS("flag\n");
13130 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13132 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13133 if (appData.autoCallFlag) {
13134 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13147 if (!appData.clockMode || appData.icsActive ||
13148 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13151 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13153 if ( !WhiteOnMove(forwardMostMove) )
13154 /* White made time control */
13155 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13156 /* [HGM] time odds: correct new time quota for time odds! */
13157 / WhitePlayer()->timeOdds;
13159 /* Black made time control */
13160 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13161 / WhitePlayer()->other->timeOdds;
13165 DisplayBothClocks()
13167 int wom = gameMode == EditPosition ?
13168 !blackPlaysFirst : WhiteOnMove(currentMove);
13169 DisplayWhiteClock(whiteTimeRemaining, wom);
13170 DisplayBlackClock(blackTimeRemaining, !wom);
13174 /* Timekeeping seems to be a portability nightmare. I think everyone
13175 has ftime(), but I'm really not sure, so I'm including some ifdefs
13176 to use other calls if you don't. Clocks will be less accurate if
13177 you have neither ftime nor gettimeofday.
13180 /* VS 2008 requires the #include outside of the function */
13181 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13182 #include <sys/timeb.h>
13185 /* Get the current time as a TimeMark */
13190 #if HAVE_GETTIMEOFDAY
13192 struct timeval timeVal;
13193 struct timezone timeZone;
13195 gettimeofday(&timeVal, &timeZone);
13196 tm->sec = (long) timeVal.tv_sec;
13197 tm->ms = (int) (timeVal.tv_usec / 1000L);
13199 #else /*!HAVE_GETTIMEOFDAY*/
13202 // include <sys/timeb.h> / moved to just above start of function
13203 struct timeb timeB;
13206 tm->sec = (long) timeB.time;
13207 tm->ms = (int) timeB.millitm;
13209 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13210 tm->sec = (long) time(NULL);
13216 /* Return the difference in milliseconds between two
13217 time marks. We assume the difference will fit in a long!
13220 SubtractTimeMarks(tm2, tm1)
13221 TimeMark *tm2, *tm1;
13223 return 1000L*(tm2->sec - tm1->sec) +
13224 (long) (tm2->ms - tm1->ms);
13229 * Code to manage the game clocks.
13231 * In tournament play, black starts the clock and then white makes a move.
13232 * We give the human user a slight advantage if he is playing white---the
13233 * clocks don't run until he makes his first move, so it takes zero time.
13234 * Also, we don't account for network lag, so we could get out of sync
13235 * with GNU Chess's clock -- but then, referees are always right.
13238 static TimeMark tickStartTM;
13239 static long intendedTickLength;
13242 NextTickLength(timeRemaining)
13243 long timeRemaining;
13245 long nominalTickLength, nextTickLength;
13247 if (timeRemaining > 0L && timeRemaining <= 10000L)
13248 nominalTickLength = 100L;
13250 nominalTickLength = 1000L;
13251 nextTickLength = timeRemaining % nominalTickLength;
13252 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13254 return nextTickLength;
13257 /* Adjust clock one minute up or down */
13259 AdjustClock(Boolean which, int dir)
13261 if(which) blackTimeRemaining += 60000*dir;
13262 else whiteTimeRemaining += 60000*dir;
13263 DisplayBothClocks();
13266 /* Stop clocks and reset to a fresh time control */
13270 (void) StopClockTimer();
13271 if (appData.icsActive) {
13272 whiteTimeRemaining = blackTimeRemaining = 0;
13273 } else { /* [HGM] correct new time quote for time odds */
13274 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13275 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13277 if (whiteFlag || blackFlag) {
13279 whiteFlag = blackFlag = FALSE;
13281 DisplayBothClocks();
13284 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13286 /* Decrement running clock by amount of time that has passed */
13290 long timeRemaining;
13291 long lastTickLength, fudge;
13294 if (!appData.clockMode) return;
13295 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13299 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13301 /* Fudge if we woke up a little too soon */
13302 fudge = intendedTickLength - lastTickLength;
13303 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13305 if (WhiteOnMove(forwardMostMove)) {
13306 if(whiteNPS >= 0) lastTickLength = 0;
13307 timeRemaining = whiteTimeRemaining -= lastTickLength;
13308 DisplayWhiteClock(whiteTimeRemaining - fudge,
13309 WhiteOnMove(currentMove));
13311 if(blackNPS >= 0) lastTickLength = 0;
13312 timeRemaining = blackTimeRemaining -= lastTickLength;
13313 DisplayBlackClock(blackTimeRemaining - fudge,
13314 !WhiteOnMove(currentMove));
13317 if (CheckFlags()) return;
13320 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13321 StartClockTimer(intendedTickLength);
13323 /* if the time remaining has fallen below the alarm threshold, sound the
13324 * alarm. if the alarm has sounded and (due to a takeback or time control
13325 * with increment) the time remaining has increased to a level above the
13326 * threshold, reset the alarm so it can sound again.
13329 if (appData.icsActive && appData.icsAlarm) {
13331 /* make sure we are dealing with the user's clock */
13332 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13333 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13336 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13337 alarmSounded = FALSE;
13338 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13340 alarmSounded = TRUE;
13346 /* A player has just moved, so stop the previously running
13347 clock and (if in clock mode) start the other one.
13348 We redisplay both clocks in case we're in ICS mode, because
13349 ICS gives us an update to both clocks after every move.
13350 Note that this routine is called *after* forwardMostMove
13351 is updated, so the last fractional tick must be subtracted
13352 from the color that is *not* on move now.
13357 long lastTickLength;
13359 int flagged = FALSE;
13363 if (StopClockTimer() && appData.clockMode) {
13364 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13365 if (WhiteOnMove(forwardMostMove)) {
13366 if(blackNPS >= 0) lastTickLength = 0;
13367 blackTimeRemaining -= lastTickLength;
13368 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13369 // if(pvInfoList[forwardMostMove-1].time == -1)
13370 pvInfoList[forwardMostMove-1].time = // use GUI time
13371 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13373 if(whiteNPS >= 0) lastTickLength = 0;
13374 whiteTimeRemaining -= lastTickLength;
13375 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13376 // if(pvInfoList[forwardMostMove-1].time == -1)
13377 pvInfoList[forwardMostMove-1].time =
13378 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13380 flagged = CheckFlags();
13382 CheckTimeControl();
13384 if (flagged || !appData.clockMode) return;
13386 switch (gameMode) {
13387 case MachinePlaysBlack:
13388 case MachinePlaysWhite:
13389 case BeginningOfGame:
13390 if (pausing) return;
13394 case PlayFromGameFile:
13403 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13404 whiteTimeRemaining : blackTimeRemaining);
13405 StartClockTimer(intendedTickLength);
13409 /* Stop both clocks */
13413 long lastTickLength;
13416 if (!StopClockTimer()) return;
13417 if (!appData.clockMode) return;
13421 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13422 if (WhiteOnMove(forwardMostMove)) {
13423 if(whiteNPS >= 0) lastTickLength = 0;
13424 whiteTimeRemaining -= lastTickLength;
13425 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13427 if(blackNPS >= 0) lastTickLength = 0;
13428 blackTimeRemaining -= lastTickLength;
13429 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13434 /* Start clock of player on move. Time may have been reset, so
13435 if clock is already running, stop and restart it. */
13439 (void) StopClockTimer(); /* in case it was running already */
13440 DisplayBothClocks();
13441 if (CheckFlags()) return;
13443 if (!appData.clockMode) return;
13444 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13446 GetTimeMark(&tickStartTM);
13447 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13448 whiteTimeRemaining : blackTimeRemaining);
13450 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13451 whiteNPS = blackNPS = -1;
13452 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13453 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13454 whiteNPS = first.nps;
13455 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13456 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13457 blackNPS = first.nps;
13458 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13459 whiteNPS = second.nps;
13460 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13461 blackNPS = second.nps;
13462 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13464 StartClockTimer(intendedTickLength);
13471 long second, minute, hour, day;
13473 static char buf[32];
13475 if (ms > 0 && ms <= 9900) {
13476 /* convert milliseconds to tenths, rounding up */
13477 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13479 sprintf(buf, " %03.1f ", tenths/10.0);
13483 /* convert milliseconds to seconds, rounding up */
13484 /* use floating point to avoid strangeness of integer division
13485 with negative dividends on many machines */
13486 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13493 day = second / (60 * 60 * 24);
13494 second = second % (60 * 60 * 24);
13495 hour = second / (60 * 60);
13496 second = second % (60 * 60);
13497 minute = second / 60;
13498 second = second % 60;
13501 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13502 sign, day, hour, minute, second);
13504 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13506 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13513 * This is necessary because some C libraries aren't ANSI C compliant yet.
13516 StrStr(string, match)
13517 char *string, *match;
13521 length = strlen(match);
13523 for (i = strlen(string) - length; i >= 0; i--, string++)
13524 if (!strncmp(match, string, length))
13531 StrCaseStr(string, match)
13532 char *string, *match;
13536 length = strlen(match);
13538 for (i = strlen(string) - length; i >= 0; i--, string++) {
13539 for (j = 0; j < length; j++) {
13540 if (ToLower(match[j]) != ToLower(string[j]))
13543 if (j == length) return string;
13557 c1 = ToLower(*s1++);
13558 c2 = ToLower(*s2++);
13559 if (c1 > c2) return 1;
13560 if (c1 < c2) return -1;
13561 if (c1 == NULLCHAR) return 0;
13570 return isupper(c) ? tolower(c) : c;
13578 return islower(c) ? toupper(c) : c;
13580 #endif /* !_amigados */
13588 if ((ret = (char *) malloc(strlen(s) + 1))) {
13595 StrSavePtr(s, savePtr)
13596 char *s, **savePtr;
13601 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13602 strcpy(*savePtr, s);
13614 clock = time((time_t *)NULL);
13615 tm = localtime(&clock);
13616 sprintf(buf, "%04d.%02d.%02d",
13617 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13618 return StrSave(buf);
13623 PositionToFEN(move, overrideCastling)
13625 char *overrideCastling;
13627 int i, j, fromX, fromY, toX, toY;
13634 whiteToPlay = (gameMode == EditPosition) ?
13635 !blackPlaysFirst : (move % 2 == 0);
13638 /* Piece placement data */
13639 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13641 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13642 if (boards[move][i][j] == EmptySquare) {
13644 } else { ChessSquare piece = boards[move][i][j];
13645 if (emptycount > 0) {
13646 if(emptycount<10) /* [HGM] can be >= 10 */
13647 *p++ = '0' + emptycount;
13648 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13651 if(PieceToChar(piece) == '+') {
13652 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13654 piece = (ChessSquare)(DEMOTED piece);
13656 *p++ = PieceToChar(piece);
13658 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13659 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13664 if (emptycount > 0) {
13665 if(emptycount<10) /* [HGM] can be >= 10 */
13666 *p++ = '0' + emptycount;
13667 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13674 /* [HGM] print Crazyhouse or Shogi holdings */
13675 if( gameInfo.holdingsWidth ) {
13676 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13678 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13679 piece = boards[move][i][BOARD_WIDTH-1];
13680 if( piece != EmptySquare )
13681 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13682 *p++ = PieceToChar(piece);
13684 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13685 piece = boards[move][BOARD_HEIGHT-i-1][0];
13686 if( piece != EmptySquare )
13687 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13688 *p++ = PieceToChar(piece);
13691 if( q == p ) *p++ = '-';
13697 *p++ = whiteToPlay ? 'w' : 'b';
13700 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13701 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13703 if(nrCastlingRights) {
13705 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13706 /* [HGM] write directly from rights */
13707 if(castlingRights[move][2] >= 0 &&
13708 castlingRights[move][0] >= 0 )
13709 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13710 if(castlingRights[move][2] >= 0 &&
13711 castlingRights[move][1] >= 0 )
13712 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13713 if(castlingRights[move][5] >= 0 &&
13714 castlingRights[move][3] >= 0 )
13715 *p++ = castlingRights[move][3] + AAA;
13716 if(castlingRights[move][5] >= 0 &&
13717 castlingRights[move][4] >= 0 )
13718 *p++ = castlingRights[move][4] + AAA;
13721 /* [HGM] write true castling rights */
13722 if( nrCastlingRights == 6 ) {
13723 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13724 castlingRights[move][2] >= 0 ) *p++ = 'K';
13725 if(castlingRights[move][1] == BOARD_LEFT &&
13726 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13727 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13728 castlingRights[move][5] >= 0 ) *p++ = 'k';
13729 if(castlingRights[move][4] == BOARD_LEFT &&
13730 castlingRights[move][5] >= 0 ) *p++ = 'q';
13733 if (q == p) *p++ = '-'; /* No castling rights */
13737 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13738 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13739 /* En passant target square */
13740 if (move > backwardMostMove) {
13741 fromX = moveList[move - 1][0] - AAA;
13742 fromY = moveList[move - 1][1] - ONE;
13743 toX = moveList[move - 1][2] - AAA;
13744 toY = moveList[move - 1][3] - ONE;
13745 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13746 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13747 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13749 /* 2-square pawn move just happened */
13751 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13755 } else if(move == backwardMostMove) {
13756 // [HGM] perhaps we should always do it like this, and forget the above?
13757 if(epStatus[move] >= 0) {
13758 *p++ = epStatus[move] + AAA;
13759 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13770 /* [HGM] find reversible plies */
13771 { int i = 0, j=move;
13773 if (appData.debugMode) { int k;
13774 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13775 for(k=backwardMostMove; k<=forwardMostMove; k++)
13776 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13780 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13781 if( j == backwardMostMove ) i += initialRulePlies;
13782 sprintf(p, "%d ", i);
13783 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13785 /* Fullmove number */
13786 sprintf(p, "%d", (move / 2) + 1);
13788 return StrSave(buf);
13792 ParseFEN(board, blackPlaysFirst, fen)
13794 int *blackPlaysFirst;
13804 /* [HGM] by default clear Crazyhouse holdings, if present */
13805 if(gameInfo.holdingsWidth) {
13806 for(i=0; i<BOARD_HEIGHT; i++) {
13807 board[i][0] = EmptySquare; /* black holdings */
13808 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13809 board[i][1] = (ChessSquare) 0; /* black counts */
13810 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13814 /* Piece placement data */
13815 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13818 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13819 if (*p == '/') p++;
13820 emptycount = gameInfo.boardWidth - j;
13821 while (emptycount--)
13822 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13824 #if(BOARD_SIZE >= 10)
13825 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13826 p++; emptycount=10;
13827 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13828 while (emptycount--)
13829 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13831 } else if (isdigit(*p)) {
13832 emptycount = *p++ - '0';
13833 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13834 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13835 while (emptycount--)
13836 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13837 } else if (*p == '+' || isalpha(*p)) {
13838 if (j >= gameInfo.boardWidth) return FALSE;
13840 piece = CharToPiece(*++p);
13841 if(piece == EmptySquare) return FALSE; /* unknown piece */
13842 piece = (ChessSquare) (PROMOTED piece ); p++;
13843 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13844 } else piece = CharToPiece(*p++);
13846 if(piece==EmptySquare) return FALSE; /* unknown piece */
13847 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13848 piece = (ChessSquare) (PROMOTED piece);
13849 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13852 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13858 while (*p == '/' || *p == ' ') p++;
13860 /* [HGM] look for Crazyhouse holdings here */
13861 while(*p==' ') p++;
13862 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13864 if(*p == '-' ) *p++; /* empty holdings */ else {
13865 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13866 /* if we would allow FEN reading to set board size, we would */
13867 /* have to add holdings and shift the board read so far here */
13868 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13870 if((int) piece >= (int) BlackPawn ) {
13871 i = (int)piece - (int)BlackPawn;
13872 i = PieceToNumber((ChessSquare)i);
13873 if( i >= gameInfo.holdingsSize ) return FALSE;
13874 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13875 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13877 i = (int)piece - (int)WhitePawn;
13878 i = PieceToNumber((ChessSquare)i);
13879 if( i >= gameInfo.holdingsSize ) return FALSE;
13880 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13881 board[i][BOARD_WIDTH-2]++; /* black holdings */
13885 if(*p == ']') *p++;
13888 while(*p == ' ') p++;
13893 *blackPlaysFirst = FALSE;
13896 *blackPlaysFirst = TRUE;
13902 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13903 /* return the extra info in global variiables */
13905 /* set defaults in case FEN is incomplete */
13906 FENepStatus = EP_UNKNOWN;
13907 for(i=0; i<nrCastlingRights; i++ ) {
13908 FENcastlingRights[i] =
13909 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13910 } /* assume possible unless obviously impossible */
13911 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13912 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13913 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13914 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13915 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13916 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13917 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13918 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13921 while(*p==' ') p++;
13922 if(nrCastlingRights) {
13923 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13924 /* castling indicator present, so default becomes no castlings */
13925 for(i=0; i<nrCastlingRights; i++ ) {
13926 FENcastlingRights[i] = -1;
13929 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13930 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13931 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13932 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13933 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13935 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13936 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13937 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13939 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13940 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13941 if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13942 && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13943 if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13944 && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13947 for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13948 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13949 FENcastlingRights[2] = whiteKingFile;
13952 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13953 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13954 FENcastlingRights[2] = whiteKingFile;
13957 for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13958 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13959 FENcastlingRights[5] = blackKingFile;
13962 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13963 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13964 FENcastlingRights[5] = blackKingFile;
13967 default: /* FRC castlings */
13968 if(c >= 'a') { /* black rights */
13969 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13970 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13971 if(i == BOARD_RGHT) break;
13972 FENcastlingRights[5] = i;
13974 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13975 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13977 FENcastlingRights[3] = c;
13979 FENcastlingRights[4] = c;
13980 } else { /* white rights */
13981 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13982 if(board[0][i] == WhiteKing) break;
13983 if(i == BOARD_RGHT) break;
13984 FENcastlingRights[2] = i;
13985 c -= AAA - 'a' + 'A';
13986 if(board[0][c] >= WhiteKing) break;
13988 FENcastlingRights[0] = c;
13990 FENcastlingRights[1] = c;
13994 for(i=0; i<nrCastlingRights; i++)
13995 if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
13996 if (appData.debugMode) {
13997 fprintf(debugFP, "FEN castling rights:");
13998 for(i=0; i<nrCastlingRights; i++)
13999 fprintf(debugFP, " %d", FENcastlingRights[i]);
14000 fprintf(debugFP, "\n");
14003 while(*p==' ') p++;
14006 /* read e.p. field in games that know e.p. capture */
14007 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14008 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14010 p++; FENepStatus = EP_NONE;
14012 char c = *p++ - AAA;
14014 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14015 if(*p >= '0' && *p <='9') *p++;
14021 if(sscanf(p, "%d", &i) == 1) {
14022 FENrulePlies = i; /* 50-move ply counter */
14023 /* (The move number is still ignored) */
14030 EditPositionPasteFEN(char *fen)
14033 Board initial_position;
14035 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14036 DisplayError(_("Bad FEN position in clipboard"), 0);
14039 int savedBlackPlaysFirst = blackPlaysFirst;
14040 EditPositionEvent();
14041 blackPlaysFirst = savedBlackPlaysFirst;
14042 CopyBoard(boards[0], initial_position);
14043 /* [HGM] copy FEN attributes as well */
14045 initialRulePlies = FENrulePlies;
14046 epStatus[0] = FENepStatus;
14047 for( i=0; i<nrCastlingRights; i++ )
14048 castlingRights[0][i] = FENcastlingRights[i];
14050 EditPositionDone(FALSE);
14051 DisplayBothClocks();
14052 DrawPosition(FALSE, boards[currentMove]);
14057 static char cseq[12] = "\\ ";
14059 Boolean set_cont_sequence(char *new_seq)
14064 // handle bad attempts to set the sequence
14066 return 0; // acceptable error - no debug
14068 len = strlen(new_seq);
14069 ret = (len > 0) && (len < sizeof(cseq));
14071 strcpy(cseq, new_seq);
14072 else if (appData.debugMode)
14073 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14078 reformat a source message so words don't cross the width boundary. internal
14079 newlines are not removed. returns the wrapped size (no null character unless
14080 included in source message). If dest is NULL, only calculate the size required
14081 for the dest buffer. lp argument indicats line position upon entry, and it's
14082 passed back upon exit.
14084 int wrap(char *dest, char *src, int count, int width, int *lp)
14086 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14088 cseq_len = strlen(cseq);
14089 old_line = line = *lp;
14090 ansi = len = clen = 0;
14092 for (i=0; i < count; i++)
14094 if (src[i] == '\033')
14097 // if we hit the width, back up
14098 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14100 // store i & len in case the word is too long
14101 old_i = i, old_len = len;
14103 // find the end of the last word
14104 while (i && src[i] != ' ' && src[i] != '\n')
14110 // word too long? restore i & len before splitting it
14111 if ((old_i-i+clen) >= width)
14118 if (i && src[i-1] == ' ')
14121 if (src[i] != ' ' && src[i] != '\n')
14128 // now append the newline and continuation sequence
14133 strncpy(dest+len, cseq, cseq_len);
14141 dest[len] = src[i];
14145 if (src[i] == '\n')
14150 if (dest && appData.debugMode)
14152 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14153 count, width, line, len, *lp);
14154 show_bytes(debugFP, src, count);
14155 fprintf(debugFP, "\ndest: ");
14156 show_bytes(debugFP, dest, len);
14157 fprintf(debugFP, "\n");
14159 *lp = dest ? line : old_line;