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 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,
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 StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char initialRights[BOARD_FILES];
446 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int initialRulePlies, FENrulePlies;
448 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
451 int mute; // mute all sounds
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((Boolean annotate));
465 void CleanupTail P((void));
467 ChessSquare FIDEArray[2][BOARD_FILES] = {
468 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471 BlackKing, BlackBishop, BlackKnight, BlackRook }
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackKing, BlackKnight, BlackRook }
481 ChessSquare KnightmateArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484 { BlackRook, BlackMan, BlackBishop, BlackQueen,
485 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492 BlackKing, BlackBishop, BlackKnight, BlackRook }
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
522 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
527 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
529 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
534 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
536 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
542 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
544 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
547 #define GothicArray CapablancaArray
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
553 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
555 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
558 #define FalconArray CapablancaArray
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
580 Board initialPosition;
583 /* Convert str to a rating. Checks for special cases of "----",
585 "++++", etc. Also strips ()'s */
587 string_to_rating(str)
590 while(*str && !isdigit(*str)) ++str;
592 return 0; /* One of the special "no rating" cases */
600 /* Init programStats */
601 programStats.movelist[0] = 0;
602 programStats.depth = 0;
603 programStats.nr_moves = 0;
604 programStats.moves_left = 0;
605 programStats.nodes = 0;
606 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
607 programStats.score = 0;
608 programStats.got_only_move = 0;
609 programStats.got_fail = 0;
610 programStats.line_is_book = 0;
616 int matched, min, sec;
618 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
620 GetTimeMark(&programStartTime);
621 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
624 programStats.ok_to_send = 1;
625 programStats.seen_stat = 0;
628 * Initialize game list
634 * Internet chess server status
636 if (appData.icsActive) {
637 appData.matchMode = FALSE;
638 appData.matchGames = 0;
640 appData.noChessProgram = !appData.zippyPlay;
642 appData.zippyPlay = FALSE;
643 appData.zippyTalk = FALSE;
644 appData.noChessProgram = TRUE;
646 if (*appData.icsHelper != NULLCHAR) {
647 appData.useTelnet = TRUE;
648 appData.telnetProgram = appData.icsHelper;
651 appData.zippyTalk = appData.zippyPlay = FALSE;
654 /* [AS] Initialize pv info list [HGM] and game state */
658 for( i=0; i<=framePtr; i++ ) {
659 pvInfoList[i].depth = -1;
660 boards[i][EP_STATUS] = EP_NONE;
661 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
666 * Parse timeControl resource
668 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669 appData.movesPerSession)) {
671 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672 DisplayFatalError(buf, 0, 2);
676 * Parse searchTime resource
678 if (*appData.searchTime != NULLCHAR) {
679 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
681 searchTime = min * 60;
682 } else if (matched == 2) {
683 searchTime = min * 60 + sec;
686 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687 DisplayFatalError(buf, 0, 2);
691 /* [AS] Adjudication threshold */
692 adjudicateLossThreshold = appData.adjudicateLossThreshold;
694 first.which = "first";
695 second.which = "second";
696 first.maybeThinking = second.maybeThinking = FALSE;
697 first.pr = second.pr = NoProc;
698 first.isr = second.isr = NULL;
699 first.sendTime = second.sendTime = 2;
700 first.sendDrawOffers = 1;
701 if (appData.firstPlaysBlack) {
702 first.twoMachinesColor = "black\n";
703 second.twoMachinesColor = "white\n";
705 first.twoMachinesColor = "white\n";
706 second.twoMachinesColor = "black\n";
708 first.program = appData.firstChessProgram;
709 second.program = appData.secondChessProgram;
710 first.host = appData.firstHost;
711 second.host = appData.secondHost;
712 first.dir = appData.firstDirectory;
713 second.dir = appData.secondDirectory;
714 first.other = &second;
715 second.other = &first;
716 first.initString = appData.initString;
717 second.initString = appData.secondInitString;
718 first.computerString = appData.firstComputerString;
719 second.computerString = appData.secondComputerString;
720 first.useSigint = second.useSigint = TRUE;
721 first.useSigterm = second.useSigterm = TRUE;
722 first.reuse = appData.reuseFirst;
723 second.reuse = appData.reuseSecond;
724 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
725 second.nps = appData.secondNPS;
726 first.useSetboard = second.useSetboard = FALSE;
727 first.useSAN = second.useSAN = FALSE;
728 first.usePing = second.usePing = FALSE;
729 first.lastPing = second.lastPing = 0;
730 first.lastPong = second.lastPong = 0;
731 first.usePlayother = second.usePlayother = FALSE;
732 first.useColors = second.useColors = TRUE;
733 first.useUsermove = second.useUsermove = FALSE;
734 first.sendICS = second.sendICS = FALSE;
735 first.sendName = second.sendName = appData.icsActive;
736 first.sdKludge = second.sdKludge = FALSE;
737 first.stKludge = second.stKludge = FALSE;
738 TidyProgramName(first.program, first.host, first.tidy);
739 TidyProgramName(second.program, second.host, second.tidy);
740 first.matchWins = second.matchWins = 0;
741 strcpy(first.variants, appData.variant);
742 strcpy(second.variants, appData.variant);
743 first.analysisSupport = second.analysisSupport = 2; /* detect */
744 first.analyzing = second.analyzing = FALSE;
745 first.initDone = second.initDone = FALSE;
747 /* New features added by Tord: */
748 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750 /* End of new features added by Tord. */
751 first.fenOverride = appData.fenOverride1;
752 second.fenOverride = appData.fenOverride2;
754 /* [HGM] time odds: set factor for each machine */
755 first.timeOdds = appData.firstTimeOdds;
756 second.timeOdds = appData.secondTimeOdds;
758 if(appData.timeOddsMode) {
759 norm = first.timeOdds;
760 if(norm > second.timeOdds) norm = second.timeOdds;
762 first.timeOdds /= norm;
763 second.timeOdds /= norm;
766 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767 first.accumulateTC = appData.firstAccumulateTC;
768 second.accumulateTC = appData.secondAccumulateTC;
769 first.maxNrOfSessions = second.maxNrOfSessions = 1;
772 first.debug = second.debug = FALSE;
773 first.supportsNPS = second.supportsNPS = UNKNOWN;
776 first.optionSettings = appData.firstOptions;
777 second.optionSettings = appData.secondOptions;
779 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781 first.isUCI = appData.firstIsUCI; /* [AS] */
782 second.isUCI = appData.secondIsUCI; /* [AS] */
783 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
786 if (appData.firstProtocolVersion > PROTOVER ||
787 appData.firstProtocolVersion < 1) {
789 sprintf(buf, _("protocol version %d not supported"),
790 appData.firstProtocolVersion);
791 DisplayFatalError(buf, 0, 2);
793 first.protocolVersion = appData.firstProtocolVersion;
796 if (appData.secondProtocolVersion > PROTOVER ||
797 appData.secondProtocolVersion < 1) {
799 sprintf(buf, _("protocol version %d not supported"),
800 appData.secondProtocolVersion);
801 DisplayFatalError(buf, 0, 2);
803 second.protocolVersion = appData.secondProtocolVersion;
806 if (appData.icsActive) {
807 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
808 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810 appData.clockMode = FALSE;
811 first.sendTime = second.sendTime = 0;
815 /* Override some settings from environment variables, for backward
816 compatibility. Unfortunately it's not feasible to have the env
817 vars just set defaults, at least in xboard. Ugh.
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
824 if (appData.noChessProgram) {
825 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826 sprintf(programVersion, "%s", PACKAGE_STRING);
828 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
833 if (!appData.icsActive) {
835 /* Check for variants that are supported only in ICS mode,
836 or not at all. Some that are accepted here nevertheless
837 have bugs; see comments below.
839 VariantClass variant = StringToVariant(appData.variant);
841 case VariantBughouse: /* need four players and two boards */
842 case VariantKriegspiel: /* need to hide pieces and move details */
843 /* case VariantFischeRandom: (Fabien: moved below) */
844 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845 DisplayFatalError(buf, 0, 2);
849 case VariantLoadable:
859 sprintf(buf, _("Unknown variant name %s"), appData.variant);
860 DisplayFatalError(buf, 0, 2);
863 case VariantXiangqi: /* [HGM] repetition rules not implemented */
864 case VariantFairy: /* [HGM] TestLegality definitely off! */
865 case VariantGothic: /* [HGM] should work */
866 case VariantCapablanca: /* [HGM] should work */
867 case VariantCourier: /* [HGM] initial forced moves not implemented */
868 case VariantShogi: /* [HGM] drops not tested for legality */
869 case VariantKnightmate: /* [HGM] should work */
870 case VariantCylinder: /* [HGM] untested */
871 case VariantFalcon: /* [HGM] untested */
872 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873 offboard interposition not understood */
874 case VariantNormal: /* definitely works! */
875 case VariantWildCastle: /* pieces not automatically shuffled */
876 case VariantNoCastle: /* pieces not automatically shuffled */
877 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878 case VariantLosers: /* should work except for win condition,
879 and doesn't know captures are mandatory */
880 case VariantSuicide: /* should work except for win condition,
881 and doesn't know captures are mandatory */
882 case VariantGiveaway: /* should work except for win condition,
883 and doesn't know captures are mandatory */
884 case VariantTwoKings: /* should work */
885 case VariantAtomic: /* should work except for win condition */
886 case Variant3Check: /* should work except for win condition */
887 case VariantShatranj: /* should work except for all win conditions */
888 case VariantBerolina: /* might work if TestLegality is off */
889 case VariantCapaRandom: /* should work */
890 case VariantJanus: /* should work */
891 case VariantSuper: /* experimental */
892 case VariantGreat: /* experimental, requires legality testing to be off */
897 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
898 InitEngineUCI( installDir, &second );
901 int NextIntegerFromString( char ** str, long * value )
906 while( *s == ' ' || *s == '\t' ) {
912 if( *s >= '0' && *s <= '9' ) {
913 while( *s >= '0' && *s <= '9' ) {
914 *value = *value * 10 + (*s - '0');
926 int NextTimeControlFromString( char ** str, long * value )
929 int result = NextIntegerFromString( str, &temp );
932 *value = temp * 60; /* Minutes */
935 result = NextIntegerFromString( str, &temp );
936 *value += temp; /* Seconds */
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 { /* [HGM] routine added to read '+moves/time' for secondary time control */
945 int result = -1; long temp, temp2;
947 if(**str != '+') return -1; // old params remain in force!
949 if( NextTimeControlFromString( str, &temp ) ) return -1;
952 /* time only: incremental or sudden-death time control */
953 if(**str == '+') { /* increment follows; read it */
955 if(result = NextIntegerFromString( str, &temp2)) return -1;
958 *moves = 0; *tc = temp * 1000;
960 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
962 (*str)++; /* classical time control */
963 result = NextTimeControlFromString( str, &temp2);
972 int GetTimeQuota(int movenr)
973 { /* [HGM] get time to add from the multi-session time-control string */
974 int moves=1; /* kludge to force reading of first session */
975 long time, increment;
976 char *s = fullTimeControlString;
978 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
980 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982 if(movenr == -1) return time; /* last move before new session */
983 if(!moves) return increment; /* current session is incremental */
984 if(movenr >= 0) movenr -= moves; /* we already finished this session */
985 } while(movenr >= -1); /* try again for next session */
987 return 0; // no new time quota on this move
991 ParseTimeControl(tc, ti, mps)
1000 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1003 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004 else sprintf(buf, "+%s+%d", tc, ti);
1007 sprintf(buf, "+%d/%s", mps, tc);
1008 else sprintf(buf, "+%s", tc);
1010 fullTimeControlString = StrSave(buf);
1012 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1017 /* Parse second time control */
1020 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1028 timeControl_2 = tc2 * 1000;
1038 timeControl = tc1 * 1000;
1041 timeIncrement = ti * 1000; /* convert to ms */
1042 movesPerSession = 0;
1045 movesPerSession = mps;
1053 if (appData.debugMode) {
1054 fprintf(debugFP, "%s\n", programVersion);
1057 set_cont_sequence(appData.wrapContSeq);
1058 if (appData.matchGames > 0) {
1059 appData.matchMode = TRUE;
1060 } else if (appData.matchMode) {
1061 appData.matchGames = 1;
1063 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064 appData.matchGames = appData.sameColorGames;
1065 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1070 if (appData.noChessProgram || first.protocolVersion == 1) {
1073 /* kludge: allow timeout for initial "feature" commands */
1075 DisplayMessage("", _("Starting chess program"));
1076 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1081 InitBackEnd3 P((void))
1083 GameMode initialMode;
1087 InitChessProgram(&first, startedFromSetupPosition);
1090 if (appData.icsActive) {
1092 /* [DM] Make a console window if needed [HGM] merged ifs */
1097 if (*appData.icsCommPort != NULLCHAR) {
1098 sprintf(buf, _("Could not open comm port %s"),
1099 appData.icsCommPort);
1101 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1102 appData.icsHost, appData.icsPort);
1104 DisplayFatalError(buf, err, 1);
1109 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1111 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112 } else if (appData.noChessProgram) {
1118 if (*appData.cmailGameName != NULLCHAR) {
1120 OpenLoopback(&cmailPR);
1122 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1126 DisplayMessage("", "");
1127 if (StrCaseCmp(appData.initialMode, "") == 0) {
1128 initialMode = BeginningOfGame;
1129 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130 initialMode = TwoMachinesPlay;
1131 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132 initialMode = AnalyzeFile;
1133 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134 initialMode = AnalyzeMode;
1135 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136 initialMode = MachinePlaysWhite;
1137 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138 initialMode = MachinePlaysBlack;
1139 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140 initialMode = EditGame;
1141 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142 initialMode = EditPosition;
1143 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144 initialMode = Training;
1146 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147 DisplayFatalError(buf, 0, 2);
1151 if (appData.matchMode) {
1152 /* Set up machine vs. machine match */
1153 if (appData.noChessProgram) {
1154 DisplayFatalError(_("Can't have a match with no chess programs"),
1160 if (*appData.loadGameFile != NULLCHAR) {
1161 int index = appData.loadGameIndex; // [HGM] autoinc
1162 if(index<0) lastIndex = index = 1;
1163 if (!LoadGameFromFile(appData.loadGameFile,
1165 appData.loadGameFile, FALSE)) {
1166 DisplayFatalError(_("Bad game file"), 0, 1);
1169 } else if (*appData.loadPositionFile != NULLCHAR) {
1170 int index = appData.loadPositionIndex; // [HGM] autoinc
1171 if(index<0) lastIndex = index = 1;
1172 if (!LoadPositionFromFile(appData.loadPositionFile,
1174 appData.loadPositionFile)) {
1175 DisplayFatalError(_("Bad position file"), 0, 1);
1180 } else if (*appData.cmailGameName != NULLCHAR) {
1181 /* Set up cmail mode */
1182 ReloadCmailMsgEvent(TRUE);
1184 /* Set up other modes */
1185 if (initialMode == AnalyzeFile) {
1186 if (*appData.loadGameFile == NULLCHAR) {
1187 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1191 if (*appData.loadGameFile != NULLCHAR) {
1192 (void) LoadGameFromFile(appData.loadGameFile,
1193 appData.loadGameIndex,
1194 appData.loadGameFile, TRUE);
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
1196 (void) LoadPositionFromFile(appData.loadPositionFile,
1197 appData.loadPositionIndex,
1198 appData.loadPositionFile);
1199 /* [HGM] try to make self-starting even after FEN load */
1200 /* to allow automatic setup of fairy variants with wtm */
1201 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202 gameMode = BeginningOfGame;
1203 setboardSpoiledMachineBlack = 1;
1205 /* [HGM] loadPos: make that every new game uses the setup */
1206 /* from file as long as we do not switch variant */
1207 if(!blackPlaysFirst) {
1208 startedFromPositionFile = TRUE;
1209 CopyBoard(filePosition, boards[0]);
1212 if (initialMode == AnalyzeMode) {
1213 if (appData.noChessProgram) {
1214 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217 if (appData.icsActive) {
1218 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1222 } else if (initialMode == AnalyzeFile) {
1223 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224 ShowThinkingEvent();
1226 AnalysisPeriodicEvent(1);
1227 } else if (initialMode == MachinePlaysWhite) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1238 MachineWhiteEvent();
1239 } else if (initialMode == MachinePlaysBlack) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1250 MachineBlackEvent();
1251 } else if (initialMode == TwoMachinesPlay) {
1252 if (appData.noChessProgram) {
1253 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1257 if (appData.icsActive) {
1258 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1263 } else if (initialMode == EditGame) {
1265 } else if (initialMode == EditPosition) {
1266 EditPositionEvent();
1267 } else if (initialMode == Training) {
1268 if (*appData.loadGameFile == NULLCHAR) {
1269 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1278 * Establish will establish a contact to a remote host.port.
1279 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280 * used to talk to the host.
1281 * Returns 0 if okay, error code if not.
1288 if (*appData.icsCommPort != NULLCHAR) {
1289 /* Talk to the host through a serial comm port */
1290 return OpenCommPort(appData.icsCommPort, &icsPR);
1292 } else if (*appData.gateway != NULLCHAR) {
1293 if (*appData.remoteShell == NULLCHAR) {
1294 /* Use the rcmd protocol to run telnet program on a gateway host */
1295 snprintf(buf, sizeof(buf), "%s %s %s",
1296 appData.telnetProgram, appData.icsHost, appData.icsPort);
1297 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300 /* Use the rsh program to run telnet program on a gateway host */
1301 if (*appData.remoteUser == NULLCHAR) {
1302 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303 appData.gateway, appData.telnetProgram,
1304 appData.icsHost, appData.icsPort);
1306 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307 appData.remoteShell, appData.gateway,
1308 appData.remoteUser, appData.telnetProgram,
1309 appData.icsHost, appData.icsPort);
1311 return StartChildProcess(buf, "", &icsPR);
1314 } else if (appData.useTelnet) {
1315 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318 /* TCP socket interface differs somewhat between
1319 Unix and NT; handle details in the front end.
1321 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1326 show_bytes(fp, buf, count)
1332 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333 fprintf(fp, "\\%03o", *buf & 0xff);
1342 /* Returns an errno value */
1344 OutputMaybeTelnet(pr, message, count, outError)
1350 char buf[8192], *p, *q, *buflim;
1351 int left, newcount, outcount;
1353 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354 *appData.gateway != NULLCHAR) {
1355 if (appData.debugMode) {
1356 fprintf(debugFP, ">ICS: ");
1357 show_bytes(debugFP, message, count);
1358 fprintf(debugFP, "\n");
1360 return OutputToProcess(pr, message, count, outError);
1363 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1370 if (appData.debugMode) {
1371 fprintf(debugFP, ">ICS: ");
1372 show_bytes(debugFP, buf, newcount);
1373 fprintf(debugFP, "\n");
1375 outcount = OutputToProcess(pr, buf, newcount, outError);
1376 if (outcount < newcount) return -1; /* to be sure */
1383 } else if (((unsigned char) *p) == TN_IAC) {
1384 *q++ = (char) TN_IAC;
1391 if (appData.debugMode) {
1392 fprintf(debugFP, ">ICS: ");
1393 show_bytes(debugFP, buf, newcount);
1394 fprintf(debugFP, "\n");
1396 outcount = OutputToProcess(pr, buf, newcount, outError);
1397 if (outcount < newcount) return -1; /* to be sure */
1402 read_from_player(isr, closure, message, count, error)
1409 int outError, outCount;
1410 static int gotEof = 0;
1412 /* Pass data read from player on to ICS */
1415 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416 if (outCount < count) {
1417 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1419 } else if (count < 0) {
1420 RemoveInputSource(isr);
1421 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422 } else if (gotEof++ > 0) {
1423 RemoveInputSource(isr);
1424 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1430 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431 SendToICS("date\n");
1432 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1438 char buffer[MSG_SIZ];
1441 va_start(args, format);
1442 vsnprintf(buffer, sizeof(buffer), format, args);
1443 buffer[sizeof(buffer)-1] = '\0';
1452 int count, outCount, outError;
1454 if (icsPR == NULL) return;
1457 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458 if (outCount < count) {
1459 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463 /* This is used for sending logon scripts to the ICS. Sending
1464 without a delay causes problems when using timestamp on ICC
1465 (at least on my machine). */
1467 SendToICSDelayed(s,msdelay)
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 if (appData.debugMode) {
1477 fprintf(debugFP, ">ICS: ");
1478 show_bytes(debugFP, s, count);
1479 fprintf(debugFP, "\n");
1481 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1483 if (outCount < count) {
1484 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1489 /* Remove all highlighting escape sequences in s
1490 Also deletes any suffix starting with '('
1493 StripHighlightAndTitle(s)
1496 static char retbuf[MSG_SIZ];
1499 while (*s != NULLCHAR) {
1500 while (*s == '\033') {
1501 while (*s != NULLCHAR && !isalpha(*s)) s++;
1502 if (*s != NULLCHAR) s++;
1504 while (*s != NULLCHAR && *s != '\033') {
1505 if (*s == '(' || *s == '[') {
1516 /* Remove all highlighting escape sequences in s */
1521 static char retbuf[MSG_SIZ];
1524 while (*s != NULLCHAR) {
1525 while (*s == '\033') {
1526 while (*s != NULLCHAR && !isalpha(*s)) s++;
1527 if (*s != NULLCHAR) s++;
1529 while (*s != NULLCHAR && *s != '\033') {
1537 char *variantNames[] = VARIANT_NAMES;
1542 return variantNames[v];
1546 /* Identify a variant from the strings the chess servers use or the
1547 PGN Variant tag names we use. */
1554 VariantClass v = VariantNormal;
1555 int i, found = FALSE;
1560 /* [HGM] skip over optional board-size prefixes */
1561 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563 while( *e++ != '_');
1566 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1570 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571 if (StrCaseStr(e, variantNames[i])) {
1572 v = (VariantClass) i;
1579 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580 || StrCaseStr(e, "wild/fr")
1581 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582 v = VariantFischeRandom;
1583 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584 (i = 1, p = StrCaseStr(e, "w"))) {
1586 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1593 case 0: /* FICS only, actually */
1595 /* Castling legal even if K starts on d-file */
1596 v = VariantWildCastle;
1601 /* Castling illegal even if K & R happen to start in
1602 normal positions. */
1603 v = VariantNoCastle;
1616 /* Castling legal iff K & R start in normal positions */
1622 /* Special wilds for position setup; unclear what to do here */
1623 v = VariantLoadable;
1626 /* Bizarre ICC game */
1627 v = VariantTwoKings;
1630 v = VariantKriegspiel;
1636 v = VariantFischeRandom;
1639 v = VariantCrazyhouse;
1642 v = VariantBughouse;
1648 /* Not quite the same as FICS suicide! */
1649 v = VariantGiveaway;
1655 v = VariantShatranj;
1658 /* Temporary names for future ICC types. The name *will* change in
1659 the next xboard/WinBoard release after ICC defines it. */
1697 v = VariantCapablanca;
1700 v = VariantKnightmate;
1706 v = VariantCylinder;
1712 v = VariantCapaRandom;
1715 v = VariantBerolina;
1727 /* Found "wild" or "w" in the string but no number;
1728 must assume it's normal chess. */
1732 sprintf(buf, _("Unknown wild type %d"), wnum);
1733 DisplayError(buf, 0);
1739 if (appData.debugMode) {
1740 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741 e, wnum, VariantName(v));
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750 advance *index beyond it, and set leftover_start to the new value of
1751 *index; else return FALSE. If pattern contains the character '*', it
1752 matches any sequence of characters not containing '\r', '\n', or the
1753 character following the '*' (if any), and the matched sequence(s) are
1754 copied into star_match.
1757 looking_at(buf, index, pattern)
1762 char *bufp = &buf[*index], *patternp = pattern;
1764 char *matchp = star_match[0];
1767 if (*patternp == NULLCHAR) {
1768 *index = leftover_start = bufp - buf;
1772 if (*bufp == NULLCHAR) return FALSE;
1773 if (*patternp == '*') {
1774 if (*bufp == *(patternp + 1)) {
1776 matchp = star_match[++star_count];
1780 } else if (*bufp == '\n' || *bufp == '\r') {
1782 if (*patternp == NULLCHAR)
1787 *matchp++ = *bufp++;
1791 if (*patternp != *bufp) return FALSE;
1798 SendToPlayer(data, length)
1802 int error, outCount;
1803 outCount = OutputToProcess(NoProc, data, length, &error);
1804 if (outCount < length) {
1805 DisplayFatalError(_("Error writing to display"), error, 1);
1810 PackHolding(packed, holding)
1822 switch (runlength) {
1833 sprintf(q, "%d", runlength);
1845 /* Telnet protocol requests from the front end */
1847 TelnetRequest(ddww, option)
1848 unsigned char ddww, option;
1850 unsigned char msg[3];
1851 int outCount, outError;
1853 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1855 if (appData.debugMode) {
1856 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1872 sprintf(buf1, "%d", ddww);
1881 sprintf(buf2, "%d", option);
1884 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1889 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1891 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898 if (!appData.icsActive) return;
1899 TelnetRequest(TN_DO, TN_ECHO);
1905 if (!appData.icsActive) return;
1906 TelnetRequest(TN_DONT, TN_ECHO);
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1912 /* put the holdings sent to us by the server on the board holdings area */
1913 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917 if(gameInfo.holdingsWidth < 2) return;
1918 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919 return; // prevent overwriting by pre-board holdings
1921 if( (int)lowestPiece >= BlackPawn ) {
1924 holdingsStartRow = BOARD_HEIGHT-1;
1927 holdingsColumn = BOARD_WIDTH-1;
1928 countsColumn = BOARD_WIDTH-2;
1929 holdingsStartRow = 0;
1933 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934 board[i][holdingsColumn] = EmptySquare;
1935 board[i][countsColumn] = (ChessSquare) 0;
1937 while( (p=*holdings++) != NULLCHAR ) {
1938 piece = CharToPiece( ToUpper(p) );
1939 if(piece == EmptySquare) continue;
1940 /*j = (int) piece - (int) WhitePawn;*/
1941 j = PieceToNumber(piece);
1942 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943 if(j < 0) continue; /* should not happen */
1944 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946 board[holdingsStartRow+j*direction][countsColumn]++;
1952 VariantSwitch(Board board, VariantClass newVariant)
1954 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957 startedFromPositionFile = FALSE;
1958 if(gameInfo.variant == newVariant) return;
1960 /* [HGM] This routine is called each time an assignment is made to
1961 * gameInfo.variant during a game, to make sure the board sizes
1962 * are set to match the new variant. If that means adding or deleting
1963 * holdings, we shift the playing board accordingly
1964 * This kludge is needed because in ICS observe mode, we get boards
1965 * of an ongoing game without knowing the variant, and learn about the
1966 * latter only later. This can be because of the move list we requested,
1967 * in which case the game history is refilled from the beginning anyway,
1968 * but also when receiving holdings of a crazyhouse game. In the latter
1969 * case we want to add those holdings to the already received position.
1972 if (appData.debugMode) {
1973 fprintf(debugFP, "Switch board from %s to %s\n",
1974 VariantName(gameInfo.variant), VariantName(newVariant));
1975 setbuf(debugFP, NULL);
1977 shuffleOpenings = 0; /* [HGM] shuffle */
1978 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982 newWidth = 9; newHeight = 9;
1983 gameInfo.holdingsSize = 7;
1984 case VariantBughouse:
1985 case VariantCrazyhouse:
1986 newHoldingsWidth = 2; break;
1990 newHoldingsWidth = 2;
1991 gameInfo.holdingsSize = 8;
1994 case VariantCapablanca:
1995 case VariantCapaRandom:
1998 newHoldingsWidth = gameInfo.holdingsSize = 0;
2001 if(newWidth != gameInfo.boardWidth ||
2002 newHeight != gameInfo.boardHeight ||
2003 newHoldingsWidth != gameInfo.holdingsWidth ) {
2005 /* shift position to new playing area, if needed */
2006 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2007 for(i=0; i<BOARD_HEIGHT; i++)
2008 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2009 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011 for(i=0; i<newHeight; i++) {
2012 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2013 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2015 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2016 for(i=0; i<BOARD_HEIGHT; i++)
2017 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2018 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2021 gameInfo.boardWidth = newWidth;
2022 gameInfo.boardHeight = newHeight;
2023 gameInfo.holdingsWidth = newHoldingsWidth;
2024 gameInfo.variant = newVariant;
2025 InitDrawingSizes(-2, 0);
2026 } else gameInfo.variant = newVariant;
2027 CopyBoard(oldBoard, board); // remember correctly formatted board
2028 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2029 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2032 static int loggedOn = FALSE;
2034 /*-- Game start info cache: --*/
2036 char gs_kind[MSG_SIZ];
2037 static char player1Name[128] = "";
2038 static char player2Name[128] = "";
2039 static char cont_seq[] = "\n\\ ";
2040 static int player1Rating = -1;
2041 static int player2Rating = -1;
2042 /*----------------------------*/
2044 ColorClass curColor = ColorNormal;
2045 int suppressKibitz = 0;
2048 read_from_ics(isr, closure, data, count, error)
2055 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2056 #define STARTED_NONE 0
2057 #define STARTED_MOVES 1
2058 #define STARTED_BOARD 2
2059 #define STARTED_OBSERVE 3
2060 #define STARTED_HOLDINGS 4
2061 #define STARTED_CHATTER 5
2062 #define STARTED_COMMENT 6
2063 #define STARTED_MOVES_NOHIDE 7
2065 static int started = STARTED_NONE;
2066 static char parse[20000];
2067 static int parse_pos = 0;
2068 static char buf[BUF_SIZE + 1];
2069 static int firstTime = TRUE, intfSet = FALSE;
2070 static ColorClass prevColor = ColorNormal;
2071 static int savingComment = FALSE;
2072 static int cmatch = 0; // continuation sequence match
2079 int backup; /* [DM] For zippy color lines */
2081 char talker[MSG_SIZ]; // [HGM] chat
2084 if (appData.debugMode) {
2086 fprintf(debugFP, "<ICS: ");
2087 show_bytes(debugFP, data, count);
2088 fprintf(debugFP, "\n");
2092 if (appData.debugMode) { int f = forwardMostMove;
2093 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2094 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2095 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2098 /* If last read ended with a partial line that we couldn't parse,
2099 prepend it to the new read and try again. */
2100 if (leftover_len > 0) {
2101 for (i=0; i<leftover_len; i++)
2102 buf[i] = buf[leftover_start + i];
2105 /* copy new characters into the buffer */
2106 bp = buf + leftover_len;
2107 buf_len=leftover_len;
2108 for (i=0; i<count; i++)
2111 if (data[i] == '\r')
2114 // join lines split by ICS?
2115 if (!appData.noJoin)
2118 Joining just consists of finding matches against the
2119 continuation sequence, and discarding that sequence
2120 if found instead of copying it. So, until a match
2121 fails, there's nothing to do since it might be the
2122 complete sequence, and thus, something we don't want
2125 if (data[i] == cont_seq[cmatch])
2128 if (cmatch == strlen(cont_seq))
2130 cmatch = 0; // complete match. just reset the counter
2133 it's possible for the ICS to not include the space
2134 at the end of the last word, making our [correct]
2135 join operation fuse two separate words. the server
2136 does this when the space occurs at the width setting.
2138 if (!buf_len || buf[buf_len-1] != ' ')
2149 match failed, so we have to copy what matched before
2150 falling through and copying this character. In reality,
2151 this will only ever be just the newline character, but
2152 it doesn't hurt to be precise.
2154 strncpy(bp, cont_seq, cmatch);
2166 buf[buf_len] = NULLCHAR;
2167 next_out = leftover_len;
2171 while (i < buf_len) {
2172 /* Deal with part of the TELNET option negotiation
2173 protocol. We refuse to do anything beyond the
2174 defaults, except that we allow the WILL ECHO option,
2175 which ICS uses to turn off password echoing when we are
2176 directly connected to it. We reject this option
2177 if localLineEditing mode is on (always on in xboard)
2178 and we are talking to port 23, which might be a real
2179 telnet server that will try to keep WILL ECHO on permanently.
2181 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2182 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2183 unsigned char option;
2185 switch ((unsigned char) buf[++i]) {
2187 if (appData.debugMode)
2188 fprintf(debugFP, "\n<WILL ");
2189 switch (option = (unsigned char) buf[++i]) {
2191 if (appData.debugMode)
2192 fprintf(debugFP, "ECHO ");
2193 /* Reply only if this is a change, according
2194 to the protocol rules. */
2195 if (remoteEchoOption) break;
2196 if (appData.localLineEditing &&
2197 atoi(appData.icsPort) == TN_PORT) {
2198 TelnetRequest(TN_DONT, TN_ECHO);
2201 TelnetRequest(TN_DO, TN_ECHO);
2202 remoteEchoOption = TRUE;
2206 if (appData.debugMode)
2207 fprintf(debugFP, "%d ", option);
2208 /* Whatever this is, we don't want it. */
2209 TelnetRequest(TN_DONT, option);
2214 if (appData.debugMode)
2215 fprintf(debugFP, "\n<WONT ");
2216 switch (option = (unsigned char) buf[++i]) {
2218 if (appData.debugMode)
2219 fprintf(debugFP, "ECHO ");
2220 /* Reply only if this is a change, according
2221 to the protocol rules. */
2222 if (!remoteEchoOption) break;
2224 TelnetRequest(TN_DONT, TN_ECHO);
2225 remoteEchoOption = FALSE;
2228 if (appData.debugMode)
2229 fprintf(debugFP, "%d ", (unsigned char) option);
2230 /* Whatever this is, it must already be turned
2231 off, because we never agree to turn on
2232 anything non-default, so according to the
2233 protocol rules, we don't reply. */
2238 if (appData.debugMode)
2239 fprintf(debugFP, "\n<DO ");
2240 switch (option = (unsigned char) buf[++i]) {
2242 /* Whatever this is, we refuse to do it. */
2243 if (appData.debugMode)
2244 fprintf(debugFP, "%d ", option);
2245 TelnetRequest(TN_WONT, option);
2250 if (appData.debugMode)
2251 fprintf(debugFP, "\n<DONT ");
2252 switch (option = (unsigned char) buf[++i]) {
2254 if (appData.debugMode)
2255 fprintf(debugFP, "%d ", option);
2256 /* Whatever this is, we are already not doing
2257 it, because we never agree to do anything
2258 non-default, so according to the protocol
2259 rules, we don't reply. */
2264 if (appData.debugMode)
2265 fprintf(debugFP, "\n<IAC ");
2266 /* Doubled IAC; pass it through */
2270 if (appData.debugMode)
2271 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2272 /* Drop all other telnet commands on the floor */
2275 if (oldi > next_out)
2276 SendToPlayer(&buf[next_out], oldi - next_out);
2282 /* OK, this at least will *usually* work */
2283 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2287 if (loggedOn && !intfSet) {
2288 if (ics_type == ICS_ICC) {
2290 "/set-quietly interface %s\n/set-quietly style 12\n",
2292 } else if (ics_type == ICS_CHESSNET) {
2293 sprintf(str, "/style 12\n");
2295 strcpy(str, "alias $ @\n$set interface ");
2296 strcat(str, programVersion);
2297 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2299 strcat(str, "$iset nohighlight 1\n");
2301 strcat(str, "$iset lock 1\n$style 12\n");
2304 NotifyFrontendLogin();
2308 if (started == STARTED_COMMENT) {
2309 /* Accumulate characters in comment */
2310 parse[parse_pos++] = buf[i];
2311 if (buf[i] == '\n') {
2312 parse[parse_pos] = NULLCHAR;
2313 if(chattingPartner>=0) {
2315 sprintf(mess, "%s%s", talker, parse);
2316 OutputChatMessage(chattingPartner, mess);
2317 chattingPartner = -1;
2319 if(!suppressKibitz) // [HGM] kibitz
2320 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2321 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2322 int nrDigit = 0, nrAlph = 0, i;
2323 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2324 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2325 parse[parse_pos] = NULLCHAR;
2326 // try to be smart: if it does not look like search info, it should go to
2327 // ICS interaction window after all, not to engine-output window.
2328 for(i=0; i<parse_pos; i++) { // count letters and digits
2329 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2330 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2331 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2333 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2334 int depth=0; float score;
2335 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2336 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2337 pvInfoList[forwardMostMove-1].depth = depth;
2338 pvInfoList[forwardMostMove-1].score = 100*score;
2340 OutputKibitz(suppressKibitz, parse);
2343 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2344 SendToPlayer(tmp, strlen(tmp));
2347 started = STARTED_NONE;
2349 /* Don't match patterns against characters in chatter */
2354 if (started == STARTED_CHATTER) {
2355 if (buf[i] != '\n') {
2356 /* Don't match patterns against characters in chatter */
2360 started = STARTED_NONE;
2363 /* Kludge to deal with rcmd protocol */
2364 if (firstTime && looking_at(buf, &i, "\001*")) {
2365 DisplayFatalError(&buf[1], 0, 1);
2371 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2374 if (appData.debugMode)
2375 fprintf(debugFP, "ics_type %d\n", ics_type);
2378 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2379 ics_type = ICS_FICS;
2381 if (appData.debugMode)
2382 fprintf(debugFP, "ics_type %d\n", ics_type);
2385 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2386 ics_type = ICS_CHESSNET;
2388 if (appData.debugMode)
2389 fprintf(debugFP, "ics_type %d\n", ics_type);
2394 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2395 looking_at(buf, &i, "Logging you in as \"*\"") ||
2396 looking_at(buf, &i, "will be \"*\""))) {
2397 strcpy(ics_handle, star_match[0]);
2401 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2403 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2404 DisplayIcsInteractionTitle(buf);
2405 have_set_title = TRUE;
2408 /* skip finger notes */
2409 if (started == STARTED_NONE &&
2410 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2411 (buf[i] == '1' && buf[i+1] == '0')) &&
2412 buf[i+2] == ':' && buf[i+3] == ' ') {
2413 started = STARTED_CHATTER;
2418 /* skip formula vars */
2419 if (started == STARTED_NONE &&
2420 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2421 started = STARTED_CHATTER;
2427 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2428 if (appData.autoKibitz && started == STARTED_NONE &&
2429 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2430 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2431 if(looking_at(buf, &i, "* kibitzes: ") &&
2432 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2433 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2434 suppressKibitz = TRUE;
2435 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2436 && (gameMode == IcsPlayingWhite)) ||
2437 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2438 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2439 started = STARTED_CHATTER; // own kibitz we simply discard
2441 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2442 parse_pos = 0; parse[0] = NULLCHAR;
2443 savingComment = TRUE;
2444 suppressKibitz = gameMode != IcsObserving ? 2 :
2445 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2449 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2450 started = STARTED_CHATTER;
2451 suppressKibitz = TRUE;
2453 } // [HGM] kibitz: end of patch
2455 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2457 // [HGM] chat: intercept tells by users for which we have an open chat window
2459 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2460 looking_at(buf, &i, "* whispers:") ||
2461 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2462 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2464 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2465 chattingPartner = -1;
2467 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2468 for(p=0; p<MAX_CHAT; p++) {
2469 if(channel == atoi(chatPartner[p])) {
2470 talker[0] = '['; strcat(talker, "]");
2471 chattingPartner = p; break;
2474 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2475 for(p=0; p<MAX_CHAT; p++) {
2476 if(!strcmp("WHISPER", chatPartner[p])) {
2477 talker[0] = '['; strcat(talker, "]");
2478 chattingPartner = p; break;
2481 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2482 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2484 chattingPartner = p; break;
2486 if(chattingPartner<0) i = oldi; else {
2487 started = STARTED_COMMENT;
2488 parse_pos = 0; parse[0] = NULLCHAR;
2489 savingComment = TRUE;
2490 suppressKibitz = TRUE;
2492 } // [HGM] chat: end of patch
2494 if (appData.zippyTalk || appData.zippyPlay) {
2495 /* [DM] Backup address for color zippy lines */
2499 if (loggedOn == TRUE)
2500 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2501 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2503 if (ZippyControl(buf, &i) ||
2504 ZippyConverse(buf, &i) ||
2505 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2507 if (!appData.colorize) continue;
2511 } // [DM] 'else { ' deleted
2513 /* Regular tells and says */
2514 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2515 looking_at(buf, &i, "* (your partner) tells you: ") ||
2516 looking_at(buf, &i, "* says: ") ||
2517 /* Don't color "message" or "messages" output */
2518 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2519 looking_at(buf, &i, "*. * at *:*: ") ||
2520 looking_at(buf, &i, "--* (*:*): ") ||
2521 /* Message notifications (same color as tells) */
2522 looking_at(buf, &i, "* has left a message ") ||
2523 looking_at(buf, &i, "* just sent you a message:\n") ||
2524 /* Whispers and kibitzes */
2525 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2526 looking_at(buf, &i, "* kibitzes: ") ||
2528 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2530 if (tkind == 1 && strchr(star_match[0], ':')) {
2531 /* Avoid "tells you:" spoofs in channels */
2534 if (star_match[0][0] == NULLCHAR ||
2535 strchr(star_match[0], ' ') ||
2536 (tkind == 3 && strchr(star_match[1], ' '))) {
2537 /* Reject bogus matches */
2540 if (appData.colorize) {
2541 if (oldi > next_out) {
2542 SendToPlayer(&buf[next_out], oldi - next_out);
2547 Colorize(ColorTell, FALSE);
2548 curColor = ColorTell;
2551 Colorize(ColorKibitz, FALSE);
2552 curColor = ColorKibitz;
2555 p = strrchr(star_match[1], '(');
2562 Colorize(ColorChannel1, FALSE);
2563 curColor = ColorChannel1;
2565 Colorize(ColorChannel, FALSE);
2566 curColor = ColorChannel;
2570 curColor = ColorNormal;
2574 if (started == STARTED_NONE && appData.autoComment &&
2575 (gameMode == IcsObserving ||
2576 gameMode == IcsPlayingWhite ||
2577 gameMode == IcsPlayingBlack)) {
2578 parse_pos = i - oldi;
2579 memcpy(parse, &buf[oldi], parse_pos);
2580 parse[parse_pos] = NULLCHAR;
2581 started = STARTED_COMMENT;
2582 savingComment = TRUE;
2584 started = STARTED_CHATTER;
2585 savingComment = FALSE;
2592 if (looking_at(buf, &i, "* s-shouts: ") ||
2593 looking_at(buf, &i, "* c-shouts: ")) {
2594 if (appData.colorize) {
2595 if (oldi > next_out) {
2596 SendToPlayer(&buf[next_out], oldi - next_out);
2599 Colorize(ColorSShout, FALSE);
2600 curColor = ColorSShout;
2603 started = STARTED_CHATTER;
2607 if (looking_at(buf, &i, "--->")) {
2612 if (looking_at(buf, &i, "* shouts: ") ||
2613 looking_at(buf, &i, "--> ")) {
2614 if (appData.colorize) {
2615 if (oldi > next_out) {
2616 SendToPlayer(&buf[next_out], oldi - next_out);
2619 Colorize(ColorShout, FALSE);
2620 curColor = ColorShout;
2623 started = STARTED_CHATTER;
2627 if (looking_at( buf, &i, "Challenge:")) {
2628 if (appData.colorize) {
2629 if (oldi > next_out) {
2630 SendToPlayer(&buf[next_out], oldi - next_out);
2633 Colorize(ColorChallenge, FALSE);
2634 curColor = ColorChallenge;
2640 if (looking_at(buf, &i, "* offers you") ||
2641 looking_at(buf, &i, "* offers to be") ||
2642 looking_at(buf, &i, "* would like to") ||
2643 looking_at(buf, &i, "* requests to") ||
2644 looking_at(buf, &i, "Your opponent offers") ||
2645 looking_at(buf, &i, "Your opponent requests")) {
2647 if (appData.colorize) {
2648 if (oldi > next_out) {
2649 SendToPlayer(&buf[next_out], oldi - next_out);
2652 Colorize(ColorRequest, FALSE);
2653 curColor = ColorRequest;
2658 if (looking_at(buf, &i, "* (*) seeking")) {
2659 if (appData.colorize) {
2660 if (oldi > next_out) {
2661 SendToPlayer(&buf[next_out], oldi - next_out);
2664 Colorize(ColorSeek, FALSE);
2665 curColor = ColorSeek;
2670 if (looking_at(buf, &i, "\\ ")) {
2671 if (prevColor != ColorNormal) {
2672 if (oldi > next_out) {
2673 SendToPlayer(&buf[next_out], oldi - next_out);
2676 Colorize(prevColor, TRUE);
2677 curColor = prevColor;
2679 if (savingComment) {
2680 parse_pos = i - oldi;
2681 memcpy(parse, &buf[oldi], parse_pos);
2682 parse[parse_pos] = NULLCHAR;
2683 started = STARTED_COMMENT;
2685 started = STARTED_CHATTER;
2690 if (looking_at(buf, &i, "Black Strength :") ||
2691 looking_at(buf, &i, "<<< style 10 board >>>") ||
2692 looking_at(buf, &i, "<10>") ||
2693 looking_at(buf, &i, "#@#")) {
2694 /* Wrong board style */
2696 SendToICS(ics_prefix);
2697 SendToICS("set style 12\n");
2698 SendToICS(ics_prefix);
2699 SendToICS("refresh\n");
2703 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2705 have_sent_ICS_logon = 1;
2709 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2710 (looking_at(buf, &i, "\n<12> ") ||
2711 looking_at(buf, &i, "<12> "))) {
2713 if (oldi > next_out) {
2714 SendToPlayer(&buf[next_out], oldi - next_out);
2717 started = STARTED_BOARD;
2722 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2723 looking_at(buf, &i, "<b1> ")) {
2724 if (oldi > next_out) {
2725 SendToPlayer(&buf[next_out], oldi - next_out);
2728 started = STARTED_HOLDINGS;
2733 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2735 /* Header for a move list -- first line */
2737 switch (ics_getting_history) {
2741 case BeginningOfGame:
2742 /* User typed "moves" or "oldmoves" while we
2743 were idle. Pretend we asked for these
2744 moves and soak them up so user can step
2745 through them and/or save them.
2748 gameMode = IcsObserving;
2751 ics_getting_history = H_GOT_UNREQ_HEADER;
2753 case EditGame: /*?*/
2754 case EditPosition: /*?*/
2755 /* Should above feature work in these modes too? */
2756 /* For now it doesn't */
2757 ics_getting_history = H_GOT_UNWANTED_HEADER;
2760 ics_getting_history = H_GOT_UNWANTED_HEADER;
2765 /* Is this the right one? */
2766 if (gameInfo.white && gameInfo.black &&
2767 strcmp(gameInfo.white, star_match[0]) == 0 &&
2768 strcmp(gameInfo.black, star_match[2]) == 0) {
2770 ics_getting_history = H_GOT_REQ_HEADER;
2773 case H_GOT_REQ_HEADER:
2774 case H_GOT_UNREQ_HEADER:
2775 case H_GOT_UNWANTED_HEADER:
2776 case H_GETTING_MOVES:
2777 /* Should not happen */
2778 DisplayError(_("Error gathering move list: two headers"), 0);
2779 ics_getting_history = H_FALSE;
2783 /* Save player ratings into gameInfo if needed */
2784 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2785 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2786 (gameInfo.whiteRating == -1 ||
2787 gameInfo.blackRating == -1)) {
2789 gameInfo.whiteRating = string_to_rating(star_match[1]);
2790 gameInfo.blackRating = string_to_rating(star_match[3]);
2791 if (appData.debugMode)
2792 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2793 gameInfo.whiteRating, gameInfo.blackRating);
2798 if (looking_at(buf, &i,
2799 "* * match, initial time: * minute*, increment: * second")) {
2800 /* Header for a move list -- second line */
2801 /* Initial board will follow if this is a wild game */
2802 if (gameInfo.event != NULL) free(gameInfo.event);
2803 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2804 gameInfo.event = StrSave(str);
2805 /* [HGM] we switched variant. Translate boards if needed. */
2806 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2810 if (looking_at(buf, &i, "Move ")) {
2811 /* Beginning of a move list */
2812 switch (ics_getting_history) {
2814 /* Normally should not happen */
2815 /* Maybe user hit reset while we were parsing */
2818 /* Happens if we are ignoring a move list that is not
2819 * the one we just requested. Common if the user
2820 * tries to observe two games without turning off
2823 case H_GETTING_MOVES:
2824 /* Should not happen */
2825 DisplayError(_("Error gathering move list: nested"), 0);
2826 ics_getting_history = H_FALSE;
2828 case H_GOT_REQ_HEADER:
2829 ics_getting_history = H_GETTING_MOVES;
2830 started = STARTED_MOVES;
2832 if (oldi > next_out) {
2833 SendToPlayer(&buf[next_out], oldi - next_out);
2836 case H_GOT_UNREQ_HEADER:
2837 ics_getting_history = H_GETTING_MOVES;
2838 started = STARTED_MOVES_NOHIDE;
2841 case H_GOT_UNWANTED_HEADER:
2842 ics_getting_history = H_FALSE;
2848 if (looking_at(buf, &i, "% ") ||
2849 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2850 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2851 savingComment = FALSE;
2854 case STARTED_MOVES_NOHIDE:
2855 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2856 parse[parse_pos + i - oldi] = NULLCHAR;
2857 ParseGameHistory(parse);
2859 if (appData.zippyPlay && first.initDone) {
2860 FeedMovesToProgram(&first, forwardMostMove);
2861 if (gameMode == IcsPlayingWhite) {
2862 if (WhiteOnMove(forwardMostMove)) {
2863 if (first.sendTime) {
2864 if (first.useColors) {
2865 SendToProgram("black\n", &first);
2867 SendTimeRemaining(&first, TRUE);
2869 if (first.useColors) {
2870 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2872 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2873 first.maybeThinking = TRUE;
2875 if (first.usePlayother) {
2876 if (first.sendTime) {
2877 SendTimeRemaining(&first, TRUE);
2879 SendToProgram("playother\n", &first);
2885 } else if (gameMode == IcsPlayingBlack) {
2886 if (!WhiteOnMove(forwardMostMove)) {
2887 if (first.sendTime) {
2888 if (first.useColors) {
2889 SendToProgram("white\n", &first);
2891 SendTimeRemaining(&first, FALSE);
2893 if (first.useColors) {
2894 SendToProgram("black\n", &first);
2896 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2897 first.maybeThinking = TRUE;
2899 if (first.usePlayother) {
2900 if (first.sendTime) {
2901 SendTimeRemaining(&first, FALSE);
2903 SendToProgram("playother\n", &first);
2912 if (gameMode == IcsObserving && ics_gamenum == -1) {
2913 /* Moves came from oldmoves or moves command
2914 while we weren't doing anything else.
2916 currentMove = forwardMostMove;
2917 ClearHighlights();/*!!could figure this out*/
2918 flipView = appData.flipView;
2919 DrawPosition(TRUE, boards[currentMove]);
2920 DisplayBothClocks();
2921 sprintf(str, "%s vs. %s",
2922 gameInfo.white, gameInfo.black);
2926 /* Moves were history of an active game */
2927 if (gameInfo.resultDetails != NULL) {
2928 free(gameInfo.resultDetails);
2929 gameInfo.resultDetails = NULL;
2932 HistorySet(parseList, backwardMostMove,
2933 forwardMostMove, currentMove-1);
2934 DisplayMove(currentMove - 1);
2935 if (started == STARTED_MOVES) next_out = i;
2936 started = STARTED_NONE;
2937 ics_getting_history = H_FALSE;
2940 case STARTED_OBSERVE:
2941 started = STARTED_NONE;
2942 SendToICS(ics_prefix);
2943 SendToICS("refresh\n");
2949 if(bookHit) { // [HGM] book: simulate book reply
2950 static char bookMove[MSG_SIZ]; // a bit generous?
2952 programStats.nodes = programStats.depth = programStats.time =
2953 programStats.score = programStats.got_only_move = 0;
2954 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2956 strcpy(bookMove, "move ");
2957 strcat(bookMove, bookHit);
2958 HandleMachineMove(bookMove, &first);
2963 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2964 started == STARTED_HOLDINGS ||
2965 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2966 /* Accumulate characters in move list or board */
2967 parse[parse_pos++] = buf[i];
2970 /* Start of game messages. Mostly we detect start of game
2971 when the first board image arrives. On some versions
2972 of the ICS, though, we need to do a "refresh" after starting
2973 to observe in order to get the current board right away. */
2974 if (looking_at(buf, &i, "Adding game * to observation list")) {
2975 started = STARTED_OBSERVE;
2979 /* Handle auto-observe */
2980 if (appData.autoObserve &&
2981 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2982 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2984 /* Choose the player that was highlighted, if any. */
2985 if (star_match[0][0] == '\033' ||
2986 star_match[1][0] != '\033') {
2987 player = star_match[0];
2989 player = star_match[2];
2991 sprintf(str, "%sobserve %s\n",
2992 ics_prefix, StripHighlightAndTitle(player));
2995 /* Save ratings from notify string */
2996 strcpy(player1Name, star_match[0]);
2997 player1Rating = string_to_rating(star_match[1]);
2998 strcpy(player2Name, star_match[2]);
2999 player2Rating = string_to_rating(star_match[3]);
3001 if (appData.debugMode)
3003 "Ratings from 'Game notification:' %s %d, %s %d\n",
3004 player1Name, player1Rating,
3005 player2Name, player2Rating);
3010 /* Deal with automatic examine mode after a game,
3011 and with IcsObserving -> IcsExamining transition */
3012 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3013 looking_at(buf, &i, "has made you an examiner of game *")) {
3015 int gamenum = atoi(star_match[0]);
3016 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3017 gamenum == ics_gamenum) {
3018 /* We were already playing or observing this game;
3019 no need to refetch history */
3020 gameMode = IcsExamining;
3022 pauseExamForwardMostMove = forwardMostMove;
3023 } else if (currentMove < forwardMostMove) {
3024 ForwardInner(forwardMostMove);
3027 /* I don't think this case really can happen */
3028 SendToICS(ics_prefix);
3029 SendToICS("refresh\n");
3034 /* Error messages */
3035 // if (ics_user_moved) {
3036 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3037 if (looking_at(buf, &i, "Illegal move") ||
3038 looking_at(buf, &i, "Not a legal move") ||
3039 looking_at(buf, &i, "Your king is in check") ||
3040 looking_at(buf, &i, "It isn't your turn") ||
3041 looking_at(buf, &i, "It is not your move")) {
3043 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3044 currentMove = --forwardMostMove;
3045 DisplayMove(currentMove - 1); /* before DMError */
3046 DrawPosition(FALSE, boards[currentMove]);
3048 DisplayBothClocks();
3050 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3056 if (looking_at(buf, &i, "still have time") ||
3057 looking_at(buf, &i, "not out of time") ||
3058 looking_at(buf, &i, "either player is out of time") ||
3059 looking_at(buf, &i, "has timeseal; checking")) {
3060 /* We must have called his flag a little too soon */
3061 whiteFlag = blackFlag = FALSE;
3065 if (looking_at(buf, &i, "added * seconds to") ||
3066 looking_at(buf, &i, "seconds were added to")) {
3067 /* Update the clocks */
3068 SendToICS(ics_prefix);
3069 SendToICS("refresh\n");
3073 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3074 ics_clock_paused = TRUE;
3079 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3080 ics_clock_paused = FALSE;
3085 /* Grab player ratings from the Creating: message.
3086 Note we have to check for the special case when
3087 the ICS inserts things like [white] or [black]. */
3088 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3089 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3091 0 player 1 name (not necessarily white)
3093 2 empty, white, or black (IGNORED)
3094 3 player 2 name (not necessarily black)
3097 The names/ratings are sorted out when the game
3098 actually starts (below).
3100 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3101 player1Rating = string_to_rating(star_match[1]);
3102 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3103 player2Rating = string_to_rating(star_match[4]);
3105 if (appData.debugMode)
3107 "Ratings from 'Creating:' %s %d, %s %d\n",
3108 player1Name, player1Rating,
3109 player2Name, player2Rating);
3114 /* Improved generic start/end-of-game messages */
3115 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3116 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3117 /* If tkind == 0: */
3118 /* star_match[0] is the game number */
3119 /* [1] is the white player's name */
3120 /* [2] is the black player's name */
3121 /* For end-of-game: */
3122 /* [3] is the reason for the game end */
3123 /* [4] is a PGN end game-token, preceded by " " */
3124 /* For start-of-game: */
3125 /* [3] begins with "Creating" or "Continuing" */
3126 /* [4] is " *" or empty (don't care). */
3127 int gamenum = atoi(star_match[0]);
3128 char *whitename, *blackname, *why, *endtoken;
3129 ChessMove endtype = (ChessMove) 0;
3132 whitename = star_match[1];
3133 blackname = star_match[2];
3134 why = star_match[3];
3135 endtoken = star_match[4];
3137 whitename = star_match[1];
3138 blackname = star_match[3];
3139 why = star_match[5];
3140 endtoken = star_match[6];
3143 /* Game start messages */
3144 if (strncmp(why, "Creating ", 9) == 0 ||
3145 strncmp(why, "Continuing ", 11) == 0) {
3146 gs_gamenum = gamenum;
3147 strcpy(gs_kind, strchr(why, ' ') + 1);
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][HOLDINGS_SET] = 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 > framePtr) { // [HGM] vari: do not run into saved variations
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][HOLDINGS_SET] = 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] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3697 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3698 if(board[0][i] == WhiteRook) j = i;
3699 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : 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] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : 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] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : 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] = boards[moveNum][CASTLING][2] = k;
3710 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3711 if(board[BOARD_HEIGHT-1][k] == bKing)
3712 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3714 r = boards[moveNum][CASTLING][0] = initialRights[0];
3715 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3716 r = boards[moveNum][CASTLING][1] = initialRights[1];
3717 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3718 r = boards[moveNum][CASTLING][3] = initialRights[3];
3719 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3720 r = boards[moveNum][CASTLING][4] = initialRights[4];
3721 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3722 /* wildcastle kludge: always assume King has rights */
3723 r = boards[moveNum][CASTLING][2] = initialRights[2];
3724 r = boards[moveNum][CASTLING][5] = initialRights[5];
3726 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3727 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3730 if (ics_getting_history == H_GOT_REQ_HEADER ||
3731 ics_getting_history == H_GOT_UNREQ_HEADER) {
3732 /* This was an initial position from a move list, not
3733 the current position */
3737 /* Update currentMove and known move number limits */
3738 newMove = newGame || moveNum > forwardMostMove;
3741 forwardMostMove = backwardMostMove = currentMove = moveNum;
3742 if (gameMode == IcsExamining && moveNum == 0) {
3743 /* Workaround for ICS limitation: we are not told the wild
3744 type when starting to examine a game. But if we ask for
3745 the move list, the move list header will tell us */
3746 ics_getting_history = H_REQUESTED;
3747 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3750 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3751 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3753 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3754 /* [HGM] applied this also to an engine that is silently watching */
3755 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3756 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3757 gameInfo.variant == currentlyInitializedVariant) {
3758 takeback = forwardMostMove - moveNum;
3759 for (i = 0; i < takeback; i++) {
3760 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3761 SendToProgram("undo\n", &first);
3766 forwardMostMove = moveNum;
3767 if (!pausing || currentMove > forwardMostMove)
3768 currentMove = forwardMostMove;
3770 /* New part of history that is not contiguous with old part */
3771 if (pausing && gameMode == IcsExamining) {
3772 pauseExamInvalid = TRUE;
3773 forwardMostMove = pauseExamForwardMostMove;
3776 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3778 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3779 // [HGM] when we will receive the move list we now request, it will be
3780 // fed to the engine from the first move on. So if the engine is not
3781 // in the initial position now, bring it there.
3782 InitChessProgram(&first, 0);
3785 ics_getting_history = H_REQUESTED;
3786 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3789 forwardMostMove = backwardMostMove = currentMove = moveNum;
3792 /* Update the clocks */
3793 if (strchr(elapsed_time, '.')) {
3795 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3796 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3798 /* Time is in seconds */
3799 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3800 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3805 if (appData.zippyPlay && newGame &&
3806 gameMode != IcsObserving && gameMode != IcsIdle &&
3807 gameMode != IcsExamining)
3808 ZippyFirstBoard(moveNum, basetime, increment);
3811 /* Put the move on the move list, first converting
3812 to canonical algebraic form. */
3814 if (appData.debugMode) {
3815 if (appData.debugMode) { int f = forwardMostMove;
3816 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3817 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3818 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3820 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3821 fprintf(debugFP, "moveNum = %d\n", moveNum);
3822 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3823 setbuf(debugFP, NULL);
3825 if (moveNum <= backwardMostMove) {
3826 /* We don't know what the board looked like before
3828 strcpy(parseList[moveNum - 1], move_str);
3829 strcat(parseList[moveNum - 1], " ");
3830 strcat(parseList[moveNum - 1], elapsed_time);
3831 moveList[moveNum - 1][0] = NULLCHAR;
3832 } else if (strcmp(move_str, "none") == 0) {
3833 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3834 /* Again, we don't know what the board looked like;
3835 this is really the start of the game. */
3836 parseList[moveNum - 1][0] = NULLCHAR;
3837 moveList[moveNum - 1][0] = NULLCHAR;
3838 backwardMostMove = moveNum;
3839 startedFromSetupPosition = TRUE;
3840 fromX = fromY = toX = toY = -1;
3842 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3843 // So we parse the long-algebraic move string in stead of the SAN move
3844 int valid; char buf[MSG_SIZ], *prom;
3846 // str looks something like "Q/a1-a2"; kill the slash
3848 sprintf(buf, "%c%s", str[0], str+2);
3849 else strcpy(buf, str); // might be castling
3850 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3851 strcat(buf, prom); // long move lacks promo specification!
3852 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3853 if(appData.debugMode)
3854 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3855 strcpy(move_str, buf);
3857 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3858 &fromX, &fromY, &toX, &toY, &promoChar)
3859 || ParseOneMove(buf, moveNum - 1, &moveType,
3860 &fromX, &fromY, &toX, &toY, &promoChar);
3861 // end of long SAN patch
3863 (void) CoordsToAlgebraic(boards[moveNum - 1],
3864 PosFlags(moveNum - 1),
3865 fromY, fromX, toY, toX, promoChar,
3866 parseList[moveNum-1]);
3867 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3873 if(gameInfo.variant != VariantShogi)
3874 strcat(parseList[moveNum - 1], "+");
3877 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3878 strcat(parseList[moveNum - 1], "#");
3881 strcat(parseList[moveNum - 1], " ");
3882 strcat(parseList[moveNum - 1], elapsed_time);
3883 /* currentMoveString is set as a side-effect of ParseOneMove */
3884 strcpy(moveList[moveNum - 1], currentMoveString);
3885 strcat(moveList[moveNum - 1], "\n");
3887 /* Move from ICS was illegal!? Punt. */
3888 if (appData.debugMode) {
3889 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3890 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3892 strcpy(parseList[moveNum - 1], move_str);
3893 strcat(parseList[moveNum - 1], " ");
3894 strcat(parseList[moveNum - 1], elapsed_time);
3895 moveList[moveNum - 1][0] = NULLCHAR;
3896 fromX = fromY = toX = toY = -1;
3899 if (appData.debugMode) {
3900 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3901 setbuf(debugFP, NULL);
3905 /* Send move to chess program (BEFORE animating it). */
3906 if (appData.zippyPlay && !newGame && newMove &&
3907 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3909 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3910 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3911 if (moveList[moveNum - 1][0] == NULLCHAR) {
3912 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3914 DisplayError(str, 0);
3916 if (first.sendTime) {
3917 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3919 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3920 if (firstMove && !bookHit) {
3922 if (first.useColors) {
3923 SendToProgram(gameMode == IcsPlayingWhite ?
3925 "black\ngo\n", &first);
3927 SendToProgram("go\n", &first);
3929 first.maybeThinking = TRUE;
3932 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3933 if (moveList[moveNum - 1][0] == NULLCHAR) {
3934 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3935 DisplayError(str, 0);
3937 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3938 SendMoveToProgram(moveNum - 1, &first);
3945 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3946 /* If move comes from a remote source, animate it. If it
3947 isn't remote, it will have already been animated. */
3948 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3949 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3951 if (!pausing && appData.highlightLastMove) {
3952 SetHighlights(fromX, fromY, toX, toY);
3956 /* Start the clocks */
3957 whiteFlag = blackFlag = FALSE;
3958 appData.clockMode = !(basetime == 0 && increment == 0);
3960 ics_clock_paused = TRUE;
3962 } else if (ticking == 1) {
3963 ics_clock_paused = FALSE;
3965 if (gameMode == IcsIdle ||
3966 relation == RELATION_OBSERVING_STATIC ||
3967 relation == RELATION_EXAMINING ||
3969 DisplayBothClocks();
3973 /* Display opponents and material strengths */
3974 if (gameInfo.variant != VariantBughouse &&
3975 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3976 if (tinyLayout || smallLayout) {
3977 if(gameInfo.variant == VariantNormal)
3978 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3979 gameInfo.white, white_stren, gameInfo.black, black_stren,
3980 basetime, increment);
3982 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3983 gameInfo.white, white_stren, gameInfo.black, black_stren,
3984 basetime, increment, (int) gameInfo.variant);
3986 if(gameInfo.variant == VariantNormal)
3987 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3988 gameInfo.white, white_stren, gameInfo.black, black_stren,
3989 basetime, increment);
3991 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3992 gameInfo.white, white_stren, gameInfo.black, black_stren,
3993 basetime, increment, VariantName(gameInfo.variant));
3996 if (appData.debugMode) {
3997 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4002 /* Display the board */
4003 if (!pausing && !appData.noGUI) {
4004 if (appData.premove)
4006 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4007 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4008 ClearPremoveHighlights();
4010 DrawPosition(FALSE, boards[currentMove]);
4011 DisplayMove(moveNum - 1);
4012 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4013 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4014 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4015 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4019 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4021 if(bookHit) { // [HGM] book: simulate book reply
4022 static char bookMove[MSG_SIZ]; // a bit generous?
4024 programStats.nodes = programStats.depth = programStats.time =
4025 programStats.score = programStats.got_only_move = 0;
4026 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4028 strcpy(bookMove, "move ");
4029 strcat(bookMove, bookHit);
4030 HandleMachineMove(bookMove, &first);
4039 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4040 ics_getting_history = H_REQUESTED;
4041 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4047 AnalysisPeriodicEvent(force)
4050 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4051 && !force) || !appData.periodicUpdates)
4054 /* Send . command to Crafty to collect stats */
4055 SendToProgram(".\n", &first);
4057 /* Don't send another until we get a response (this makes
4058 us stop sending to old Crafty's which don't understand
4059 the "." command (sending illegal cmds resets node count & time,
4060 which looks bad)) */
4061 programStats.ok_to_send = 0;
4064 void ics_update_width(new_width)
4067 ics_printf("set width %d\n", new_width);
4071 SendMoveToProgram(moveNum, cps)
4073 ChessProgramState *cps;
4077 if (cps->useUsermove) {
4078 SendToProgram("usermove ", cps);
4082 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4083 int len = space - parseList[moveNum];
4084 memcpy(buf, parseList[moveNum], len);
4086 buf[len] = NULLCHAR;
4088 sprintf(buf, "%s\n", parseList[moveNum]);
4090 SendToProgram(buf, cps);
4092 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4093 AlphaRank(moveList[moveNum], 4);
4094 SendToProgram(moveList[moveNum], cps);
4095 AlphaRank(moveList[moveNum], 4); // and back
4097 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4098 * the engine. It would be nice to have a better way to identify castle
4100 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4101 && cps->useOOCastle) {
4102 int fromX = moveList[moveNum][0] - AAA;
4103 int fromY = moveList[moveNum][1] - ONE;
4104 int toX = moveList[moveNum][2] - AAA;
4105 int toY = moveList[moveNum][3] - ONE;
4106 if((boards[moveNum][fromY][fromX] == WhiteKing
4107 && boards[moveNum][toY][toX] == WhiteRook)
4108 || (boards[moveNum][fromY][fromX] == BlackKing
4109 && boards[moveNum][toY][toX] == BlackRook)) {
4110 if(toX > fromX) SendToProgram("O-O\n", cps);
4111 else SendToProgram("O-O-O\n", cps);
4113 else SendToProgram(moveList[moveNum], cps);
4115 else SendToProgram(moveList[moveNum], cps);
4116 /* End of additions by Tord */
4119 /* [HGM] setting up the opening has brought engine in force mode! */
4120 /* Send 'go' if we are in a mode where machine should play. */
4121 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4122 (gameMode == TwoMachinesPlay ||
4124 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4126 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4127 SendToProgram("go\n", cps);
4128 if (appData.debugMode) {
4129 fprintf(debugFP, "(extra)\n");
4132 setboardSpoiledMachineBlack = 0;
4136 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4138 int fromX, fromY, toX, toY;
4140 char user_move[MSG_SIZ];
4144 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4145 (int)moveType, fromX, fromY, toX, toY);
4146 DisplayError(user_move + strlen("say "), 0);
4148 case WhiteKingSideCastle:
4149 case BlackKingSideCastle:
4150 case WhiteQueenSideCastleWild:
4151 case BlackQueenSideCastleWild:
4153 case WhiteHSideCastleFR:
4154 case BlackHSideCastleFR:
4156 sprintf(user_move, "o-o\n");
4158 case WhiteQueenSideCastle:
4159 case BlackQueenSideCastle:
4160 case WhiteKingSideCastleWild:
4161 case BlackKingSideCastleWild:
4163 case WhiteASideCastleFR:
4164 case BlackASideCastleFR:
4166 sprintf(user_move, "o-o-o\n");
4168 case WhitePromotionQueen:
4169 case BlackPromotionQueen:
4170 case WhitePromotionRook:
4171 case BlackPromotionRook:
4172 case WhitePromotionBishop:
4173 case BlackPromotionBishop:
4174 case WhitePromotionKnight:
4175 case BlackPromotionKnight:
4176 case WhitePromotionKing:
4177 case BlackPromotionKing:
4178 case WhitePromotionChancellor:
4179 case BlackPromotionChancellor:
4180 case WhitePromotionArchbishop:
4181 case BlackPromotionArchbishop:
4182 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4183 sprintf(user_move, "%c%c%c%c=%c\n",
4184 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4185 PieceToChar(WhiteFerz));
4186 else if(gameInfo.variant == VariantGreat)
4187 sprintf(user_move, "%c%c%c%c=%c\n",
4188 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189 PieceToChar(WhiteMan));
4191 sprintf(user_move, "%c%c%c%c=%c\n",
4192 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193 PieceToChar(PromoPiece(moveType)));
4197 sprintf(user_move, "%c@%c%c\n",
4198 ToUpper(PieceToChar((ChessSquare) fromX)),
4199 AAA + toX, ONE + toY);
4202 case WhiteCapturesEnPassant:
4203 case BlackCapturesEnPassant:
4204 case IllegalMove: /* could be a variant we don't quite understand */
4205 sprintf(user_move, "%c%c%c%c\n",
4206 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4209 SendToICS(user_move);
4210 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4211 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4215 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4220 if (rf == DROP_RANK) {
4221 sprintf(move, "%c@%c%c\n",
4222 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4224 if (promoChar == 'x' || promoChar == NULLCHAR) {
4225 sprintf(move, "%c%c%c%c\n",
4226 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4228 sprintf(move, "%c%c%c%c%c\n",
4229 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4235 ProcessICSInitScript(f)
4240 while (fgets(buf, MSG_SIZ, f)) {
4241 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4248 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4250 AlphaRank(char *move, int n)
4252 // char *p = move, c; int x, y;
4254 if (appData.debugMode) {
4255 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4259 move[2]>='0' && move[2]<='9' &&
4260 move[3]>='a' && move[3]<='x' ) {
4262 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4263 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4265 if(move[0]>='0' && move[0]<='9' &&
4266 move[1]>='a' && move[1]<='x' &&
4267 move[2]>='0' && move[2]<='9' &&
4268 move[3]>='a' && move[3]<='x' ) {
4269 /* input move, Shogi -> normal */
4270 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4271 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4272 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4273 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4276 move[3]>='0' && move[3]<='9' &&
4277 move[2]>='a' && move[2]<='x' ) {
4279 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4280 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4283 move[0]>='a' && move[0]<='x' &&
4284 move[3]>='0' && move[3]<='9' &&
4285 move[2]>='a' && move[2]<='x' ) {
4286 /* output move, normal -> Shogi */
4287 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4288 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4289 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4290 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4291 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4293 if (appData.debugMode) {
4294 fprintf(debugFP, " out = '%s'\n", move);
4298 /* Parser for moves from gnuchess, ICS, or user typein box */
4300 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4303 ChessMove *moveType;
4304 int *fromX, *fromY, *toX, *toY;
4307 if (appData.debugMode) {
4308 fprintf(debugFP, "move to parse: %s\n", move);
4310 *moveType = yylexstr(moveNum, move);
4312 switch (*moveType) {
4313 case WhitePromotionChancellor:
4314 case BlackPromotionChancellor:
4315 case WhitePromotionArchbishop:
4316 case BlackPromotionArchbishop:
4317 case WhitePromotionQueen:
4318 case BlackPromotionQueen:
4319 case WhitePromotionRook:
4320 case BlackPromotionRook:
4321 case WhitePromotionBishop:
4322 case BlackPromotionBishop:
4323 case WhitePromotionKnight:
4324 case BlackPromotionKnight:
4325 case WhitePromotionKing:
4326 case BlackPromotionKing:
4328 case WhiteCapturesEnPassant:
4329 case BlackCapturesEnPassant:
4330 case WhiteKingSideCastle:
4331 case WhiteQueenSideCastle:
4332 case BlackKingSideCastle:
4333 case BlackQueenSideCastle:
4334 case WhiteKingSideCastleWild:
4335 case WhiteQueenSideCastleWild:
4336 case BlackKingSideCastleWild:
4337 case BlackQueenSideCastleWild:
4338 /* Code added by Tord: */
4339 case WhiteHSideCastleFR:
4340 case WhiteASideCastleFR:
4341 case BlackHSideCastleFR:
4342 case BlackASideCastleFR:
4343 /* End of code added by Tord */
4344 case IllegalMove: /* bug or odd chess variant */
4345 *fromX = currentMoveString[0] - AAA;
4346 *fromY = currentMoveString[1] - ONE;
4347 *toX = currentMoveString[2] - AAA;
4348 *toY = currentMoveString[3] - ONE;
4349 *promoChar = currentMoveString[4];
4350 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4351 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4352 if (appData.debugMode) {
4353 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4355 *fromX = *fromY = *toX = *toY = 0;
4358 if (appData.testLegality) {
4359 return (*moveType != IllegalMove);
4361 return !(fromX == fromY && toX == toY);
4366 *fromX = *moveType == WhiteDrop ?
4367 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4368 (int) CharToPiece(ToLower(currentMoveString[0]));
4370 *toX = currentMoveString[2] - AAA;
4371 *toY = currentMoveString[3] - ONE;
4372 *promoChar = NULLCHAR;
4376 case ImpossibleMove:
4377 case (ChessMove) 0: /* end of file */
4386 if (appData.debugMode) {
4387 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4390 *fromX = *fromY = *toX = *toY = 0;
4391 *promoChar = NULLCHAR;
4396 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4397 // All positions will have equal probability, but the current method will not provide a unique
4398 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4404 int piecesLeft[(int)BlackPawn];
4405 int seed, nrOfShuffles;
4407 void GetPositionNumber()
4408 { // sets global variable seed
4411 seed = appData.defaultFrcPosition;
4412 if(seed < 0) { // randomize based on time for negative FRC position numbers
4413 for(i=0; i<50; i++) seed += random();
4414 seed = random() ^ random() >> 8 ^ random() << 8;
4415 if(seed<0) seed = -seed;
4419 int put(Board board, int pieceType, int rank, int n, int shade)
4420 // put the piece on the (n-1)-th empty squares of the given shade
4424 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4425 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4426 board[rank][i] = (ChessSquare) pieceType;
4427 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4429 piecesLeft[pieceType]--;
4437 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4438 // calculate where the next piece goes, (any empty square), and put it there
4442 i = seed % squaresLeft[shade];
4443 nrOfShuffles *= squaresLeft[shade];
4444 seed /= squaresLeft[shade];
4445 put(board, pieceType, rank, i, shade);
4448 void AddTwoPieces(Board board, int pieceType, int rank)
4449 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4451 int i, n=squaresLeft[ANY], j=n-1, k;
4453 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4454 i = seed % k; // pick one
4457 while(i >= j) i -= j--;
4458 j = n - 1 - j; i += j;
4459 put(board, pieceType, rank, j, ANY);
4460 put(board, pieceType, rank, i, ANY);
4463 void SetUpShuffle(Board board, int number)
4467 GetPositionNumber(); nrOfShuffles = 1;
4469 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4470 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4471 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4473 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4475 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4476 p = (int) board[0][i];
4477 if(p < (int) BlackPawn) piecesLeft[p] ++;
4478 board[0][i] = EmptySquare;
4481 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4482 // shuffles restricted to allow normal castling put KRR first
4483 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4484 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4485 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4486 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4487 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4488 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4489 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4490 put(board, WhiteRook, 0, 0, ANY);
4491 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4494 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4495 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4496 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4497 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4498 while(piecesLeft[p] >= 2) {
4499 AddOnePiece(board, p, 0, LITE);
4500 AddOnePiece(board, p, 0, DARK);
4502 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4505 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4506 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4507 // but we leave King and Rooks for last, to possibly obey FRC restriction
4508 if(p == (int)WhiteRook) continue;
4509 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4510 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4513 // now everything is placed, except perhaps King (Unicorn) and Rooks
4515 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4516 // Last King gets castling rights
4517 while(piecesLeft[(int)WhiteUnicorn]) {
4518 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4519 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4522 while(piecesLeft[(int)WhiteKing]) {
4523 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4524 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4529 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4530 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4533 // Only Rooks can be left; simply place them all
4534 while(piecesLeft[(int)WhiteRook]) {
4535 i = put(board, WhiteRook, 0, 0, ANY);
4536 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4539 initialRights[1] = initialRights[4] = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4541 initialRights[0] = initialRights[3] = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4544 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4545 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4548 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4551 int SetCharTable( char *table, const char * map )
4552 /* [HGM] moved here from winboard.c because of its general usefulness */
4553 /* Basically a safe strcpy that uses the last character as King */
4555 int result = FALSE; int NrPieces;
4557 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4558 && NrPieces >= 12 && !(NrPieces&1)) {
4559 int i; /* [HGM] Accept even length from 12 to 34 */
4561 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4562 for( i=0; i<NrPieces/2-1; i++ ) {
4564 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4566 table[(int) WhiteKing] = map[NrPieces/2-1];
4567 table[(int) BlackKing] = map[NrPieces-1];
4575 void Prelude(Board board)
4576 { // [HGM] superchess: random selection of exo-pieces
4577 int i, j, k; ChessSquare p;
4578 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4580 GetPositionNumber(); // use FRC position number
4582 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4583 SetCharTable(pieceToChar, appData.pieceToCharTable);
4584 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4585 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4588 j = seed%4; seed /= 4;
4589 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4590 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4591 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4592 j = seed%3 + (seed%3 >= j); seed /= 3;
4593 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4594 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4595 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4596 j = seed%3; seed /= 3;
4597 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4598 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4599 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4600 j = seed%2 + (seed%2 >= j); seed /= 2;
4601 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4602 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4603 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4604 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4605 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4606 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4607 put(board, exoPieces[0], 0, 0, ANY);
4608 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4612 InitPosition(redraw)
4615 ChessSquare (* pieces)[BOARD_FILES];
4616 int i, j, pawnRow, overrule,
4617 oldx = gameInfo.boardWidth,
4618 oldy = gameInfo.boardHeight,
4619 oldh = gameInfo.holdingsWidth,
4620 oldv = gameInfo.variant;
4622 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4624 /* [AS] Initialize pv info list [HGM] and game status */
4626 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4627 pvInfoList[i].depth = 0;
4628 boards[i][EP_STATUS] = EP_NONE;
4629 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4632 initialRulePlies = 0; /* 50-move counter start */
4634 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4635 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4639 /* [HGM] logic here is completely changed. In stead of full positions */
4640 /* the initialized data only consist of the two backranks. The switch */
4641 /* selects which one we will use, which is than copied to the Board */
4642 /* initialPosition, which for the rest is initialized by Pawns and */
4643 /* empty squares. This initial position is then copied to boards[0], */
4644 /* possibly after shuffling, so that it remains available. */
4646 gameInfo.holdingsWidth = 0; /* default board sizes */
4647 gameInfo.boardWidth = 8;
4648 gameInfo.boardHeight = 8;
4649 gameInfo.holdingsSize = 0;
4650 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4651 for(i=0; i<BOARD_FILES-2; i++)
4652 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4653 initialPosition[EP_STATUS] = EP_NONE;
4654 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4656 switch (gameInfo.variant) {
4657 case VariantFischeRandom:
4658 shuffleOpenings = TRUE;
4662 case VariantShatranj:
4663 pieces = ShatranjArray;
4664 nrCastlingRights = 0;
4665 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4667 case VariantTwoKings:
4668 pieces = twoKingsArray;
4670 case VariantCapaRandom:
4671 shuffleOpenings = TRUE;
4672 case VariantCapablanca:
4673 pieces = CapablancaArray;
4674 gameInfo.boardWidth = 10;
4675 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4678 pieces = GothicArray;
4679 gameInfo.boardWidth = 10;
4680 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4683 pieces = JanusArray;
4684 gameInfo.boardWidth = 10;
4685 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4686 nrCastlingRights = 6;
4687 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4688 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4689 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4690 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4691 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4692 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4695 pieces = FalconArray;
4696 gameInfo.boardWidth = 10;
4697 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4699 case VariantXiangqi:
4700 pieces = XiangqiArray;
4701 gameInfo.boardWidth = 9;
4702 gameInfo.boardHeight = 10;
4703 nrCastlingRights = 0;
4704 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4707 pieces = ShogiArray;
4708 gameInfo.boardWidth = 9;
4709 gameInfo.boardHeight = 9;
4710 gameInfo.holdingsSize = 7;
4711 nrCastlingRights = 0;
4712 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4714 case VariantCourier:
4715 pieces = CourierArray;
4716 gameInfo.boardWidth = 12;
4717 nrCastlingRights = 0;
4718 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4720 case VariantKnightmate:
4721 pieces = KnightmateArray;
4722 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4725 pieces = fairyArray;
4726 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4729 pieces = GreatArray;
4730 gameInfo.boardWidth = 10;
4731 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4732 gameInfo.holdingsSize = 8;
4736 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4737 gameInfo.holdingsSize = 8;
4738 startedFromSetupPosition = TRUE;
4740 case VariantCrazyhouse:
4741 case VariantBughouse:
4743 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4744 gameInfo.holdingsSize = 5;
4746 case VariantWildCastle:
4748 /* !!?shuffle with kings guaranteed to be on d or e file */
4749 shuffleOpenings = 1;
4751 case VariantNoCastle:
4753 nrCastlingRights = 0;
4754 /* !!?unconstrained back-rank shuffle */
4755 shuffleOpenings = 1;
4760 if(appData.NrFiles >= 0) {
4761 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4762 gameInfo.boardWidth = appData.NrFiles;
4764 if(appData.NrRanks >= 0) {
4765 gameInfo.boardHeight = appData.NrRanks;
4767 if(appData.holdingsSize >= 0) {
4768 i = appData.holdingsSize;
4769 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4770 gameInfo.holdingsSize = i;
4772 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4773 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4774 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4776 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4777 if(pawnRow < 1) pawnRow = 1;
4779 /* User pieceToChar list overrules defaults */
4780 if(appData.pieceToCharTable != NULL)
4781 SetCharTable(pieceToChar, appData.pieceToCharTable);
4783 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4785 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4786 s = (ChessSquare) 0; /* account holding counts in guard band */
4787 for( i=0; i<BOARD_HEIGHT; i++ )
4788 initialPosition[i][j] = s;
4790 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4791 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4792 initialPosition[pawnRow][j] = WhitePawn;
4793 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4794 if(gameInfo.variant == VariantXiangqi) {
4796 initialPosition[pawnRow][j] =
4797 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4798 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4799 initialPosition[2][j] = WhiteCannon;
4800 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4804 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4806 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4809 initialPosition[1][j] = WhiteBishop;
4810 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4812 initialPosition[1][j] = WhiteRook;
4813 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4816 if( nrCastlingRights == -1) {
4817 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4818 /* This sets default castling rights from none to normal corners */
4819 /* Variants with other castling rights must set them themselves above */
4820 nrCastlingRights = 6;
4821 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4822 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4823 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4824 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4825 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4826 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4829 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4830 if(gameInfo.variant == VariantGreat) { // promotion commoners
4831 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4832 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4833 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4834 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4836 if (appData.debugMode) {
4837 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4839 if(shuffleOpenings) {
4840 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4841 startedFromSetupPosition = TRUE;
4843 if(startedFromPositionFile) {
4844 /* [HGM] loadPos: use PositionFile for every new game */
4845 CopyBoard(initialPosition, filePosition);
4846 for(i=0; i<nrCastlingRights; i++)
4847 initialRights[i] = filePosition[CASTLING][i];
4848 startedFromSetupPosition = TRUE;
4851 CopyBoard(boards[0], initialPosition);
4852 if(oldx != gameInfo.boardWidth ||
4853 oldy != gameInfo.boardHeight ||
4854 oldh != gameInfo.holdingsWidth
4856 || oldv == VariantGothic || // For licensing popups
4857 gameInfo.variant == VariantGothic
4860 || oldv == VariantFalcon ||
4861 gameInfo.variant == VariantFalcon
4865 InitDrawingSizes(-2 ,0);
4869 DrawPosition(TRUE, boards[currentMove]);
4874 SendBoard(cps, moveNum)
4875 ChessProgramState *cps;
4878 char message[MSG_SIZ];
4880 if (cps->useSetboard) {
4881 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4882 sprintf(message, "setboard %s\n", fen);
4883 SendToProgram(message, cps);
4889 /* Kludge to set black to move, avoiding the troublesome and now
4890 * deprecated "black" command.
4892 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4894 SendToProgram("edit\n", cps);
4895 SendToProgram("#\n", cps);
4896 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4897 bp = &boards[moveNum][i][BOARD_LEFT];
4898 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4899 if ((int) *bp < (int) BlackPawn) {
4900 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4902 if(message[0] == '+' || message[0] == '~') {
4903 sprintf(message, "%c%c%c+\n",
4904 PieceToChar((ChessSquare)(DEMOTED *bp)),
4907 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4908 message[1] = BOARD_RGHT - 1 - j + '1';
4909 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4911 SendToProgram(message, cps);
4916 SendToProgram("c\n", cps);
4917 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4918 bp = &boards[moveNum][i][BOARD_LEFT];
4919 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4920 if (((int) *bp != (int) EmptySquare)
4921 && ((int) *bp >= (int) BlackPawn)) {
4922 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4924 if(message[0] == '+' || message[0] == '~') {
4925 sprintf(message, "%c%c%c+\n",
4926 PieceToChar((ChessSquare)(DEMOTED *bp)),
4929 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4930 message[1] = BOARD_RGHT - 1 - j + '1';
4931 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4933 SendToProgram(message, cps);
4938 SendToProgram(".\n", cps);
4940 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4944 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4946 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4947 /* [HGM] add Shogi promotions */
4948 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4953 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4954 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4956 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4957 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4960 piece = boards[currentMove][fromY][fromX];
4961 if(gameInfo.variant == VariantShogi) {
4962 promotionZoneSize = 3;
4963 highestPromotingPiece = (int)WhiteFerz;
4966 // next weed out all moves that do not touch the promotion zone at all
4967 if((int)piece >= BlackPawn) {
4968 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4970 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4972 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4973 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4976 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4978 // weed out mandatory Shogi promotions
4979 if(gameInfo.variant == VariantShogi) {
4980 if(piece >= BlackPawn) {
4981 if(toY == 0 && piece == BlackPawn ||
4982 toY == 0 && piece == BlackQueen ||
4983 toY <= 1 && piece == BlackKnight) {
4988 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4989 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4990 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4997 // weed out obviously illegal Pawn moves
4998 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4999 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5000 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5001 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5002 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5003 // note we are not allowed to test for valid (non-)capture, due to premove
5006 // we either have a choice what to promote to, or (in Shogi) whether to promote
5007 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5008 *promoChoice = PieceToChar(BlackFerz); // no choice
5011 if(appData.alwaysPromoteToQueen) { // predetermined
5012 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5013 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5014 else *promoChoice = PieceToChar(BlackQueen);
5018 // suppress promotion popup on illegal moves that are not premoves
5019 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5020 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5021 if(appData.testLegality && !premove) {
5022 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5023 fromY, fromX, toY, toX, NULLCHAR);
5024 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5025 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5033 InPalace(row, column)
5035 { /* [HGM] for Xiangqi */
5036 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5037 column < (BOARD_WIDTH + 4)/2 &&
5038 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5043 PieceForSquare (x, y)
5047 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5050 return boards[currentMove][y][x];
5054 OKToStartUserMove(x, y)
5057 ChessSquare from_piece;
5060 if (matchMode) return FALSE;
5061 if (gameMode == EditPosition) return TRUE;
5063 if (x >= 0 && y >= 0)
5064 from_piece = boards[currentMove][y][x];
5066 from_piece = EmptySquare;
5068 if (from_piece == EmptySquare) return FALSE;
5070 white_piece = (int)from_piece >= (int)WhitePawn &&
5071 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5074 case PlayFromGameFile:
5076 case TwoMachinesPlay:
5084 case MachinePlaysWhite:
5085 case IcsPlayingBlack:
5086 if (appData.zippyPlay) return FALSE;
5088 DisplayMoveError(_("You are playing Black"));
5093 case MachinePlaysBlack:
5094 case IcsPlayingWhite:
5095 if (appData.zippyPlay) return FALSE;
5097 DisplayMoveError(_("You are playing White"));
5103 if (!white_piece && WhiteOnMove(currentMove)) {
5104 DisplayMoveError(_("It is White's turn"));
5107 if (white_piece && !WhiteOnMove(currentMove)) {
5108 DisplayMoveError(_("It is Black's turn"));
5111 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5112 /* Editing correspondence game history */
5113 /* Could disallow this or prompt for confirmation */
5118 case BeginningOfGame:
5119 if (appData.icsActive) return FALSE;
5120 if (!appData.noChessProgram) {
5122 DisplayMoveError(_("You are playing White"));
5129 if (!white_piece && WhiteOnMove(currentMove)) {
5130 DisplayMoveError(_("It is White's turn"));
5133 if (white_piece && !WhiteOnMove(currentMove)) {
5134 DisplayMoveError(_("It is Black's turn"));
5143 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5144 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5145 && gameMode != AnalyzeFile && gameMode != Training) {
5146 DisplayMoveError(_("Displayed position is not current"));
5152 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5153 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5154 int lastLoadGameUseList = FALSE;
5155 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5156 ChessMove lastLoadGameStart = (ChessMove) 0;
5159 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5160 int fromX, fromY, toX, toY;
5165 ChessSquare pdown, pup;
5167 /* Check if the user is playing in turn. This is complicated because we
5168 let the user "pick up" a piece before it is his turn. So the piece he
5169 tried to pick up may have been captured by the time he puts it down!
5170 Therefore we use the color the user is supposed to be playing in this
5171 test, not the color of the piece that is currently on the starting
5172 square---except in EditGame mode, where the user is playing both
5173 sides; fortunately there the capture race can't happen. (It can
5174 now happen in IcsExamining mode, but that's just too bad. The user
5175 will get a somewhat confusing message in that case.)
5179 case PlayFromGameFile:
5181 case TwoMachinesPlay:
5185 /* We switched into a game mode where moves are not accepted,
5186 perhaps while the mouse button was down. */
5187 return ImpossibleMove;
5189 case MachinePlaysWhite:
5190 /* User is moving for Black */
5191 if (WhiteOnMove(currentMove)) {
5192 DisplayMoveError(_("It is White's turn"));
5193 return ImpossibleMove;
5197 case MachinePlaysBlack:
5198 /* User is moving for White */
5199 if (!WhiteOnMove(currentMove)) {
5200 DisplayMoveError(_("It is Black's turn"));
5201 return ImpossibleMove;
5207 case BeginningOfGame:
5210 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5211 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5212 /* User is moving for Black */
5213 if (WhiteOnMove(currentMove)) {
5214 DisplayMoveError(_("It is White's turn"));
5215 return ImpossibleMove;
5218 /* User is moving for White */
5219 if (!WhiteOnMove(currentMove)) {
5220 DisplayMoveError(_("It is Black's turn"));
5221 return ImpossibleMove;
5226 case IcsPlayingBlack:
5227 /* User is moving for Black */
5228 if (WhiteOnMove(currentMove)) {
5229 if (!appData.premove) {
5230 DisplayMoveError(_("It is White's turn"));
5231 } else if (toX >= 0 && toY >= 0) {
5234 premoveFromX = fromX;
5235 premoveFromY = fromY;
5236 premovePromoChar = promoChar;
5238 if (appData.debugMode)
5239 fprintf(debugFP, "Got premove: fromX %d,"
5240 "fromY %d, toX %d, toY %d\n",
5241 fromX, fromY, toX, toY);
5243 return ImpossibleMove;
5247 case IcsPlayingWhite:
5248 /* User is moving for White */
5249 if (!WhiteOnMove(currentMove)) {
5250 if (!appData.premove) {
5251 DisplayMoveError(_("It is Black's turn"));
5252 } else if (toX >= 0 && toY >= 0) {
5255 premoveFromX = fromX;
5256 premoveFromY = fromY;
5257 premovePromoChar = promoChar;
5259 if (appData.debugMode)
5260 fprintf(debugFP, "Got premove: fromX %d,"
5261 "fromY %d, toX %d, toY %d\n",
5262 fromX, fromY, toX, toY);
5264 return ImpossibleMove;
5272 /* EditPosition, empty square, or different color piece;
5273 click-click move is possible */
5274 if (toX == -2 || toY == -2) {
5275 boards[0][fromY][fromX] = EmptySquare;
5276 return AmbiguousMove;
5277 } else if (toX >= 0 && toY >= 0) {
5278 boards[0][toY][toX] = boards[0][fromY][fromX];
5279 boards[0][fromY][fromX] = EmptySquare;
5280 return AmbiguousMove;
5282 return ImpossibleMove;
5285 if(toX < 0 || toY < 0) return ImpossibleMove;
5286 pdown = boards[currentMove][fromY][fromX];
5287 pup = boards[currentMove][toY][toX];
5289 /* [HGM] If move started in holdings, it means a drop */
5290 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5291 if( pup != EmptySquare ) return ImpossibleMove;
5292 if(appData.testLegality) {
5293 /* it would be more logical if LegalityTest() also figured out
5294 * which drops are legal. For now we forbid pawns on back rank.
5295 * Shogi is on its own here...
5297 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5298 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5299 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5301 return WhiteDrop; /* Not needed to specify white or black yet */
5304 userOfferedDraw = FALSE;
5306 /* [HGM] always test for legality, to get promotion info */
5307 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5308 fromY, fromX, toY, toX, promoChar);
5309 /* [HGM] but possibly ignore an IllegalMove result */
5310 if (appData.testLegality) {
5311 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5312 DisplayMoveError(_("Illegal move"));
5313 return ImpossibleMove;
5318 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5319 function is made into one that returns an OK move type if FinishMove
5320 should be called. This to give the calling driver routine the
5321 opportunity to finish the userMove input with a promotion popup,
5322 without bothering the user with this for invalid or illegal moves */
5324 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5327 /* Common tail of UserMoveEvent and DropMenuEvent */
5329 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5331 int fromX, fromY, toX, toY;
5332 /*char*/int promoChar;
5336 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5338 // [HGM] superchess: suppress promotions to non-available piece
5339 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340 if(WhiteOnMove(currentMove))
5342 if(!boards[currentMove][k][BOARD_WIDTH-2])
5347 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5352 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5353 move type in caller when we know the move is a legal promotion */
5354 if(moveType == NormalMove && promoChar)
5355 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5357 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5358 move type in caller when we know the move is a legal promotion */
5359 if(moveType == NormalMove && promoChar)
5360 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5362 /* [HGM] convert drag-and-drop piece drops to standard form */
5363 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)
5381 /* compare the move played on the board to the next move in the
5382 * game. If they match, display the move and the opponent's response.
5383 * If they don't match, display an error message.
5387 CopyBoard(testBoard, boards[currentMove]);
5388 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5390 if (CompareBoards(testBoard, boards[currentMove+1]))
5392 ForwardInner(currentMove+1);
5394 /* Autoplay the opponent's response.
5395 * if appData.animate was TRUE when Training mode was entered,
5396 * the response will be animated.
5398 saveAnimate = appData.animate;
5399 appData.animate = animateTraining;
5400 ForwardInner(currentMove+1);
5401 appData.animate = saveAnimate;
5403 /* check for the end of the game */
5404 if (currentMove >= forwardMostMove)
5406 gameMode = PlayFromGameFile;
5408 SetTrainingModeOff();
5409 DisplayInformation(_("End of game"));
5414 DisplayError(_("Incorrect move"), 0);
5419 /* Ok, now we know that the move is good, so we can kill
5420 the previous line in Analysis Mode */
5421 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5422 && currentMove < forwardMostMove) {
5423 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5426 /* If we need the chess program but it's dead, restart it */
5427 ResurrectChessProgram();
5429 /* A user move restarts a paused game*/
5433 thinkOutput[0] = NULLCHAR;
5435 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5437 if (gameMode == BeginningOfGame)
5439 if (appData.noChessProgram)
5441 gameMode = EditGame;
5447 gameMode = MachinePlaysBlack;
5450 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5454 sprintf(buf, "name %s\n", gameInfo.white);
5455 SendToProgram(buf, &first);
5462 /* Relay move to ICS or chess engine */
5463 if (appData.icsActive)
5465 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5466 gameMode == IcsExamining)
5468 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5474 if (first.sendTime && (gameMode == BeginningOfGame ||
5475 gameMode == MachinePlaysWhite ||
5476 gameMode == MachinePlaysBlack))
5478 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5480 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5482 // [HGM] book: if program might be playing, let it use book
5483 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5484 first.maybeThinking = TRUE;
5487 SendMoveToProgram(forwardMostMove-1, &first);
5488 if (currentMove == cmailOldMove + 1)
5490 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5494 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5499 switch (MateTest(boards[currentMove], PosFlags(currentMove)) )
5506 if (WhiteOnMove(currentMove)) {
5507 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5509 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5513 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5518 case MachinePlaysBlack:
5519 case MachinePlaysWhite:
5520 /* disable certain menu options while machine is thinking */
5521 SetMachineThinkingEnables();
5529 { // [HGM] book: simulate book reply
5530 static char bookMove[MSG_SIZ]; // a bit generous?
5532 programStats.nodes = programStats.depth = programStats.time =
5533 programStats.score = programStats.got_only_move = 0;
5534 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5536 strcpy(bookMove, "move ");
5537 strcat(bookMove, bookHit);
5538 HandleMachineMove(bookMove, &first);
5545 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5546 int fromX, fromY, toX, toY;
5549 /* [HGM] This routine was added to allow calling of its two logical
5550 parts from other modules in the old way. Before, UserMoveEvent()
5551 automatically called FinishMove() if the move was OK, and returned
5552 otherwise. I separated the two, in order to make it possible to
5553 slip a promotion popup in between. But that it always needs two
5554 calls, to the first part, (now called UserMoveTest() ), and to
5555 FinishMove if the first part succeeded. Calls that do not need
5556 to do anything in between, can call this routine the old way.
5558 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5559 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5560 if(moveType == AmbiguousMove)
5561 DrawPosition(FALSE, boards[currentMove]);
5562 else if(moveType != ImpossibleMove && moveType != Comment)
5563 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5566 void LeftClick(ClickType clickType, int xPix, int yPix)
5569 Boolean saveAnimate;
5570 static int second = 0, promotionChoice = 0;
5571 char promoChoice = NULLCHAR;
5573 if (clickType == Press) ErrorPopDown();
5575 x = EventToSquare(xPix, BOARD_WIDTH);
5576 y = EventToSquare(yPix, BOARD_HEIGHT);
5577 if (!flipView && y >= 0) {
5578 y = BOARD_HEIGHT - 1 - y;
5580 if (flipView && x >= 0) {
5581 x = BOARD_WIDTH - 1 - x;
5584 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5585 if(clickType == Release) return; // ignore upclick of click-click destination
5586 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5587 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5588 if(gameInfo.holdingsWidth &&
5589 (WhiteOnMove(currentMove)
5590 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5591 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5592 // click in right holdings, for determining promotion piece
5593 ChessSquare p = boards[currentMove][y][x];
5594 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5595 if(p != EmptySquare) {
5596 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5601 DrawPosition(FALSE, boards[currentMove]);
5605 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5606 if(clickType == Press
5607 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5608 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5609 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5613 if (clickType == Press) {
5615 if (OKToStartUserMove(x, y)) {
5619 DragPieceBegin(xPix, yPix);
5620 if (appData.highlightDragging) {
5621 SetHighlights(x, y, -1, -1);
5629 if (clickType == Press && gameMode != EditPosition) {
5634 // ignore off-board to clicks
5635 if(y < 0 || x < 0) return;
5637 /* Check if clicking again on the same color piece */
5638 fromP = boards[currentMove][fromY][fromX];
5639 toP = boards[currentMove][y][x];
5640 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5641 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5642 WhitePawn <= toP && toP <= WhiteKing &&
5643 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5644 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5645 (BlackPawn <= fromP && fromP <= BlackKing &&
5646 BlackPawn <= toP && toP <= BlackKing &&
5647 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5648 !(fromP == BlackKing && toP == BlackRook && frc))) {
5649 /* Clicked again on same color piece -- changed his mind */
5650 second = (x == fromX && y == fromY);
5651 if (appData.highlightDragging) {
5652 SetHighlights(x, y, -1, -1);
5656 if (OKToStartUserMove(x, y)) {
5659 DragPieceBegin(xPix, yPix);
5663 // ignore clicks on holdings
5664 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5667 if (clickType == Release && x == fromX && y == fromY) {
5668 DragPieceEnd(xPix, yPix);
5669 if (appData.animateDragging) {
5670 /* Undo animation damage if any */
5671 DrawPosition(FALSE, NULL);
5674 /* Second up/down in same square; just abort move */
5679 ClearPremoveHighlights();
5681 /* First upclick in same square; start click-click mode */
5682 SetHighlights(x, y, -1, -1);
5687 /* we now have a different from- and (possibly off-board) to-square */
5688 /* Completed move */
5691 saveAnimate = appData.animate;
5692 if (clickType == Press) {
5693 /* Finish clickclick move */
5694 if (appData.animate || appData.highlightLastMove) {
5695 SetHighlights(fromX, fromY, toX, toY);
5700 /* Finish drag move */
5701 if (appData.highlightLastMove) {
5702 SetHighlights(fromX, fromY, toX, toY);
5706 DragPieceEnd(xPix, yPix);
5707 /* Don't animate move and drag both */
5708 appData.animate = FALSE;
5711 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5712 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5715 DrawPosition(TRUE, NULL);
5719 // off-board moves should not be highlighted
5720 if(x < 0 || x < 0) ClearHighlights();
5722 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5723 SetHighlights(fromX, fromY, toX, toY);
5724 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5725 // [HGM] super: promotion to captured piece selected from holdings
5726 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5727 promotionChoice = TRUE;
5728 // kludge follows to temporarily execute move on display, without promoting yet
5729 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5730 boards[currentMove][toY][toX] = p;
5731 DrawPosition(FALSE, boards[currentMove]);
5732 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5733 boards[currentMove][toY][toX] = q;
5734 DisplayMessage("Click in holdings to choose piece", "");
5739 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5740 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5741 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5744 appData.animate = saveAnimate;
5745 if (appData.animate || appData.animateDragging) {
5746 /* Undo animation damage if needed */
5747 DrawPosition(FALSE, NULL);
5751 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5753 // char * hint = lastHint;
5754 FrontEndProgramStats stats;
5756 stats.which = cps == &first ? 0 : 1;
5757 stats.depth = cpstats->depth;
5758 stats.nodes = cpstats->nodes;
5759 stats.score = cpstats->score;
5760 stats.time = cpstats->time;
5761 stats.pv = cpstats->movelist;
5762 stats.hint = lastHint;
5763 stats.an_move_index = 0;
5764 stats.an_move_count = 0;
5766 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5767 stats.hint = cpstats->move_name;
5768 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5769 stats.an_move_count = cpstats->nr_moves;
5772 SetProgramStats( &stats );
5775 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5776 { // [HGM] book: this routine intercepts moves to simulate book replies
5777 char *bookHit = NULL;
5779 //first determine if the incoming move brings opponent into his book
5780 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5781 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5782 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5783 if(bookHit != NULL && !cps->bookSuspend) {
5784 // make sure opponent is not going to reply after receiving move to book position
5785 SendToProgram("force\n", cps);
5786 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5788 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5789 // now arrange restart after book miss
5791 // after a book hit we never send 'go', and the code after the call to this routine
5792 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5794 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5795 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5796 SendToProgram(buf, cps);
5797 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5798 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5799 SendToProgram("go\n", cps);
5800 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5801 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5802 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5803 SendToProgram("go\n", cps);
5804 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5806 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5810 ChessProgramState *savedState;
5811 void DeferredBookMove(void)
5813 if(savedState->lastPing != savedState->lastPong)
5814 ScheduleDelayedEvent(DeferredBookMove, 10);
5816 HandleMachineMove(savedMessage, savedState);
5820 HandleMachineMove(message, cps)
5822 ChessProgramState *cps;
5824 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5825 char realname[MSG_SIZ];
5826 int fromX, fromY, toX, toY;
5833 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5835 * Kludge to ignore BEL characters
5837 while (*message == '\007') message++;
5840 * [HGM] engine debug message: ignore lines starting with '#' character
5842 if(cps->debug && *message == '#') return;
5845 * Look for book output
5847 if (cps == &first && bookRequested) {
5848 if (message[0] == '\t' || message[0] == ' ') {
5849 /* Part of the book output is here; append it */
5850 strcat(bookOutput, message);
5851 strcat(bookOutput, " \n");
5853 } else if (bookOutput[0] != NULLCHAR) {
5854 /* All of book output has arrived; display it */
5855 char *p = bookOutput;
5856 while (*p != NULLCHAR) {
5857 if (*p == '\t') *p = ' ';
5860 DisplayInformation(bookOutput);
5861 bookRequested = FALSE;
5862 /* Fall through to parse the current output */
5867 * Look for machine move.
5869 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5870 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5872 /* This method is only useful on engines that support ping */
5873 if (cps->lastPing != cps->lastPong) {
5874 if (gameMode == BeginningOfGame) {
5875 /* Extra move from before last new; ignore */
5876 if (appData.debugMode) {
5877 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5880 if (appData.debugMode) {
5881 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5882 cps->which, gameMode);
5885 SendToProgram("undo\n", cps);
5891 case BeginningOfGame:
5892 /* Extra move from before last reset; ignore */
5893 if (appData.debugMode) {
5894 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5901 /* Extra move after we tried to stop. The mode test is
5902 not a reliable way of detecting this problem, but it's
5903 the best we can do on engines that don't support ping.
5905 if (appData.debugMode) {
5906 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5907 cps->which, gameMode);
5909 SendToProgram("undo\n", cps);
5912 case MachinePlaysWhite:
5913 case IcsPlayingWhite:
5914 machineWhite = TRUE;
5917 case MachinePlaysBlack:
5918 case IcsPlayingBlack:
5919 machineWhite = FALSE;
5922 case TwoMachinesPlay:
5923 machineWhite = (cps->twoMachinesColor[0] == 'w');
5926 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5927 if (appData.debugMode) {
5929 "Ignoring move out of turn by %s, gameMode %d"
5930 ", forwardMost %d\n",
5931 cps->which, gameMode, forwardMostMove);
5936 if (appData.debugMode) { int f = forwardMostMove;
5937 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5938 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5939 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5941 if(cps->alphaRank) AlphaRank(machineMove, 4);
5942 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5943 &fromX, &fromY, &toX, &toY, &promoChar)) {
5944 /* Machine move could not be parsed; ignore it. */
5945 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5946 machineMove, cps->which);
5947 DisplayError(buf1, 0);
5948 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5949 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5950 if (gameMode == TwoMachinesPlay) {
5951 GameEnds(machineWhite ? BlackWins : WhiteWins,
5957 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5958 /* So we have to redo legality test with true e.p. status here, */
5959 /* to make sure an illegal e.p. capture does not slip through, */
5960 /* to cause a forfeit on a justified illegal-move complaint */
5961 /* of the opponent. */
5962 if( gameMode==TwoMachinesPlay && appData.testLegality
5963 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5966 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5967 fromY, fromX, toY, toX, promoChar);
5968 if (appData.debugMode) {
5970 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5971 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5972 fprintf(debugFP, "castling rights\n");
5974 if(moveType == IllegalMove) {
5975 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5976 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5977 GameEnds(machineWhite ? BlackWins : WhiteWins,
5980 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5981 /* [HGM] Kludge to handle engines that send FRC-style castling
5982 when they shouldn't (like TSCP-Gothic) */
5984 case WhiteASideCastleFR:
5985 case BlackASideCastleFR:
5987 currentMoveString[2]++;
5989 case WhiteHSideCastleFR:
5990 case BlackHSideCastleFR:
5992 currentMoveString[2]--;
5994 default: ; // nothing to do, but suppresses warning of pedantic compilers
5997 hintRequested = FALSE;
5998 lastHint[0] = NULLCHAR;
5999 bookRequested = FALSE;
6000 /* Program may be pondering now */
6001 cps->maybeThinking = TRUE;
6002 if (cps->sendTime == 2) cps->sendTime = 1;
6003 if (cps->offeredDraw) cps->offeredDraw--;
6006 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6008 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6010 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6011 char buf[3*MSG_SIZ];
6013 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6014 programStats.score / 100.,
6016 programStats.time / 100.,
6017 (unsigned int)programStats.nodes,
6018 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6019 programStats.movelist);
6021 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6025 /* currentMoveString is set as a side-effect of ParseOneMove */
6026 strcpy(machineMove, currentMoveString);
6027 strcat(machineMove, "\n");
6028 strcpy(moveList[forwardMostMove], machineMove);
6030 /* [AS] Save move info and clear stats for next move */
6031 pvInfoList[ forwardMostMove ].score = programStats.score;
6032 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6033 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6034 ClearProgramStats();
6035 thinkOutput[0] = NULLCHAR;
6036 hiddenThinkOutputState = 0;
6038 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6040 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6041 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6044 while( count < adjudicateLossPlies ) {
6045 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6048 score = -score; /* Flip score for winning side */
6051 if( score > adjudicateLossThreshold ) {
6058 if( count >= adjudicateLossPlies ) {
6059 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6061 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6062 "Xboard adjudication",
6069 if( gameMode == TwoMachinesPlay ) {
6070 // [HGM] some adjudications useful with buggy engines
6071 int k, count = 0; static int bare = 1;
6072 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6075 if( appData.testLegality )
6076 { /* [HGM] Some more adjudications for obstinate engines */
6077 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6078 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6079 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6080 static int moveCount = 6;
6082 char *reason = NULL;
6084 /* Count what is on board. */
6085 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6086 { ChessSquare p = boards[forwardMostMove][i][j];
6090 { /* count B,N,R and other of each side */
6093 NrK++; break; // [HGM] atomic: count Kings
6097 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6098 bishopsColor |= 1 << ((i^j)&1);
6103 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6104 bishopsColor |= 1 << ((i^j)&1);
6119 PawnAdvance += m; NrPawns++;
6121 NrPieces += (p != EmptySquare);
6122 NrW += ((int)p < (int)BlackPawn);
6123 if(gameInfo.variant == VariantXiangqi &&
6124 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6125 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6126 NrW -= ((int)p < (int)BlackPawn);
6130 /* Some material-based adjudications that have to be made before stalemate test */
6131 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6132 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6133 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6134 if(appData.checkMates) {
6135 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6136 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6137 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6138 "Xboard adjudication: King destroyed", GE_XBOARD );
6143 /* Bare King in Shatranj (loses) or Losers (wins) */
6144 if( NrW == 1 || NrPieces - NrW == 1) {
6145 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6146 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6147 if(appData.checkMates) {
6148 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6149 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6151 "Xboard adjudication: Bare king", GE_XBOARD );
6155 if( gameInfo.variant == VariantShatranj && --bare < 0)
6157 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6158 if(appData.checkMates) {
6159 /* but only adjudicate if adjudication enabled */
6160 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6161 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6162 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6163 "Xboard adjudication: Bare king", GE_XBOARD );
6170 // don't wait for engine to announce game end if we can judge ourselves
6171 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6173 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6174 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6175 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6176 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6179 reason = "Xboard adjudication: 3rd check";
6180 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6190 reason = "Xboard adjudication: Stalemate";
6191 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6192 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6193 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6194 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6195 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6196 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6197 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6198 EP_CHECKMATE : EP_WINS);
6199 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6200 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6204 reason = "Xboard adjudication: Checkmate";
6205 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6209 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6211 result = GameIsDrawn; break;
6213 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6215 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6217 result = (ChessMove) 0;
6219 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6220 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6221 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6222 GameEnds( result, reason, GE_XBOARD );
6226 /* Next absolutely insufficient mating material. */
6227 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6228 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6229 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6230 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6231 { /* KBK, KNK, KK of KBKB with like Bishops */
6233 /* always flag draws, for judging claims */
6234 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6236 if(appData.materialDraws) {
6237 /* but only adjudicate them if adjudication enabled */
6238 SendToProgram("force\n", cps->other); // suppress reply
6239 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6240 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6241 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6246 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6248 ( NrWR == 1 && NrBR == 1 /* KRKR */
6249 || NrWQ==1 && NrBQ==1 /* KQKQ */
6250 || NrWN==2 || NrBN==2 /* KNNK */
6251 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6253 if(--moveCount < 0 && appData.trivialDraws)
6254 { /* if the first 3 moves do not show a tactical win, declare draw */
6255 SendToProgram("force\n", cps->other); // suppress reply
6256 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6257 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6258 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6261 } else moveCount = 6;
6265 if (appData.debugMode) { int i;
6266 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6267 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6268 appData.drawRepeats);
6269 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6270 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6274 /* Check for rep-draws */
6276 for(k = forwardMostMove-2;
6277 k>=backwardMostMove && k>=forwardMostMove-100 &&
6278 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6279 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6282 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6283 /* compare castling rights */
6284 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6285 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6286 rights++; /* King lost rights, while rook still had them */
6287 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6288 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6289 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6290 rights++; /* but at least one rook lost them */
6292 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6293 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6295 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6296 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6297 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6300 if( rights == 0 && ++count > appData.drawRepeats-2
6301 && appData.drawRepeats > 1) {
6302 /* adjudicate after user-specified nr of repeats */
6303 SendToProgram("force\n", cps->other); // suppress reply
6304 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6305 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6306 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6307 // [HGM] xiangqi: check for forbidden perpetuals
6308 int m, ourPerpetual = 1, hisPerpetual = 1;
6309 for(m=forwardMostMove; m>k; m-=2) {
6310 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6311 ourPerpetual = 0; // the current mover did not always check
6312 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6313 hisPerpetual = 0; // the opponent did not always check
6315 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6316 ourPerpetual, hisPerpetual);
6317 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6318 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6319 "Xboard adjudication: perpetual checking", GE_XBOARD );
6322 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6323 break; // (or we would have caught him before). Abort repetition-checking loop.
6324 // Now check for perpetual chases
6325 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6326 hisPerpetual = PerpetualChase(k, forwardMostMove);
6327 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6328 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6329 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6330 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6333 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6334 break; // Abort repetition-checking loop.
6336 // if neither of us is checking or chasing all the time, or both are, it is draw
6338 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6341 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6342 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6346 /* Now we test for 50-move draws. Determine ply count */
6347 count = forwardMostMove;
6348 /* look for last irreversble move */
6349 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6351 /* if we hit starting position, add initial plies */
6352 if( count == backwardMostMove )
6353 count -= initialRulePlies;
6354 count = forwardMostMove - count;
6356 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6357 /* this is used to judge if draw claims are legal */
6358 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6359 SendToProgram("force\n", cps->other); // suppress reply
6360 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6361 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6362 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6366 /* if draw offer is pending, treat it as a draw claim
6367 * when draw condition present, to allow engines a way to
6368 * claim draws before making their move to avoid a race
6369 * condition occurring after their move
6371 if( cps->other->offeredDraw || cps->offeredDraw ) {
6373 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6374 p = "Draw claim: 50-move rule";
6375 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6376 p = "Draw claim: 3-fold repetition";
6377 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6378 p = "Draw claim: insufficient mating material";
6380 SendToProgram("force\n", cps->other); // suppress reply
6381 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6382 GameEnds( GameIsDrawn, p, GE_XBOARD );
6383 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6389 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6390 SendToProgram("force\n", cps->other); // suppress reply
6391 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6392 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6394 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6401 if (gameMode == TwoMachinesPlay) {
6402 /* [HGM] relaying draw offers moved to after reception of move */
6403 /* and interpreting offer as claim if it brings draw condition */
6404 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6405 SendToProgram("draw\n", cps->other);
6407 if (cps->other->sendTime) {
6408 SendTimeRemaining(cps->other,
6409 cps->other->twoMachinesColor[0] == 'w');
6411 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6412 if (firstMove && !bookHit) {
6414 if (cps->other->useColors) {
6415 SendToProgram(cps->other->twoMachinesColor, cps->other);
6417 SendToProgram("go\n", cps->other);
6419 cps->other->maybeThinking = TRUE;
6422 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6424 if (!pausing && appData.ringBellAfterMoves) {
6429 * Reenable menu items that were disabled while
6430 * machine was thinking
6432 if (gameMode != TwoMachinesPlay)
6433 SetUserThinkingEnables();
6435 // [HGM] book: after book hit opponent has received move and is now in force mode
6436 // force the book reply into it, and then fake that it outputted this move by jumping
6437 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6439 static char bookMove[MSG_SIZ]; // a bit generous?
6441 strcpy(bookMove, "move ");
6442 strcat(bookMove, bookHit);
6445 programStats.nodes = programStats.depth = programStats.time =
6446 programStats.score = programStats.got_only_move = 0;
6447 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6449 if(cps->lastPing != cps->lastPong) {
6450 savedMessage = message; // args for deferred call
6452 ScheduleDelayedEvent(DeferredBookMove, 10);
6461 /* Set special modes for chess engines. Later something general
6462 * could be added here; for now there is just one kludge feature,
6463 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6464 * when "xboard" is given as an interactive command.
6466 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6467 cps->useSigint = FALSE;
6468 cps->useSigterm = FALSE;
6470 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6471 ParseFeatures(message+8, cps);
6472 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6475 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6476 * want this, I was asked to put it in, and obliged.
6478 if (!strncmp(message, "setboard ", 9)) {
6479 Board initial_position;
6481 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6483 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6484 DisplayError(_("Bad FEN received from engine"), 0);
6488 CopyBoard(boards[0], initial_position);
6489 initialRulePlies = FENrulePlies;
6490 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6491 else gameMode = MachinePlaysBlack;
6492 DrawPosition(FALSE, boards[currentMove]);
6498 * Look for communication commands
6500 if (!strncmp(message, "telluser ", 9)) {
6501 DisplayNote(message + 9);
6504 if (!strncmp(message, "tellusererror ", 14)) {
6505 DisplayError(message + 14, 0);
6508 if (!strncmp(message, "tellopponent ", 13)) {
6509 if (appData.icsActive) {
6511 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6515 DisplayNote(message + 13);
6519 if (!strncmp(message, "tellothers ", 11)) {
6520 if (appData.icsActive) {
6522 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6528 if (!strncmp(message, "tellall ", 8)) {
6529 if (appData.icsActive) {
6531 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6535 DisplayNote(message + 8);
6539 if (strncmp(message, "warning", 7) == 0) {
6540 /* Undocumented feature, use tellusererror in new code */
6541 DisplayError(message, 0);
6544 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6545 strcpy(realname, cps->tidy);
6546 strcat(realname, " query");
6547 AskQuestion(realname, buf2, buf1, cps->pr);
6550 /* Commands from the engine directly to ICS. We don't allow these to be
6551 * sent until we are logged on. Crafty kibitzes have been known to
6552 * interfere with the login process.
6555 if (!strncmp(message, "tellics ", 8)) {
6556 SendToICS(message + 8);
6560 if (!strncmp(message, "tellicsnoalias ", 15)) {
6561 SendToICS(ics_prefix);
6562 SendToICS(message + 15);
6566 /* The following are for backward compatibility only */
6567 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6568 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6569 SendToICS(ics_prefix);
6575 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6579 * If the move is illegal, cancel it and redraw the board.
6580 * Also deal with other error cases. Matching is rather loose
6581 * here to accommodate engines written before the spec.
6583 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6584 strncmp(message, "Error", 5) == 0) {
6585 if (StrStr(message, "name") ||
6586 StrStr(message, "rating") || StrStr(message, "?") ||
6587 StrStr(message, "result") || StrStr(message, "board") ||
6588 StrStr(message, "bk") || StrStr(message, "computer") ||
6589 StrStr(message, "variant") || StrStr(message, "hint") ||
6590 StrStr(message, "random") || StrStr(message, "depth") ||
6591 StrStr(message, "accepted")) {
6594 if (StrStr(message, "protover")) {
6595 /* Program is responding to input, so it's apparently done
6596 initializing, and this error message indicates it is
6597 protocol version 1. So we don't need to wait any longer
6598 for it to initialize and send feature commands. */
6599 FeatureDone(cps, 1);
6600 cps->protocolVersion = 1;
6603 cps->maybeThinking = FALSE;
6605 if (StrStr(message, "draw")) {
6606 /* Program doesn't have "draw" command */
6607 cps->sendDrawOffers = 0;
6610 if (cps->sendTime != 1 &&
6611 (StrStr(message, "time") || StrStr(message, "otim"))) {
6612 /* Program apparently doesn't have "time" or "otim" command */
6616 if (StrStr(message, "analyze")) {
6617 cps->analysisSupport = FALSE;
6618 cps->analyzing = FALSE;
6620 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6621 DisplayError(buf2, 0);
6624 if (StrStr(message, "(no matching move)st")) {
6625 /* Special kludge for GNU Chess 4 only */
6626 cps->stKludge = TRUE;
6627 SendTimeControl(cps, movesPerSession, timeControl,
6628 timeIncrement, appData.searchDepth,
6632 if (StrStr(message, "(no matching move)sd")) {
6633 /* Special kludge for GNU Chess 4 only */
6634 cps->sdKludge = TRUE;
6635 SendTimeControl(cps, movesPerSession, timeControl,
6636 timeIncrement, appData.searchDepth,
6640 if (!StrStr(message, "llegal")) {
6643 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6644 gameMode == IcsIdle) return;
6645 if (forwardMostMove <= backwardMostMove) return;
6646 if (pausing) PauseEvent();
6647 if(appData.forceIllegal) {
6648 // [HGM] illegal: machine refused move; force position after move into it
6649 SendToProgram("force\n", cps);
6650 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6651 // we have a real problem now, as SendBoard will use the a2a3 kludge
6652 // when black is to move, while there might be nothing on a2 or black
6653 // might already have the move. So send the board as if white has the move.
6654 // But first we must change the stm of the engine, as it refused the last move
6655 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6656 if(WhiteOnMove(forwardMostMove)) {
6657 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6658 SendBoard(cps, forwardMostMove); // kludgeless board
6660 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6661 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6662 SendBoard(cps, forwardMostMove+1); // kludgeless board
6664 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6665 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6666 gameMode == TwoMachinesPlay)
6667 SendToProgram("go\n", cps);
6670 if (gameMode == PlayFromGameFile) {
6671 /* Stop reading this game file */
6672 gameMode = EditGame;
6675 currentMove = --forwardMostMove;
6676 DisplayMove(currentMove-1); /* before DisplayMoveError */
6678 DisplayBothClocks();
6679 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6680 parseList[currentMove], cps->which);
6681 DisplayMoveError(buf1);
6682 DrawPosition(FALSE, boards[currentMove]);
6684 /* [HGM] illegal-move claim should forfeit game when Xboard */
6685 /* only passes fully legal moves */
6686 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6687 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6688 "False illegal-move claim", GE_XBOARD );
6692 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6693 /* Program has a broken "time" command that
6694 outputs a string not ending in newline.
6700 * If chess program startup fails, exit with an error message.
6701 * Attempts to recover here are futile.
6703 if ((StrStr(message, "unknown host") != NULL)
6704 || (StrStr(message, "No remote directory") != NULL)
6705 || (StrStr(message, "not found") != NULL)
6706 || (StrStr(message, "No such file") != NULL)
6707 || (StrStr(message, "can't alloc") != NULL)
6708 || (StrStr(message, "Permission denied") != NULL)) {
6710 cps->maybeThinking = FALSE;
6711 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6712 cps->which, cps->program, cps->host, message);
6713 RemoveInputSource(cps->isr);
6714 DisplayFatalError(buf1, 0, 1);
6719 * Look for hint output
6721 if (sscanf(message, "Hint: %s", buf1) == 1) {
6722 if (cps == &first && hintRequested) {
6723 hintRequested = FALSE;
6724 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6725 &fromX, &fromY, &toX, &toY, &promoChar)) {
6726 (void) CoordsToAlgebraic(boards[forwardMostMove],
6727 PosFlags(forwardMostMove),
6728 fromY, fromX, toY, toX, promoChar, buf1);
6729 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6730 DisplayInformation(buf2);
6732 /* Hint move could not be parsed!? */
6733 snprintf(buf2, sizeof(buf2),
6734 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6736 DisplayError(buf2, 0);
6739 strcpy(lastHint, buf1);
6745 * Ignore other messages if game is not in progress
6747 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6748 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6751 * look for win, lose, draw, or draw offer
6753 if (strncmp(message, "1-0", 3) == 0) {
6754 char *p, *q, *r = "";
6755 p = strchr(message, '{');
6763 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6765 } else if (strncmp(message, "0-1", 3) == 0) {
6766 char *p, *q, *r = "";
6767 p = strchr(message, '{');
6775 /* Kludge for Arasan 4.1 bug */
6776 if (strcmp(r, "Black resigns") == 0) {
6777 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6780 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6782 } else if (strncmp(message, "1/2", 3) == 0) {
6783 char *p, *q, *r = "";
6784 p = strchr(message, '{');
6793 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6796 } else if (strncmp(message, "White resign", 12) == 0) {
6797 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6799 } else if (strncmp(message, "Black resign", 12) == 0) {
6800 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6802 } else if (strncmp(message, "White matches", 13) == 0 ||
6803 strncmp(message, "Black matches", 13) == 0 ) {
6804 /* [HGM] ignore GNUShogi noises */
6806 } else if (strncmp(message, "White", 5) == 0 &&
6807 message[5] != '(' &&
6808 StrStr(message, "Black") == NULL) {
6809 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6811 } else if (strncmp(message, "Black", 5) == 0 &&
6812 message[5] != '(') {
6813 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6815 } else if (strcmp(message, "resign") == 0 ||
6816 strcmp(message, "computer resigns") == 0) {
6818 case MachinePlaysBlack:
6819 case IcsPlayingBlack:
6820 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6822 case MachinePlaysWhite:
6823 case IcsPlayingWhite:
6824 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6826 case TwoMachinesPlay:
6827 if (cps->twoMachinesColor[0] == 'w')
6828 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6830 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6837 } else if (strncmp(message, "opponent mates", 14) == 0) {
6839 case MachinePlaysBlack:
6840 case IcsPlayingBlack:
6841 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6843 case MachinePlaysWhite:
6844 case IcsPlayingWhite:
6845 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6847 case TwoMachinesPlay:
6848 if (cps->twoMachinesColor[0] == 'w')
6849 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6851 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6858 } else if (strncmp(message, "computer mates", 14) == 0) {
6860 case MachinePlaysBlack:
6861 case IcsPlayingBlack:
6862 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6864 case MachinePlaysWhite:
6865 case IcsPlayingWhite:
6866 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6868 case TwoMachinesPlay:
6869 if (cps->twoMachinesColor[0] == 'w')
6870 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6872 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6879 } else if (strncmp(message, "checkmate", 9) == 0) {
6880 if (WhiteOnMove(forwardMostMove)) {
6881 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6883 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6886 } else if (strstr(message, "Draw") != NULL ||
6887 strstr(message, "game is a draw") != NULL) {
6888 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6890 } else if (strstr(message, "offer") != NULL &&
6891 strstr(message, "draw") != NULL) {
6893 if (appData.zippyPlay && first.initDone) {
6894 /* Relay offer to ICS */
6895 SendToICS(ics_prefix);
6896 SendToICS("draw\n");
6899 cps->offeredDraw = 2; /* valid until this engine moves twice */
6900 if (gameMode == TwoMachinesPlay) {
6901 if (cps->other->offeredDraw) {
6902 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6903 /* [HGM] in two-machine mode we delay relaying draw offer */
6904 /* until after we also have move, to see if it is really claim */
6906 } else if (gameMode == MachinePlaysWhite ||
6907 gameMode == MachinePlaysBlack) {
6908 if (userOfferedDraw) {
6909 DisplayInformation(_("Machine accepts your draw offer"));
6910 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6912 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6919 * Look for thinking output
6921 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6922 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6924 int plylev, mvleft, mvtot, curscore, time;
6925 char mvname[MOVE_LEN];
6929 int prefixHint = FALSE;
6930 mvname[0] = NULLCHAR;
6933 case MachinePlaysBlack:
6934 case IcsPlayingBlack:
6935 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6937 case MachinePlaysWhite:
6938 case IcsPlayingWhite:
6939 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6944 case IcsObserving: /* [DM] icsEngineAnalyze */
6945 if (!appData.icsEngineAnalyze) ignore = TRUE;
6947 case TwoMachinesPlay:
6948 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6959 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6960 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6962 if (plyext != ' ' && plyext != '\t') {
6966 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6967 if( cps->scoreIsAbsolute &&
6968 ( gameMode == MachinePlaysBlack ||
6969 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6970 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6971 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6972 !WhiteOnMove(currentMove)
6975 curscore = -curscore;
6979 programStats.depth = plylev;
6980 programStats.nodes = nodes;
6981 programStats.time = time;
6982 programStats.score = curscore;
6983 programStats.got_only_move = 0;
6985 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6988 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6989 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6990 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6991 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6992 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6993 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6994 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6995 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6998 /* Buffer overflow protection */
6999 if (buf1[0] != NULLCHAR) {
7000 if (strlen(buf1) >= sizeof(programStats.movelist)
7001 && appData.debugMode) {
7003 "PV is too long; using the first %u bytes.\n",
7004 (unsigned) sizeof(programStats.movelist) - 1);
7007 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7009 sprintf(programStats.movelist, " no PV\n");
7012 if (programStats.seen_stat) {
7013 programStats.ok_to_send = 1;
7016 if (strchr(programStats.movelist, '(') != NULL) {
7017 programStats.line_is_book = 1;
7018 programStats.nr_moves = 0;
7019 programStats.moves_left = 0;
7021 programStats.line_is_book = 0;
7024 SendProgramStatsToFrontend( cps, &programStats );
7027 [AS] Protect the thinkOutput buffer from overflow... this
7028 is only useful if buf1 hasn't overflowed first!
7030 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7032 (gameMode == TwoMachinesPlay ?
7033 ToUpper(cps->twoMachinesColor[0]) : ' '),
7034 ((double) curscore) / 100.0,
7035 prefixHint ? lastHint : "",
7036 prefixHint ? " " : "" );
7038 if( buf1[0] != NULLCHAR ) {
7039 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7041 if( strlen(buf1) > max_len ) {
7042 if( appData.debugMode) {
7043 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7045 buf1[max_len+1] = '\0';
7048 strcat( thinkOutput, buf1 );
7051 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7052 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7053 DisplayMove(currentMove - 1);
7057 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7058 /* crafty (9.25+) says "(only move) <move>"
7059 * if there is only 1 legal move
7061 sscanf(p, "(only move) %s", buf1);
7062 sprintf(thinkOutput, "%s (only move)", buf1);
7063 sprintf(programStats.movelist, "%s (only move)", buf1);
7064 programStats.depth = 1;
7065 programStats.nr_moves = 1;
7066 programStats.moves_left = 1;
7067 programStats.nodes = 1;
7068 programStats.time = 1;
7069 programStats.got_only_move = 1;
7071 /* Not really, but we also use this member to
7072 mean "line isn't going to change" (Crafty
7073 isn't searching, so stats won't change) */
7074 programStats.line_is_book = 1;
7076 SendProgramStatsToFrontend( cps, &programStats );
7078 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7079 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7080 DisplayMove(currentMove - 1);
7083 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7084 &time, &nodes, &plylev, &mvleft,
7085 &mvtot, mvname) >= 5) {
7086 /* The stat01: line is from Crafty (9.29+) in response
7087 to the "." command */
7088 programStats.seen_stat = 1;
7089 cps->maybeThinking = TRUE;
7091 if (programStats.got_only_move || !appData.periodicUpdates)
7094 programStats.depth = plylev;
7095 programStats.time = time;
7096 programStats.nodes = nodes;
7097 programStats.moves_left = mvleft;
7098 programStats.nr_moves = mvtot;
7099 strcpy(programStats.move_name, mvname);
7100 programStats.ok_to_send = 1;
7101 programStats.movelist[0] = '\0';
7103 SendProgramStatsToFrontend( cps, &programStats );
7107 } else if (strncmp(message,"++",2) == 0) {
7108 /* Crafty 9.29+ outputs this */
7109 programStats.got_fail = 2;
7112 } else if (strncmp(message,"--",2) == 0) {
7113 /* Crafty 9.29+ outputs this */
7114 programStats.got_fail = 1;
7117 } else if (thinkOutput[0] != NULLCHAR &&
7118 strncmp(message, " ", 4) == 0) {
7119 unsigned message_len;
7122 while (*p && *p == ' ') p++;
7124 message_len = strlen( p );
7126 /* [AS] Avoid buffer overflow */
7127 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7128 strcat(thinkOutput, " ");
7129 strcat(thinkOutput, p);
7132 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7133 strcat(programStats.movelist, " ");
7134 strcat(programStats.movelist, p);
7137 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7138 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7139 DisplayMove(currentMove - 1);
7147 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7148 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7150 ChessProgramStats cpstats;
7152 if (plyext != ' ' && plyext != '\t') {
7156 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7157 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7158 curscore = -curscore;
7161 cpstats.depth = plylev;
7162 cpstats.nodes = nodes;
7163 cpstats.time = time;
7164 cpstats.score = curscore;
7165 cpstats.got_only_move = 0;
7166 cpstats.movelist[0] = '\0';
7168 if (buf1[0] != NULLCHAR) {
7169 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7172 cpstats.ok_to_send = 0;
7173 cpstats.line_is_book = 0;
7174 cpstats.nr_moves = 0;
7175 cpstats.moves_left = 0;
7177 SendProgramStatsToFrontend( cps, &cpstats );
7184 /* Parse a game score from the character string "game", and
7185 record it as the history of the current game. The game
7186 score is NOT assumed to start from the standard position.
7187 The display is not updated in any way.
7190 ParseGameHistory(game)
7194 int fromX, fromY, toX, toY, boardIndex;
7199 if (appData.debugMode)
7200 fprintf(debugFP, "Parsing game history: %s\n", game);
7202 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7203 gameInfo.site = StrSave(appData.icsHost);
7204 gameInfo.date = PGNDate();
7205 gameInfo.round = StrSave("-");
7207 /* Parse out names of players */
7208 while (*game == ' ') game++;
7210 while (*game != ' ') *p++ = *game++;
7212 gameInfo.white = StrSave(buf);
7213 while (*game == ' ') game++;
7215 while (*game != ' ' && *game != '\n') *p++ = *game++;
7217 gameInfo.black = StrSave(buf);
7220 boardIndex = blackPlaysFirst ? 1 : 0;
7223 yyboardindex = boardIndex;
7224 moveType = (ChessMove) yylex();
7226 case IllegalMove: /* maybe suicide chess, etc. */
7227 if (appData.debugMode) {
7228 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7229 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7230 setbuf(debugFP, NULL);
7232 case WhitePromotionChancellor:
7233 case BlackPromotionChancellor:
7234 case WhitePromotionArchbishop:
7235 case BlackPromotionArchbishop:
7236 case WhitePromotionQueen:
7237 case BlackPromotionQueen:
7238 case WhitePromotionRook:
7239 case BlackPromotionRook:
7240 case WhitePromotionBishop:
7241 case BlackPromotionBishop:
7242 case WhitePromotionKnight:
7243 case BlackPromotionKnight:
7244 case WhitePromotionKing:
7245 case BlackPromotionKing:
7247 case WhiteCapturesEnPassant:
7248 case BlackCapturesEnPassant:
7249 case WhiteKingSideCastle:
7250 case WhiteQueenSideCastle:
7251 case BlackKingSideCastle:
7252 case BlackQueenSideCastle:
7253 case WhiteKingSideCastleWild:
7254 case WhiteQueenSideCastleWild:
7255 case BlackKingSideCastleWild:
7256 case BlackQueenSideCastleWild:
7258 case WhiteHSideCastleFR:
7259 case WhiteASideCastleFR:
7260 case BlackHSideCastleFR:
7261 case BlackASideCastleFR:
7263 fromX = currentMoveString[0] - AAA;
7264 fromY = currentMoveString[1] - ONE;
7265 toX = currentMoveString[2] - AAA;
7266 toY = currentMoveString[3] - ONE;
7267 promoChar = currentMoveString[4];
7271 fromX = moveType == WhiteDrop ?
7272 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7273 (int) CharToPiece(ToLower(currentMoveString[0]));
7275 toX = currentMoveString[2] - AAA;
7276 toY = currentMoveString[3] - ONE;
7277 promoChar = NULLCHAR;
7281 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7282 if (appData.debugMode) {
7283 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7284 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7285 setbuf(debugFP, NULL);
7287 DisplayError(buf, 0);
7289 case ImpossibleMove:
7291 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7292 if (appData.debugMode) {
7293 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7294 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7295 setbuf(debugFP, NULL);
7297 DisplayError(buf, 0);
7299 case (ChessMove) 0: /* end of file */
7300 if (boardIndex < backwardMostMove) {
7301 /* Oops, gap. How did that happen? */
7302 DisplayError(_("Gap in move list"), 0);
7305 backwardMostMove = blackPlaysFirst ? 1 : 0;
7306 if (boardIndex > forwardMostMove) {
7307 forwardMostMove = boardIndex;
7311 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7312 strcat(parseList[boardIndex-1], " ");
7313 strcat(parseList[boardIndex-1], yy_text);
7325 case GameUnfinished:
7326 if (gameMode == IcsExamining) {
7327 if (boardIndex < backwardMostMove) {
7328 /* Oops, gap. How did that happen? */
7331 backwardMostMove = blackPlaysFirst ? 1 : 0;
7334 gameInfo.result = moveType;
7335 p = strchr(yy_text, '{');
7336 if (p == NULL) p = strchr(yy_text, '(');
7339 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7341 q = strchr(p, *p == '{' ? '}' : ')');
7342 if (q != NULL) *q = NULLCHAR;
7345 gameInfo.resultDetails = StrSave(p);
7348 if (boardIndex >= forwardMostMove &&
7349 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7350 backwardMostMove = blackPlaysFirst ? 1 : 0;
7353 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7354 fromY, fromX, toY, toX, promoChar,
7355 parseList[boardIndex]);
7356 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7357 /* currentMoveString is set as a side-effect of yylex */
7358 strcpy(moveList[boardIndex], currentMoveString);
7359 strcat(moveList[boardIndex], "\n");
7361 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7362 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7368 if(gameInfo.variant != VariantShogi)
7369 strcat(parseList[boardIndex - 1], "+");
7373 strcat(parseList[boardIndex - 1], "#");
7380 /* Apply a move to the given board */
7382 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7383 int fromX, fromY, toX, toY;
7387 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7389 /* [HGM] compute & store e.p. status and castling rights for new position */
7390 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7393 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7394 oldEP = (signed char)board[EP_STATUS];
7395 board[EP_STATUS] = EP_NONE;
7397 if( board[toY][toX] != EmptySquare )
7398 board[EP_STATUS] = EP_CAPTURE;
7400 if( board[fromY][fromX] == WhitePawn ) {
7401 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7402 board[EP_STATUS] = EP_PAWN_MOVE;
7404 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7405 gameInfo.variant != VariantBerolina || toX < fromX)
7406 board[EP_STATUS] = toX | berolina;
7407 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7408 gameInfo.variant != VariantBerolina || toX > fromX)
7409 board[EP_STATUS] = toX;
7412 if( board[fromY][fromX] == BlackPawn ) {
7413 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7414 board[EP_STATUS] = EP_PAWN_MOVE;
7415 if( toY-fromY== -2) {
7416 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7417 gameInfo.variant != VariantBerolina || toX < fromX)
7418 board[EP_STATUS] = toX | berolina;
7419 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7420 gameInfo.variant != VariantBerolina || toX > fromX)
7421 board[EP_STATUS] = toX;
7425 for(i=0; i<nrCastlingRights; i++) {
7426 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7427 board[CASTLING][i] == toX && castlingRank[i] == toY
7428 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7433 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7434 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7435 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7437 if (fromX == toX && fromY == toY) return;
7439 if (fromY == DROP_RANK) {
7441 piece = board[toY][toX] = (ChessSquare) fromX;
7443 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7444 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7445 if(gameInfo.variant == VariantKnightmate)
7446 king += (int) WhiteUnicorn - (int) WhiteKing;
7448 /* Code added by Tord: */
7449 /* FRC castling assumed when king captures friendly rook. */
7450 if (board[fromY][fromX] == WhiteKing &&
7451 board[toY][toX] == WhiteRook) {
7452 board[fromY][fromX] = EmptySquare;
7453 board[toY][toX] = EmptySquare;
7455 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7457 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7459 } else if (board[fromY][fromX] == BlackKing &&
7460 board[toY][toX] == BlackRook) {
7461 board[fromY][fromX] = EmptySquare;
7462 board[toY][toX] = EmptySquare;
7464 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7466 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7468 /* End of code added by Tord */
7470 } else if (board[fromY][fromX] == king
7471 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7472 && toY == fromY && toX > fromX+1) {
7473 board[fromY][fromX] = EmptySquare;
7474 board[toY][toX] = king;
7475 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7476 board[fromY][BOARD_RGHT-1] = EmptySquare;
7477 } else if (board[fromY][fromX] == king
7478 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7479 && toY == fromY && toX < fromX-1) {
7480 board[fromY][fromX] = EmptySquare;
7481 board[toY][toX] = king;
7482 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7483 board[fromY][BOARD_LEFT] = EmptySquare;
7484 } else if (board[fromY][fromX] == WhitePawn
7485 && toY == BOARD_HEIGHT-1
7486 && gameInfo.variant != VariantXiangqi
7488 /* white pawn promotion */
7489 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7490 if (board[toY][toX] == EmptySquare) {
7491 board[toY][toX] = WhiteQueen;
7493 if(gameInfo.variant==VariantBughouse ||
7494 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7495 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7496 board[fromY][fromX] = EmptySquare;
7497 } else if ((fromY == BOARD_HEIGHT-4)
7499 && gameInfo.variant != VariantXiangqi
7500 && gameInfo.variant != VariantBerolina
7501 && (board[fromY][fromX] == WhitePawn)
7502 && (board[toY][toX] == EmptySquare)) {
7503 board[fromY][fromX] = EmptySquare;
7504 board[toY][toX] = WhitePawn;
7505 captured = board[toY - 1][toX];
7506 board[toY - 1][toX] = EmptySquare;
7507 } else if ((fromY == BOARD_HEIGHT-4)
7509 && gameInfo.variant == VariantBerolina
7510 && (board[fromY][fromX] == WhitePawn)
7511 && (board[toY][toX] == EmptySquare)) {
7512 board[fromY][fromX] = EmptySquare;
7513 board[toY][toX] = WhitePawn;
7514 if(oldEP & EP_BEROLIN_A) {
7515 captured = board[fromY][fromX-1];
7516 board[fromY][fromX-1] = EmptySquare;
7517 }else{ captured = board[fromY][fromX+1];
7518 board[fromY][fromX+1] = EmptySquare;
7520 } else if (board[fromY][fromX] == king
7521 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7522 && toY == fromY && toX > fromX+1) {
7523 board[fromY][fromX] = EmptySquare;
7524 board[toY][toX] = king;
7525 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7526 board[fromY][BOARD_RGHT-1] = EmptySquare;
7527 } else if (board[fromY][fromX] == king
7528 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7529 && toY == fromY && toX < fromX-1) {
7530 board[fromY][fromX] = EmptySquare;
7531 board[toY][toX] = king;
7532 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7533 board[fromY][BOARD_LEFT] = EmptySquare;
7534 } else if (fromY == 7 && fromX == 3
7535 && board[fromY][fromX] == BlackKing
7536 && toY == 7 && toX == 5) {
7537 board[fromY][fromX] = EmptySquare;
7538 board[toY][toX] = BlackKing;
7539 board[fromY][7] = EmptySquare;
7540 board[toY][4] = BlackRook;
7541 } else if (fromY == 7 && fromX == 3
7542 && board[fromY][fromX] == BlackKing
7543 && toY == 7 && toX == 1) {
7544 board[fromY][fromX] = EmptySquare;
7545 board[toY][toX] = BlackKing;
7546 board[fromY][0] = EmptySquare;
7547 board[toY][2] = BlackRook;
7548 } else if (board[fromY][fromX] == BlackPawn
7550 && gameInfo.variant != VariantXiangqi
7552 /* black pawn promotion */
7553 board[0][toX] = CharToPiece(ToLower(promoChar));
7554 if (board[0][toX] == EmptySquare) {
7555 board[0][toX] = BlackQueen;
7557 if(gameInfo.variant==VariantBughouse ||
7558 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7559 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7560 board[fromY][fromX] = EmptySquare;
7561 } else if ((fromY == 3)
7563 && gameInfo.variant != VariantXiangqi
7564 && gameInfo.variant != VariantBerolina
7565 && (board[fromY][fromX] == BlackPawn)
7566 && (board[toY][toX] == EmptySquare)) {
7567 board[fromY][fromX] = EmptySquare;
7568 board[toY][toX] = BlackPawn;
7569 captured = board[toY + 1][toX];
7570 board[toY + 1][toX] = EmptySquare;
7571 } else if ((fromY == 3)
7573 && gameInfo.variant == VariantBerolina
7574 && (board[fromY][fromX] == BlackPawn)
7575 && (board[toY][toX] == EmptySquare)) {
7576 board[fromY][fromX] = EmptySquare;
7577 board[toY][toX] = BlackPawn;
7578 if(oldEP & EP_BEROLIN_A) {
7579 captured = board[fromY][fromX-1];
7580 board[fromY][fromX-1] = EmptySquare;
7581 }else{ captured = board[fromY][fromX+1];
7582 board[fromY][fromX+1] = EmptySquare;
7585 board[toY][toX] = board[fromY][fromX];
7586 board[fromY][fromX] = EmptySquare;
7589 /* [HGM] now we promote for Shogi, if needed */
7590 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7591 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7594 if (gameInfo.holdingsWidth != 0) {
7596 /* !!A lot more code needs to be written to support holdings */
7597 /* [HGM] OK, so I have written it. Holdings are stored in the */
7598 /* penultimate board files, so they are automaticlly stored */
7599 /* in the game history. */
7600 if (fromY == DROP_RANK) {
7601 /* Delete from holdings, by decreasing count */
7602 /* and erasing image if necessary */
7604 if(p < (int) BlackPawn) { /* white drop */
7605 p -= (int)WhitePawn;
7606 p = PieceToNumber((ChessSquare)p);
7607 if(p >= gameInfo.holdingsSize) p = 0;
7608 if(--board[p][BOARD_WIDTH-2] <= 0)
7609 board[p][BOARD_WIDTH-1] = EmptySquare;
7610 if((int)board[p][BOARD_WIDTH-2] < 0)
7611 board[p][BOARD_WIDTH-2] = 0;
7612 } else { /* black drop */
7613 p -= (int)BlackPawn;
7614 p = PieceToNumber((ChessSquare)p);
7615 if(p >= gameInfo.holdingsSize) p = 0;
7616 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7617 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7618 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7619 board[BOARD_HEIGHT-1-p][1] = 0;
7622 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7623 && gameInfo.variant != VariantBughouse ) {
7624 /* [HGM] holdings: Add to holdings, if holdings exist */
7625 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7626 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7627 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7630 if (p >= (int) BlackPawn) {
7631 p -= (int)BlackPawn;
7632 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7633 /* in Shogi restore piece to its original first */
7634 captured = (ChessSquare) (DEMOTED captured);
7637 p = PieceToNumber((ChessSquare)p);
7638 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7639 board[p][BOARD_WIDTH-2]++;
7640 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7642 p -= (int)WhitePawn;
7643 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7644 captured = (ChessSquare) (DEMOTED captured);
7647 p = PieceToNumber((ChessSquare)p);
7648 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7649 board[BOARD_HEIGHT-1-p][1]++;
7650 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7653 } else if (gameInfo.variant == VariantAtomic) {
7654 if (captured != EmptySquare) {
7656 for (y = toY-1; y <= toY+1; y++) {
7657 for (x = toX-1; x <= toX+1; x++) {
7658 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7659 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7660 board[y][x] = EmptySquare;
7664 board[toY][toX] = EmptySquare;
7667 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7668 /* [HGM] Shogi promotions */
7669 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7672 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7673 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7674 // [HGM] superchess: take promotion piece out of holdings
7675 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7676 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7677 if(!--board[k][BOARD_WIDTH-2])
7678 board[k][BOARD_WIDTH-1] = EmptySquare;
7680 if(!--board[BOARD_HEIGHT-1-k][1])
7681 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7687 /* Updates forwardMostMove */
7689 MakeMove(fromX, fromY, toX, toY, promoChar)
7690 int fromX, fromY, toX, toY;
7693 // forwardMostMove++; // [HGM] bare: moved downstream
7695 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7696 int timeLeft; static int lastLoadFlag=0; int king, piece;
7697 piece = boards[forwardMostMove][fromY][fromX];
7698 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7699 if(gameInfo.variant == VariantKnightmate)
7700 king += (int) WhiteUnicorn - (int) WhiteKing;
7701 if(forwardMostMove == 0) {
7703 fprintf(serverMoves, "%s;", second.tidy);
7704 fprintf(serverMoves, "%s;", first.tidy);
7705 if(!blackPlaysFirst)
7706 fprintf(serverMoves, "%s;", second.tidy);
7707 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7708 lastLoadFlag = loadFlag;
7710 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7711 // print castling suffix
7712 if( toY == fromY && piece == king ) {
7714 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7716 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7719 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7720 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7721 boards[forwardMostMove][toY][toX] == EmptySquare
7723 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7725 if(promoChar != NULLCHAR)
7726 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7728 fprintf(serverMoves, "/%d/%d",
7729 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7730 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7731 else timeLeft = blackTimeRemaining/1000;
7732 fprintf(serverMoves, "/%d", timeLeft);
7734 fflush(serverMoves);
7737 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7738 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7742 if (commentList[forwardMostMove+1] != NULL) {
7743 free(commentList[forwardMostMove+1]);
7744 commentList[forwardMostMove+1] = NULL;
7746 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7747 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7748 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7749 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7750 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7751 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7752 gameInfo.result = GameUnfinished;
7753 if (gameInfo.resultDetails != NULL) {
7754 free(gameInfo.resultDetails);
7755 gameInfo.resultDetails = NULL;
7757 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7758 moveList[forwardMostMove - 1]);
7759 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7760 PosFlags(forwardMostMove - 1),
7761 fromY, fromX, toY, toX, promoChar,
7762 parseList[forwardMostMove - 1]);
7763 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7769 if(gameInfo.variant != VariantShogi)
7770 strcat(parseList[forwardMostMove - 1], "+");
7774 strcat(parseList[forwardMostMove - 1], "#");
7777 if (appData.debugMode) {
7778 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7783 /* Updates currentMove if not pausing */
7785 ShowMove(fromX, fromY, toX, toY)
7787 int instant = (gameMode == PlayFromGameFile) ?
7788 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7790 if(appData.noGUI) return;
7792 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7796 if (forwardMostMove == currentMove + 1)
7799 // AnimateMove(boards[forwardMostMove - 1],
7800 // fromX, fromY, toX, toY);
7802 if (appData.highlightLastMove)
7804 SetHighlights(fromX, fromY, toX, toY);
7807 currentMove = forwardMostMove;
7810 if (instant) return;
7812 DisplayMove(currentMove - 1);
7813 DrawPosition(FALSE, boards[currentMove]);
7814 DisplayBothClocks();
7815 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7820 void SendEgtPath(ChessProgramState *cps)
7821 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7822 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7824 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7827 char c, *q = name+1, *r, *s;
7829 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7830 while(*p && *p != ',') *q++ = *p++;
7832 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7833 strcmp(name, ",nalimov:") == 0 ) {
7834 // take nalimov path from the menu-changeable option first, if it is defined
7835 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7836 SendToProgram(buf,cps); // send egtbpath command for nalimov
7838 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7839 (s = StrStr(appData.egtFormats, name)) != NULL) {
7840 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7841 s = r = StrStr(s, ":") + 1; // beginning of path info
7842 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7843 c = *r; *r = 0; // temporarily null-terminate path info
7844 *--q = 0; // strip of trailig ':' from name
7845 sprintf(buf, "egtpath %s %s\n", name+1, s);
7847 SendToProgram(buf,cps); // send egtbpath command for this format
7849 if(*p == ',') p++; // read away comma to position for next format name
7854 InitChessProgram(cps, setup)
7855 ChessProgramState *cps;
7856 int setup; /* [HGM] needed to setup FRC opening position */
7858 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7859 if (appData.noChessProgram) return;
7860 hintRequested = FALSE;
7861 bookRequested = FALSE;
7863 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7864 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7865 if(cps->memSize) { /* [HGM] memory */
7866 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7867 SendToProgram(buf, cps);
7869 SendEgtPath(cps); /* [HGM] EGT */
7870 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7871 sprintf(buf, "cores %d\n", appData.smpCores);
7872 SendToProgram(buf, cps);
7875 SendToProgram(cps->initString, cps);
7876 if (gameInfo.variant != VariantNormal &&
7877 gameInfo.variant != VariantLoadable
7878 /* [HGM] also send variant if board size non-standard */
7879 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7881 char *v = VariantName(gameInfo.variant);
7882 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7883 /* [HGM] in protocol 1 we have to assume all variants valid */
7884 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7885 DisplayFatalError(buf, 0, 1);
7889 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7890 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7891 if( gameInfo.variant == VariantXiangqi )
7892 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7893 if( gameInfo.variant == VariantShogi )
7894 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7895 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7896 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7897 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7898 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7899 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7900 if( gameInfo.variant == VariantCourier )
7901 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7902 if( gameInfo.variant == VariantSuper )
7903 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7904 if( gameInfo.variant == VariantGreat )
7905 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7908 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7909 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7910 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7911 if(StrStr(cps->variants, b) == NULL) {
7912 // specific sized variant not known, check if general sizing allowed
7913 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7914 if(StrStr(cps->variants, "boardsize") == NULL) {
7915 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7916 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7917 DisplayFatalError(buf, 0, 1);
7920 /* [HGM] here we really should compare with the maximum supported board size */
7923 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7924 sprintf(buf, "variant %s\n", b);
7925 SendToProgram(buf, cps);
7927 currentlyInitializedVariant = gameInfo.variant;
7929 /* [HGM] send opening position in FRC to first engine */
7931 SendToProgram("force\n", cps);
7933 /* engine is now in force mode! Set flag to wake it up after first move. */
7934 setboardSpoiledMachineBlack = 1;
7938 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7939 SendToProgram(buf, cps);
7941 cps->maybeThinking = FALSE;
7942 cps->offeredDraw = 0;
7943 if (!appData.icsActive) {
7944 SendTimeControl(cps, movesPerSession, timeControl,
7945 timeIncrement, appData.searchDepth,
7948 if (appData.showThinking
7949 // [HGM] thinking: four options require thinking output to be sent
7950 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7952 SendToProgram("post\n", cps);
7954 SendToProgram("hard\n", cps);
7955 if (!appData.ponderNextMove) {
7956 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7957 it without being sure what state we are in first. "hard"
7958 is not a toggle, so that one is OK.
7960 SendToProgram("easy\n", cps);
7963 sprintf(buf, "ping %d\n", ++cps->lastPing);
7964 SendToProgram(buf, cps);
7966 cps->initDone = TRUE;
7971 StartChessProgram(cps)
7972 ChessProgramState *cps;
7977 if (appData.noChessProgram) return;
7978 cps->initDone = FALSE;
7980 if (strcmp(cps->host, "localhost") == 0) {
7981 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7982 } else if (*appData.remoteShell == NULLCHAR) {
7983 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7985 if (*appData.remoteUser == NULLCHAR) {
7986 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7989 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7990 cps->host, appData.remoteUser, cps->program);
7992 err = StartChildProcess(buf, "", &cps->pr);
7996 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7997 DisplayFatalError(buf, err, 1);
8003 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8004 if (cps->protocolVersion > 1) {
8005 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8006 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8007 cps->comboCnt = 0; // and values of combo boxes
8008 SendToProgram(buf, cps);
8010 SendToProgram("xboard\n", cps);
8016 TwoMachinesEventIfReady P((void))
8018 if (first.lastPing != first.lastPong) {
8019 DisplayMessage("", _("Waiting for first chess program"));
8020 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8023 if (second.lastPing != second.lastPong) {
8024 DisplayMessage("", _("Waiting for second chess program"));
8025 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8033 NextMatchGame P((void))
8035 int index; /* [HGM] autoinc: step load index during match */
8037 if (*appData.loadGameFile != NULLCHAR) {
8038 index = appData.loadGameIndex;
8039 if(index < 0) { // [HGM] autoinc
8040 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8041 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8043 LoadGameFromFile(appData.loadGameFile,
8045 appData.loadGameFile, FALSE);
8046 } else if (*appData.loadPositionFile != NULLCHAR) {
8047 index = appData.loadPositionIndex;
8048 if(index < 0) { // [HGM] autoinc
8049 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8050 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8052 LoadPositionFromFile(appData.loadPositionFile,
8054 appData.loadPositionFile);
8056 TwoMachinesEventIfReady();
8059 void UserAdjudicationEvent( int result )
8061 ChessMove gameResult = GameIsDrawn;
8064 gameResult = WhiteWins;
8066 else if( result < 0 ) {
8067 gameResult = BlackWins;
8070 if( gameMode == TwoMachinesPlay ) {
8071 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8076 // [HGM] save: calculate checksum of game to make games easily identifiable
8077 int StringCheckSum(char *s)
8080 if(s==NULL) return 0;
8081 while(*s) i = i*259 + *s++;
8088 for(i=backwardMostMove; i<forwardMostMove; i++) {
8089 sum += pvInfoList[i].depth;
8090 sum += StringCheckSum(parseList[i]);
8091 sum += StringCheckSum(commentList[i]);
8094 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8095 return sum + StringCheckSum(commentList[i]);
8096 } // end of save patch
8099 GameEnds(result, resultDetails, whosays)
8101 char *resultDetails;
8104 GameMode nextGameMode;
8108 if(endingGame) return; /* [HGM] crash: forbid recursion */
8111 if (appData.debugMode) {
8112 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8113 result, resultDetails ? resultDetails : "(null)", whosays);
8116 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8117 /* If we are playing on ICS, the server decides when the
8118 game is over, but the engine can offer to draw, claim
8122 if (appData.zippyPlay && first.initDone) {
8123 if (result == GameIsDrawn) {
8124 /* In case draw still needs to be claimed */
8125 SendToICS(ics_prefix);
8126 SendToICS("draw\n");
8127 } else if (StrCaseStr(resultDetails, "resign")) {
8128 SendToICS(ics_prefix);
8129 SendToICS("resign\n");
8133 endingGame = 0; /* [HGM] crash */
8137 /* If we're loading the game from a file, stop */
8138 if (whosays == GE_FILE) {
8139 (void) StopLoadGameTimer();
8143 /* Cancel draw offers */
8144 first.offeredDraw = second.offeredDraw = 0;
8146 /* If this is an ICS game, only ICS can really say it's done;
8147 if not, anyone can. */
8148 isIcsGame = (gameMode == IcsPlayingWhite ||
8149 gameMode == IcsPlayingBlack ||
8150 gameMode == IcsObserving ||
8151 gameMode == IcsExamining);
8153 if (!isIcsGame || whosays == GE_ICS) {
8154 /* OK -- not an ICS game, or ICS said it was done */
8156 if (!isIcsGame && !appData.noChessProgram)
8157 SetUserThinkingEnables();
8159 /* [HGM] if a machine claims the game end we verify this claim */
8160 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8161 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8163 ChessMove trueResult = (ChessMove) -1;
8165 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8166 first.twoMachinesColor[0] :
8167 second.twoMachinesColor[0] ;
8169 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8170 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8171 /* [HGM] verify: engine mate claims accepted if they were flagged */
8172 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8174 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8175 /* [HGM] verify: engine mate claims accepted if they were flagged */
8176 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8178 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8179 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8182 // now verify win claims, but not in drop games, as we don't understand those yet
8183 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8184 || gameInfo.variant == VariantGreat) &&
8185 (result == WhiteWins && claimer == 'w' ||
8186 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8187 if (appData.debugMode) {
8188 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8189 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8191 if(result != trueResult) {
8192 sprintf(buf, "False win claim: '%s'", resultDetails);
8193 result = claimer == 'w' ? BlackWins : WhiteWins;
8194 resultDetails = buf;
8197 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8198 && (forwardMostMove <= backwardMostMove ||
8199 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8200 (claimer=='b')==(forwardMostMove&1))
8202 /* [HGM] verify: draws that were not flagged are false claims */
8203 sprintf(buf, "False draw claim: '%s'", resultDetails);
8204 result = claimer == 'w' ? BlackWins : WhiteWins;
8205 resultDetails = buf;
8207 /* (Claiming a loss is accepted no questions asked!) */
8210 /* [HGM] bare: don't allow bare King to win */
8211 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8212 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8213 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8214 && result != GameIsDrawn)
8215 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8216 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8217 int p = (signed char)boards[forwardMostMove][i][j] - color;
8218 if(p >= 0 && p <= (int)WhiteKing) k++;
8220 if (appData.debugMode) {
8221 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8222 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8225 result = GameIsDrawn;
8226 sprintf(buf, "%s but bare king", resultDetails);
8227 resultDetails = buf;
8232 if(serverMoves != NULL && !loadFlag) { char c = '=';
8233 if(result==WhiteWins) c = '+';
8234 if(result==BlackWins) c = '-';
8235 if(resultDetails != NULL)
8236 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8238 if (resultDetails != NULL) {
8239 gameInfo.result = result;
8240 gameInfo.resultDetails = StrSave(resultDetails);
8242 /* display last move only if game was not loaded from file */
8243 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8244 DisplayMove(currentMove - 1);
8246 if (forwardMostMove != 0) {
8247 if (gameMode != PlayFromGameFile && gameMode != EditGame
8248 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8250 if (*appData.saveGameFile != NULLCHAR) {
8251 SaveGameToFile(appData.saveGameFile, TRUE);
8252 } else if (appData.autoSaveGames) {
8255 if (*appData.savePositionFile != NULLCHAR) {
8256 SavePositionToFile(appData.savePositionFile);
8261 /* Tell program how game ended in case it is learning */
8262 /* [HGM] Moved this to after saving the PGN, just in case */
8263 /* engine died and we got here through time loss. In that */
8264 /* case we will get a fatal error writing the pipe, which */
8265 /* would otherwise lose us the PGN. */
8266 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8267 /* output during GameEnds should never be fatal anymore */
8268 if (gameMode == MachinePlaysWhite ||
8269 gameMode == MachinePlaysBlack ||
8270 gameMode == TwoMachinesPlay ||
8271 gameMode == IcsPlayingWhite ||
8272 gameMode == IcsPlayingBlack ||
8273 gameMode == BeginningOfGame) {
8275 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8277 if (first.pr != NoProc) {
8278 SendToProgram(buf, &first);
8280 if (second.pr != NoProc &&
8281 gameMode == TwoMachinesPlay) {
8282 SendToProgram(buf, &second);
8287 if (appData.icsActive) {
8288 if (appData.quietPlay &&
8289 (gameMode == IcsPlayingWhite ||
8290 gameMode == IcsPlayingBlack)) {
8291 SendToICS(ics_prefix);
8292 SendToICS("set shout 1\n");
8294 nextGameMode = IcsIdle;
8295 ics_user_moved = FALSE;
8296 /* clean up premove. It's ugly when the game has ended and the
8297 * premove highlights are still on the board.
8301 ClearPremoveHighlights();
8302 DrawPosition(FALSE, boards[currentMove]);
8304 if (whosays == GE_ICS) {
8307 if (gameMode == IcsPlayingWhite)
8309 else if(gameMode == IcsPlayingBlack)
8313 if (gameMode == IcsPlayingBlack)
8315 else if(gameMode == IcsPlayingWhite)
8322 PlayIcsUnfinishedSound();
8325 } else if (gameMode == EditGame ||
8326 gameMode == PlayFromGameFile ||
8327 gameMode == AnalyzeMode ||
8328 gameMode == AnalyzeFile) {
8329 nextGameMode = gameMode;
8331 nextGameMode = EndOfGame;
8336 nextGameMode = gameMode;
8339 if (appData.noChessProgram) {
8340 gameMode = nextGameMode;
8342 endingGame = 0; /* [HGM] crash */
8347 /* Put first chess program into idle state */
8348 if (first.pr != NoProc &&
8349 (gameMode == MachinePlaysWhite ||
8350 gameMode == MachinePlaysBlack ||
8351 gameMode == TwoMachinesPlay ||
8352 gameMode == IcsPlayingWhite ||
8353 gameMode == IcsPlayingBlack ||
8354 gameMode == BeginningOfGame)) {
8355 SendToProgram("force\n", &first);
8356 if (first.usePing) {
8358 sprintf(buf, "ping %d\n", ++first.lastPing);
8359 SendToProgram(buf, &first);
8362 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8363 /* Kill off first chess program */
8364 if (first.isr != NULL)
8365 RemoveInputSource(first.isr);
8368 if (first.pr != NoProc) {
8370 DoSleep( appData.delayBeforeQuit );
8371 SendToProgram("quit\n", &first);
8372 DoSleep( appData.delayAfterQuit );
8373 DestroyChildProcess(first.pr, first.useSigterm);
8378 /* Put second chess program into idle state */
8379 if (second.pr != NoProc &&
8380 gameMode == TwoMachinesPlay) {
8381 SendToProgram("force\n", &second);
8382 if (second.usePing) {
8384 sprintf(buf, "ping %d\n", ++second.lastPing);
8385 SendToProgram(buf, &second);
8388 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8389 /* Kill off second chess program */
8390 if (second.isr != NULL)
8391 RemoveInputSource(second.isr);
8394 if (second.pr != NoProc) {
8395 DoSleep( appData.delayBeforeQuit );
8396 SendToProgram("quit\n", &second);
8397 DoSleep( appData.delayAfterQuit );
8398 DestroyChildProcess(second.pr, second.useSigterm);
8403 if (matchMode && gameMode == TwoMachinesPlay) {
8406 if (first.twoMachinesColor[0] == 'w') {
8413 if (first.twoMachinesColor[0] == 'b') {
8422 if (matchGame < appData.matchGames) {
8424 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8425 tmp = first.twoMachinesColor;
8426 first.twoMachinesColor = second.twoMachinesColor;
8427 second.twoMachinesColor = tmp;
8429 gameMode = nextGameMode;
8431 if(appData.matchPause>10000 || appData.matchPause<10)
8432 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8433 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8434 endingGame = 0; /* [HGM] crash */
8438 gameMode = nextGameMode;
8439 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8440 first.tidy, second.tidy,
8441 first.matchWins, second.matchWins,
8442 appData.matchGames - (first.matchWins + second.matchWins));
8443 DisplayFatalError(buf, 0, 0);
8446 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8447 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8449 gameMode = nextGameMode;
8451 endingGame = 0; /* [HGM] crash */
8454 /* Assumes program was just initialized (initString sent).
8455 Leaves program in force mode. */
8457 FeedMovesToProgram(cps, upto)
8458 ChessProgramState *cps;
8463 if (appData.debugMode)
8464 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8465 startedFromSetupPosition ? "position and " : "",
8466 backwardMostMove, upto, cps->which);
8467 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8468 // [HGM] variantswitch: make engine aware of new variant
8469 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8470 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8471 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8472 SendToProgram(buf, cps);
8473 currentlyInitializedVariant = gameInfo.variant;
8475 SendToProgram("force\n", cps);
8476 if (startedFromSetupPosition) {
8477 SendBoard(cps, backwardMostMove);
8478 if (appData.debugMode) {
8479 fprintf(debugFP, "feedMoves\n");
8482 for (i = backwardMostMove; i < upto; i++) {
8483 SendMoveToProgram(i, cps);
8489 ResurrectChessProgram()
8491 /* The chess program may have exited.
8492 If so, restart it and feed it all the moves made so far. */
8494 if (appData.noChessProgram || first.pr != NoProc) return;
8496 StartChessProgram(&first);
8497 InitChessProgram(&first, FALSE);
8498 FeedMovesToProgram(&first, currentMove);
8500 if (!first.sendTime) {
8501 /* can't tell gnuchess what its clock should read,
8502 so we bow to its notion. */
8504 timeRemaining[0][currentMove] = whiteTimeRemaining;
8505 timeRemaining[1][currentMove] = blackTimeRemaining;
8508 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8509 appData.icsEngineAnalyze) && first.analysisSupport) {
8510 SendToProgram("analyze\n", &first);
8511 first.analyzing = TRUE;
8524 if (appData.debugMode) {
8525 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8526 redraw, init, gameMode);
8528 CleanupTail(); // [HGM] vari: delete any stored variations
8529 pausing = pauseExamInvalid = FALSE;
8530 startedFromSetupPosition = blackPlaysFirst = FALSE;
8532 whiteFlag = blackFlag = FALSE;
8533 userOfferedDraw = FALSE;
8534 hintRequested = bookRequested = FALSE;
8535 first.maybeThinking = FALSE;
8536 second.maybeThinking = FALSE;
8537 first.bookSuspend = FALSE; // [HGM] book
8538 second.bookSuspend = FALSE;
8539 thinkOutput[0] = NULLCHAR;
8540 lastHint[0] = NULLCHAR;
8541 ClearGameInfo(&gameInfo);
8542 gameInfo.variant = StringToVariant(appData.variant);
8543 ics_user_moved = ics_clock_paused = FALSE;
8544 ics_getting_history = H_FALSE;
8546 white_holding[0] = black_holding[0] = NULLCHAR;
8547 ClearProgramStats();
8548 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8552 flipView = appData.flipView;
8553 ClearPremoveHighlights();
8555 alarmSounded = FALSE;
8557 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8558 if(appData.serverMovesName != NULL) {
8559 /* [HGM] prepare to make moves file for broadcasting */
8560 clock_t t = clock();
8561 if(serverMoves != NULL) fclose(serverMoves);
8562 serverMoves = fopen(appData.serverMovesName, "r");
8563 if(serverMoves != NULL) {
8564 fclose(serverMoves);
8565 /* delay 15 sec before overwriting, so all clients can see end */
8566 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8568 serverMoves = fopen(appData.serverMovesName, "w");
8572 gameMode = BeginningOfGame;
8575 if(appData.icsActive) gameInfo.variant = VariantNormal;
8576 currentMove = forwardMostMove = backwardMostMove = 0;
8577 InitPosition(redraw);
8578 for (i = 0; i < MAX_MOVES; i++) {
8579 if (commentList[i] != NULL) {
8580 free(commentList[i]);
8581 commentList[i] = NULL;
8586 timeRemaining[0][0] = whiteTimeRemaining;
8587 timeRemaining[1][0] = blackTimeRemaining;
8588 if (first.pr == NULL) {
8589 StartChessProgram(&first);
8592 InitChessProgram(&first, startedFromSetupPosition);
8596 DisplayMessage("", "");
8597 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8598 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8606 if (!AutoPlayOneMove())
8608 if (matchMode || appData.timeDelay == 0)
8610 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8612 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8621 int fromX, fromY, toX, toY;
8623 if (appData.debugMode) {
8624 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8627 if (gameMode != PlayFromGameFile)
8630 if (currentMove >= forwardMostMove) {
8631 gameMode = EditGame;
8634 /* [AS] Clear current move marker at the end of a game */
8635 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8640 toX = moveList[currentMove][2] - AAA;
8641 toY = moveList[currentMove][3] - ONE;
8643 if (moveList[currentMove][1] == '@') {
8644 if (appData.highlightLastMove) {
8645 SetHighlights(-1, -1, toX, toY);
8648 fromX = moveList[currentMove][0] - AAA;
8649 fromY = moveList[currentMove][1] - ONE;
8651 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8653 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8655 if (appData.highlightLastMove) {
8656 SetHighlights(fromX, fromY, toX, toY);
8659 DisplayMove(currentMove);
8660 SendMoveToProgram(currentMove++, &first);
8661 DisplayBothClocks();
8662 DrawPosition(FALSE, boards[currentMove]);
8663 // [HGM] PV info: always display, routine tests if empty
8664 DisplayComment(currentMove - 1, commentList[currentMove]);
8670 LoadGameOneMove(readAhead)
8671 ChessMove readAhead;
8673 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8674 char promoChar = NULLCHAR;
8679 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8680 gameMode != AnalyzeMode && gameMode != Training) {
8685 yyboardindex = forwardMostMove;
8686 if (readAhead != (ChessMove)0) {
8687 moveType = readAhead;
8689 if (gameFileFP == NULL)
8691 moveType = (ChessMove) yylex();
8697 if (appData.debugMode)
8698 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8701 /* append the comment but don't display it */
8702 AppendComment(currentMove, p, FALSE);
8705 case WhiteCapturesEnPassant:
8706 case BlackCapturesEnPassant:
8707 case WhitePromotionChancellor:
8708 case BlackPromotionChancellor:
8709 case WhitePromotionArchbishop:
8710 case BlackPromotionArchbishop:
8711 case WhitePromotionCentaur:
8712 case BlackPromotionCentaur:
8713 case WhitePromotionQueen:
8714 case BlackPromotionQueen:
8715 case WhitePromotionRook:
8716 case BlackPromotionRook:
8717 case WhitePromotionBishop:
8718 case BlackPromotionBishop:
8719 case WhitePromotionKnight:
8720 case BlackPromotionKnight:
8721 case WhitePromotionKing:
8722 case BlackPromotionKing:
8724 case WhiteKingSideCastle:
8725 case WhiteQueenSideCastle:
8726 case BlackKingSideCastle:
8727 case BlackQueenSideCastle:
8728 case WhiteKingSideCastleWild:
8729 case WhiteQueenSideCastleWild:
8730 case BlackKingSideCastleWild:
8731 case BlackQueenSideCastleWild:
8733 case WhiteHSideCastleFR:
8734 case WhiteASideCastleFR:
8735 case BlackHSideCastleFR:
8736 case BlackASideCastleFR:
8738 if (appData.debugMode)
8739 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8740 fromX = currentMoveString[0] - AAA;
8741 fromY = currentMoveString[1] - ONE;
8742 toX = currentMoveString[2] - AAA;
8743 toY = currentMoveString[3] - ONE;
8744 promoChar = currentMoveString[4];
8749 if (appData.debugMode)
8750 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8751 fromX = moveType == WhiteDrop ?
8752 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8753 (int) CharToPiece(ToLower(currentMoveString[0]));
8755 toX = currentMoveString[2] - AAA;
8756 toY = currentMoveString[3] - ONE;
8762 case GameUnfinished:
8763 if (appData.debugMode)
8764 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8765 p = strchr(yy_text, '{');
8766 if (p == NULL) p = strchr(yy_text, '(');
8769 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8771 q = strchr(p, *p == '{' ? '}' : ')');
8772 if (q != NULL) *q = NULLCHAR;
8775 GameEnds(moveType, p, GE_FILE);
8777 if (cmailMsgLoaded) {
8779 flipView = WhiteOnMove(currentMove);
8780 if (moveType == GameUnfinished) flipView = !flipView;
8781 if (appData.debugMode)
8782 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8786 case (ChessMove) 0: /* end of file */
8787 if (appData.debugMode)
8788 fprintf(debugFP, "Parser hit end of file\n");
8789 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8795 if (WhiteOnMove(currentMove)) {
8796 GameEnds(BlackWins, "Black mates", GE_FILE);
8798 GameEnds(WhiteWins, "White mates", GE_FILE);
8802 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8809 if (lastLoadGameStart == GNUChessGame) {
8810 /* GNUChessGames have numbers, but they aren't move numbers */
8811 if (appData.debugMode)
8812 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8813 yy_text, (int) moveType);
8814 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8816 /* else fall thru */
8821 /* Reached start of next game in file */
8822 if (appData.debugMode)
8823 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8824 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8830 if (WhiteOnMove(currentMove)) {
8831 GameEnds(BlackWins, "Black mates", GE_FILE);
8833 GameEnds(WhiteWins, "White mates", GE_FILE);
8837 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8843 case PositionDiagram: /* should not happen; ignore */
8844 case ElapsedTime: /* ignore */
8845 case NAG: /* ignore */
8846 if (appData.debugMode)
8847 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8848 yy_text, (int) moveType);
8849 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8852 if (appData.testLegality) {
8853 if (appData.debugMode)
8854 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8855 sprintf(move, _("Illegal move: %d.%s%s"),
8856 (forwardMostMove / 2) + 1,
8857 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8858 DisplayError(move, 0);
8861 if (appData.debugMode)
8862 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8863 yy_text, currentMoveString);
8864 fromX = currentMoveString[0] - AAA;
8865 fromY = currentMoveString[1] - ONE;
8866 toX = currentMoveString[2] - AAA;
8867 toY = currentMoveString[3] - ONE;
8868 promoChar = currentMoveString[4];
8873 if (appData.debugMode)
8874 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8875 sprintf(move, _("Ambiguous move: %d.%s%s"),
8876 (forwardMostMove / 2) + 1,
8877 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8878 DisplayError(move, 0);
8883 case ImpossibleMove:
8884 if (appData.debugMode)
8885 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8886 sprintf(move, _("Illegal move: %d.%s%s"),
8887 (forwardMostMove / 2) + 1,
8888 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8889 DisplayError(move, 0);
8895 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8896 DrawPosition(FALSE, boards[currentMove]);
8897 DisplayBothClocks();
8898 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8899 DisplayComment(currentMove - 1, commentList[currentMove]);
8901 (void) StopLoadGameTimer();
8903 cmailOldMove = forwardMostMove;
8906 /* currentMoveString is set as a side-effect of yylex */
8907 strcat(currentMoveString, "\n");
8908 strcpy(moveList[forwardMostMove], currentMoveString);
8910 thinkOutput[0] = NULLCHAR;
8911 MakeMove(fromX, fromY, toX, toY, promoChar);
8912 currentMove = forwardMostMove;
8917 /* Load the nth game from the given file */
8919 LoadGameFromFile(filename, n, title, useList)
8923 /*Boolean*/ int useList;
8928 if (strcmp(filename, "-") == 0) {
8932 f = fopen(filename, "rb");
8934 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8935 DisplayError(buf, errno);
8939 if (fseek(f, 0, 0) == -1) {
8940 /* f is not seekable; probably a pipe */
8943 if (useList && n == 0) {
8944 int error = GameListBuild(f);
8946 DisplayError(_("Cannot build game list"), error);
8947 } else if (!ListEmpty(&gameList) &&
8948 ((ListGame *) gameList.tailPred)->number > 1) {
8949 // TODO convert to GTK
8950 // GameListPopUp(f, title);
8957 return LoadGame(f, n, title, FALSE);
8962 MakeRegisteredMove()
8964 int fromX, fromY, toX, toY;
8966 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8967 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8970 if (appData.debugMode)
8971 fprintf(debugFP, "Restoring %s for game %d\n",
8972 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8974 thinkOutput[0] = NULLCHAR;
8975 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8976 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8977 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8978 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8979 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8980 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8981 MakeMove(fromX, fromY, toX, toY, promoChar);
8982 ShowMove(fromX, fromY, toX, toY);
8983 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8990 if (WhiteOnMove(currentMove)) {
8991 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8993 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8998 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9005 if (WhiteOnMove(currentMove)) {
9006 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9008 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9013 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9024 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9026 CmailLoadGame(f, gameNumber, title, useList)
9034 if (gameNumber > nCmailGames) {
9035 DisplayError(_("No more games in this message"), 0);
9038 if (f == lastLoadGameFP) {
9039 int offset = gameNumber - lastLoadGameNumber;
9041 cmailMsg[0] = NULLCHAR;
9042 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9043 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9044 nCmailMovesRegistered--;
9046 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9047 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9048 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9051 if (! RegisterMove()) return FALSE;
9055 retVal = LoadGame(f, gameNumber, title, useList);
9057 /* Make move registered during previous look at this game, if any */
9058 MakeRegisteredMove();
9060 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9061 commentList[currentMove]
9062 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9063 DisplayComment(currentMove - 1, commentList[currentMove]);
9069 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9074 int gameNumber = lastLoadGameNumber + offset;
9075 if (lastLoadGameFP == NULL) {
9076 DisplayError(_("No game has been loaded yet"), 0);
9079 if (gameNumber <= 0) {
9080 DisplayError(_("Can't back up any further"), 0);
9083 if (cmailMsgLoaded) {
9084 return CmailLoadGame(lastLoadGameFP, gameNumber,
9085 lastLoadGameTitle, lastLoadGameUseList);
9087 return LoadGame(lastLoadGameFP, gameNumber,
9088 lastLoadGameTitle, lastLoadGameUseList);
9094 /* Load the nth game from open file f */
9096 LoadGame(f, gameNumber, title, useList)
9104 int gn = gameNumber;
9105 ListGame *lg = NULL;
9108 GameMode oldGameMode;
9109 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9111 if (appData.debugMode)
9112 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9114 if (gameMode == Training )
9115 SetTrainingModeOff();
9117 oldGameMode = gameMode;
9118 if (gameMode != BeginningOfGame)
9124 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
9126 fclose(lastLoadGameFP);
9131 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9135 fseek(f, lg->offset, 0);
9136 GameListHighlight(gameNumber);
9141 DisplayError(_("Game number out of range"), 0);
9148 if (fseek(f, 0, 0) == -1)
9150 if (f == lastLoadGameFP ?
9151 gameNumber == lastLoadGameNumber + 1 :
9158 DisplayError(_("Can't seek on game file"), 0);
9165 lastLoadGameNumber = gameNumber;
9166 strcpy(lastLoadGameTitle, title);
9167 lastLoadGameUseList = useList;
9171 if (lg && lg->gameInfo.white && lg->gameInfo.black)
9173 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9174 lg->gameInfo.black);
9177 else if (*title != NULLCHAR)
9181 sprintf(buf, "%s %d", title, gameNumber);
9186 DisplayTitle(title);
9190 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
9192 gameMode = PlayFromGameFile;
9196 currentMove = forwardMostMove = backwardMostMove = 0;
9197 CopyBoard(boards[0], initialPosition);
9201 * Skip the first gn-1 games in the file.
9202 * Also skip over anything that precedes an identifiable
9203 * start of game marker, to avoid being confused by
9204 * garbage at the start of the file. Currently
9205 * recognized start of game markers are the move number "1",
9206 * the pattern "gnuchess .* game", the pattern
9207 * "^[#;%] [^ ]* game file", and a PGN tag block.
9208 * A game that starts with one of the latter two patterns
9209 * will also have a move number 1, possibly
9210 * following a position diagram.
9211 * 5-4-02: Let's try being more lenient and allowing a game to
9212 * start with an unnumbered move. Does that break anything?
9214 cm = lastLoadGameStart = (ChessMove) 0;
9216 yyboardindex = forwardMostMove;
9217 cm = (ChessMove) yylex();
9220 if (cmailMsgLoaded) {
9221 nCmailGames = CMAIL_MAX_GAMES - gn;
9224 DisplayError(_("Game not found in file"), 0);
9231 lastLoadGameStart = cm;
9235 switch (lastLoadGameStart) {
9242 gn--; /* count this game */
9243 lastLoadGameStart = cm;
9252 switch (lastLoadGameStart) {
9257 gn--; /* count this game */
9258 lastLoadGameStart = cm;
9261 lastLoadGameStart = cm; /* game counted already */
9269 yyboardindex = forwardMostMove;
9270 cm = (ChessMove) yylex();
9271 } while (cm == PGNTag || cm == Comment);
9278 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9279 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9280 != CMAIL_OLD_RESULT) {
9282 cmailResult[ CMAIL_MAX_GAMES
9283 - gn - 1] = CMAIL_OLD_RESULT;
9289 /* Only a NormalMove can be at the start of a game
9290 * without a position diagram. */
9291 if (lastLoadGameStart == (ChessMove) 0) {
9293 lastLoadGameStart = MoveNumberOne;
9302 if (appData.debugMode)
9303 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9305 if (cm == XBoardGame) {
9306 /* Skip any header junk before position diagram and/or move 1 */
9308 yyboardindex = forwardMostMove;
9309 cm = (ChessMove) yylex();
9311 if (cm == (ChessMove) 0 ||
9312 cm == GNUChessGame || cm == XBoardGame) {
9313 /* Empty game; pretend end-of-file and handle later */
9318 if (cm == MoveNumberOne || cm == PositionDiagram ||
9319 cm == PGNTag || cm == Comment)
9322 } else if (cm == GNUChessGame) {
9323 if (gameInfo.event != NULL) {
9324 free(gameInfo.event);
9326 gameInfo.event = StrSave(yy_text);
9329 startedFromSetupPosition = FALSE;
9330 while (cm == PGNTag) {
9331 if (appData.debugMode)
9332 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9333 err = ParsePGNTag(yy_text, &gameInfo);
9334 if (!err) numPGNTags++;
9336 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9337 if(gameInfo.variant != oldVariant) {
9338 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9340 oldVariant = gameInfo.variant;
9341 if (appData.debugMode)
9342 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9346 if (gameInfo.fen != NULL) {
9347 Board initial_position;
9348 startedFromSetupPosition = TRUE;
9349 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9351 DisplayError(_("Bad FEN position in file"), 0);
9354 CopyBoard(boards[0], initial_position);
9355 if (blackPlaysFirst) {
9356 currentMove = forwardMostMove = backwardMostMove = 1;
9357 CopyBoard(boards[1], initial_position);
9358 strcpy(moveList[0], "");
9359 strcpy(parseList[0], "");
9360 timeRemaining[0][1] = whiteTimeRemaining;
9361 timeRemaining[1][1] = blackTimeRemaining;
9362 if (commentList[0] != NULL) {
9363 commentList[1] = commentList[0];
9364 commentList[0] = NULL;
9367 currentMove = forwardMostMove = backwardMostMove = 0;
9369 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9371 initialRulePlies = FENrulePlies;
9372 for( i=0; i< nrCastlingRights; i++ )
9373 initialRights[i] = initial_position[CASTLING][i];
9375 yyboardindex = forwardMostMove;
9377 gameInfo.fen = NULL;
9380 yyboardindex = forwardMostMove;
9381 cm = (ChessMove) yylex();
9383 /* Handle comments interspersed among the tags */
9384 while (cm == Comment) {
9386 if (appData.debugMode)
9387 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9389 AppendComment(currentMove, p, FALSE);
9390 yyboardindex = forwardMostMove;
9391 cm = (ChessMove) yylex();
9395 /* don't rely on existence of Event tag since if game was
9396 * pasted from clipboard the Event tag may not exist
9398 if (numPGNTags > 0){
9400 if (gameInfo.variant == VariantNormal) {
9401 gameInfo.variant = StringToVariant(gameInfo.event);
9404 if( appData.autoDisplayTags ) {
9405 tags = PGNTags(&gameInfo);
9406 TagsPopUp(tags, CmailMsg());
9411 /* Make something up, but don't display it now */
9416 if (cm == PositionDiagram) {
9419 Board initial_position;
9421 if (appData.debugMode)
9422 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9424 if (!startedFromSetupPosition) {
9426 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9427 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9437 initial_position[i][j++] = CharToPiece(*p);
9440 while (*p == ' ' || *p == '\t' ||
9441 *p == '\n' || *p == '\r') p++;
9443 if (strncmp(p, "black", strlen("black"))==0)
9444 blackPlaysFirst = TRUE;
9446 blackPlaysFirst = FALSE;
9447 startedFromSetupPosition = TRUE;
9449 CopyBoard(boards[0], initial_position);
9450 if (blackPlaysFirst) {
9451 currentMove = forwardMostMove = backwardMostMove = 1;
9452 CopyBoard(boards[1], initial_position);
9453 strcpy(moveList[0], "");
9454 strcpy(parseList[0], "");
9455 timeRemaining[0][1] = whiteTimeRemaining;
9456 timeRemaining[1][1] = blackTimeRemaining;
9457 if (commentList[0] != NULL) {
9458 commentList[1] = commentList[0];
9459 commentList[0] = NULL;
9462 currentMove = forwardMostMove = backwardMostMove = 0;
9465 yyboardindex = forwardMostMove;
9466 cm = (ChessMove) yylex();
9469 if (first.pr == NoProc) {
9470 StartChessProgram(&first);
9472 InitChessProgram(&first, FALSE);
9473 SendToProgram("force\n", &first);
9474 if (startedFromSetupPosition) {
9475 SendBoard(&first, forwardMostMove);
9476 if (appData.debugMode) {
9477 fprintf(debugFP, "Load Game\n");
9479 DisplayBothClocks();
9482 /* [HGM] server: flag to write setup moves in broadcast file as one */
9483 loadFlag = appData.suppressLoadMoves;
9485 while (cm == Comment) {
9487 if (appData.debugMode)
9488 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9490 AppendComment(currentMove, p, FALSE);
9491 yyboardindex = forwardMostMove;
9492 cm = (ChessMove) yylex();
9495 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9496 cm == WhiteWins || cm == BlackWins ||
9497 cm == GameIsDrawn || cm == GameUnfinished) {
9498 DisplayMessage("", _("No moves in game"));
9499 if (cmailMsgLoaded) {
9500 if (appData.debugMode)
9501 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9505 DrawPosition(FALSE, boards[currentMove]);
9506 DisplayBothClocks();
9507 gameMode = EditGame;
9514 // [HGM] PV info: routine tests if comment empty
9515 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9516 DisplayComment(currentMove - 1, commentList[currentMove]);
9518 if (!matchMode && appData.timeDelay != 0)
9519 DrawPosition(FALSE, boards[currentMove]);
9521 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9522 programStats.ok_to_send = 1;
9525 /* if the first token after the PGN tags is a move
9526 * and not move number 1, retrieve it from the parser
9528 if (cm != MoveNumberOne)
9529 LoadGameOneMove(cm);
9531 /* load the remaining moves from the file */
9532 while (LoadGameOneMove((ChessMove)0)) {
9533 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9534 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9537 /* rewind to the start of the game */
9538 currentMove = backwardMostMove;
9540 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9542 if (oldGameMode == AnalyzeFile ||
9543 oldGameMode == AnalyzeMode) {
9547 if (matchMode || appData.timeDelay == 0) {
9549 gameMode = EditGame;
9551 } else if (appData.timeDelay > 0) {
9555 if (appData.debugMode)
9556 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9558 loadFlag = 0; /* [HGM] true game starts */
9562 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9564 ReloadPosition(offset)
9567 int positionNumber = lastLoadPositionNumber + offset;
9568 if (lastLoadPositionFP == NULL) {
9569 DisplayError(_("No position has been loaded yet"), 0);
9572 if (positionNumber <= 0) {
9573 DisplayError(_("Can't back up any further"), 0);
9576 return LoadPosition(lastLoadPositionFP, positionNumber,
9577 lastLoadPositionTitle);
9580 /* Load the nth position from the given file */
9582 LoadPositionFromFile(filename, n, title)
9590 if (strcmp(filename, "-") == 0) {
9591 return LoadPosition(stdin, n, "stdin");
9593 f = fopen(filename, "rb");
9595 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9596 DisplayError(buf, errno);
9599 return LoadPosition(f, n, title);
9604 /* Load the nth position from the given open file, and close it */
9606 LoadPosition(f, positionNumber, title)
9611 char *p, line[MSG_SIZ];
9612 Board initial_position;
9613 int i, j, fenMode, pn;
9615 if (gameMode == Training )
9616 SetTrainingModeOff();
9618 if (gameMode != BeginningOfGame) {
9621 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9622 fclose(lastLoadPositionFP);
9624 if (positionNumber == 0) positionNumber = 1;
9625 lastLoadPositionFP = f;
9626 lastLoadPositionNumber = positionNumber;
9627 strcpy(lastLoadPositionTitle, title);
9628 if (first.pr == NoProc) {
9629 StartChessProgram(&first);
9630 InitChessProgram(&first, FALSE);
9632 pn = positionNumber;
9633 if (positionNumber < 0) {
9634 /* Negative position number means to seek to that byte offset */
9635 if (fseek(f, -positionNumber, 0) == -1) {
9636 DisplayError(_("Can't seek on position file"), 0);
9641 if (fseek(f, 0, 0) == -1) {
9642 if (f == lastLoadPositionFP ?
9643 positionNumber == lastLoadPositionNumber + 1 :
9644 positionNumber == 1) {
9647 DisplayError(_("Can't seek on position file"), 0);
9652 /* See if this file is FEN or old-style xboard */
9653 if (fgets(line, MSG_SIZ, f) == NULL) {
9654 DisplayError(_("Position not found in file"), 0);
9657 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9658 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9661 if (fenMode || line[0] == '#') pn--;
9663 /* skip positions before number pn */
9664 if (fgets(line, MSG_SIZ, f) == NULL) {
9666 DisplayError(_("Position not found in file"), 0);
9669 if (fenMode || line[0] == '#') pn--;
9674 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9675 DisplayError(_("Bad FEN position in file"), 0);
9679 (void) fgets(line, MSG_SIZ, f);
9680 (void) fgets(line, MSG_SIZ, f);
9682 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9683 (void) fgets(line, MSG_SIZ, f);
9684 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9687 initial_position[i][j++] = CharToPiece(*p);
9691 blackPlaysFirst = FALSE;
9693 (void) fgets(line, MSG_SIZ, f);
9694 if (strncmp(line, "black", strlen("black"))==0)
9695 blackPlaysFirst = TRUE;
9698 startedFromSetupPosition = TRUE;
9700 SendToProgram("force\n", &first);
9701 CopyBoard(boards[0], initial_position);
9702 if (blackPlaysFirst) {
9703 currentMove = forwardMostMove = backwardMostMove = 1;
9704 strcpy(moveList[0], "");
9705 strcpy(parseList[0], "");
9706 CopyBoard(boards[1], initial_position);
9707 DisplayMessage("", _("Black to play"));
9709 currentMove = forwardMostMove = backwardMostMove = 0;
9710 DisplayMessage("", _("White to play"));
9712 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9713 SendBoard(&first, forwardMostMove);
9714 if (appData.debugMode) {
9716 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9717 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9718 fprintf(debugFP, "Load Position\n");
9721 if (positionNumber > 1) {
9722 sprintf(line, "%s %d", title, positionNumber);
9725 DisplayTitle(title);
9727 gameMode = EditGame;
9730 timeRemaining[0][1] = whiteTimeRemaining;
9731 timeRemaining[1][1] = blackTimeRemaining;
9732 DrawPosition(FALSE, boards[currentMove]);
9739 CopyPlayerNameIntoFileName(dest, src)
9742 while (*src != NULLCHAR && *src != ',') {
9747 *(*dest)++ = *src++;
9752 char *DefaultFileName(ext)
9755 static char def[MSG_SIZ];
9758 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9760 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9762 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9771 /* Save the current game to the given file */
9773 SaveGameToFile(filename, append)
9780 if (strcmp(filename, "-") == 0) {
9781 return SaveGame(stdout, 0, NULL);
9783 f = fopen(filename, append ? "a" : "w");
9785 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9786 DisplayError(buf, errno);
9789 return SaveGame(f, 0, NULL);
9798 static char buf[MSG_SIZ];
9801 p = strchr(str, ' ');
9802 if (p == NULL) return str;
9803 strncpy(buf, str, p - str);
9804 buf[p - str] = NULLCHAR;
9808 #define PGN_MAX_LINE 75
9810 #define PGN_SIDE_WHITE 0
9811 #define PGN_SIDE_BLACK 1
9814 static int FindFirstMoveOutOfBook( int side )
9818 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9819 int index = backwardMostMove;
9820 int has_book_hit = 0;
9822 if( (index % 2) != side ) {
9826 while( index < forwardMostMove ) {
9827 /* Check to see if engine is in book */
9828 int depth = pvInfoList[index].depth;
9829 int score = pvInfoList[index].score;
9835 else if( score == 0 && depth == 63 ) {
9836 in_book = 1; /* Zappa */
9838 else if( score == 2 && depth == 99 ) {
9839 in_book = 1; /* Abrok */
9842 has_book_hit += in_book;
9858 void GetOutOfBookInfo( char * buf )
9862 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9864 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9865 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9869 if( oob[0] >= 0 || oob[1] >= 0 ) {
9870 for( i=0; i<2; i++ ) {
9874 if( i > 0 && oob[0] >= 0 ) {
9878 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9879 sprintf( buf+strlen(buf), "%s%.2f",
9880 pvInfoList[idx].score >= 0 ? "+" : "",
9881 pvInfoList[idx].score / 100.0 );
9887 /* Save game in PGN style and close the file */
9892 int i, offset, linelen, newblock;
9896 int movelen, numlen, blank;
9897 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9899 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9901 tm = time((time_t *) NULL);
9903 PrintPGNTags(f, &gameInfo);
9905 if (backwardMostMove > 0 || startedFromSetupPosition) {
9906 char *fen = PositionToFEN(backwardMostMove, NULL);
9907 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9908 fprintf(f, "\n{--------------\n");
9909 PrintPosition(f, backwardMostMove);
9910 fprintf(f, "--------------}\n");
9914 /* [AS] Out of book annotation */
9915 if( appData.saveOutOfBookInfo ) {
9918 GetOutOfBookInfo( buf );
9920 if( buf[0] != '\0' ) {
9921 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9928 i = backwardMostMove;
9932 while (i < forwardMostMove) {
9933 /* Print comments preceding this move */
9934 if (commentList[i] != NULL) {
9935 if (linelen > 0) fprintf(f, "\n");
9936 fprintf(f, "%s", commentList[i]);
9941 /* Format move number */
9943 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9946 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9948 numtext[0] = NULLCHAR;
9951 numlen = strlen(numtext);
9954 /* Print move number */
9955 blank = linelen > 0 && numlen > 0;
9956 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9965 fprintf(f, "%s", numtext);
9969 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9970 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9973 blank = linelen > 0 && movelen > 0;
9974 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9983 fprintf(f, "%s", move_buffer);
9986 /* [AS] Add PV info if present */
9987 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9988 /* [HGM] add time */
9989 char buf[MSG_SIZ]; int seconds;
9991 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9993 if( seconds <= 0) buf[0] = 0; else
9994 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9995 seconds = (seconds + 4)/10; // round to full seconds
9996 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9997 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10000 sprintf( move_buffer, "{%s%.2f/%d%s}",
10001 pvInfoList[i].score >= 0 ? "+" : "",
10002 pvInfoList[i].score / 100.0,
10003 pvInfoList[i].depth,
10006 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10008 /* Print score/depth */
10009 blank = linelen > 0 && movelen > 0;
10010 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10019 fprintf(f, "%s", move_buffer);
10020 linelen += movelen;
10026 /* Start a new line */
10027 if (linelen > 0) fprintf(f, "\n");
10029 /* Print comments after last move */
10030 if (commentList[i] != NULL) {
10031 fprintf(f, "%s\n", commentList[i]);
10035 if (gameInfo.resultDetails != NULL &&
10036 gameInfo.resultDetails[0] != NULLCHAR) {
10037 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10038 PGNResult(gameInfo.result));
10040 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10044 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10048 /* Save game in old style and close the file */
10050 SaveGameOldStyle(f)
10056 tm = time((time_t *) NULL);
10058 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10061 if (backwardMostMove > 0 || startedFromSetupPosition) {
10062 fprintf(f, "\n[--------------\n");
10063 PrintPosition(f, backwardMostMove);
10064 fprintf(f, "--------------]\n");
10069 i = backwardMostMove;
10070 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10072 while (i < forwardMostMove) {
10073 if (commentList[i] != NULL) {
10074 fprintf(f, "[%s]\n", commentList[i]);
10077 if ((i % 2) == 1) {
10078 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10081 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10083 if (commentList[i] != NULL) {
10087 if (i >= forwardMostMove) {
10091 fprintf(f, "%s\n", parseList[i]);
10096 if (commentList[i] != NULL) {
10097 fprintf(f, "[%s]\n", commentList[i]);
10100 /* This isn't really the old style, but it's close enough */
10101 if (gameInfo.resultDetails != NULL &&
10102 gameInfo.resultDetails[0] != NULLCHAR) {
10103 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10104 gameInfo.resultDetails);
10106 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10113 /* Save the current game to open file f and close the file */
10115 SaveGame(f, dummy, dummy2)
10120 if (gameMode == EditPosition) EditPositionDone(TRUE);
10121 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10122 if (appData.oldSaveStyle)
10123 return SaveGameOldStyle(f);
10125 return SaveGamePGN(f);
10128 /* Save the current position to the given file */
10130 SavePositionToFile(filename)
10136 if (strcmp(filename, "-") == 0) {
10137 return SavePosition(stdout, 0, NULL);
10139 f = fopen(filename, "a");
10141 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10142 DisplayError(buf, errno);
10145 SavePosition(f, 0, NULL);
10151 /* Save the current position to the given open file and close the file */
10153 SavePosition(f, dummy, dummy2)
10160 if (gameMode == EditPosition) EditPositionDone(TRUE);
10161 if (appData.oldSaveStyle) {
10162 tm = time((time_t *) NULL);
10164 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10166 fprintf(f, "[--------------\n");
10167 PrintPosition(f, currentMove);
10168 fprintf(f, "--------------]\n");
10170 fen = PositionToFEN(currentMove, NULL);
10171 fprintf(f, "%s\n", fen);
10179 ReloadCmailMsgEvent(unregister)
10183 static char *inFilename = NULL;
10184 static char *outFilename;
10186 struct stat inbuf, outbuf;
10189 /* Any registered moves are unregistered if unregister is set, */
10190 /* i.e. invoked by the signal handler */
10192 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10193 cmailMoveRegistered[i] = FALSE;
10194 if (cmailCommentList[i] != NULL) {
10195 free(cmailCommentList[i]);
10196 cmailCommentList[i] = NULL;
10199 nCmailMovesRegistered = 0;
10202 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10203 cmailResult[i] = CMAIL_NOT_RESULT;
10207 if (inFilename == NULL) {
10208 /* Because the filenames are static they only get malloced once */
10209 /* and they never get freed */
10210 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10211 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10213 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10214 sprintf(outFilename, "%s.out", appData.cmailGameName);
10217 status = stat(outFilename, &outbuf);
10219 cmailMailedMove = FALSE;
10221 status = stat(inFilename, &inbuf);
10222 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10225 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10226 counts the games, notes how each one terminated, etc.
10228 It would be nice to remove this kludge and instead gather all
10229 the information while building the game list. (And to keep it
10230 in the game list nodes instead of having a bunch of fixed-size
10231 parallel arrays.) Note this will require getting each game's
10232 termination from the PGN tags, as the game list builder does
10233 not process the game moves. --mann
10235 cmailMsgLoaded = TRUE;
10236 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10238 /* Load first game in the file or popup game menu */
10239 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10241 #endif /* !WIN32 */
10249 char string[MSG_SIZ];
10251 if ( cmailMailedMove
10252 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10253 return TRUE; /* Allow free viewing */
10256 /* Unregister move to ensure that we don't leave RegisterMove */
10257 /* with the move registered when the conditions for registering no */
10259 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10260 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10261 nCmailMovesRegistered --;
10263 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10265 free(cmailCommentList[lastLoadGameNumber - 1]);
10266 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10270 if (cmailOldMove == -1) {
10271 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10275 if (currentMove > cmailOldMove + 1) {
10276 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10280 if (currentMove < cmailOldMove) {
10281 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10285 if (forwardMostMove > currentMove) {
10286 /* Silently truncate extra moves */
10290 if ( (currentMove == cmailOldMove + 1)
10291 || ( (currentMove == cmailOldMove)
10292 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10293 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10294 if (gameInfo.result != GameUnfinished) {
10295 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10298 if (commentList[currentMove] != NULL) {
10299 cmailCommentList[lastLoadGameNumber - 1]
10300 = StrSave(commentList[currentMove]);
10302 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10304 if (appData.debugMode)
10305 fprintf(debugFP, "Saving %s for game %d\n",
10306 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10309 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10311 f = fopen(string, "w");
10312 if (appData.oldSaveStyle) {
10313 SaveGameOldStyle(f); /* also closes the file */
10315 sprintf(string, "%s.pos.out", appData.cmailGameName);
10316 f = fopen(string, "w");
10317 SavePosition(f, 0, NULL); /* also closes the file */
10319 fprintf(f, "{--------------\n");
10320 PrintPosition(f, currentMove);
10321 fprintf(f, "--------------}\n\n");
10323 SaveGame(f, 0, NULL); /* also closes the file*/
10326 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10327 nCmailMovesRegistered ++;
10328 } else if (nCmailGames == 1) {
10329 DisplayError(_("You have not made a move yet"), 0);
10340 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10341 FILE *commandOutput;
10342 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10343 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10349 if (! cmailMsgLoaded) {
10350 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10354 if (nCmailGames == nCmailResults) {
10355 DisplayError(_("No unfinished games"), 0);
10359 #if CMAIL_PROHIBIT_REMAIL
10360 if (cmailMailedMove) {
10361 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);
10362 DisplayError(msg, 0);
10367 if (! (cmailMailedMove || RegisterMove())) return;
10369 if ( cmailMailedMove
10370 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10371 sprintf(string, partCommandString,
10372 appData.debugMode ? " -v" : "", appData.cmailGameName);
10373 commandOutput = popen(string, "r");
10375 if (commandOutput == NULL) {
10376 DisplayError(_("Failed to invoke cmail"), 0);
10378 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10379 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10381 if (nBuffers > 1) {
10382 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10383 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10384 nBytes = MSG_SIZ - 1;
10386 (void) memcpy(msg, buffer, nBytes);
10388 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10390 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10391 cmailMailedMove = TRUE; /* Prevent >1 moves */
10394 for (i = 0; i < nCmailGames; i ++) {
10395 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10400 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10402 sprintf(buffer, "%s/%s.%s.archive",
10404 appData.cmailGameName,
10406 LoadGameFromFile(buffer, 1, buffer, FALSE);
10407 cmailMsgLoaded = FALSE;
10411 DisplayInformation(msg);
10412 pclose(commandOutput);
10415 if ((*cmailMsg) != '\0') {
10416 DisplayInformation(cmailMsg);
10421 #endif /* !WIN32 */
10430 int prependComma = 0;
10432 char string[MSG_SIZ]; /* Space for game-list */
10435 if (!cmailMsgLoaded) return "";
10437 if (cmailMailedMove) {
10438 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10440 /* Create a list of games left */
10441 sprintf(string, "[");
10442 for (i = 0; i < nCmailGames; i ++) {
10443 if (! ( cmailMoveRegistered[i]
10444 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10445 if (prependComma) {
10446 sprintf(number, ",%d", i + 1);
10448 sprintf(number, "%d", i + 1);
10452 strcat(string, number);
10455 strcat(string, "]");
10457 if (nCmailMovesRegistered + nCmailResults == 0) {
10458 switch (nCmailGames) {
10461 _("Still need to make move for game\n"));
10466 _("Still need to make moves for both games\n"));
10471 _("Still need to make moves for all %d games\n"),
10476 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10479 _("Still need to make a move for game %s\n"),
10484 if (nCmailResults == nCmailGames) {
10485 sprintf(cmailMsg, _("No unfinished games\n"));
10487 sprintf(cmailMsg, _("Ready to send mail\n"));
10493 _("Still need to make moves for games %s\n"),
10505 if (gameMode == Training)
10506 SetTrainingModeOff();
10509 cmailMsgLoaded = FALSE;
10510 if (appData.icsActive) {
10511 SendToICS(ics_prefix);
10512 SendToICS("refresh\n");
10522 /* Give up on clean exit */
10526 /* Keep trying for clean exit */
10530 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10532 if (telnetISR != NULL) {
10533 RemoveInputSource(telnetISR);
10535 if (icsPR != NoProc) {
10536 DestroyChildProcess(icsPR, TRUE);
10539 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10540 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10542 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10543 /* make sure this other one finishes before killing it! */
10544 if(endingGame) { int count = 0;
10545 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10546 while(endingGame && count++ < 10) DoSleep(1);
10547 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10550 /* Kill off chess programs */
10551 if (first.pr != NoProc) {
10554 DoSleep( appData.delayBeforeQuit );
10555 SendToProgram("quit\n", &first);
10556 DoSleep( appData.delayAfterQuit );
10557 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10559 if (second.pr != NoProc) {
10560 DoSleep( appData.delayBeforeQuit );
10561 SendToProgram("quit\n", &second);
10562 DoSleep( appData.delayAfterQuit );
10563 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10565 if (first.isr != NULL) {
10566 RemoveInputSource(first.isr);
10568 if (second.isr != NULL) {
10569 RemoveInputSource(second.isr);
10572 ShutDownFrontEnd();
10579 if (appData.debugMode)
10580 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10584 if (gameMode == MachinePlaysWhite ||
10585 gameMode == MachinePlaysBlack) {
10588 DisplayBothClocks();
10590 if (gameMode == PlayFromGameFile) {
10591 if (appData.timeDelay >= 0)
10592 AutoPlayGameLoop();
10593 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10594 Reset(FALSE, TRUE);
10595 SendToICS(ics_prefix);
10596 SendToICS("refresh\n");
10597 } else if (currentMove < forwardMostMove) {
10598 ForwardInner(forwardMostMove);
10600 pauseExamInvalid = FALSE;
10602 switch (gameMode) {
10606 pauseExamForwardMostMove = forwardMostMove;
10607 pauseExamInvalid = FALSE;
10610 case IcsPlayingWhite:
10611 case IcsPlayingBlack:
10615 case PlayFromGameFile:
10616 (void) StopLoadGameTimer();
10620 case BeginningOfGame:
10621 if (appData.icsActive) return;
10622 /* else fall through */
10623 case MachinePlaysWhite:
10624 case MachinePlaysBlack:
10625 case TwoMachinesPlay:
10626 if (forwardMostMove == 0)
10627 return; /* don't pause if no one has moved */
10628 if ((gameMode == MachinePlaysWhite &&
10629 !WhiteOnMove(forwardMostMove)) ||
10630 (gameMode == MachinePlaysBlack &&
10631 WhiteOnMove(forwardMostMove))) {
10644 char title[MSG_SIZ];
10646 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10647 strcpy(title, _("Edit comment"));
10649 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10650 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10651 parseList[currentMove - 1]);
10654 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10661 char *tags = PGNTags(&gameInfo);
10662 EditTagsPopUp(tags);
10669 if (appData.noChessProgram || gameMode == AnalyzeMode)
10672 if (gameMode != AnalyzeFile) {
10673 if (!appData.icsEngineAnalyze) {
10675 if (gameMode != EditGame) return;
10677 ResurrectChessProgram();
10678 SendToProgram("analyze\n", &first);
10679 first.analyzing = TRUE;
10680 /*first.maybeThinking = TRUE;*/
10681 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10682 EngineOutputPopUp();
10684 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10689 StartAnalysisClock();
10690 GetTimeMark(&lastNodeCountTime);
10697 if (appData.noChessProgram || gameMode == AnalyzeFile)
10700 if (gameMode != AnalyzeMode) {
10702 if (gameMode != EditGame) return;
10703 ResurrectChessProgram();
10704 SendToProgram("analyze\n", &first);
10705 first.analyzing = TRUE;
10706 /*first.maybeThinking = TRUE;*/
10707 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10708 EngineOutputPopUp();
10710 gameMode = AnalyzeFile;
10715 StartAnalysisClock();
10716 GetTimeMark(&lastNodeCountTime);
10721 MachineWhiteEvent()
10724 char *bookHit = NULL;
10726 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10730 if (gameMode == PlayFromGameFile ||
10731 gameMode == TwoMachinesPlay ||
10732 gameMode == Training ||
10733 gameMode == AnalyzeMode ||
10734 gameMode == EndOfGame)
10737 if (gameMode == EditPosition)
10738 EditPositionDone(TRUE);
10740 if (!WhiteOnMove(currentMove)) {
10741 DisplayError(_("It is not White's turn"), 0);
10745 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10748 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10749 gameMode == AnalyzeFile)
10752 ResurrectChessProgram(); /* in case it isn't running */
10753 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10754 gameMode = MachinePlaysWhite;
10757 gameMode = MachinePlaysWhite;
10761 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10763 if (first.sendName) {
10764 sprintf(buf, "name %s\n", gameInfo.black);
10765 SendToProgram(buf, &first);
10767 if (first.sendTime) {
10768 if (first.useColors) {
10769 SendToProgram("black\n", &first); /*gnu kludge*/
10771 SendTimeRemaining(&first, TRUE);
10773 if (first.useColors) {
10774 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10776 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10777 SetMachineThinkingEnables();
10778 first.maybeThinking = TRUE;
10782 if (appData.autoFlipView && !flipView) {
10783 flipView = !flipView;
10784 DrawPosition(FALSE, NULL);
10785 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10788 if(bookHit) { // [HGM] book: simulate book reply
10789 static char bookMove[MSG_SIZ]; // a bit generous?
10791 programStats.nodes = programStats.depth = programStats.time =
10792 programStats.score = programStats.got_only_move = 0;
10793 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10795 strcpy(bookMove, "move ");
10796 strcat(bookMove, bookHit);
10797 HandleMachineMove(bookMove, &first);
10802 MachineBlackEvent()
10805 char *bookHit = NULL;
10807 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10811 if (gameMode == PlayFromGameFile ||
10812 gameMode == TwoMachinesPlay ||
10813 gameMode == Training ||
10814 gameMode == AnalyzeMode ||
10815 gameMode == EndOfGame)
10818 if (gameMode == EditPosition)
10819 EditPositionDone(TRUE);
10821 if (WhiteOnMove(currentMove)) {
10822 DisplayError(_("It is not Black's turn"), 0);
10826 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10829 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10830 gameMode == AnalyzeFile)
10833 ResurrectChessProgram(); /* in case it isn't running */
10834 gameMode = MachinePlaysBlack;
10838 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10840 if (first.sendName) {
10841 sprintf(buf, "name %s\n", gameInfo.white);
10842 SendToProgram(buf, &first);
10844 if (first.sendTime) {
10845 if (first.useColors) {
10846 SendToProgram("white\n", &first); /*gnu kludge*/
10848 SendTimeRemaining(&first, FALSE);
10850 if (first.useColors) {
10851 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10853 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10854 SetMachineThinkingEnables();
10855 first.maybeThinking = TRUE;
10858 if (appData.autoFlipView && flipView) {
10859 flipView = !flipView;
10860 DrawPosition(FALSE, NULL);
10861 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10863 if(bookHit) { // [HGM] book: simulate book reply
10864 static char bookMove[MSG_SIZ]; // a bit generous?
10866 programStats.nodes = programStats.depth = programStats.time =
10867 programStats.score = programStats.got_only_move = 0;
10868 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10870 strcpy(bookMove, "move ");
10871 strcat(bookMove, bookHit);
10872 HandleMachineMove(bookMove, &first);
10878 DisplayTwoMachinesTitle()
10881 if (appData.matchGames > 0) {
10882 if (first.twoMachinesColor[0] == 'w') {
10883 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10884 gameInfo.white, gameInfo.black,
10885 first.matchWins, second.matchWins,
10886 matchGame - 1 - (first.matchWins + second.matchWins));
10888 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10889 gameInfo.white, gameInfo.black,
10890 second.matchWins, first.matchWins,
10891 matchGame - 1 - (first.matchWins + second.matchWins));
10894 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10900 TwoMachinesEvent P((void))
10904 ChessProgramState *onmove;
10905 char *bookHit = NULL;
10907 if (appData.noChessProgram) return;
10909 switch (gameMode) {
10910 case TwoMachinesPlay:
10912 case MachinePlaysWhite:
10913 case MachinePlaysBlack:
10914 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10915 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10919 case BeginningOfGame:
10920 case PlayFromGameFile:
10923 if (gameMode != EditGame) return;
10926 EditPositionDone(TRUE);
10937 // forwardMostMove = currentMove;
10938 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10939 ResurrectChessProgram(); /* in case first program isn't running */
10941 if (second.pr == NULL) {
10942 StartChessProgram(&second);
10943 if (second.protocolVersion == 1) {
10944 TwoMachinesEventIfReady();
10946 /* kludge: allow timeout for initial "feature" command */
10948 DisplayMessage("", _("Starting second chess program"));
10949 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10953 DisplayMessage("", "");
10954 InitChessProgram(&second, FALSE);
10955 SendToProgram("force\n", &second);
10956 if (startedFromSetupPosition) {
10957 SendBoard(&second, backwardMostMove);
10958 if (appData.debugMode) {
10959 fprintf(debugFP, "Two Machines\n");
10962 for (i = backwardMostMove; i < forwardMostMove; i++) {
10963 SendMoveToProgram(i, &second);
10966 gameMode = TwoMachinesPlay;
10970 DisplayTwoMachinesTitle();
10972 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10978 SendToProgram(first.computerString, &first);
10979 if (first.sendName) {
10980 sprintf(buf, "name %s\n", second.tidy);
10981 SendToProgram(buf, &first);
10983 SendToProgram(second.computerString, &second);
10984 if (second.sendName) {
10985 sprintf(buf, "name %s\n", first.tidy);
10986 SendToProgram(buf, &second);
10990 if (!first.sendTime || !second.sendTime) {
10991 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10992 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10994 if (onmove->sendTime) {
10995 if (onmove->useColors) {
10996 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10998 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11000 if (onmove->useColors) {
11001 SendToProgram(onmove->twoMachinesColor, onmove);
11003 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11004 // SendToProgram("go\n", onmove);
11005 onmove->maybeThinking = TRUE;
11006 SetMachineThinkingEnables();
11010 if(bookHit) { // [HGM] book: simulate book reply
11011 static char bookMove[MSG_SIZ]; // a bit generous?
11013 programStats.nodes = programStats.depth = programStats.time =
11014 programStats.score = programStats.got_only_move = 0;
11015 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11017 strcpy(bookMove, "move ");
11018 strcat(bookMove, bookHit);
11019 savedMessage = bookMove; // args for deferred call
11020 savedState = onmove;
11021 ScheduleDelayedEvent(DeferredBookMove, 1);
11028 if (gameMode == Training) {
11029 SetTrainingModeOff();
11030 gameMode = PlayFromGameFile;
11031 DisplayMessage("", _("Training mode off"));
11033 gameMode = Training;
11034 animateTraining = appData.animate;
11036 /* make sure we are not already at the end of the game */
11037 if (currentMove < forwardMostMove) {
11038 SetTrainingModeOn();
11039 DisplayMessage("", _("Training mode on"));
11041 gameMode = PlayFromGameFile;
11042 DisplayError(_("Already at end of game"), 0);
11051 if (!appData.icsActive) return;
11052 switch (gameMode) {
11053 case IcsPlayingWhite:
11054 case IcsPlayingBlack:
11057 case BeginningOfGame:
11065 EditPositionDone(TRUE);
11078 gameMode = IcsIdle;
11089 switch (gameMode) {
11091 SetTrainingModeOff();
11093 case MachinePlaysWhite:
11094 case MachinePlaysBlack:
11095 case BeginningOfGame:
11096 SendToProgram("force\n", &first);
11097 SetUserThinkingEnables();
11099 case PlayFromGameFile:
11100 (void) StopLoadGameTimer();
11101 if (gameFileFP != NULL) {
11106 EditPositionDone(TRUE);
11111 SendToProgram("force\n", &first);
11113 case TwoMachinesPlay:
11114 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11115 ResurrectChessProgram();
11116 SetUserThinkingEnables();
11119 ResurrectChessProgram();
11121 case IcsPlayingBlack:
11122 case IcsPlayingWhite:
11123 DisplayError(_("Warning: You are still playing a game"), 0);
11126 DisplayError(_("Warning: You are still observing a game"), 0);
11129 DisplayError(_("Warning: You are still examining a game"), 0);
11140 first.offeredDraw = second.offeredDraw = 0;
11142 if (gameMode == PlayFromGameFile) {
11143 whiteTimeRemaining = timeRemaining[0][currentMove];
11144 blackTimeRemaining = timeRemaining[1][currentMove];
11148 if (gameMode == MachinePlaysWhite ||
11149 gameMode == MachinePlaysBlack ||
11150 gameMode == TwoMachinesPlay ||
11151 gameMode == EndOfGame) {
11152 i = forwardMostMove;
11153 while (i > currentMove) {
11154 SendToProgram("undo\n", &first);
11157 whiteTimeRemaining = timeRemaining[0][currentMove];
11158 blackTimeRemaining = timeRemaining[1][currentMove];
11159 DisplayBothClocks();
11160 if (whiteFlag || blackFlag) {
11161 whiteFlag = blackFlag = 0;
11166 gameMode = EditGame;
11173 EditPositionEvent()
11175 if (gameMode == EditPosition) {
11181 if (gameMode != EditGame) return;
11183 gameMode = EditPosition;
11186 if (currentMove > 0)
11187 CopyBoard(boards[0], boards[currentMove]);
11189 blackPlaysFirst = !WhiteOnMove(currentMove);
11191 currentMove = forwardMostMove = backwardMostMove = 0;
11192 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11199 /* [DM] icsEngineAnalyze - possible call from other functions */
11200 if (appData.icsEngineAnalyze) {
11201 appData.icsEngineAnalyze = FALSE;
11203 DisplayMessage("",_("Close ICS engine analyze..."));
11205 if (first.analysisSupport && first.analyzing) {
11206 SendToProgram("exit\n", &first);
11207 first.analyzing = FALSE;
11209 thinkOutput[0] = NULLCHAR;
11213 EditPositionDone(Boolean fakeRights)
11215 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11217 startedFromSetupPosition = TRUE;
11218 InitChessProgram(&first, FALSE);
11219 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11220 boards[0][EP_STATUS] = EP_NONE;
11221 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11222 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11223 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11224 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11225 } else boards[0][CASTLING][2] = NoRights;
11226 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11227 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11228 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11229 } else boards[0][CASTLING][5] = NoRights;
11231 SendToProgram("force\n", &first);
11232 if (blackPlaysFirst) {
11233 strcpy(moveList[0], "");
11234 strcpy(parseList[0], "");
11235 currentMove = forwardMostMove = backwardMostMove = 1;
11236 CopyBoard(boards[1], boards[0]);
11238 currentMove = forwardMostMove = backwardMostMove = 0;
11240 SendBoard(&first, forwardMostMove);
11241 if (appData.debugMode) {
11242 fprintf(debugFP, "EditPosDone\n");
11245 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11246 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11247 gameMode = EditGame;
11249 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11250 ClearHighlights(); /* [AS] */
11253 /* Pause for `ms' milliseconds */
11254 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11264 } while (SubtractTimeMarks(&m2, &m1) < ms);
11267 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11269 SendMultiLineToICS(buf)
11272 char temp[MSG_SIZ+1], *p;
11279 strncpy(temp, buf, len);
11284 if (*p == '\n' || *p == '\r')
11289 strcat(temp, "\n");
11291 SendToPlayer(temp, strlen(temp));
11295 SetWhiteToPlayEvent()
11297 if (gameMode == EditPosition) {
11298 blackPlaysFirst = FALSE;
11299 DisplayBothClocks(); /* works because currentMove is 0 */
11300 } else if (gameMode == IcsExamining) {
11301 SendToICS(ics_prefix);
11302 SendToICS("tomove white\n");
11307 SetBlackToPlayEvent()
11309 if (gameMode == EditPosition) {
11310 blackPlaysFirst = TRUE;
11311 currentMove = 1; /* kludge */
11312 DisplayBothClocks();
11314 } else if (gameMode == IcsExamining) {
11315 SendToICS(ics_prefix);
11316 SendToICS("tomove black\n");
11321 EditPositionMenuEvent(selection, x, y)
11322 ChessSquare selection;
11326 ChessSquare piece = boards[0][y][x];
11328 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11330 switch (selection) {
11332 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11333 SendToICS(ics_prefix);
11334 SendToICS("bsetup clear\n");
11335 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11336 SendToICS(ics_prefix);
11337 SendToICS("clearboard\n");
11339 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11340 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11341 for (y = 0; y < BOARD_HEIGHT; y++) {
11342 if (gameMode == IcsExamining) {
11343 if (boards[currentMove][y][x] != EmptySquare) {
11344 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11349 boards[0][y][x] = p;
11354 if (gameMode == EditPosition) {
11355 DrawPosition(FALSE, boards[0]);
11360 SetWhiteToPlayEvent();
11364 SetBlackToPlayEvent();
11368 if (gameMode == IcsExamining) {
11369 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11372 boards[0][y][x] = EmptySquare;
11373 DrawPosition(FALSE, boards[0]);
11378 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11379 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11380 selection = (ChessSquare) (PROMOTED piece);
11381 } else if(piece == EmptySquare) selection = WhiteSilver;
11382 else selection = (ChessSquare)((int)piece - 1);
11386 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11387 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11388 selection = (ChessSquare) (DEMOTED piece);
11389 } else if(piece == EmptySquare) selection = BlackSilver;
11390 else selection = (ChessSquare)((int)piece + 1);
11395 if(gameInfo.variant == VariantShatranj ||
11396 gameInfo.variant == VariantXiangqi ||
11397 gameInfo.variant == VariantCourier )
11398 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11403 if(gameInfo.variant == VariantXiangqi)
11404 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11405 if(gameInfo.variant == VariantKnightmate)
11406 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11409 if (gameMode == IcsExamining) {
11410 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11411 PieceToChar(selection), AAA + x, ONE + y);
11414 boards[0][y][x] = selection;
11415 DrawPosition(FALSE, boards[0]);
11423 DropMenuEvent(selection, x, y)
11424 ChessSquare selection;
11427 ChessMove moveType;
11429 switch (gameMode) {
11430 case IcsPlayingWhite:
11431 case MachinePlaysBlack:
11432 if (!WhiteOnMove(currentMove)) {
11433 DisplayMoveError(_("It is Black's turn"));
11436 moveType = WhiteDrop;
11438 case IcsPlayingBlack:
11439 case MachinePlaysWhite:
11440 if (WhiteOnMove(currentMove)) {
11441 DisplayMoveError(_("It is White's turn"));
11444 moveType = BlackDrop;
11447 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11453 if (moveType == BlackDrop && selection < BlackPawn) {
11454 selection = (ChessSquare) ((int) selection
11455 + (int) BlackPawn - (int) WhitePawn);
11457 if (boards[currentMove][y][x] != EmptySquare) {
11458 DisplayMoveError(_("That square is occupied"));
11462 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11468 /* Accept a pending offer of any kind from opponent */
11470 if (appData.icsActive) {
11471 SendToICS(ics_prefix);
11472 SendToICS("accept\n");
11473 } else if (cmailMsgLoaded) {
11474 if (currentMove == cmailOldMove &&
11475 commentList[cmailOldMove] != NULL &&
11476 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11477 "Black offers a draw" : "White offers a draw")) {
11479 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11480 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11482 DisplayError(_("There is no pending offer on this move"), 0);
11483 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11486 /* Not used for offers from chess program */
11493 /* Decline a pending offer of any kind from opponent */
11495 if (appData.icsActive) {
11496 SendToICS(ics_prefix);
11497 SendToICS("decline\n");
11498 } else if (cmailMsgLoaded) {
11499 if (currentMove == cmailOldMove &&
11500 commentList[cmailOldMove] != NULL &&
11501 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11502 "Black offers a draw" : "White offers a draw")) {
11504 AppendComment(cmailOldMove, "Draw declined", TRUE);
11505 DisplayComment(cmailOldMove - 1, "Draw declined");
11508 DisplayError(_("There is no pending offer on this move"), 0);
11511 /* Not used for offers from chess program */
11518 /* Issue ICS rematch command */
11519 if (appData.icsActive) {
11520 SendToICS(ics_prefix);
11521 SendToICS("rematch\n");
11528 /* Call your opponent's flag (claim a win on time) */
11529 if (appData.icsActive) {
11530 SendToICS(ics_prefix);
11531 SendToICS("flag\n");
11533 switch (gameMode) {
11536 case MachinePlaysWhite:
11539 GameEnds(GameIsDrawn, "Both players ran out of time",
11542 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11544 DisplayError(_("Your opponent is not out of time"), 0);
11547 case MachinePlaysBlack:
11550 GameEnds(GameIsDrawn, "Both players ran out of time",
11553 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11555 DisplayError(_("Your opponent is not out of time"), 0);
11565 /* Offer draw or accept pending draw offer from opponent */
11567 if (appData.icsActive) {
11568 /* Note: tournament rules require draw offers to be
11569 made after you make your move but before you punch
11570 your clock. Currently ICS doesn't let you do that;
11571 instead, you immediately punch your clock after making
11572 a move, but you can offer a draw at any time. */
11574 SendToICS(ics_prefix);
11575 SendToICS("draw\n");
11576 } else if (cmailMsgLoaded) {
11577 if (currentMove == cmailOldMove &&
11578 commentList[cmailOldMove] != NULL &&
11579 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11580 "Black offers a draw" : "White offers a draw")) {
11581 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11582 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11583 } else if (currentMove == cmailOldMove + 1) {
11584 char *offer = WhiteOnMove(cmailOldMove) ?
11585 "White offers a draw" : "Black offers a draw";
11586 AppendComment(currentMove, offer, TRUE);
11587 DisplayComment(currentMove - 1, offer);
11588 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11590 DisplayError(_("You must make your move before offering a draw"), 0);
11591 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11593 } else if (first.offeredDraw) {
11594 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11596 if (first.sendDrawOffers) {
11597 SendToProgram("draw\n", &first);
11598 userOfferedDraw = TRUE;
11606 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11608 if (appData.icsActive) {
11609 SendToICS(ics_prefix);
11610 SendToICS("adjourn\n");
11612 /* Currently GNU Chess doesn't offer or accept Adjourns */
11620 /* Offer Abort or accept pending Abort offer from opponent */
11622 if (appData.icsActive) {
11623 SendToICS(ics_prefix);
11624 SendToICS("abort\n");
11626 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11633 /* Resign. You can do this even if it's not your turn. */
11635 if (appData.icsActive) {
11636 SendToICS(ics_prefix);
11637 SendToICS("resign\n");
11639 switch (gameMode) {
11640 case MachinePlaysWhite:
11641 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11643 case MachinePlaysBlack:
11644 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11647 if (cmailMsgLoaded) {
11649 if (WhiteOnMove(cmailOldMove)) {
11650 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11652 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11654 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11665 StopObservingEvent()
11667 /* Stop observing current games */
11668 SendToICS(ics_prefix);
11669 SendToICS("unobserve\n");
11673 StopExaminingEvent()
11675 /* Stop observing current game */
11676 SendToICS(ics_prefix);
11677 SendToICS("unexamine\n");
11681 ForwardInner(target)
11686 if (appData.debugMode)
11687 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11688 target, currentMove, forwardMostMove);
11690 if (gameMode == EditPosition)
11693 if (gameMode == PlayFromGameFile && !pausing)
11696 if (gameMode == IcsExamining && pausing)
11697 limit = pauseExamForwardMostMove;
11699 limit = forwardMostMove;
11701 if (target > limit) target = limit;
11703 if (target > 0 && moveList[target - 1][0]) {
11704 int fromX, fromY, toX, toY;
11705 toX = moveList[target - 1][2] - AAA;
11706 toY = moveList[target - 1][3] - ONE;
11707 if (moveList[target - 1][1] == '@') {
11708 if (appData.highlightLastMove) {
11709 SetHighlights(-1, -1, toX, toY);
11712 fromX = moveList[target - 1][0] - AAA;
11713 fromY = moveList[target - 1][1] - ONE;
11714 if (target == currentMove + 1) {
11715 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11717 if (appData.highlightLastMove) {
11718 SetHighlights(fromX, fromY, toX, toY);
11722 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11723 gameMode == Training || gameMode == PlayFromGameFile ||
11724 gameMode == AnalyzeFile) {
11725 while (currentMove < target) {
11726 SendMoveToProgram(currentMove++, &first);
11729 currentMove = target;
11732 if (gameMode == EditGame || gameMode == EndOfGame) {
11733 whiteTimeRemaining = timeRemaining[0][currentMove];
11734 blackTimeRemaining = timeRemaining[1][currentMove];
11736 DisplayBothClocks();
11737 DisplayMove(currentMove - 1);
11738 DrawPosition(FALSE, boards[currentMove]);
11739 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11740 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11741 DisplayComment(currentMove - 1, commentList[currentMove]);
11749 if (gameMode == IcsExamining && !pausing) {
11750 SendToICS(ics_prefix);
11751 SendToICS("forward\n");
11753 ForwardInner(currentMove + 1);
11760 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11761 /* to optimze, we temporarily turn off analysis mode while we feed
11762 * the remaining moves to the engine. Otherwise we get analysis output
11765 if (first.analysisSupport) {
11766 SendToProgram("exit\nforce\n", &first);
11767 first.analyzing = FALSE;
11771 if (gameMode == IcsExamining && !pausing) {
11772 SendToICS(ics_prefix);
11773 SendToICS("forward 999999\n");
11775 ForwardInner(forwardMostMove);
11778 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11779 /* we have fed all the moves, so reactivate analysis mode */
11780 SendToProgram("analyze\n", &first);
11781 first.analyzing = TRUE;
11782 /*first.maybeThinking = TRUE;*/
11783 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11788 BackwardInner(target)
11791 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11793 if (appData.debugMode)
11794 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11795 target, currentMove, forwardMostMove);
11797 if (gameMode == EditPosition) return;
11798 if (currentMove <= backwardMostMove) {
11800 DrawPosition(full_redraw, boards[currentMove]);
11803 if (gameMode == PlayFromGameFile && !pausing)
11806 if (moveList[target][0]) {
11807 int fromX, fromY, toX, toY;
11808 toX = moveList[target][2] - AAA;
11809 toY = moveList[target][3] - ONE;
11810 if (moveList[target][1] == '@') {
11811 if (appData.highlightLastMove) {
11812 SetHighlights(-1, -1, toX, toY);
11815 fromX = moveList[target][0] - AAA;
11816 fromY = moveList[target][1] - ONE;
11817 if (target == currentMove - 1) {
11818 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11820 if (appData.highlightLastMove) {
11821 SetHighlights(fromX, fromY, toX, toY);
11825 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11826 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11827 while (currentMove > target) {
11828 SendToProgram("undo\n", &first);
11832 currentMove = target;
11835 if (gameMode == EditGame || gameMode == EndOfGame) {
11836 whiteTimeRemaining = timeRemaining[0][currentMove];
11837 blackTimeRemaining = timeRemaining[1][currentMove];
11839 DisplayBothClocks();
11840 DisplayMove(currentMove - 1);
11841 DrawPosition(full_redraw, boards[currentMove]);
11842 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11843 // [HGM] PV info: routine tests if comment empty
11844 DisplayComment(currentMove - 1, commentList[currentMove]);
11850 if (gameMode == IcsExamining && !pausing) {
11851 SendToICS(ics_prefix);
11852 SendToICS("backward\n");
11854 BackwardInner(currentMove - 1);
11861 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11862 /* to optimize, we temporarily turn off analysis mode while we undo
11863 * all the moves. Otherwise we get analysis output after each undo.
11865 if (first.analysisSupport) {
11866 SendToProgram("exit\nforce\n", &first);
11867 first.analyzing = FALSE;
11871 if (gameMode == IcsExamining && !pausing) {
11872 SendToICS(ics_prefix);
11873 SendToICS("backward 999999\n");
11875 BackwardInner(backwardMostMove);
11878 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11879 /* we have fed all the moves, so reactivate analysis mode */
11880 SendToProgram("analyze\n", &first);
11881 first.analyzing = TRUE;
11882 /*first.maybeThinking = TRUE;*/
11883 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11890 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11891 if (to >= forwardMostMove) to = forwardMostMove;
11892 if (to <= backwardMostMove) to = backwardMostMove;
11893 if (to < currentMove) {
11903 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11906 if (gameMode != IcsExamining) {
11907 DisplayError(_("You are not examining a game"), 0);
11911 DisplayError(_("You can't revert while pausing"), 0);
11914 SendToICS(ics_prefix);
11915 SendToICS("revert\n");
11921 switch (gameMode) {
11922 case MachinePlaysWhite:
11923 case MachinePlaysBlack:
11924 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11925 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11928 if (forwardMostMove < 2) return;
11929 currentMove = forwardMostMove = forwardMostMove - 2;
11930 whiteTimeRemaining = timeRemaining[0][currentMove];
11931 blackTimeRemaining = timeRemaining[1][currentMove];
11932 DisplayBothClocks();
11933 DisplayMove(currentMove - 1);
11934 ClearHighlights();/*!! could figure this out*/
11935 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11936 SendToProgram("remove\n", &first);
11937 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11940 case BeginningOfGame:
11944 case IcsPlayingWhite:
11945 case IcsPlayingBlack:
11946 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11947 SendToICS(ics_prefix);
11948 SendToICS("takeback 2\n");
11950 SendToICS(ics_prefix);
11951 SendToICS("takeback 1\n");
11960 ChessProgramState *cps;
11962 switch (gameMode) {
11963 case MachinePlaysWhite:
11964 if (!WhiteOnMove(forwardMostMove)) {
11965 DisplayError(_("It is your turn"), 0);
11970 case MachinePlaysBlack:
11971 if (WhiteOnMove(forwardMostMove)) {
11972 DisplayError(_("It is your turn"), 0);
11977 case TwoMachinesPlay:
11978 if (WhiteOnMove(forwardMostMove) ==
11979 (first.twoMachinesColor[0] == 'w')) {
11985 case BeginningOfGame:
11989 SendToProgram("?\n", cps);
11993 TruncateGameEvent()
11996 if (gameMode != EditGame) return;
12003 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12004 if (forwardMostMove > currentMove) {
12005 if (gameInfo.resultDetails != NULL) {
12006 free(gameInfo.resultDetails);
12007 gameInfo.resultDetails = NULL;
12008 gameInfo.result = GameUnfinished;
12010 forwardMostMove = currentMove;
12011 HistorySet(parseList, backwardMostMove, forwardMostMove,
12019 if (appData.noChessProgram) return;
12020 switch (gameMode) {
12021 case MachinePlaysWhite:
12022 if (WhiteOnMove(forwardMostMove)) {
12023 DisplayError(_("Wait until your turn"), 0);
12027 case BeginningOfGame:
12028 case MachinePlaysBlack:
12029 if (!WhiteOnMove(forwardMostMove)) {
12030 DisplayError(_("Wait until your turn"), 0);
12035 DisplayError(_("No hint available"), 0);
12038 SendToProgram("hint\n", &first);
12039 hintRequested = TRUE;
12045 if (appData.noChessProgram) return;
12046 switch (gameMode) {
12047 case MachinePlaysWhite:
12048 if (WhiteOnMove(forwardMostMove)) {
12049 DisplayError(_("Wait until your turn"), 0);
12053 case BeginningOfGame:
12054 case MachinePlaysBlack:
12055 if (!WhiteOnMove(forwardMostMove)) {
12056 DisplayError(_("Wait until your turn"), 0);
12061 EditPositionDone(TRUE);
12063 case TwoMachinesPlay:
12068 SendToProgram("bk\n", &first);
12069 bookOutput[0] = NULLCHAR;
12070 bookRequested = TRUE;
12076 char *tags = PGNTags(&gameInfo);
12077 TagsPopUp(tags, CmailMsg());
12081 /* end button procedures */
12084 PrintPosition(fp, move)
12090 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12091 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12092 char c = PieceToChar(boards[move][i][j]);
12093 fputc(c == 'x' ? '.' : c, fp);
12094 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12097 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12098 fprintf(fp, "white to play\n");
12100 fprintf(fp, "black to play\n");
12107 if (gameInfo.white != NULL) {
12108 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12114 /* Find last component of program's own name, using some heuristics */
12116 TidyProgramName(prog, host, buf)
12117 char *prog, *host, buf[MSG_SIZ];
12120 int local = (strcmp(host, "localhost") == 0);
12121 while (!local && (p = strchr(prog, ';')) != NULL) {
12123 while (*p == ' ') p++;
12126 if (*prog == '"' || *prog == '\'') {
12127 q = strchr(prog + 1, *prog);
12129 q = strchr(prog, ' ');
12131 if (q == NULL) q = prog + strlen(prog);
12133 while (p >= prog && *p != '/' && *p != '\\') p--;
12135 if(p == prog && *p == '"') p++;
12136 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12137 memcpy(buf, p, q - p);
12138 buf[q - p] = NULLCHAR;
12146 TimeControlTagValue()
12149 if (!appData.clockMode) {
12151 } else if (movesPerSession > 0) {
12152 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12153 } else if (timeIncrement == 0) {
12154 sprintf(buf, "%ld", timeControl/1000);
12156 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12158 return StrSave(buf);
12164 /* This routine is used only for certain modes */
12165 VariantClass v = gameInfo.variant;
12166 ChessMove r = GameUnfinished;
12169 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12170 r = gameInfo.result;
12171 p = gameInfo.resultDetails;
12172 gameInfo.resultDetails = NULL;
12174 ClearGameInfo(&gameInfo);
12175 gameInfo.variant = v;
12177 switch (gameMode) {
12178 case MachinePlaysWhite:
12179 gameInfo.event = StrSave( appData.pgnEventHeader );
12180 gameInfo.site = StrSave(HostName());
12181 gameInfo.date = PGNDate();
12182 gameInfo.round = StrSave("-");
12183 gameInfo.white = StrSave(first.tidy);
12184 gameInfo.black = StrSave(UserName());
12185 gameInfo.timeControl = TimeControlTagValue();
12188 case MachinePlaysBlack:
12189 gameInfo.event = StrSave( appData.pgnEventHeader );
12190 gameInfo.site = StrSave(HostName());
12191 gameInfo.date = PGNDate();
12192 gameInfo.round = StrSave("-");
12193 gameInfo.white = StrSave(UserName());
12194 gameInfo.black = StrSave(first.tidy);
12195 gameInfo.timeControl = TimeControlTagValue();
12198 case TwoMachinesPlay:
12199 gameInfo.event = StrSave( appData.pgnEventHeader );
12200 gameInfo.site = StrSave(HostName());
12201 gameInfo.date = PGNDate();
12202 if (matchGame > 0) {
12204 sprintf(buf, "%d", matchGame);
12205 gameInfo.round = StrSave(buf);
12207 gameInfo.round = StrSave("-");
12209 if (first.twoMachinesColor[0] == 'w') {
12210 gameInfo.white = StrSave(first.tidy);
12211 gameInfo.black = StrSave(second.tidy);
12213 gameInfo.white = StrSave(second.tidy);
12214 gameInfo.black = StrSave(first.tidy);
12216 gameInfo.timeControl = TimeControlTagValue();
12220 gameInfo.event = StrSave("Edited game");
12221 gameInfo.site = StrSave(HostName());
12222 gameInfo.date = PGNDate();
12223 gameInfo.round = StrSave("-");
12224 gameInfo.white = StrSave("-");
12225 gameInfo.black = StrSave("-");
12226 gameInfo.result = r;
12227 gameInfo.resultDetails = p;
12231 gameInfo.event = StrSave("Edited position");
12232 gameInfo.site = StrSave(HostName());
12233 gameInfo.date = PGNDate();
12234 gameInfo.round = StrSave("-");
12235 gameInfo.white = StrSave("-");
12236 gameInfo.black = StrSave("-");
12239 case IcsPlayingWhite:
12240 case IcsPlayingBlack:
12245 case PlayFromGameFile:
12246 gameInfo.event = StrSave("Game from non-PGN file");
12247 gameInfo.site = StrSave(HostName());
12248 gameInfo.date = PGNDate();
12249 gameInfo.round = StrSave("-");
12250 gameInfo.white = StrSave("?");
12251 gameInfo.black = StrSave("?");
12260 ReplaceComment(index, text)
12266 while (*text == '\n') text++;
12267 len = strlen(text);
12268 while (len > 0 && text[len - 1] == '\n') len--;
12270 if (commentList[index] != NULL)
12271 free(commentList[index]);
12274 commentList[index] = NULL;
12277 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12278 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12279 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12280 commentList[index] = (char *) malloc(len + 2);
12281 strncpy(commentList[index], text, len);
12282 commentList[index][len] = '\n';
12283 commentList[index][len + 1] = NULLCHAR;
12285 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12287 commentList[index] = (char *) malloc(len + 6);
12288 strcpy(commentList[index], "{\n");
12289 strncpy(commentList[index]+2, text, len);
12290 commentList[index][len+2] = NULLCHAR;
12291 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12292 strcat(commentList[index], "\n}\n");
12306 if (ch == '\r') continue;
12308 } while (ch != '\0');
12312 AppendComment(index, text, addBraces)
12315 Boolean addBraces; // [HGM] braces: tells if we should add {}
12320 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12321 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12324 while (*text == '\n') text++;
12325 len = strlen(text);
12326 while (len > 0 && text[len - 1] == '\n') len--;
12328 if (len == 0) return;
12330 if (commentList[index] != NULL) {
12331 old = commentList[index];
12332 oldlen = strlen(old);
12333 while(commentList[index][oldlen-1] == '\n')
12334 commentList[index][--oldlen] = NULLCHAR;
12335 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12336 strcpy(commentList[index], old);
12338 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12339 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12340 if(addBraces) addBraces = FALSE; else { text++; len--; }
12341 while (*text == '\n') { text++; len--; }
12342 commentList[index][--oldlen] = NULLCHAR;
12344 if(addBraces) strcat(commentList[index], "\n{\n");
12345 else strcat(commentList[index], "\n");
12346 strcat(commentList[index], text);
12347 if(addBraces) strcat(commentList[index], "\n}\n");
12348 else strcat(commentList[index], "\n");
12350 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12352 strcpy(commentList[index], "{\n");
12353 else commentList[index][0] = NULLCHAR;
12354 strcat(commentList[index], text);
12355 strcat(commentList[index], "\n");
12356 if(addBraces) strcat(commentList[index], "}\n");
12360 static char * FindStr( char * text, char * sub_text )
12362 char * result = strstr( text, sub_text );
12364 if( result != NULL ) {
12365 result += strlen( sub_text );
12371 /* [AS] Try to extract PV info from PGN comment */
12372 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12373 char *GetInfoFromComment( int index, char * text )
12377 if( text != NULL && index > 0 ) {
12380 int time = -1, sec = 0, deci;
12381 char * s_eval = FindStr( text, "[%eval " );
12382 char * s_emt = FindStr( text, "[%emt " );
12384 if( s_eval != NULL || s_emt != NULL ) {
12388 if( s_eval != NULL ) {
12389 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12393 if( delim != ']' ) {
12398 if( s_emt != NULL ) {
12403 /* We expect something like: [+|-]nnn.nn/dd */
12406 if(*text != '{') return text; // [HGM] braces: must be normal comment
12408 sep = strchr( text, '/' );
12409 if( sep == NULL || sep < (text+4) ) {
12413 time = -1; sec = -1; deci = -1;
12414 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12415 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12416 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12417 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12421 if( score_lo < 0 || score_lo >= 100 ) {
12425 if(sec >= 0) time = 600*time + 10*sec; else
12426 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12428 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12430 /* [HGM] PV time: now locate end of PV info */
12431 while( *++sep >= '0' && *sep <= '9'); // strip depth
12433 while( *++sep >= '0' && *sep <= '9'); // strip time
12435 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12437 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12438 while(*sep == ' ') sep++;
12449 pvInfoList[index-1].depth = depth;
12450 pvInfoList[index-1].score = score;
12451 pvInfoList[index-1].time = 10*time; // centi-sec
12452 if(*sep == '}') *sep = 0; else *--sep = '{';
12458 SendToProgram(message, cps)
12460 ChessProgramState *cps;
12462 int count, outCount, error;
12465 if (cps->pr == NULL) return;
12468 if (appData.debugMode) {
12471 fprintf(debugFP, "%ld >%-6s: %s",
12472 SubtractTimeMarks(&now, &programStartTime),
12473 cps->which, message);
12476 count = strlen(message);
12477 outCount = OutputToProcess(cps->pr, message, count, &error);
12478 if (outCount < count && !exiting
12479 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12480 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12481 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12482 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12483 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12484 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12486 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12488 gameInfo.resultDetails = StrSave(buf);
12490 DisplayFatalError(buf, error, 1);
12495 ReceiveFromProgram(isr, closure, message, count, error)
12496 InputSourceRef isr;
12504 ChessProgramState *cps = (ChessProgramState *)closure;
12506 if (isr != cps->isr) return; /* Killed intentionally */
12510 _("Error: %s chess program (%s) exited unexpectedly"),
12511 cps->which, cps->program);
12512 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12513 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12514 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12515 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12517 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12519 gameInfo.resultDetails = StrSave(buf);
12521 RemoveInputSource(cps->isr);
12522 DisplayFatalError(buf, 0, 1);
12525 _("Error reading from %s chess program (%s)"),
12526 cps->which, cps->program);
12527 RemoveInputSource(cps->isr);
12529 /* [AS] Program is misbehaving badly... kill it */
12530 if( count == -2 ) {
12531 DestroyChildProcess( cps->pr, 9 );
12535 DisplayFatalError(buf, error, 1);
12540 if ((end_str = strchr(message, '\r')) != NULL)
12541 *end_str = NULLCHAR;
12542 if ((end_str = strchr(message, '\n')) != NULL)
12543 *end_str = NULLCHAR;
12545 if (appData.debugMode) {
12546 TimeMark now; int print = 1;
12547 char *quote = ""; char c; int i;
12549 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12550 char start = message[0];
12551 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12552 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12553 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12554 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12555 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12556 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12557 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12558 sscanf(message, "pong %c", &c)!=1 && start != '#')
12559 { quote = "# "; print = (appData.engineComments == 2); }
12560 message[0] = start; // restore original message
12564 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12565 SubtractTimeMarks(&now, &programStartTime), cps->which,
12571 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12572 if (appData.icsEngineAnalyze) {
12573 if (strstr(message, "whisper") != NULL ||
12574 strstr(message, "kibitz") != NULL ||
12575 strstr(message, "tellics") != NULL) return;
12578 HandleMachineMove(message, cps);
12583 SendTimeControl(cps, mps, tc, inc, sd, st)
12584 ChessProgramState *cps;
12585 int mps, inc, sd, st;
12591 if( timeControl_2 > 0 ) {
12592 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12593 tc = timeControl_2;
12596 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12597 inc /= cps->timeOdds;
12598 st /= cps->timeOdds;
12600 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12603 /* Set exact time per move, normally using st command */
12604 if (cps->stKludge) {
12605 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12607 if (seconds == 0) {
12608 sprintf(buf, "level 1 %d\n", st/60);
12610 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12613 sprintf(buf, "st %d\n", st);
12616 /* Set conventional or incremental time control, using level command */
12617 if (seconds == 0) {
12618 /* Note old gnuchess bug -- minutes:seconds used to not work.
12619 Fixed in later versions, but still avoid :seconds
12620 when seconds is 0. */
12621 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12623 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12624 seconds, inc/1000);
12627 SendToProgram(buf, cps);
12629 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12630 /* Orthogonally, limit search to given depth */
12632 if (cps->sdKludge) {
12633 sprintf(buf, "depth\n%d\n", sd);
12635 sprintf(buf, "sd %d\n", sd);
12637 SendToProgram(buf, cps);
12640 if(cps->nps > 0) { /* [HGM] nps */
12641 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12643 sprintf(buf, "nps %d\n", cps->nps);
12644 SendToProgram(buf, cps);
12649 ChessProgramState *WhitePlayer()
12650 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12652 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12653 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12659 SendTimeRemaining(cps, machineWhite)
12660 ChessProgramState *cps;
12661 int /*boolean*/ machineWhite;
12663 char message[MSG_SIZ];
12666 /* Note: this routine must be called when the clocks are stopped
12667 or when they have *just* been set or switched; otherwise
12668 it will be off by the time since the current tick started.
12670 if (machineWhite) {
12671 time = whiteTimeRemaining / 10;
12672 otime = blackTimeRemaining / 10;
12674 time = blackTimeRemaining / 10;
12675 otime = whiteTimeRemaining / 10;
12677 /* [HGM] translate opponent's time by time-odds factor */
12678 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12679 if (appData.debugMode) {
12680 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12683 if (time <= 0) time = 1;
12684 if (otime <= 0) otime = 1;
12686 sprintf(message, "time %ld\n", time);
12687 SendToProgram(message, cps);
12689 sprintf(message, "otim %ld\n", otime);
12690 SendToProgram(message, cps);
12694 BoolFeature(p, name, loc, cps)
12698 ChessProgramState *cps;
12701 int len = strlen(name);
12703 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12705 sscanf(*p, "%d", &val);
12707 while (**p && **p != ' ') (*p)++;
12708 sprintf(buf, "accepted %s\n", name);
12709 SendToProgram(buf, cps);
12716 IntFeature(p, name, loc, cps)
12720 ChessProgramState *cps;
12723 int len = strlen(name);
12724 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12726 sscanf(*p, "%d", loc);
12727 while (**p && **p != ' ') (*p)++;
12728 sprintf(buf, "accepted %s\n", name);
12729 SendToProgram(buf, cps);
12736 StringFeature(p, name, loc, cps)
12740 ChessProgramState *cps;
12743 int len = strlen(name);
12744 if (strncmp((*p), name, len) == 0
12745 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12747 sscanf(*p, "%[^\"]", loc);
12748 while (**p && **p != '\"') (*p)++;
12749 if (**p == '\"') (*p)++;
12750 sprintf(buf, "accepted %s\n", name);
12751 SendToProgram(buf, cps);
12758 ParseOption(Option *opt, ChessProgramState *cps)
12759 // [HGM] options: process the string that defines an engine option, and determine
12760 // name, type, default value, and allowed value range
12762 char *p, *q, buf[MSG_SIZ];
12763 int n, min = (-1)<<31, max = 1<<31, def;
12765 if(p = strstr(opt->name, " -spin ")) {
12766 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12767 if(max < min) max = min; // enforce consistency
12768 if(def < min) def = min;
12769 if(def > max) def = max;
12774 } else if((p = strstr(opt->name, " -slider "))) {
12775 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12776 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12777 if(max < min) max = min; // enforce consistency
12778 if(def < min) def = min;
12779 if(def > max) def = max;
12783 opt->type = Spin; // Slider;
12784 } else if((p = strstr(opt->name, " -string "))) {
12785 opt->textValue = p+9;
12786 opt->type = TextBox;
12787 } else if((p = strstr(opt->name, " -file "))) {
12788 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12789 opt->textValue = p+7;
12790 opt->type = TextBox; // FileName;
12791 } else if((p = strstr(opt->name, " -path "))) {
12792 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12793 opt->textValue = p+7;
12794 opt->type = TextBox; // PathName;
12795 } else if(p = strstr(opt->name, " -check ")) {
12796 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12797 opt->value = (def != 0);
12798 opt->type = CheckBox;
12799 } else if(p = strstr(opt->name, " -combo ")) {
12800 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12801 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12802 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12803 opt->value = n = 0;
12804 while(q = StrStr(q, " /// ")) {
12805 n++; *q = 0; // count choices, and null-terminate each of them
12807 if(*q == '*') { // remember default, which is marked with * prefix
12811 cps->comboList[cps->comboCnt++] = q;
12813 cps->comboList[cps->comboCnt++] = NULL;
12815 opt->type = ComboBox;
12816 } else if(p = strstr(opt->name, " -button")) {
12817 opt->type = Button;
12818 } else if(p = strstr(opt->name, " -save")) {
12819 opt->type = SaveButton;
12820 } else return FALSE;
12821 *p = 0; // terminate option name
12822 // now look if the command-line options define a setting for this engine option.
12823 if(cps->optionSettings && cps->optionSettings[0])
12824 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12825 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12826 sprintf(buf, "option %s", p);
12827 if(p = strstr(buf, ",")) *p = 0;
12829 SendToProgram(buf, cps);
12835 FeatureDone(cps, val)
12836 ChessProgramState* cps;
12839 DelayedEventCallback cb = GetDelayedEvent();
12840 if ((cb == InitBackEnd3 && cps == &first) ||
12841 (cb == TwoMachinesEventIfReady && cps == &second)) {
12842 CancelDelayedEvent();
12843 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12845 cps->initDone = val;
12848 /* Parse feature command from engine */
12850 ParseFeatures(args, cps)
12852 ChessProgramState *cps;
12860 while (*p == ' ') p++;
12861 if (*p == NULLCHAR) return;
12863 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12864 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12865 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12866 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12867 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12868 if (BoolFeature(&p, "reuse", &val, cps)) {
12869 /* Engine can disable reuse, but can't enable it if user said no */
12870 if (!val) cps->reuse = FALSE;
12873 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12874 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12875 if (gameMode == TwoMachinesPlay) {
12876 DisplayTwoMachinesTitle();
12882 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12883 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12884 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12885 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12886 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12887 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12888 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12889 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12890 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12891 if (IntFeature(&p, "done", &val, cps)) {
12892 FeatureDone(cps, val);
12895 /* Added by Tord: */
12896 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12897 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12898 /* End of additions by Tord */
12900 /* [HGM] added features: */
12901 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12902 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12903 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12904 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12905 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12906 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12907 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12908 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12909 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12910 SendToProgram(buf, cps);
12913 if(cps->nrOptions >= MAX_OPTIONS) {
12915 sprintf(buf, "%s engine has too many options\n", cps->which);
12916 DisplayError(buf, 0);
12920 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12921 /* End of additions by HGM */
12923 /* unknown feature: complain and skip */
12925 while (*q && *q != '=') q++;
12926 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12927 SendToProgram(buf, cps);
12933 while (*p && *p != '\"') p++;
12934 if (*p == '\"') p++;
12936 while (*p && *p != ' ') p++;
12944 PeriodicUpdatesEvent(newState)
12947 if (newState == appData.periodicUpdates)
12950 appData.periodicUpdates=newState;
12952 /* Display type changes, so update it now */
12953 // DisplayAnalysis();
12955 /* Get the ball rolling again... */
12957 AnalysisPeriodicEvent(1);
12958 StartAnalysisClock();
12963 PonderNextMoveEvent(newState)
12966 if (newState == appData.ponderNextMove) return;
12967 if (gameMode == EditPosition) EditPositionDone(TRUE);
12969 SendToProgram("hard\n", &first);
12970 if (gameMode == TwoMachinesPlay) {
12971 SendToProgram("hard\n", &second);
12974 SendToProgram("easy\n", &first);
12975 thinkOutput[0] = NULLCHAR;
12976 if (gameMode == TwoMachinesPlay) {
12977 SendToProgram("easy\n", &second);
12980 appData.ponderNextMove = newState;
12984 NewSettingEvent(option, command, value)
12990 if (gameMode == EditPosition) EditPositionDone(TRUE);
12991 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12992 SendToProgram(buf, &first);
12993 if (gameMode == TwoMachinesPlay) {
12994 SendToProgram(buf, &second);
12999 ShowThinkingEvent()
13000 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13002 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13003 int newState = appData.showThinking
13004 // [HGM] thinking: other features now need thinking output as well
13005 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13007 if (oldState == newState) return;
13008 oldState = newState;
13009 if (gameMode == EditPosition) EditPositionDone(TRUE);
13011 SendToProgram("post\n", &first);
13012 if (gameMode == TwoMachinesPlay) {
13013 SendToProgram("post\n", &second);
13016 SendToProgram("nopost\n", &first);
13017 thinkOutput[0] = NULLCHAR;
13018 if (gameMode == TwoMachinesPlay) {
13019 SendToProgram("nopost\n", &second);
13022 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13026 AskQuestionEvent(title, question, replyPrefix, which)
13027 char *title; char *question; char *replyPrefix; char *which;
13029 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13030 if (pr == NoProc) return;
13031 AskQuestion(title, question, replyPrefix, pr);
13035 DisplayMove(moveNumber)
13038 char message[MSG_SIZ];
13040 char cpThinkOutput[MSG_SIZ];
13042 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13044 if (moveNumber == forwardMostMove - 1 ||
13045 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13047 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13049 if (strchr(cpThinkOutput, '\n')) {
13050 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13053 *cpThinkOutput = NULLCHAR;
13056 /* [AS] Hide thinking from human user */
13057 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13058 *cpThinkOutput = NULLCHAR;
13059 if( thinkOutput[0] != NULLCHAR ) {
13062 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13063 cpThinkOutput[i] = '.';
13065 cpThinkOutput[i] = NULLCHAR;
13066 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13070 if (moveNumber == forwardMostMove - 1 &&
13071 gameInfo.resultDetails != NULL) {
13072 if (gameInfo.resultDetails[0] == NULLCHAR) {
13073 sprintf(res, " %s", PGNResult(gameInfo.result));
13075 sprintf(res, " {%s} %s",
13076 gameInfo.resultDetails, PGNResult(gameInfo.result));
13082 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13083 DisplayMessage(res, cpThinkOutput);
13085 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13086 WhiteOnMove(moveNumber) ? " " : ".. ",
13087 parseList[moveNumber], res);
13088 DisplayMessage(message, cpThinkOutput);
13093 DisplayComment(moveNumber, text)
13097 char title[MSG_SIZ];
13098 char buf[8000]; // comment can be long!
13100 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13101 strcpy(title, "Comment");
13103 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13104 WhiteOnMove(moveNumber) ? " " : ".. ",
13105 parseList[moveNumber]);
13107 // [HGM] PV info: display PV info together with (or as) comment
13108 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13109 if(text == NULL) text = "";
13110 score = pvInfoList[moveNumber].score;
13111 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13112 depth, (pvInfoList[moveNumber].time+50)/100, text);
13115 if (text != NULL && (appData.autoDisplayComment || commentUp))
13116 CommentPopUp(title, text);
13119 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13120 * might be busy thinking or pondering. It can be omitted if your
13121 * gnuchess is configured to stop thinking immediately on any user
13122 * input. However, that gnuchess feature depends on the FIONREAD
13123 * ioctl, which does not work properly on some flavors of Unix.
13127 ChessProgramState *cps;
13130 if (!cps->useSigint) return;
13131 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13132 switch (gameMode) {
13133 case MachinePlaysWhite:
13134 case MachinePlaysBlack:
13135 case TwoMachinesPlay:
13136 case IcsPlayingWhite:
13137 case IcsPlayingBlack:
13140 /* Skip if we know it isn't thinking */
13141 if (!cps->maybeThinking) return;
13142 if (appData.debugMode)
13143 fprintf(debugFP, "Interrupting %s\n", cps->which);
13144 InterruptChildProcess(cps->pr);
13145 cps->maybeThinking = FALSE;
13150 #endif /*ATTENTION*/
13156 if (whiteTimeRemaining <= 0) {
13159 if (appData.icsActive) {
13160 if (appData.autoCallFlag &&
13161 gameMode == IcsPlayingBlack && !blackFlag) {
13162 SendToICS(ics_prefix);
13163 SendToICS("flag\n");
13167 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13169 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13170 if (appData.autoCallFlag) {
13171 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13178 if (blackTimeRemaining <= 0) {
13181 if (appData.icsActive) {
13182 if (appData.autoCallFlag &&
13183 gameMode == IcsPlayingWhite && !whiteFlag) {
13184 SendToICS(ics_prefix);
13185 SendToICS("flag\n");
13189 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13191 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13192 if (appData.autoCallFlag) {
13193 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13206 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13207 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13210 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13212 if ( !WhiteOnMove(forwardMostMove) )
13213 /* White made time control */
13214 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13215 /* [HGM] time odds: correct new time quota for time odds! */
13216 / WhitePlayer()->timeOdds;
13218 /* Black made time control */
13219 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13220 / WhitePlayer()->other->timeOdds;
13224 DisplayBothClocks()
13226 int wom = gameMode == EditPosition ?
13227 !blackPlaysFirst : WhiteOnMove(currentMove);
13228 DisplayWhiteClock(whiteTimeRemaining, wom);
13229 DisplayBlackClock(blackTimeRemaining, !wom);
13233 /* Timekeeping seems to be a portability nightmare. I think everyone
13234 has ftime(), but I'm really not sure, so I'm including some ifdefs
13235 to use other calls if you don't. Clocks will be less accurate if
13236 you have neither ftime nor gettimeofday.
13239 /* VS 2008 requires the #include outside of the function */
13240 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13241 #include <sys/timeb.h>
13244 /* Get the current time as a TimeMark */
13249 #if HAVE_GETTIMEOFDAY
13251 struct timeval timeVal;
13252 struct timezone timeZone;
13254 gettimeofday(&timeVal, &timeZone);
13255 tm->sec = (long) timeVal.tv_sec;
13256 tm->ms = (int) (timeVal.tv_usec / 1000L);
13258 #else /*!HAVE_GETTIMEOFDAY*/
13261 // include <sys/timeb.h> / moved to just above start of function
13262 struct timeb timeB;
13265 tm->sec = (long) timeB.time;
13266 tm->ms = (int) timeB.millitm;
13268 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13269 tm->sec = (long) time(NULL);
13275 /* Return the difference in milliseconds between two
13276 time marks. We assume the difference will fit in a long!
13279 SubtractTimeMarks(tm2, tm1)
13280 TimeMark *tm2, *tm1;
13282 return 1000L*(tm2->sec - tm1->sec) +
13283 (long) (tm2->ms - tm1->ms);
13288 * Code to manage the game clocks.
13290 * In tournament play, black starts the clock and then white makes a move.
13291 * We give the human user a slight advantage if he is playing white---the
13292 * clocks don't run until he makes his first move, so it takes zero time.
13293 * Also, we don't account for network lag, so we could get out of sync
13294 * with GNU Chess's clock -- but then, referees are always right.
13297 static TimeMark tickStartTM;
13298 static long intendedTickLength;
13301 NextTickLength(timeRemaining)
13302 long timeRemaining;
13304 long nominalTickLength, nextTickLength;
13306 if (timeRemaining > 0L && timeRemaining <= 10000L)
13307 nominalTickLength = 100L;
13309 nominalTickLength = 1000L;
13310 nextTickLength = timeRemaining % nominalTickLength;
13311 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13313 return nextTickLength;
13316 /* Adjust clock one minute up or down */
13318 AdjustClock(Boolean which, int dir)
13320 if(which) blackTimeRemaining += 60000*dir;
13321 else whiteTimeRemaining += 60000*dir;
13322 DisplayBothClocks();
13325 /* Stop clocks and reset to a fresh time control */
13329 (void) StopClockTimer();
13330 if (appData.icsActive) {
13331 whiteTimeRemaining = blackTimeRemaining = 0;
13332 } else if (searchTime) {
13333 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13334 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13335 } else { /* [HGM] correct new time quote for time odds */
13336 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13337 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13339 if (whiteFlag || blackFlag) {
13341 whiteFlag = blackFlag = FALSE;
13343 DisplayBothClocks();
13346 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13348 /* Decrement running clock by amount of time that has passed */
13352 long timeRemaining;
13353 long lastTickLength, fudge;
13356 if (!appData.clockMode) return;
13357 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13361 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13363 /* Fudge if we woke up a little too soon */
13364 fudge = intendedTickLength - lastTickLength;
13365 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13367 if (WhiteOnMove(forwardMostMove)) {
13368 if(whiteNPS >= 0) lastTickLength = 0;
13369 timeRemaining = whiteTimeRemaining -= lastTickLength;
13370 DisplayWhiteClock(whiteTimeRemaining - fudge,
13371 WhiteOnMove(currentMove));
13373 if(blackNPS >= 0) lastTickLength = 0;
13374 timeRemaining = blackTimeRemaining -= lastTickLength;
13375 DisplayBlackClock(blackTimeRemaining - fudge,
13376 !WhiteOnMove(currentMove));
13379 if (CheckFlags()) return;
13382 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13383 StartClockTimer(intendedTickLength);
13385 /* if the time remaining has fallen below the alarm threshold, sound the
13386 * alarm. if the alarm has sounded and (due to a takeback or time control
13387 * with increment) the time remaining has increased to a level above the
13388 * threshold, reset the alarm so it can sound again.
13391 if (appData.icsActive && appData.icsAlarm) {
13393 /* make sure we are dealing with the user's clock */
13394 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13395 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13398 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13399 alarmSounded = FALSE;
13400 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13402 alarmSounded = TRUE;
13408 /* A player has just moved, so stop the previously running
13409 clock and (if in clock mode) start the other one.
13410 We redisplay both clocks in case we're in ICS mode, because
13411 ICS gives us an update to both clocks after every move.
13412 Note that this routine is called *after* forwardMostMove
13413 is updated, so the last fractional tick must be subtracted
13414 from the color that is *not* on move now.
13419 long lastTickLength;
13421 int flagged = FALSE;
13425 if (StopClockTimer() && appData.clockMode) {
13426 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13427 if (WhiteOnMove(forwardMostMove)) {
13428 if(blackNPS >= 0) lastTickLength = 0;
13429 blackTimeRemaining -= lastTickLength;
13430 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13431 // if(pvInfoList[forwardMostMove-1].time == -1)
13432 pvInfoList[forwardMostMove-1].time = // use GUI time
13433 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13435 if(whiteNPS >= 0) lastTickLength = 0;
13436 whiteTimeRemaining -= lastTickLength;
13437 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13438 // if(pvInfoList[forwardMostMove-1].time == -1)
13439 pvInfoList[forwardMostMove-1].time =
13440 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13442 flagged = CheckFlags();
13444 CheckTimeControl();
13446 if (flagged || !appData.clockMode) return;
13448 switch (gameMode) {
13449 case MachinePlaysBlack:
13450 case MachinePlaysWhite:
13451 case BeginningOfGame:
13452 if (pausing) return;
13456 case PlayFromGameFile:
13464 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13465 if(WhiteOnMove(forwardMostMove))
13466 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13467 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13471 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13472 whiteTimeRemaining : blackTimeRemaining);
13473 StartClockTimer(intendedTickLength);
13477 /* Stop both clocks */
13481 long lastTickLength;
13484 if (!StopClockTimer()) return;
13485 if (!appData.clockMode) return;
13489 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13490 if (WhiteOnMove(forwardMostMove)) {
13491 if(whiteNPS >= 0) lastTickLength = 0;
13492 whiteTimeRemaining -= lastTickLength;
13493 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13495 if(blackNPS >= 0) lastTickLength = 0;
13496 blackTimeRemaining -= lastTickLength;
13497 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13502 /* Start clock of player on move. Time may have been reset, so
13503 if clock is already running, stop and restart it. */
13507 (void) StopClockTimer(); /* in case it was running already */
13508 DisplayBothClocks();
13509 if (CheckFlags()) return;
13511 if (!appData.clockMode) return;
13512 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13514 GetTimeMark(&tickStartTM);
13515 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13516 whiteTimeRemaining : blackTimeRemaining);
13518 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13519 whiteNPS = blackNPS = -1;
13520 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13521 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13522 whiteNPS = first.nps;
13523 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13524 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13525 blackNPS = first.nps;
13526 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13527 whiteNPS = second.nps;
13528 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13529 blackNPS = second.nps;
13530 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13532 StartClockTimer(intendedTickLength);
13539 long second, minute, hour, day;
13541 static char buf[32];
13543 if (ms > 0 && ms <= 9900) {
13544 /* convert milliseconds to tenths, rounding up */
13545 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13547 sprintf(buf, " %03.1f ", tenths/10.0);
13551 /* convert milliseconds to seconds, rounding up */
13552 /* use floating point to avoid strangeness of integer division
13553 with negative dividends on many machines */
13554 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13561 day = second / (60 * 60 * 24);
13562 second = second % (60 * 60 * 24);
13563 hour = second / (60 * 60);
13564 second = second % (60 * 60);
13565 minute = second / 60;
13566 second = second % 60;
13569 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13570 sign, day, hour, minute, second);
13572 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13574 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13581 * This is necessary because some C libraries aren't ANSI C compliant yet.
13584 StrStr(string, match)
13585 char *string, *match;
13589 length = strlen(match);
13591 for (i = strlen(string) - length; i >= 0; i--, string++)
13592 if (!strncmp(match, string, length))
13599 StrCaseStr(string, match)
13600 char *string, *match;
13604 length = strlen(match);
13606 for (i = strlen(string) - length; i >= 0; i--, string++) {
13607 for (j = 0; j < length; j++) {
13608 if (ToLower(match[j]) != ToLower(string[j]))
13611 if (j == length) return string;
13625 c1 = ToLower(*s1++);
13626 c2 = ToLower(*s2++);
13627 if (c1 > c2) return 1;
13628 if (c1 < c2) return -1;
13629 if (c1 == NULLCHAR) return 0;
13638 return isupper(c) ? tolower(c) : c;
13646 return islower(c) ? toupper(c) : c;
13648 #endif /* !_amigados */
13656 if ((ret = (char *) malloc(strlen(s) + 1))) {
13663 StrSavePtr(s, savePtr)
13664 char *s, **savePtr;
13669 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13670 strcpy(*savePtr, s);
13682 clock = time((time_t *)NULL);
13683 tm = localtime(&clock);
13684 sprintf(buf, "%04d.%02d.%02d",
13685 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13686 return StrSave(buf);
13691 PositionToFEN(move, overrideCastling)
13693 char *overrideCastling;
13695 int i, j, fromX, fromY, toX, toY;
13702 whiteToPlay = (gameMode == EditPosition) ?
13703 !blackPlaysFirst : (move % 2 == 0);
13706 /* Piece placement data */
13707 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13709 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13710 if (boards[move][i][j] == EmptySquare) {
13712 } else { ChessSquare piece = boards[move][i][j];
13713 if (emptycount > 0) {
13714 if(emptycount<10) /* [HGM] can be >= 10 */
13715 *p++ = '0' + emptycount;
13716 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13719 if(PieceToChar(piece) == '+') {
13720 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13722 piece = (ChessSquare)(DEMOTED piece);
13724 *p++ = PieceToChar(piece);
13726 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13727 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13732 if (emptycount > 0) {
13733 if(emptycount<10) /* [HGM] can be >= 10 */
13734 *p++ = '0' + emptycount;
13735 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13742 /* [HGM] print Crazyhouse or Shogi holdings */
13743 if( gameInfo.holdingsWidth ) {
13744 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13746 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13747 piece = boards[move][i][BOARD_WIDTH-1];
13748 if( piece != EmptySquare )
13749 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13750 *p++ = PieceToChar(piece);
13752 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13753 piece = boards[move][BOARD_HEIGHT-i-1][0];
13754 if( piece != EmptySquare )
13755 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13756 *p++ = PieceToChar(piece);
13759 if( q == p ) *p++ = '-';
13765 *p++ = whiteToPlay ? 'w' : 'b';
13768 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13769 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13771 if(nrCastlingRights) {
13773 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13774 /* [HGM] write directly from rights */
13775 if(boards[move][CASTLING][2] != NoRights &&
13776 boards[move][CASTLING][0] != NoRights )
13777 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13778 if(boards[move][CASTLING][2] != NoRights &&
13779 boards[move][CASTLING][1] != NoRights )
13780 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13781 if(boards[move][CASTLING][5] != NoRights &&
13782 boards[move][CASTLING][3] != NoRights )
13783 *p++ = boards[move][CASTLING][3] + AAA;
13784 if(boards[move][CASTLING][5] != NoRights &&
13785 boards[move][CASTLING][4] != NoRights )
13786 *p++ = boards[move][CASTLING][4] + AAA;
13789 /* [HGM] write true castling rights */
13790 if( nrCastlingRights == 6 ) {
13791 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13792 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13793 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13794 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13795 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13796 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13797 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13798 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13801 if (q == p) *p++ = '-'; /* No castling rights */
13805 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13806 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13807 /* En passant target square */
13808 if (move > backwardMostMove) {
13809 fromX = moveList[move - 1][0] - AAA;
13810 fromY = moveList[move - 1][1] - ONE;
13811 toX = moveList[move - 1][2] - AAA;
13812 toY = moveList[move - 1][3] - ONE;
13813 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13814 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13815 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13817 /* 2-square pawn move just happened */
13819 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13823 } else if(move == backwardMostMove) {
13824 // [HGM] perhaps we should always do it like this, and forget the above?
13825 if((signed char)boards[move][EP_STATUS] >= 0) {
13826 *p++ = boards[move][EP_STATUS] + AAA;
13827 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13838 /* [HGM] find reversible plies */
13839 { int i = 0, j=move;
13841 if (appData.debugMode) { int k;
13842 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13843 for(k=backwardMostMove; k<=forwardMostMove; k++)
13844 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13848 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13849 if( j == backwardMostMove ) i += initialRulePlies;
13850 sprintf(p, "%d ", i);
13851 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13853 /* Fullmove number */
13854 sprintf(p, "%d", (move / 2) + 1);
13856 return StrSave(buf);
13860 ParseFEN(board, blackPlaysFirst, fen)
13862 int *blackPlaysFirst;
13872 /* [HGM] by default clear Crazyhouse holdings, if present */
13873 if(gameInfo.holdingsWidth) {
13874 for(i=0; i<BOARD_HEIGHT; i++) {
13875 board[i][0] = EmptySquare; /* black holdings */
13876 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13877 board[i][1] = (ChessSquare) 0; /* black counts */
13878 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13882 /* Piece placement data */
13883 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13886 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13887 if (*p == '/') p++;
13888 emptycount = gameInfo.boardWidth - j;
13889 while (emptycount--)
13890 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13892 #if(BOARD_FILES >= 10)
13893 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13894 p++; emptycount=10;
13895 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13896 while (emptycount--)
13897 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13899 } else if (isdigit(*p)) {
13900 emptycount = *p++ - '0';
13901 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13902 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13903 while (emptycount--)
13904 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13905 } else if (*p == '+' || isalpha(*p)) {
13906 if (j >= gameInfo.boardWidth) return FALSE;
13908 piece = CharToPiece(*++p);
13909 if(piece == EmptySquare) return FALSE; /* unknown piece */
13910 piece = (ChessSquare) (PROMOTED piece ); p++;
13911 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13912 } else piece = CharToPiece(*p++);
13914 if(piece==EmptySquare) return FALSE; /* unknown piece */
13915 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13916 piece = (ChessSquare) (PROMOTED piece);
13917 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13920 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13926 while (*p == '/' || *p == ' ') p++;
13928 /* [HGM] look for Crazyhouse holdings here */
13929 while(*p==' ') p++;
13930 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13932 if(*p == '-' ) *p++; /* empty holdings */ else {
13933 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13934 /* if we would allow FEN reading to set board size, we would */
13935 /* have to add holdings and shift the board read so far here */
13936 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13938 if((int) piece >= (int) BlackPawn ) {
13939 i = (int)piece - (int)BlackPawn;
13940 i = PieceToNumber((ChessSquare)i);
13941 if( i >= gameInfo.holdingsSize ) return FALSE;
13942 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13943 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13945 i = (int)piece - (int)WhitePawn;
13946 i = PieceToNumber((ChessSquare)i);
13947 if( i >= gameInfo.holdingsSize ) return FALSE;
13948 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13949 board[i][BOARD_WIDTH-2]++; /* black holdings */
13953 if(*p == ']') *p++;
13956 while(*p == ' ') p++;
13961 *blackPlaysFirst = FALSE;
13964 *blackPlaysFirst = TRUE;
13970 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13971 /* return the extra info in global variiables */
13973 /* set defaults in case FEN is incomplete */
13974 board[EP_STATUS] = EP_UNKNOWN;
13975 for(i=0; i<nrCastlingRights; i++ ) {
13976 board[CASTLING][i] =
13977 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13978 } /* assume possible unless obviously impossible */
13979 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13980 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13981 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13982 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13983 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13984 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13987 while(*p==' ') p++;
13988 if(nrCastlingRights) {
13989 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13990 /* castling indicator present, so default becomes no castlings */
13991 for(i=0; i<nrCastlingRights; i++ ) {
13992 board[CASTLING][i] = NoRights;
13995 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13996 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13997 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13998 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13999 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14001 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14002 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14003 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14007 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14008 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14009 board[CASTLING][2] = whiteKingFile;
14012 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14013 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14014 board[CASTLING][2] = whiteKingFile;
14017 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14018 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14019 board[CASTLING][5] = blackKingFile;
14022 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14023 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14024 board[CASTLING][5] = blackKingFile;
14027 default: /* FRC castlings */
14028 if(c >= 'a') { /* black rights */
14029 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14030 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14031 if(i == BOARD_RGHT) break;
14032 board[CASTLING][5] = i;
14034 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14035 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14037 board[CASTLING][3] = c;
14039 board[CASTLING][4] = c;
14040 } else { /* white rights */
14041 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14042 if(board[0][i] == WhiteKing) break;
14043 if(i == BOARD_RGHT) break;
14044 board[CASTLING][2] = i;
14045 c -= AAA - 'a' + 'A';
14046 if(board[0][c] >= WhiteKing) break;
14048 board[CASTLING][0] = c;
14050 board[CASTLING][1] = c;
14054 if (appData.debugMode) {
14055 fprintf(debugFP, "FEN castling rights:");
14056 for(i=0; i<nrCastlingRights; i++)
14057 fprintf(debugFP, " %d", board[CASTLING][i]);
14058 fprintf(debugFP, "\n");
14061 while(*p==' ') p++;
14064 /* read e.p. field in games that know e.p. capture */
14065 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14066 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14068 p++; board[EP_STATUS] = EP_NONE;
14070 char c = *p++ - AAA;
14072 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14073 if(*p >= '0' && *p <='9') *p++;
14074 board[EP_STATUS] = c;
14079 if(sscanf(p, "%d", &i) == 1) {
14080 FENrulePlies = i; /* 50-move ply counter */
14081 /* (The move number is still ignored) */
14088 EditPositionPasteFEN(char *fen)
14091 Board initial_position;
14093 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14094 DisplayError(_("Bad FEN position in clipboard"), 0);
14097 int savedBlackPlaysFirst = blackPlaysFirst;
14098 EditPositionEvent();
14099 blackPlaysFirst = savedBlackPlaysFirst;
14100 CopyBoard(boards[0], initial_position);
14101 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14102 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14103 DisplayBothClocks();
14104 DrawPosition(FALSE, boards[currentMove]);
14109 static char cseq[12] = "\\ ";
14111 Boolean set_cont_sequence(char *new_seq)
14116 // handle bad attempts to set the sequence
14118 return 0; // acceptable error - no debug
14120 len = strlen(new_seq);
14121 ret = (len > 0) && (len < sizeof(cseq));
14123 strcpy(cseq, new_seq);
14124 else if (appData.debugMode)
14125 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14130 reformat a source message so words don't cross the width boundary. internal
14131 newlines are not removed. returns the wrapped size (no null character unless
14132 included in source message). If dest is NULL, only calculate the size required
14133 for the dest buffer. lp argument indicats line position upon entry, and it's
14134 passed back upon exit.
14136 int wrap(char *dest, char *src, int count, int width, int *lp)
14138 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14140 cseq_len = strlen(cseq);
14141 old_line = line = *lp;
14142 ansi = len = clen = 0;
14144 for (i=0; i < count; i++)
14146 if (src[i] == '\033')
14149 // if we hit the width, back up
14150 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14152 // store i & len in case the word is too long
14153 old_i = i, old_len = len;
14155 // find the end of the last word
14156 while (i && src[i] != ' ' && src[i] != '\n')
14162 // word too long? restore i & len before splitting it
14163 if ((old_i-i+clen) >= width)
14170 if (i && src[i-1] == ' ')
14173 if (src[i] != ' ' && src[i] != '\n')
14180 // now append the newline and continuation sequence
14185 strncpy(dest+len, cseq, cseq_len);
14193 dest[len] = src[i];
14197 if (src[i] == '\n')
14202 if (dest && appData.debugMode)
14204 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14205 count, width, line, len, *lp);
14206 show_bytes(debugFP, src, count);
14207 fprintf(debugFP, "\ndest: ");
14208 show_bytes(debugFP, dest, len);
14209 fprintf(debugFP, "\n");
14211 *lp = dest ? line : old_line;
14216 // [HGM] vari: routines for shelving variations
14219 PushTail(int firstMove, int lastMove)
14221 int i, j, nrMoves = lastMove - firstMove;
14223 if(appData.icsActive) { // only in local mode
14224 forwardMostMove = currentMove; // mimic old ICS behavior
14227 if(storedGames >= MAX_VARIATIONS-1) return;
14229 // push current tail of game on stack
14230 savedResult[storedGames] = gameInfo.result;
14231 savedDetails[storedGames] = gameInfo.resultDetails;
14232 gameInfo.resultDetails = NULL;
14233 savedFirst[storedGames] = firstMove;
14234 savedLast [storedGames] = lastMove;
14235 savedFramePtr[storedGames] = framePtr;
14236 framePtr -= nrMoves; // reserve space for the boards
14237 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14238 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14239 for(j=0; j<MOVE_LEN; j++)
14240 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14241 for(j=0; j<2*MOVE_LEN; j++)
14242 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14243 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14244 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14245 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14246 pvInfoList[firstMove+i-1].depth = 0;
14247 commentList[framePtr+i] = commentList[firstMove+i];
14248 commentList[firstMove+i] = NULL;
14252 forwardMostMove = currentMove; // truncte game so we can start variation
14253 if(storedGames == 1) GreyRevert(FALSE);
14257 PopTail(Boolean annotate)
14260 char buf[8000], moveBuf[20];
14262 if(appData.icsActive) return FALSE; // only in local mode
14263 if(!storedGames) return FALSE; // sanity
14266 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14267 nrMoves = savedLast[storedGames] - currentMove;
14270 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14271 else strcpy(buf, "(");
14272 for(i=currentMove; i<forwardMostMove; i++) {
14274 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14275 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14276 strcat(buf, moveBuf);
14277 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14281 for(i=1; i<nrMoves; i++) { // copy last variation back
14282 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14283 for(j=0; j<MOVE_LEN; j++)
14284 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14285 for(j=0; j<2*MOVE_LEN; j++)
14286 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14287 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14288 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14289 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14290 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14291 commentList[currentMove+i] = commentList[framePtr+i];
14292 commentList[framePtr+i] = NULL;
14294 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14295 framePtr = savedFramePtr[storedGames];
14296 gameInfo.result = savedResult[storedGames];
14297 if(gameInfo.resultDetails != NULL) {
14298 free(gameInfo.resultDetails);
14300 gameInfo.resultDetails = savedDetails[storedGames];
14301 forwardMostMove = currentMove + nrMoves;
14302 if(storedGames == 0) GreyRevert(TRUE);
14308 { // remove all shelved variations
14310 for(i=0; i<storedGames; i++) {
14311 if(savedDetails[i])
14312 free(savedDetails[i]);
14313 savedDetails[i] = NULL;
14315 for(i=framePtr; i<MAX_MOVES; i++) {
14316 if(commentList[i]) free(commentList[i]);
14317 commentList[i] = NULL;
14319 framePtr = MAX_MOVES-1;