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.
1973 if (appData.debugMode) {
1974 fprintf(debugFP, "Switch board from %s to %s\n",
1975 VariantName(gameInfo.variant), VariantName(newVariant));
1976 setbuf(debugFP, NULL);
1978 shuffleOpenings = 0; /* [HGM] shuffle */
1979 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983 newWidth = 9; newHeight = 9;
1984 gameInfo.holdingsSize = 7;
1985 case VariantBughouse:
1986 case VariantCrazyhouse:
1987 newHoldingsWidth = 2; break;
1991 newHoldingsWidth = 2;
1992 gameInfo.holdingsSize = 8;
1995 case VariantCapablanca:
1996 case VariantCapaRandom:
1999 newHoldingsWidth = gameInfo.holdingsSize = 0;
2002 if(newWidth != gameInfo.boardWidth ||
2003 newHeight != gameInfo.boardHeight ||
2004 newHoldingsWidth != gameInfo.holdingsWidth ) {
2006 /* shift position to new playing area, if needed */
2007 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008 for(i=0; i<BOARD_HEIGHT; i++)
2009 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2012 for(i=0; i<newHeight; i++) {
2013 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2016 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017 for(i=0; i<BOARD_HEIGHT; i++)
2018 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022 gameInfo.boardWidth = newWidth;
2023 gameInfo.boardHeight = newHeight;
2024 gameInfo.holdingsWidth = newHoldingsWidth;
2025 gameInfo.variant = newVariant;
2026 InitDrawingSizes(-2, 0);
2027 } else gameInfo.variant = newVariant;
2028 CopyBoard(oldBoard, board); // remember correctly formatted board
2029 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2030 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2033 static int loggedOn = FALSE;
2035 /*-- Game start info cache: --*/
2037 char gs_kind[MSG_SIZ];
2038 static char player1Name[128] = "";
2039 static char player2Name[128] = "";
2040 static char cont_seq[] = "\n\\ ";
2041 static int player1Rating = -1;
2042 static int player2Rating = -1;
2043 /*----------------------------*/
2045 ColorClass curColor = ColorNormal;
2046 int suppressKibitz = 0;
2049 read_from_ics(isr, closure, data, count, error)
2056 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2057 #define STARTED_NONE 0
2058 #define STARTED_MOVES 1
2059 #define STARTED_BOARD 2
2060 #define STARTED_OBSERVE 3
2061 #define STARTED_HOLDINGS 4
2062 #define STARTED_CHATTER 5
2063 #define STARTED_COMMENT 6
2064 #define STARTED_MOVES_NOHIDE 7
2066 static int started = STARTED_NONE;
2067 static char parse[20000];
2068 static int parse_pos = 0;
2069 static char buf[BUF_SIZE + 1];
2070 static int firstTime = TRUE, intfSet = FALSE;
2071 static ColorClass prevColor = ColorNormal;
2072 static int savingComment = FALSE;
2073 static int cmatch = 0; // continuation sequence match
2080 int backup; /* [DM] For zippy color lines */
2082 char talker[MSG_SIZ]; // [HGM] chat
2085 if (appData.debugMode) {
2087 fprintf(debugFP, "<ICS: ");
2088 show_bytes(debugFP, data, count);
2089 fprintf(debugFP, "\n");
2093 if (appData.debugMode) { int f = forwardMostMove;
2094 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2095 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2096 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2099 /* If last read ended with a partial line that we couldn't parse,
2100 prepend it to the new read and try again. */
2101 if (leftover_len > 0) {
2102 for (i=0; i<leftover_len; i++)
2103 buf[i] = buf[leftover_start + i];
2106 /* copy new characters into the buffer */
2107 bp = buf + leftover_len;
2108 buf_len=leftover_len;
2109 for (i=0; i<count; i++)
2112 if (data[i] == '\r')
2115 // join lines split by ICS?
2116 if (!appData.noJoin)
2119 Joining just consists of finding matches against the
2120 continuation sequence, and discarding that sequence
2121 if found instead of copying it. So, until a match
2122 fails, there's nothing to do since it might be the
2123 complete sequence, and thus, something we don't want
2126 if (data[i] == cont_seq[cmatch])
2129 if (cmatch == strlen(cont_seq))
2131 cmatch = 0; // complete match. just reset the counter
2134 it's possible for the ICS to not include the space
2135 at the end of the last word, making our [correct]
2136 join operation fuse two separate words. the server
2137 does this when the space occurs at the width setting.
2139 if (!buf_len || buf[buf_len-1] != ' ')
2150 match failed, so we have to copy what matched before
2151 falling through and copying this character. In reality,
2152 this will only ever be just the newline character, but
2153 it doesn't hurt to be precise.
2155 strncpy(bp, cont_seq, cmatch);
2167 buf[buf_len] = NULLCHAR;
2168 next_out = leftover_len;
2172 while (i < buf_len) {
2173 /* Deal with part of the TELNET option negotiation
2174 protocol. We refuse to do anything beyond the
2175 defaults, except that we allow the WILL ECHO option,
2176 which ICS uses to turn off password echoing when we are
2177 directly connected to it. We reject this option
2178 if localLineEditing mode is on (always on in xboard)
2179 and we are talking to port 23, which might be a real
2180 telnet server that will try to keep WILL ECHO on permanently.
2182 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2183 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2184 unsigned char option;
2186 switch ((unsigned char) buf[++i]) {
2188 if (appData.debugMode)
2189 fprintf(debugFP, "\n<WILL ");
2190 switch (option = (unsigned char) buf[++i]) {
2192 if (appData.debugMode)
2193 fprintf(debugFP, "ECHO ");
2194 /* Reply only if this is a change, according
2195 to the protocol rules. */
2196 if (remoteEchoOption) break;
2197 if (appData.localLineEditing &&
2198 atoi(appData.icsPort) == TN_PORT) {
2199 TelnetRequest(TN_DONT, TN_ECHO);
2202 TelnetRequest(TN_DO, TN_ECHO);
2203 remoteEchoOption = TRUE;
2207 if (appData.debugMode)
2208 fprintf(debugFP, "%d ", option);
2209 /* Whatever this is, we don't want it. */
2210 TelnetRequest(TN_DONT, option);
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<WONT ");
2217 switch (option = (unsigned char) buf[++i]) {
2219 if (appData.debugMode)
2220 fprintf(debugFP, "ECHO ");
2221 /* Reply only if this is a change, according
2222 to the protocol rules. */
2223 if (!remoteEchoOption) break;
2225 TelnetRequest(TN_DONT, TN_ECHO);
2226 remoteEchoOption = FALSE;
2229 if (appData.debugMode)
2230 fprintf(debugFP, "%d ", (unsigned char) option);
2231 /* Whatever this is, it must already be turned
2232 off, because we never agree to turn on
2233 anything non-default, so according to the
2234 protocol rules, we don't reply. */
2239 if (appData.debugMode)
2240 fprintf(debugFP, "\n<DO ");
2241 switch (option = (unsigned char) buf[++i]) {
2243 /* Whatever this is, we refuse to do it. */
2244 if (appData.debugMode)
2245 fprintf(debugFP, "%d ", option);
2246 TelnetRequest(TN_WONT, option);
2251 if (appData.debugMode)
2252 fprintf(debugFP, "\n<DONT ");
2253 switch (option = (unsigned char) buf[++i]) {
2255 if (appData.debugMode)
2256 fprintf(debugFP, "%d ", option);
2257 /* Whatever this is, we are already not doing
2258 it, because we never agree to do anything
2259 non-default, so according to the protocol
2260 rules, we don't reply. */
2265 if (appData.debugMode)
2266 fprintf(debugFP, "\n<IAC ");
2267 /* Doubled IAC; pass it through */
2271 if (appData.debugMode)
2272 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2273 /* Drop all other telnet commands on the floor */
2276 if (oldi > next_out)
2277 SendToPlayer(&buf[next_out], oldi - next_out);
2283 /* OK, this at least will *usually* work */
2284 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2288 if (loggedOn && !intfSet) {
2289 if (ics_type == ICS_ICC) {
2291 "/set-quietly interface %s\n/set-quietly style 12\n",
2293 } else if (ics_type == ICS_CHESSNET) {
2294 sprintf(str, "/style 12\n");
2296 strcpy(str, "alias $ @\n$set interface ");
2297 strcat(str, programVersion);
2298 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2300 strcat(str, "$iset nohighlight 1\n");
2302 strcat(str, "$iset lock 1\n$style 12\n");
2305 NotifyFrontendLogin();
2309 if (started == STARTED_COMMENT) {
2310 /* Accumulate characters in comment */
2311 parse[parse_pos++] = buf[i];
2312 if (buf[i] == '\n') {
2313 parse[parse_pos] = NULLCHAR;
2314 if(chattingPartner>=0) {
2316 sprintf(mess, "%s%s", talker, parse);
2317 OutputChatMessage(chattingPartner, mess);
2318 chattingPartner = -1;
2320 if(!suppressKibitz) // [HGM] kibitz
2321 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2322 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2323 int nrDigit = 0, nrAlph = 0, i;
2324 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2325 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2326 parse[parse_pos] = NULLCHAR;
2327 // try to be smart: if it does not look like search info, it should go to
2328 // ICS interaction window after all, not to engine-output window.
2329 for(i=0; i<parse_pos; i++) { // count letters and digits
2330 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2331 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2332 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2334 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2335 int depth=0; float score;
2336 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2337 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2338 pvInfoList[forwardMostMove-1].depth = depth;
2339 pvInfoList[forwardMostMove-1].score = 100*score;
2341 OutputKibitz(suppressKibitz, parse);
2344 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2345 SendToPlayer(tmp, strlen(tmp));
2348 started = STARTED_NONE;
2350 /* Don't match patterns against characters in chatter */
2355 if (started == STARTED_CHATTER) {
2356 if (buf[i] != '\n') {
2357 /* Don't match patterns against characters in chatter */
2361 started = STARTED_NONE;
2364 /* Kludge to deal with rcmd protocol */
2365 if (firstTime && looking_at(buf, &i, "\001*")) {
2366 DisplayFatalError(&buf[1], 0, 1);
2372 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2375 if (appData.debugMode)
2376 fprintf(debugFP, "ics_type %d\n", ics_type);
2379 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2380 ics_type = ICS_FICS;
2382 if (appData.debugMode)
2383 fprintf(debugFP, "ics_type %d\n", ics_type);
2386 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2387 ics_type = ICS_CHESSNET;
2389 if (appData.debugMode)
2390 fprintf(debugFP, "ics_type %d\n", ics_type);
2395 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2396 looking_at(buf, &i, "Logging you in as \"*\"") ||
2397 looking_at(buf, &i, "will be \"*\""))) {
2398 strcpy(ics_handle, star_match[0]);
2402 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2404 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2405 DisplayIcsInteractionTitle(buf);
2406 have_set_title = TRUE;
2409 /* skip finger notes */
2410 if (started == STARTED_NONE &&
2411 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2412 (buf[i] == '1' && buf[i+1] == '0')) &&
2413 buf[i+2] == ':' && buf[i+3] == ' ') {
2414 started = STARTED_CHATTER;
2419 /* skip formula vars */
2420 if (started == STARTED_NONE &&
2421 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2422 started = STARTED_CHATTER;
2428 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2429 if (appData.autoKibitz && started == STARTED_NONE &&
2430 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2431 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2432 if(looking_at(buf, &i, "* kibitzes: ") &&
2433 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2434 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2435 suppressKibitz = TRUE;
2436 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2437 && (gameMode == IcsPlayingWhite)) ||
2438 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2439 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2440 started = STARTED_CHATTER; // own kibitz we simply discard
2442 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2443 parse_pos = 0; parse[0] = NULLCHAR;
2444 savingComment = TRUE;
2445 suppressKibitz = gameMode != IcsObserving ? 2 :
2446 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2450 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2451 started = STARTED_CHATTER;
2452 suppressKibitz = TRUE;
2454 } // [HGM] kibitz: end of patch
2456 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2458 // [HGM] chat: intercept tells by users for which we have an open chat window
2460 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2461 looking_at(buf, &i, "* whispers:") ||
2462 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2463 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2465 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2466 chattingPartner = -1;
2468 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2469 for(p=0; p<MAX_CHAT; p++) {
2470 if(channel == atoi(chatPartner[p])) {
2471 talker[0] = '['; strcat(talker, "]");
2472 chattingPartner = p; break;
2475 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2476 for(p=0; p<MAX_CHAT; p++) {
2477 if(!strcmp("WHISPER", chatPartner[p])) {
2478 talker[0] = '['; strcat(talker, "]");
2479 chattingPartner = p; break;
2482 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2483 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2485 chattingPartner = p; break;
2487 if(chattingPartner<0) i = oldi; else {
2488 started = STARTED_COMMENT;
2489 parse_pos = 0; parse[0] = NULLCHAR;
2490 savingComment = TRUE;
2491 suppressKibitz = TRUE;
2493 } // [HGM] chat: end of patch
2495 if (appData.zippyTalk || appData.zippyPlay) {
2496 /* [DM] Backup address for color zippy lines */
2500 if (loggedOn == TRUE)
2501 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2502 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2504 if (ZippyControl(buf, &i) ||
2505 ZippyConverse(buf, &i) ||
2506 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2508 if (!appData.colorize) continue;
2512 } // [DM] 'else { ' deleted
2514 /* Regular tells and says */
2515 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2516 looking_at(buf, &i, "* (your partner) tells you: ") ||
2517 looking_at(buf, &i, "* says: ") ||
2518 /* Don't color "message" or "messages" output */
2519 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2520 looking_at(buf, &i, "*. * at *:*: ") ||
2521 looking_at(buf, &i, "--* (*:*): ") ||
2522 /* Message notifications (same color as tells) */
2523 looking_at(buf, &i, "* has left a message ") ||
2524 looking_at(buf, &i, "* just sent you a message:\n") ||
2525 /* Whispers and kibitzes */
2526 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2527 looking_at(buf, &i, "* kibitzes: ") ||
2529 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2531 if (tkind == 1 && strchr(star_match[0], ':')) {
2532 /* Avoid "tells you:" spoofs in channels */
2535 if (star_match[0][0] == NULLCHAR ||
2536 strchr(star_match[0], ' ') ||
2537 (tkind == 3 && strchr(star_match[1], ' '))) {
2538 /* Reject bogus matches */
2541 if (appData.colorize) {
2542 if (oldi > next_out) {
2543 SendToPlayer(&buf[next_out], oldi - next_out);
2548 Colorize(ColorTell, FALSE);
2549 curColor = ColorTell;
2552 Colorize(ColorKibitz, FALSE);
2553 curColor = ColorKibitz;
2556 p = strrchr(star_match[1], '(');
2563 Colorize(ColorChannel1, FALSE);
2564 curColor = ColorChannel1;
2566 Colorize(ColorChannel, FALSE);
2567 curColor = ColorChannel;
2571 curColor = ColorNormal;
2575 if (started == STARTED_NONE && appData.autoComment &&
2576 (gameMode == IcsObserving ||
2577 gameMode == IcsPlayingWhite ||
2578 gameMode == IcsPlayingBlack)) {
2579 parse_pos = i - oldi;
2580 memcpy(parse, &buf[oldi], parse_pos);
2581 parse[parse_pos] = NULLCHAR;
2582 started = STARTED_COMMENT;
2583 savingComment = TRUE;
2585 started = STARTED_CHATTER;
2586 savingComment = FALSE;
2593 if (looking_at(buf, &i, "* s-shouts: ") ||
2594 looking_at(buf, &i, "* c-shouts: ")) {
2595 if (appData.colorize) {
2596 if (oldi > next_out) {
2597 SendToPlayer(&buf[next_out], oldi - next_out);
2600 Colorize(ColorSShout, FALSE);
2601 curColor = ColorSShout;
2604 started = STARTED_CHATTER;
2608 if (looking_at(buf, &i, "--->")) {
2613 if (looking_at(buf, &i, "* shouts: ") ||
2614 looking_at(buf, &i, "--> ")) {
2615 if (appData.colorize) {
2616 if (oldi > next_out) {
2617 SendToPlayer(&buf[next_out], oldi - next_out);
2620 Colorize(ColorShout, FALSE);
2621 curColor = ColorShout;
2624 started = STARTED_CHATTER;
2628 if (looking_at( buf, &i, "Challenge:")) {
2629 if (appData.colorize) {
2630 if (oldi > next_out) {
2631 SendToPlayer(&buf[next_out], oldi - next_out);
2634 Colorize(ColorChallenge, FALSE);
2635 curColor = ColorChallenge;
2641 if (looking_at(buf, &i, "* offers you") ||
2642 looking_at(buf, &i, "* offers to be") ||
2643 looking_at(buf, &i, "* would like to") ||
2644 looking_at(buf, &i, "* requests to") ||
2645 looking_at(buf, &i, "Your opponent offers") ||
2646 looking_at(buf, &i, "Your opponent requests")) {
2648 if (appData.colorize) {
2649 if (oldi > next_out) {
2650 SendToPlayer(&buf[next_out], oldi - next_out);
2653 Colorize(ColorRequest, FALSE);
2654 curColor = ColorRequest;
2659 if (looking_at(buf, &i, "* (*) seeking")) {
2660 if (appData.colorize) {
2661 if (oldi > next_out) {
2662 SendToPlayer(&buf[next_out], oldi - next_out);
2665 Colorize(ColorSeek, FALSE);
2666 curColor = ColorSeek;
2671 if (looking_at(buf, &i, "\\ ")) {
2672 if (prevColor != ColorNormal) {
2673 if (oldi > next_out) {
2674 SendToPlayer(&buf[next_out], oldi - next_out);
2677 Colorize(prevColor, TRUE);
2678 curColor = prevColor;
2680 if (savingComment) {
2681 parse_pos = i - oldi;
2682 memcpy(parse, &buf[oldi], parse_pos);
2683 parse[parse_pos] = NULLCHAR;
2684 started = STARTED_COMMENT;
2686 started = STARTED_CHATTER;
2691 if (looking_at(buf, &i, "Black Strength :") ||
2692 looking_at(buf, &i, "<<< style 10 board >>>") ||
2693 looking_at(buf, &i, "<10>") ||
2694 looking_at(buf, &i, "#@#")) {
2695 /* Wrong board style */
2697 SendToICS(ics_prefix);
2698 SendToICS("set style 12\n");
2699 SendToICS(ics_prefix);
2700 SendToICS("refresh\n");
2704 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2706 have_sent_ICS_logon = 1;
2710 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2711 (looking_at(buf, &i, "\n<12> ") ||
2712 looking_at(buf, &i, "<12> "))) {
2714 if (oldi > next_out) {
2715 SendToPlayer(&buf[next_out], oldi - next_out);
2718 started = STARTED_BOARD;
2723 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2724 looking_at(buf, &i, "<b1> ")) {
2725 if (oldi > next_out) {
2726 SendToPlayer(&buf[next_out], oldi - next_out);
2729 started = STARTED_HOLDINGS;
2734 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2736 /* Header for a move list -- first line */
2738 switch (ics_getting_history) {
2742 case BeginningOfGame:
2743 /* User typed "moves" or "oldmoves" while we
2744 were idle. Pretend we asked for these
2745 moves and soak them up so user can step
2746 through them and/or save them.
2749 gameMode = IcsObserving;
2752 ics_getting_history = H_GOT_UNREQ_HEADER;
2754 case EditGame: /*?*/
2755 case EditPosition: /*?*/
2756 /* Should above feature work in these modes too? */
2757 /* For now it doesn't */
2758 ics_getting_history = H_GOT_UNWANTED_HEADER;
2761 ics_getting_history = H_GOT_UNWANTED_HEADER;
2766 /* Is this the right one? */
2767 if (gameInfo.white && gameInfo.black &&
2768 strcmp(gameInfo.white, star_match[0]) == 0 &&
2769 strcmp(gameInfo.black, star_match[2]) == 0) {
2771 ics_getting_history = H_GOT_REQ_HEADER;
2774 case H_GOT_REQ_HEADER:
2775 case H_GOT_UNREQ_HEADER:
2776 case H_GOT_UNWANTED_HEADER:
2777 case H_GETTING_MOVES:
2778 /* Should not happen */
2779 DisplayError(_("Error gathering move list: two headers"), 0);
2780 ics_getting_history = H_FALSE;
2784 /* Save player ratings into gameInfo if needed */
2785 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2786 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2787 (gameInfo.whiteRating == -1 ||
2788 gameInfo.blackRating == -1)) {
2790 gameInfo.whiteRating = string_to_rating(star_match[1]);
2791 gameInfo.blackRating = string_to_rating(star_match[3]);
2792 if (appData.debugMode)
2793 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2794 gameInfo.whiteRating, gameInfo.blackRating);
2799 if (looking_at(buf, &i,
2800 "* * match, initial time: * minute*, increment: * second")) {
2801 /* Header for a move list -- second line */
2802 /* Initial board will follow if this is a wild game */
2803 if (gameInfo.event != NULL) free(gameInfo.event);
2804 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2805 gameInfo.event = StrSave(str);
2806 /* [HGM] we switched variant. Translate boards if needed. */
2807 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2811 if (looking_at(buf, &i, "Move ")) {
2812 /* Beginning of a move list */
2813 switch (ics_getting_history) {
2815 /* Normally should not happen */
2816 /* Maybe user hit reset while we were parsing */
2819 /* Happens if we are ignoring a move list that is not
2820 * the one we just requested. Common if the user
2821 * tries to observe two games without turning off
2824 case H_GETTING_MOVES:
2825 /* Should not happen */
2826 DisplayError(_("Error gathering move list: nested"), 0);
2827 ics_getting_history = H_FALSE;
2829 case H_GOT_REQ_HEADER:
2830 ics_getting_history = H_GETTING_MOVES;
2831 started = STARTED_MOVES;
2833 if (oldi > next_out) {
2834 SendToPlayer(&buf[next_out], oldi - next_out);
2837 case H_GOT_UNREQ_HEADER:
2838 ics_getting_history = H_GETTING_MOVES;
2839 started = STARTED_MOVES_NOHIDE;
2842 case H_GOT_UNWANTED_HEADER:
2843 ics_getting_history = H_FALSE;
2849 if (looking_at(buf, &i, "% ") ||
2850 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2851 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2852 savingComment = FALSE;
2855 case STARTED_MOVES_NOHIDE:
2856 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2857 parse[parse_pos + i - oldi] = NULLCHAR;
2858 ParseGameHistory(parse);
2860 if (appData.zippyPlay && first.initDone) {
2861 FeedMovesToProgram(&first, forwardMostMove);
2862 if (gameMode == IcsPlayingWhite) {
2863 if (WhiteOnMove(forwardMostMove)) {
2864 if (first.sendTime) {
2865 if (first.useColors) {
2866 SendToProgram("black\n", &first);
2868 SendTimeRemaining(&first, TRUE);
2870 if (first.useColors) {
2871 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2873 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2874 first.maybeThinking = TRUE;
2876 if (first.usePlayother) {
2877 if (first.sendTime) {
2878 SendTimeRemaining(&first, TRUE);
2880 SendToProgram("playother\n", &first);
2886 } else if (gameMode == IcsPlayingBlack) {
2887 if (!WhiteOnMove(forwardMostMove)) {
2888 if (first.sendTime) {
2889 if (first.useColors) {
2890 SendToProgram("white\n", &first);
2892 SendTimeRemaining(&first, FALSE);
2894 if (first.useColors) {
2895 SendToProgram("black\n", &first);
2897 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2898 first.maybeThinking = TRUE;
2900 if (first.usePlayother) {
2901 if (first.sendTime) {
2902 SendTimeRemaining(&first, FALSE);
2904 SendToProgram("playother\n", &first);
2913 if (gameMode == IcsObserving && ics_gamenum == -1) {
2914 /* Moves came from oldmoves or moves command
2915 while we weren't doing anything else.
2917 currentMove = forwardMostMove;
2918 ClearHighlights();/*!!could figure this out*/
2919 flipView = appData.flipView;
2920 DrawPosition(TRUE, boards[currentMove]);
2921 DisplayBothClocks();
2922 sprintf(str, "%s vs. %s",
2923 gameInfo.white, gameInfo.black);
2927 /* Moves were history of an active game */
2928 if (gameInfo.resultDetails != NULL) {
2929 free(gameInfo.resultDetails);
2930 gameInfo.resultDetails = NULL;
2933 HistorySet(parseList, backwardMostMove,
2934 forwardMostMove, currentMove-1);
2935 DisplayMove(currentMove - 1);
2936 if (started == STARTED_MOVES) next_out = i;
2937 started = STARTED_NONE;
2938 ics_getting_history = H_FALSE;
2941 case STARTED_OBSERVE:
2942 started = STARTED_NONE;
2943 SendToICS(ics_prefix);
2944 SendToICS("refresh\n");
2950 if(bookHit) { // [HGM] book: simulate book reply
2951 static char bookMove[MSG_SIZ]; // a bit generous?
2953 programStats.nodes = programStats.depth = programStats.time =
2954 programStats.score = programStats.got_only_move = 0;
2955 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2957 strcpy(bookMove, "move ");
2958 strcat(bookMove, bookHit);
2959 HandleMachineMove(bookMove, &first);
2964 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2965 started == STARTED_HOLDINGS ||
2966 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2967 /* Accumulate characters in move list or board */
2968 parse[parse_pos++] = buf[i];
2971 /* Start of game messages. Mostly we detect start of game
2972 when the first board image arrives. On some versions
2973 of the ICS, though, we need to do a "refresh" after starting
2974 to observe in order to get the current board right away. */
2975 if (looking_at(buf, &i, "Adding game * to observation list")) {
2976 started = STARTED_OBSERVE;
2980 /* Handle auto-observe */
2981 if (appData.autoObserve &&
2982 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2983 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2985 /* Choose the player that was highlighted, if any. */
2986 if (star_match[0][0] == '\033' ||
2987 star_match[1][0] != '\033') {
2988 player = star_match[0];
2990 player = star_match[2];
2992 sprintf(str, "%sobserve %s\n",
2993 ics_prefix, StripHighlightAndTitle(player));
2996 /* Save ratings from notify string */
2997 strcpy(player1Name, star_match[0]);
2998 player1Rating = string_to_rating(star_match[1]);
2999 strcpy(player2Name, star_match[2]);
3000 player2Rating = string_to_rating(star_match[3]);
3002 if (appData.debugMode)
3004 "Ratings from 'Game notification:' %s %d, %s %d\n",
3005 player1Name, player1Rating,
3006 player2Name, player2Rating);
3011 /* Deal with automatic examine mode after a game,
3012 and with IcsObserving -> IcsExamining transition */
3013 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3014 looking_at(buf, &i, "has made you an examiner of game *")) {
3016 int gamenum = atoi(star_match[0]);
3017 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3018 gamenum == ics_gamenum) {
3019 /* We were already playing or observing this game;
3020 no need to refetch history */
3021 gameMode = IcsExamining;
3023 pauseExamForwardMostMove = forwardMostMove;
3024 } else if (currentMove < forwardMostMove) {
3025 ForwardInner(forwardMostMove);
3028 /* I don't think this case really can happen */
3029 SendToICS(ics_prefix);
3030 SendToICS("refresh\n");
3035 /* Error messages */
3036 // if (ics_user_moved) {
3037 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3038 if (looking_at(buf, &i, "Illegal move") ||
3039 looking_at(buf, &i, "Not a legal move") ||
3040 looking_at(buf, &i, "Your king is in check") ||
3041 looking_at(buf, &i, "It isn't your turn") ||
3042 looking_at(buf, &i, "It is not your move")) {
3044 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3045 currentMove = --forwardMostMove;
3046 DisplayMove(currentMove - 1); /* before DMError */
3047 DrawPosition(FALSE, boards[currentMove]);
3049 DisplayBothClocks();
3051 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3057 if (looking_at(buf, &i, "still have time") ||
3058 looking_at(buf, &i, "not out of time") ||
3059 looking_at(buf, &i, "either player is out of time") ||
3060 looking_at(buf, &i, "has timeseal; checking")) {
3061 /* We must have called his flag a little too soon */
3062 whiteFlag = blackFlag = FALSE;
3066 if (looking_at(buf, &i, "added * seconds to") ||
3067 looking_at(buf, &i, "seconds were added to")) {
3068 /* Update the clocks */
3069 SendToICS(ics_prefix);
3070 SendToICS("refresh\n");
3074 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3075 ics_clock_paused = TRUE;
3080 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3081 ics_clock_paused = FALSE;
3086 /* Grab player ratings from the Creating: message.
3087 Note we have to check for the special case when
3088 the ICS inserts things like [white] or [black]. */
3089 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3090 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3092 0 player 1 name (not necessarily white)
3094 2 empty, white, or black (IGNORED)
3095 3 player 2 name (not necessarily black)
3098 The names/ratings are sorted out when the game
3099 actually starts (below).
3101 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3102 player1Rating = string_to_rating(star_match[1]);
3103 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3104 player2Rating = string_to_rating(star_match[4]);
3106 if (appData.debugMode)
3108 "Ratings from 'Creating:' %s %d, %s %d\n",
3109 player1Name, player1Rating,
3110 player2Name, player2Rating);
3115 /* Improved generic start/end-of-game messages */
3116 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3117 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3118 /* If tkind == 0: */
3119 /* star_match[0] is the game number */
3120 /* [1] is the white player's name */
3121 /* [2] is the black player's name */
3122 /* For end-of-game: */
3123 /* [3] is the reason for the game end */
3124 /* [4] is a PGN end game-token, preceded by " " */
3125 /* For start-of-game: */
3126 /* [3] begins with "Creating" or "Continuing" */
3127 /* [4] is " *" or empty (don't care). */
3128 int gamenum = atoi(star_match[0]);
3129 char *whitename, *blackname, *why, *endtoken;
3130 ChessMove endtype = (ChessMove) 0;
3133 whitename = star_match[1];
3134 blackname = star_match[2];
3135 why = star_match[3];
3136 endtoken = star_match[4];
3138 whitename = star_match[1];
3139 blackname = star_match[3];
3140 why = star_match[5];
3141 endtoken = star_match[6];
3144 /* Game start messages */
3145 if (strncmp(why, "Creating ", 9) == 0 ||
3146 strncmp(why, "Continuing ", 11) == 0) {
3147 gs_gamenum = gamenum;
3148 strcpy(gs_kind, strchr(why, ' ') + 1);
3150 if (appData.zippyPlay) {
3151 ZippyGameStart(whitename, blackname);
3157 /* Game end messages */
3158 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3159 ics_gamenum != gamenum) {
3162 while (endtoken[0] == ' ') endtoken++;
3163 switch (endtoken[0]) {
3166 endtype = GameUnfinished;
3169 endtype = BlackWins;
3172 if (endtoken[1] == '/')
3173 endtype = GameIsDrawn;
3175 endtype = WhiteWins;
3178 GameEnds(endtype, why, GE_ICS);
3180 if (appData.zippyPlay && first.initDone) {
3181 ZippyGameEnd(endtype, why);
3182 if (first.pr == NULL) {
3183 /* Start the next process early so that we'll
3184 be ready for the next challenge */
3185 StartChessProgram(&first);
3187 /* Send "new" early, in case this command takes
3188 a long time to finish, so that we'll be ready
3189 for the next challenge. */
3190 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3197 if (looking_at(buf, &i, "Removing game * from observation") ||
3198 looking_at(buf, &i, "no longer observing game *") ||
3199 looking_at(buf, &i, "Game * (*) has no examiners")) {
3200 if (gameMode == IcsObserving &&
3201 atoi(star_match[0]) == ics_gamenum)
3203 /* icsEngineAnalyze */
3204 if (appData.icsEngineAnalyze) {
3211 ics_user_moved = FALSE;
3216 if (looking_at(buf, &i, "no longer examining game *")) {
3217 if (gameMode == IcsExamining &&
3218 atoi(star_match[0]) == ics_gamenum)
3222 ics_user_moved = FALSE;
3227 /* Advance leftover_start past any newlines we find,
3228 so only partial lines can get reparsed */
3229 if (looking_at(buf, &i, "\n")) {
3230 prevColor = curColor;
3231 if (curColor != ColorNormal) {
3232 if (oldi > next_out) {
3233 SendToPlayer(&buf[next_out], oldi - next_out);
3236 Colorize(ColorNormal, FALSE);
3237 curColor = ColorNormal;
3239 if (started == STARTED_BOARD) {
3240 started = STARTED_NONE;
3241 parse[parse_pos] = NULLCHAR;
3242 ParseBoard12(parse);
3245 /* Send premove here */
3246 if (appData.premove) {
3248 if (currentMove == 0 &&
3249 gameMode == IcsPlayingWhite &&
3250 appData.premoveWhite) {
3251 sprintf(str, "%s\n", appData.premoveWhiteText);
3252 if (appData.debugMode)
3253 fprintf(debugFP, "Sending premove:\n");
3255 } else if (currentMove == 1 &&
3256 gameMode == IcsPlayingBlack &&
3257 appData.premoveBlack) {
3258 sprintf(str, "%s\n", appData.premoveBlackText);
3259 if (appData.debugMode)
3260 fprintf(debugFP, "Sending premove:\n");
3262 } else if (gotPremove) {
3264 ClearPremoveHighlights();
3265 if (appData.debugMode)
3266 fprintf(debugFP, "Sending premove:\n");
3267 UserMoveEvent(premoveFromX, premoveFromY,
3268 premoveToX, premoveToY,
3273 /* Usually suppress following prompt */
3274 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3275 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3276 if (looking_at(buf, &i, "*% ")) {
3277 savingComment = FALSE;
3281 } else if (started == STARTED_HOLDINGS) {
3283 char new_piece[MSG_SIZ];
3284 started = STARTED_NONE;
3285 parse[parse_pos] = NULLCHAR;
3286 if (appData.debugMode)
3287 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3288 parse, currentMove);
3289 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3290 gamenum == ics_gamenum) {
3291 if (gameInfo.variant == VariantNormal) {
3292 /* [HGM] We seem to switch variant during a game!
3293 * Presumably no holdings were displayed, so we have
3294 * to move the position two files to the right to
3295 * create room for them!
3297 VariantClass newVariant;
3298 switch(gameInfo.boardWidth) { // base guess on board width
3299 case 9: newVariant = VariantShogi; break;
3300 case 10: newVariant = VariantGreat; break;
3301 default: newVariant = VariantCrazyhouse; break;
3303 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3304 /* Get a move list just to see the header, which
3305 will tell us whether this is really bug or zh */
3306 if (ics_getting_history == H_FALSE) {
3307 ics_getting_history = H_REQUESTED;
3308 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3312 new_piece[0] = NULLCHAR;
3313 sscanf(parse, "game %d white [%s black [%s <- %s",
3314 &gamenum, white_holding, black_holding,
3316 white_holding[strlen(white_holding)-1] = NULLCHAR;
3317 black_holding[strlen(black_holding)-1] = NULLCHAR;
3318 /* [HGM] copy holdings to board holdings area */
3319 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3320 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3321 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3323 if (appData.zippyPlay && first.initDone) {
3324 ZippyHoldings(white_holding, black_holding,
3328 if (tinyLayout || smallLayout) {
3329 char wh[16], bh[16];
3330 PackHolding(wh, white_holding);
3331 PackHolding(bh, black_holding);
3332 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3333 gameInfo.white, gameInfo.black);
3335 sprintf(str, "%s [%s] vs. %s [%s]",
3336 gameInfo.white, white_holding,
3337 gameInfo.black, black_holding);
3340 DrawPosition(FALSE, boards[currentMove]);
3343 /* Suppress following prompt */
3344 if (looking_at(buf, &i, "*% ")) {
3345 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3346 savingComment = FALSE;
3353 i++; /* skip unparsed character and loop back */
3356 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3357 started != STARTED_HOLDINGS && i > next_out) {
3358 SendToPlayer(&buf[next_out], i - next_out);
3361 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3363 leftover_len = buf_len - leftover_start;
3364 /* if buffer ends with something we couldn't parse,
3365 reparse it after appending the next read */
3367 } else if (count == 0) {
3368 RemoveInputSource(isr);
3369 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3371 DisplayFatalError(_("Error reading from ICS"), error, 1);
3376 /* Board style 12 looks like this:
3378 <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
3380 * The "<12> " is stripped before it gets to this routine. The two
3381 * trailing 0's (flip state and clock ticking) are later addition, and
3382 * some chess servers may not have them, or may have only the first.
3383 * Additional trailing fields may be added in the future.
3386 #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"
3388 #define RELATION_OBSERVING_PLAYED 0
3389 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3390 #define RELATION_PLAYING_MYMOVE 1
3391 #define RELATION_PLAYING_NOTMYMOVE -1
3392 #define RELATION_EXAMINING 2
3393 #define RELATION_ISOLATED_BOARD -3
3394 #define RELATION_STARTING_POSITION -4 /* FICS only */
3397 ParseBoard12(string)
3400 GameMode newGameMode;
3401 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3402 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3403 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3404 char to_play, board_chars[200];
3405 char move_str[500], str[500], elapsed_time[500];
3406 char black[32], white[32];
3408 int prevMove = currentMove;
3411 int fromX, fromY, toX, toY;
3413 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3414 char *bookHit = NULL; // [HGM] book
3415 Boolean weird = FALSE, reqFlag = FALSE;
3417 fromX = fromY = toX = toY = -1;
3421 if (appData.debugMode)
3422 fprintf(debugFP, _("Parsing board: %s\n"), string);
3424 move_str[0] = NULLCHAR;
3425 elapsed_time[0] = NULLCHAR;
3426 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3428 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3429 if(string[i] == ' ') { ranks++; files = 0; }
3431 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3434 for(j = 0; j <i; j++) board_chars[j] = string[j];
3435 board_chars[i] = '\0';
3438 n = sscanf(string, PATTERN, &to_play, &double_push,
3439 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3440 &gamenum, white, black, &relation, &basetime, &increment,
3441 &white_stren, &black_stren, &white_time, &black_time,
3442 &moveNum, str, elapsed_time, move_str, &ics_flip,
3446 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3447 DisplayError(str, 0);
3451 /* Convert the move number to internal form */
3452 moveNum = (moveNum - 1) * 2;
3453 if (to_play == 'B') moveNum++;
3454 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3455 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3461 case RELATION_OBSERVING_PLAYED:
3462 case RELATION_OBSERVING_STATIC:
3463 if (gamenum == -1) {
3464 /* Old ICC buglet */
3465 relation = RELATION_OBSERVING_STATIC;
3467 newGameMode = IcsObserving;
3469 case RELATION_PLAYING_MYMOVE:
3470 case RELATION_PLAYING_NOTMYMOVE:
3472 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3473 IcsPlayingWhite : IcsPlayingBlack;
3475 case RELATION_EXAMINING:
3476 newGameMode = IcsExamining;
3478 case RELATION_ISOLATED_BOARD:
3480 /* Just display this board. If user was doing something else,
3481 we will forget about it until the next board comes. */
3482 newGameMode = IcsIdle;
3484 case RELATION_STARTING_POSITION:
3485 newGameMode = gameMode;
3489 /* Modify behavior for initial board display on move listing
3492 switch (ics_getting_history) {
3496 case H_GOT_REQ_HEADER:
3497 case H_GOT_UNREQ_HEADER:
3498 /* This is the initial position of the current game */
3499 gamenum = ics_gamenum;
3500 moveNum = 0; /* old ICS bug workaround */
3501 if (to_play == 'B') {
3502 startedFromSetupPosition = TRUE;
3503 blackPlaysFirst = TRUE;
3505 if (forwardMostMove == 0) forwardMostMove = 1;
3506 if (backwardMostMove == 0) backwardMostMove = 1;
3507 if (currentMove == 0) currentMove = 1;
3509 newGameMode = gameMode;
3510 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3512 case H_GOT_UNWANTED_HEADER:
3513 /* This is an initial board that we don't want */
3515 case H_GETTING_MOVES:
3516 /* Should not happen */
3517 DisplayError(_("Error gathering move list: extra board"), 0);
3518 ics_getting_history = H_FALSE;
3522 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3523 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3524 /* [HGM] We seem to have switched variant unexpectedly
3525 * Try to guess new variant from board size
3527 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3528 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3529 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3530 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3531 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3532 if(!weird) newVariant = VariantNormal;
3533 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3534 /* Get a move list just to see the header, which
3535 will tell us whether this is really bug or zh */
3536 if (ics_getting_history == H_FALSE) {
3537 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3538 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3543 /* Take action if this is the first board of a new game, or of a
3544 different game than is currently being displayed. */
3545 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3546 relation == RELATION_ISOLATED_BOARD) {
3548 /* Forget the old game and get the history (if any) of the new one */
3549 if (gameMode != BeginningOfGame) {
3553 if (appData.autoRaiseBoard) BoardToTop();
3555 if (gamenum == -1) {
3556 newGameMode = IcsIdle;
3557 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3558 appData.getMoveList && !reqFlag) {
3559 /* Need to get game history */
3560 ics_getting_history = H_REQUESTED;
3561 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3565 /* Initially flip the board to have black on the bottom if playing
3566 black or if the ICS flip flag is set, but let the user change
3567 it with the Flip View button. */
3568 flipView = appData.autoFlipView ?
3569 (newGameMode == IcsPlayingBlack) || ics_flip :
3572 /* Done with values from previous mode; copy in new ones */
3573 gameMode = newGameMode;
3575 ics_gamenum = gamenum;
3576 if (gamenum == gs_gamenum) {
3577 int klen = strlen(gs_kind);
3578 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3579 sprintf(str, "ICS %s", gs_kind);
3580 gameInfo.event = StrSave(str);
3582 gameInfo.event = StrSave("ICS game");
3584 gameInfo.site = StrSave(appData.icsHost);
3585 gameInfo.date = PGNDate();
3586 gameInfo.round = StrSave("-");
3587 gameInfo.white = StrSave(white);
3588 gameInfo.black = StrSave(black);
3589 timeControl = basetime * 60 * 1000;
3591 timeIncrement = increment * 1000;
3592 movesPerSession = 0;
3593 gameInfo.timeControl = TimeControlTagValue();
3594 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3595 if (appData.debugMode) {
3596 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3597 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3598 setbuf(debugFP, NULL);
3601 gameInfo.outOfBook = NULL;
3603 /* Do we have the ratings? */
3604 if (strcmp(player1Name, white) == 0 &&
3605 strcmp(player2Name, black) == 0) {
3606 if (appData.debugMode)
3607 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3608 player1Rating, player2Rating);
3609 gameInfo.whiteRating = player1Rating;
3610 gameInfo.blackRating = player2Rating;
3611 } else if (strcmp(player2Name, white) == 0 &&
3612 strcmp(player1Name, black) == 0) {
3613 if (appData.debugMode)
3614 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3615 player2Rating, player1Rating);
3616 gameInfo.whiteRating = player2Rating;
3617 gameInfo.blackRating = player1Rating;
3619 player1Name[0] = player2Name[0] = NULLCHAR;
3621 /* Silence shouts if requested */
3622 if (appData.quietPlay &&
3623 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3624 SendToICS(ics_prefix);
3625 SendToICS("set shout 0\n");
3629 /* Deal with midgame name changes */
3631 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3632 if (gameInfo.white) free(gameInfo.white);
3633 gameInfo.white = StrSave(white);
3635 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3636 if (gameInfo.black) free(gameInfo.black);
3637 gameInfo.black = StrSave(black);
3641 /* Throw away game result if anything actually changes in examine mode */
3642 if (gameMode == IcsExamining && !newGame) {
3643 gameInfo.result = GameUnfinished;
3644 if (gameInfo.resultDetails != NULL) {
3645 free(gameInfo.resultDetails);
3646 gameInfo.resultDetails = NULL;
3650 /* In pausing && IcsExamining mode, we ignore boards coming
3651 in if they are in a different variation than we are. */
3652 if (pauseExamInvalid) return;
3653 if (pausing && gameMode == IcsExamining) {
3654 if (moveNum <= pauseExamForwardMostMove) {
3655 pauseExamInvalid = TRUE;
3656 forwardMostMove = pauseExamForwardMostMove;
3661 if (appData.debugMode) {
3662 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3664 /* Parse the board */
3665 for (k = 0; k < ranks; k++) {
3666 for (j = 0; j < files; j++)
3667 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3668 if(gameInfo.holdingsWidth > 1) {
3669 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3670 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3673 CopyBoard(boards[moveNum], board);
3674 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3676 startedFromSetupPosition =
3677 !CompareBoards(board, initialPosition);
3678 if(startedFromSetupPosition)
3679 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3682 /* [HGM] Set castling rights. Take the outermost Rooks,
3683 to make it also work for FRC opening positions. Note that board12
3684 is really defective for later FRC positions, as it has no way to
3685 indicate which Rook can castle if they are on the same side of King.
3686 For the initial position we grant rights to the outermost Rooks,
3687 and remember thos rights, and we then copy them on positions
3688 later in an FRC game. This means WB might not recognize castlings with
3689 Rooks that have moved back to their original position as illegal,
3690 but in ICS mode that is not its job anyway.
3692 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3693 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3695 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3696 if(board[0][i] == WhiteRook) j = i;
3697 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3698 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3699 if(board[0][i] == WhiteRook) j = i;
3700 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3702 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3705 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3708 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3709 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3710 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3711 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712 if(board[BOARD_HEIGHT-1][k] == bKing)
3713 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3715 r = boards[moveNum][CASTLING][0] = initialRights[0];
3716 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3717 r = boards[moveNum][CASTLING][1] = initialRights[1];
3718 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3719 r = boards[moveNum][CASTLING][3] = initialRights[3];
3720 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3721 r = boards[moveNum][CASTLING][4] = initialRights[4];
3722 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3723 /* wildcastle kludge: always assume King has rights */
3724 r = boards[moveNum][CASTLING][2] = initialRights[2];
3725 r = boards[moveNum][CASTLING][5] = initialRights[5];
3727 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3728 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3731 if (ics_getting_history == H_GOT_REQ_HEADER ||
3732 ics_getting_history == H_GOT_UNREQ_HEADER) {
3733 /* This was an initial position from a move list, not
3734 the current position */
3738 /* Update currentMove and known move number limits */
3739 newMove = newGame || moveNum > forwardMostMove;
3742 forwardMostMove = backwardMostMove = currentMove = moveNum;
3743 if (gameMode == IcsExamining && moveNum == 0) {
3744 /* Workaround for ICS limitation: we are not told the wild
3745 type when starting to examine a game. But if we ask for
3746 the move list, the move list header will tell us */
3747 ics_getting_history = H_REQUESTED;
3748 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3751 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3752 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3754 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3755 /* [HGM] applied this also to an engine that is silently watching */
3756 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3757 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3758 gameInfo.variant == currentlyInitializedVariant) {
3759 takeback = forwardMostMove - moveNum;
3760 for (i = 0; i < takeback; i++) {
3761 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3762 SendToProgram("undo\n", &first);
3767 forwardMostMove = moveNum;
3768 if (!pausing || currentMove > forwardMostMove)
3769 currentMove = forwardMostMove;
3771 /* New part of history that is not contiguous with old part */
3772 if (pausing && gameMode == IcsExamining) {
3773 pauseExamInvalid = TRUE;
3774 forwardMostMove = pauseExamForwardMostMove;
3777 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3779 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3780 // [HGM] when we will receive the move list we now request, it will be
3781 // fed to the engine from the first move on. So if the engine is not
3782 // in the initial position now, bring it there.
3783 InitChessProgram(&first, 0);
3786 ics_getting_history = H_REQUESTED;
3787 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3790 forwardMostMove = backwardMostMove = currentMove = moveNum;
3793 /* Update the clocks */
3794 if (strchr(elapsed_time, '.')) {
3796 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3797 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3799 /* Time is in seconds */
3800 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3801 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3806 if (appData.zippyPlay && newGame &&
3807 gameMode != IcsObserving && gameMode != IcsIdle &&
3808 gameMode != IcsExamining)
3809 ZippyFirstBoard(moveNum, basetime, increment);
3812 /* Put the move on the move list, first converting
3813 to canonical algebraic form. */
3815 if (appData.debugMode) {
3816 if (appData.debugMode) { int f = forwardMostMove;
3817 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3818 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3819 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3821 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3822 fprintf(debugFP, "moveNum = %d\n", moveNum);
3823 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3824 setbuf(debugFP, NULL);
3826 if (moveNum <= backwardMostMove) {
3827 /* We don't know what the board looked like before
3829 strcpy(parseList[moveNum - 1], move_str);
3830 strcat(parseList[moveNum - 1], " ");
3831 strcat(parseList[moveNum - 1], elapsed_time);
3832 moveList[moveNum - 1][0] = NULLCHAR;
3833 } else if (strcmp(move_str, "none") == 0) {
3834 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3835 /* Again, we don't know what the board looked like;
3836 this is really the start of the game. */
3837 parseList[moveNum - 1][0] = NULLCHAR;
3838 moveList[moveNum - 1][0] = NULLCHAR;
3839 backwardMostMove = moveNum;
3840 startedFromSetupPosition = TRUE;
3841 fromX = fromY = toX = toY = -1;
3843 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3844 // So we parse the long-algebraic move string in stead of the SAN move
3845 int valid; char buf[MSG_SIZ], *prom;
3847 // str looks something like "Q/a1-a2"; kill the slash
3849 sprintf(buf, "%c%s", str[0], str+2);
3850 else strcpy(buf, str); // might be castling
3851 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3852 strcat(buf, prom); // long move lacks promo specification!
3853 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3854 if(appData.debugMode)
3855 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3856 strcpy(move_str, buf);
3858 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3859 &fromX, &fromY, &toX, &toY, &promoChar)
3860 || ParseOneMove(buf, moveNum - 1, &moveType,
3861 &fromX, &fromY, &toX, &toY, &promoChar);
3862 // end of long SAN patch
3864 (void) CoordsToAlgebraic(boards[moveNum - 1],
3865 PosFlags(moveNum - 1),
3866 fromY, fromX, toY, toX, promoChar,
3867 parseList[moveNum-1]);
3868 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3874 if(gameInfo.variant != VariantShogi)
3875 strcat(parseList[moveNum - 1], "+");
3878 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3879 strcat(parseList[moveNum - 1], "#");
3882 strcat(parseList[moveNum - 1], " ");
3883 strcat(parseList[moveNum - 1], elapsed_time);
3884 /* currentMoveString is set as a side-effect of ParseOneMove */
3885 strcpy(moveList[moveNum - 1], currentMoveString);
3886 strcat(moveList[moveNum - 1], "\n");
3888 /* Move from ICS was illegal!? Punt. */
3889 if (appData.debugMode) {
3890 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3891 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3893 strcpy(parseList[moveNum - 1], move_str);
3894 strcat(parseList[moveNum - 1], " ");
3895 strcat(parseList[moveNum - 1], elapsed_time);
3896 moveList[moveNum - 1][0] = NULLCHAR;
3897 fromX = fromY = toX = toY = -1;
3900 if (appData.debugMode) {
3901 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3902 setbuf(debugFP, NULL);
3906 /* Send move to chess program (BEFORE animating it). */
3907 if (appData.zippyPlay && !newGame && newMove &&
3908 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3910 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3911 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3912 if (moveList[moveNum - 1][0] == NULLCHAR) {
3913 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3915 DisplayError(str, 0);
3917 if (first.sendTime) {
3918 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3920 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3921 if (firstMove && !bookHit) {
3923 if (first.useColors) {
3924 SendToProgram(gameMode == IcsPlayingWhite ?
3926 "black\ngo\n", &first);
3928 SendToProgram("go\n", &first);
3930 first.maybeThinking = TRUE;
3933 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3934 if (moveList[moveNum - 1][0] == NULLCHAR) {
3935 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3936 DisplayError(str, 0);
3938 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3939 SendMoveToProgram(moveNum - 1, &first);
3946 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3947 /* If move comes from a remote source, animate it. If it
3948 isn't remote, it will have already been animated. */
3949 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3950 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3952 if (!pausing && appData.highlightLastMove) {
3953 SetHighlights(fromX, fromY, toX, toY);
3957 /* Start the clocks */
3958 whiteFlag = blackFlag = FALSE;
3959 appData.clockMode = !(basetime == 0 && increment == 0);
3961 ics_clock_paused = TRUE;
3963 } else if (ticking == 1) {
3964 ics_clock_paused = FALSE;
3966 if (gameMode == IcsIdle ||
3967 relation == RELATION_OBSERVING_STATIC ||
3968 relation == RELATION_EXAMINING ||
3970 DisplayBothClocks();
3974 /* Display opponents and material strengths */
3975 if (gameInfo.variant != VariantBughouse &&
3976 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3977 if (tinyLayout || smallLayout) {
3978 if(gameInfo.variant == VariantNormal)
3979 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3980 gameInfo.white, white_stren, gameInfo.black, black_stren,
3981 basetime, increment);
3983 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3984 gameInfo.white, white_stren, gameInfo.black, black_stren,
3985 basetime, increment, (int) gameInfo.variant);
3987 if(gameInfo.variant == VariantNormal)
3988 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3989 gameInfo.white, white_stren, gameInfo.black, black_stren,
3990 basetime, increment);
3992 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3993 gameInfo.white, white_stren, gameInfo.black, black_stren,
3994 basetime, increment, VariantName(gameInfo.variant));
3997 if (appData.debugMode) {
3998 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4003 /* Display the board */
4004 if (!pausing && !appData.noGUI) {
4006 if (appData.premove)
4008 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4009 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4010 ClearPremoveHighlights();
4012 DrawPosition(FALSE, boards[currentMove]);
4013 DisplayMove(moveNum - 1);
4014 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4015 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4016 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4017 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4021 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4023 if(bookHit) { // [HGM] book: simulate book reply
4024 static char bookMove[MSG_SIZ]; // a bit generous?
4026 programStats.nodes = programStats.depth = programStats.time =
4027 programStats.score = programStats.got_only_move = 0;
4028 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4030 strcpy(bookMove, "move ");
4031 strcat(bookMove, bookHit);
4032 HandleMachineMove(bookMove, &first);
4041 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4042 ics_getting_history = H_REQUESTED;
4043 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4049 AnalysisPeriodicEvent(force)
4052 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4053 && !force) || !appData.periodicUpdates)
4056 /* Send . command to Crafty to collect stats */
4057 SendToProgram(".\n", &first);
4059 /* Don't send another until we get a response (this makes
4060 us stop sending to old Crafty's which don't understand
4061 the "." command (sending illegal cmds resets node count & time,
4062 which looks bad)) */
4063 programStats.ok_to_send = 0;
4066 void ics_update_width(new_width)
4069 ics_printf("set width %d\n", new_width);
4073 SendMoveToProgram(moveNum, cps)
4075 ChessProgramState *cps;
4079 if (cps->useUsermove) {
4080 SendToProgram("usermove ", cps);
4084 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4085 int len = space - parseList[moveNum];
4086 memcpy(buf, parseList[moveNum], len);
4088 buf[len] = NULLCHAR;
4090 sprintf(buf, "%s\n", parseList[moveNum]);
4092 SendToProgram(buf, cps);
4094 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4095 AlphaRank(moveList[moveNum], 4);
4096 SendToProgram(moveList[moveNum], cps);
4097 AlphaRank(moveList[moveNum], 4); // and back
4099 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4100 * the engine. It would be nice to have a better way to identify castle
4102 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4103 && cps->useOOCastle) {
4104 int fromX = moveList[moveNum][0] - AAA;
4105 int fromY = moveList[moveNum][1] - ONE;
4106 int toX = moveList[moveNum][2] - AAA;
4107 int toY = moveList[moveNum][3] - ONE;
4108 if((boards[moveNum][fromY][fromX] == WhiteKing
4109 && boards[moveNum][toY][toX] == WhiteRook)
4110 || (boards[moveNum][fromY][fromX] == BlackKing
4111 && boards[moveNum][toY][toX] == BlackRook)) {
4112 if(toX > fromX) SendToProgram("O-O\n", cps);
4113 else SendToProgram("O-O-O\n", cps);
4115 else SendToProgram(moveList[moveNum], cps);
4117 else SendToProgram(moveList[moveNum], cps);
4118 /* End of additions by Tord */
4121 /* [HGM] setting up the opening has brought engine in force mode! */
4122 /* Send 'go' if we are in a mode where machine should play. */
4123 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4124 (gameMode == TwoMachinesPlay ||
4126 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4128 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4129 SendToProgram("go\n", cps);
4130 if (appData.debugMode) {
4131 fprintf(debugFP, "(extra)\n");
4134 setboardSpoiledMachineBlack = 0;
4138 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4140 int fromX, fromY, toX, toY;
4142 char user_move[MSG_SIZ];
4146 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4147 (int)moveType, fromX, fromY, toX, toY);
4148 DisplayError(user_move + strlen("say "), 0);
4150 case WhiteKingSideCastle:
4151 case BlackKingSideCastle:
4152 case WhiteQueenSideCastleWild:
4153 case BlackQueenSideCastleWild:
4155 case WhiteHSideCastleFR:
4156 case BlackHSideCastleFR:
4158 sprintf(user_move, "o-o\n");
4160 case WhiteQueenSideCastle:
4161 case BlackQueenSideCastle:
4162 case WhiteKingSideCastleWild:
4163 case BlackKingSideCastleWild:
4165 case WhiteASideCastleFR:
4166 case BlackASideCastleFR:
4168 sprintf(user_move, "o-o-o\n");
4170 case WhitePromotionQueen:
4171 case BlackPromotionQueen:
4172 case WhitePromotionRook:
4173 case BlackPromotionRook:
4174 case WhitePromotionBishop:
4175 case BlackPromotionBishop:
4176 case WhitePromotionKnight:
4177 case BlackPromotionKnight:
4178 case WhitePromotionKing:
4179 case BlackPromotionKing:
4180 case WhitePromotionChancellor:
4181 case BlackPromotionChancellor:
4182 case WhitePromotionArchbishop:
4183 case BlackPromotionArchbishop:
4184 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4185 sprintf(user_move, "%c%c%c%c=%c\n",
4186 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4187 PieceToChar(WhiteFerz));
4188 else if(gameInfo.variant == VariantGreat)
4189 sprintf(user_move, "%c%c%c%c=%c\n",
4190 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191 PieceToChar(WhiteMan));
4193 sprintf(user_move, "%c%c%c%c=%c\n",
4194 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195 PieceToChar(PromoPiece(moveType)));
4199 sprintf(user_move, "%c@%c%c\n",
4200 ToUpper(PieceToChar((ChessSquare) fromX)),
4201 AAA + toX, ONE + toY);
4204 case WhiteCapturesEnPassant:
4205 case BlackCapturesEnPassant:
4206 case IllegalMove: /* could be a variant we don't quite understand */
4207 sprintf(user_move, "%c%c%c%c\n",
4208 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4211 SendToICS(user_move);
4212 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4213 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4217 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4222 if (rf == DROP_RANK) {
4223 sprintf(move, "%c@%c%c\n",
4224 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4226 if (promoChar == 'x' || promoChar == NULLCHAR) {
4227 sprintf(move, "%c%c%c%c\n",
4228 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4230 sprintf(move, "%c%c%c%c%c\n",
4231 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4237 ProcessICSInitScript(f)
4242 while (fgets(buf, MSG_SIZ, f)) {
4243 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4250 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4252 AlphaRank(char *move, int n)
4254 // char *p = move, c; int x, y;
4256 if (appData.debugMode) {
4257 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4261 move[2]>='0' && move[2]<='9' &&
4262 move[3]>='a' && move[3]<='x' ) {
4264 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4265 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4267 if(move[0]>='0' && move[0]<='9' &&
4268 move[1]>='a' && move[1]<='x' &&
4269 move[2]>='0' && move[2]<='9' &&
4270 move[3]>='a' && move[3]<='x' ) {
4271 /* input move, Shogi -> normal */
4272 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4273 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4274 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4275 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4278 move[3]>='0' && move[3]<='9' &&
4279 move[2]>='a' && move[2]<='x' ) {
4281 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4282 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4285 move[0]>='a' && move[0]<='x' &&
4286 move[3]>='0' && move[3]<='9' &&
4287 move[2]>='a' && move[2]<='x' ) {
4288 /* output move, normal -> Shogi */
4289 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4290 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4291 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4292 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4293 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4295 if (appData.debugMode) {
4296 fprintf(debugFP, " out = '%s'\n", move);
4300 /* Parser for moves from gnuchess, ICS, or user typein box */
4302 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4305 ChessMove *moveType;
4306 int *fromX, *fromY, *toX, *toY;
4309 if (appData.debugMode) {
4310 fprintf(debugFP, "move to parse: %s\n", move);
4312 *moveType = yylexstr(moveNum, move);
4314 switch (*moveType) {
4315 case WhitePromotionChancellor:
4316 case BlackPromotionChancellor:
4317 case WhitePromotionArchbishop:
4318 case BlackPromotionArchbishop:
4319 case WhitePromotionQueen:
4320 case BlackPromotionQueen:
4321 case WhitePromotionRook:
4322 case BlackPromotionRook:
4323 case WhitePromotionBishop:
4324 case BlackPromotionBishop:
4325 case WhitePromotionKnight:
4326 case BlackPromotionKnight:
4327 case WhitePromotionKing:
4328 case BlackPromotionKing:
4330 case WhiteCapturesEnPassant:
4331 case BlackCapturesEnPassant:
4332 case WhiteKingSideCastle:
4333 case WhiteQueenSideCastle:
4334 case BlackKingSideCastle:
4335 case BlackQueenSideCastle:
4336 case WhiteKingSideCastleWild:
4337 case WhiteQueenSideCastleWild:
4338 case BlackKingSideCastleWild:
4339 case BlackQueenSideCastleWild:
4340 /* Code added by Tord: */
4341 case WhiteHSideCastleFR:
4342 case WhiteASideCastleFR:
4343 case BlackHSideCastleFR:
4344 case BlackASideCastleFR:
4345 /* End of code added by Tord */
4346 case IllegalMove: /* bug or odd chess variant */
4347 *fromX = currentMoveString[0] - AAA;
4348 *fromY = currentMoveString[1] - ONE;
4349 *toX = currentMoveString[2] - AAA;
4350 *toY = currentMoveString[3] - ONE;
4351 *promoChar = currentMoveString[4];
4352 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4353 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4354 if (appData.debugMode) {
4355 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4357 *fromX = *fromY = *toX = *toY = 0;
4360 if (appData.testLegality) {
4361 return (*moveType != IllegalMove);
4363 return !(fromX == fromY && toX == toY);
4368 *fromX = *moveType == WhiteDrop ?
4369 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4370 (int) CharToPiece(ToLower(currentMoveString[0]));
4372 *toX = currentMoveString[2] - AAA;
4373 *toY = currentMoveString[3] - ONE;
4374 *promoChar = NULLCHAR;
4378 case ImpossibleMove:
4379 case (ChessMove) 0: /* end of file */
4388 if (appData.debugMode) {
4389 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4392 *fromX = *fromY = *toX = *toY = 0;
4393 *promoChar = NULLCHAR;
4398 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4399 // All positions will have equal probability, but the current method will not provide a unique
4400 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4406 int piecesLeft[(int)BlackPawn];
4407 int seed, nrOfShuffles;
4409 void GetPositionNumber()
4410 { // sets global variable seed
4413 seed = appData.defaultFrcPosition;
4414 if(seed < 0) { // randomize based on time for negative FRC position numbers
4415 for(i=0; i<50; i++) seed += random();
4416 seed = random() ^ random() >> 8 ^ random() << 8;
4417 if(seed<0) seed = -seed;
4421 int put(Board board, int pieceType, int rank, int n, int shade)
4422 // put the piece on the (n-1)-th empty squares of the given shade
4426 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4427 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4428 board[rank][i] = (ChessSquare) pieceType;
4429 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4431 piecesLeft[pieceType]--;
4439 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4440 // calculate where the next piece goes, (any empty square), and put it there
4444 i = seed % squaresLeft[shade];
4445 nrOfShuffles *= squaresLeft[shade];
4446 seed /= squaresLeft[shade];
4447 put(board, pieceType, rank, i, shade);
4450 void AddTwoPieces(Board board, int pieceType, int rank)
4451 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4453 int i, n=squaresLeft[ANY], j=n-1, k;
4455 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4456 i = seed % k; // pick one
4459 while(i >= j) i -= j--;
4460 j = n - 1 - j; i += j;
4461 put(board, pieceType, rank, j, ANY);
4462 put(board, pieceType, rank, i, ANY);
4465 void SetUpShuffle(Board board, int number)
4469 GetPositionNumber(); nrOfShuffles = 1;
4471 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4472 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4473 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4475 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4477 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4478 p = (int) board[0][i];
4479 if(p < (int) BlackPawn) piecesLeft[p] ++;
4480 board[0][i] = EmptySquare;
4483 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4484 // shuffles restricted to allow normal castling put KRR first
4485 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4486 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4487 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4488 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4489 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4490 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4491 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4492 put(board, WhiteRook, 0, 0, ANY);
4493 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4496 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4497 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4498 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4499 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4500 while(piecesLeft[p] >= 2) {
4501 AddOnePiece(board, p, 0, LITE);
4502 AddOnePiece(board, p, 0, DARK);
4504 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4507 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4508 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4509 // but we leave King and Rooks for last, to possibly obey FRC restriction
4510 if(p == (int)WhiteRook) continue;
4511 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4512 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4515 // now everything is placed, except perhaps King (Unicorn) and Rooks
4517 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4518 // Last King gets castling rights
4519 while(piecesLeft[(int)WhiteUnicorn]) {
4520 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4521 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4524 while(piecesLeft[(int)WhiteKing]) {
4525 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4526 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4531 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4532 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4535 // Only Rooks can be left; simply place them all
4536 while(piecesLeft[(int)WhiteRook]) {
4537 i = put(board, WhiteRook, 0, 0, ANY);
4538 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4541 initialRights[1] = initialRights[4] = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4543 initialRights[0] = initialRights[3] = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4546 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4547 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4550 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4553 int SetCharTable( char *table, const char * map )
4554 /* [HGM] moved here from winboard.c because of its general usefulness */
4555 /* Basically a safe strcpy that uses the last character as King */
4557 int result = FALSE; int NrPieces;
4559 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4560 && NrPieces >= 12 && !(NrPieces&1)) {
4561 int i; /* [HGM] Accept even length from 12 to 34 */
4563 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4564 for( i=0; i<NrPieces/2-1; i++ ) {
4566 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4568 table[(int) WhiteKing] = map[NrPieces/2-1];
4569 table[(int) BlackKing] = map[NrPieces-1];
4577 void Prelude(Board board)
4578 { // [HGM] superchess: random selection of exo-pieces
4579 int i, j, k; ChessSquare p;
4580 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4582 GetPositionNumber(); // use FRC position number
4584 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4585 SetCharTable(pieceToChar, appData.pieceToCharTable);
4586 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4587 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4590 j = seed%4; seed /= 4;
4591 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4592 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4593 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4594 j = seed%3 + (seed%3 >= j); seed /= 3;
4595 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4596 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4597 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4598 j = seed%3; seed /= 3;
4599 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4600 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4601 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4602 j = seed%2 + (seed%2 >= j); seed /= 2;
4603 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4604 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4605 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4606 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4607 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4608 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4609 put(board, exoPieces[0], 0, 0, ANY);
4610 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4614 InitPosition(redraw)
4617 ChessSquare (* pieces)[BOARD_FILES];
4618 int i, j, pawnRow, overrule,
4619 oldx = gameInfo.boardWidth,
4620 oldy = gameInfo.boardHeight,
4621 oldh = gameInfo.holdingsWidth,
4622 oldv = gameInfo.variant;
4624 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4626 /* [AS] Initialize pv info list [HGM] and game status */
4628 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4629 pvInfoList[i].depth = 0;
4630 boards[i][EP_STATUS] = EP_NONE;
4631 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4634 initialRulePlies = 0; /* 50-move counter start */
4636 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4637 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4641 /* [HGM] logic here is completely changed. In stead of full positions */
4642 /* the initialized data only consist of the two backranks. The switch */
4643 /* selects which one we will use, which is than copied to the Board */
4644 /* initialPosition, which for the rest is initialized by Pawns and */
4645 /* empty squares. This initial position is then copied to boards[0], */
4646 /* possibly after shuffling, so that it remains available. */
4648 gameInfo.holdingsWidth = 0; /* default board sizes */
4649 gameInfo.boardWidth = 8;
4650 gameInfo.boardHeight = 8;
4651 gameInfo.holdingsSize = 0;
4652 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4653 for(i=0; i<BOARD_FILES-2; i++)
4654 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4655 initialPosition[EP_STATUS] = EP_NONE;
4656 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4658 switch (gameInfo.variant) {
4659 case VariantFischeRandom:
4660 shuffleOpenings = TRUE;
4664 case VariantShatranj:
4665 pieces = ShatranjArray;
4666 nrCastlingRights = 0;
4667 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4669 case VariantTwoKings:
4670 pieces = twoKingsArray;
4672 case VariantCapaRandom:
4673 shuffleOpenings = TRUE;
4674 case VariantCapablanca:
4675 pieces = CapablancaArray;
4676 gameInfo.boardWidth = 10;
4677 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4680 pieces = GothicArray;
4681 gameInfo.boardWidth = 10;
4682 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4685 pieces = JanusArray;
4686 gameInfo.boardWidth = 10;
4687 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4688 nrCastlingRights = 6;
4689 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4690 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4691 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4692 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4693 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4694 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4697 pieces = FalconArray;
4698 gameInfo.boardWidth = 10;
4699 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4701 case VariantXiangqi:
4702 pieces = XiangqiArray;
4703 gameInfo.boardWidth = 9;
4704 gameInfo.boardHeight = 10;
4705 nrCastlingRights = 0;
4706 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4709 pieces = ShogiArray;
4710 gameInfo.boardWidth = 9;
4711 gameInfo.boardHeight = 9;
4712 gameInfo.holdingsSize = 7;
4713 nrCastlingRights = 0;
4714 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4716 case VariantCourier:
4717 pieces = CourierArray;
4718 gameInfo.boardWidth = 12;
4719 nrCastlingRights = 0;
4720 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4722 case VariantKnightmate:
4723 pieces = KnightmateArray;
4724 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4727 pieces = fairyArray;
4728 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4731 pieces = GreatArray;
4732 gameInfo.boardWidth = 10;
4733 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4734 gameInfo.holdingsSize = 8;
4738 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4739 gameInfo.holdingsSize = 8;
4740 startedFromSetupPosition = TRUE;
4742 case VariantCrazyhouse:
4743 case VariantBughouse:
4745 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4746 gameInfo.holdingsSize = 5;
4748 case VariantWildCastle:
4750 /* !!?shuffle with kings guaranteed to be on d or e file */
4751 shuffleOpenings = 1;
4753 case VariantNoCastle:
4755 nrCastlingRights = 0;
4756 /* !!?unconstrained back-rank shuffle */
4757 shuffleOpenings = 1;
4762 if(appData.NrFiles >= 0) {
4763 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4764 gameInfo.boardWidth = appData.NrFiles;
4766 if(appData.NrRanks >= 0) {
4767 gameInfo.boardHeight = appData.NrRanks;
4769 if(appData.holdingsSize >= 0) {
4770 i = appData.holdingsSize;
4771 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4772 gameInfo.holdingsSize = i;
4774 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4775 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4776 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4778 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4779 if(pawnRow < 1) pawnRow = 1;
4781 /* User pieceToChar list overrules defaults */
4782 if(appData.pieceToCharTable != NULL)
4783 SetCharTable(pieceToChar, appData.pieceToCharTable);
4785 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4787 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4788 s = (ChessSquare) 0; /* account holding counts in guard band */
4789 for( i=0; i<BOARD_HEIGHT; i++ )
4790 initialPosition[i][j] = s;
4792 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4793 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4794 initialPosition[pawnRow][j] = WhitePawn;
4795 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4796 if(gameInfo.variant == VariantXiangqi) {
4798 initialPosition[pawnRow][j] =
4799 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4800 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4801 initialPosition[2][j] = WhiteCannon;
4802 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4806 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4808 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4811 initialPosition[1][j] = WhiteBishop;
4812 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4814 initialPosition[1][j] = WhiteRook;
4815 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4818 if( nrCastlingRights == -1) {
4819 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4820 /* This sets default castling rights from none to normal corners */
4821 /* Variants with other castling rights must set them themselves above */
4822 nrCastlingRights = 6;
4824 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4825 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4826 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4827 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4828 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4829 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4832 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4833 if(gameInfo.variant == VariantGreat) { // promotion commoners
4834 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4835 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4836 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4837 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4839 if (appData.debugMode) {
4840 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4842 if(shuffleOpenings) {
4843 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4844 startedFromSetupPosition = TRUE;
4846 if(startedFromPositionFile) {
4847 /* [HGM] loadPos: use PositionFile for every new game */
4848 CopyBoard(initialPosition, filePosition);
4849 for(i=0; i<nrCastlingRights; i++)
4850 initialRights[i] = filePosition[CASTLING][i];
4851 startedFromSetupPosition = TRUE;
4854 CopyBoard(boards[0], initialPosition);
4856 if(oldx != gameInfo.boardWidth ||
4857 oldy != gameInfo.boardHeight ||
4858 oldh != gameInfo.holdingsWidth
4860 || oldv == VariantGothic || // For licensing popups
4861 gameInfo.variant == VariantGothic
4864 || oldv == VariantFalcon ||
4865 gameInfo.variant == VariantFalcon
4868 InitDrawingSizes(-2 ,0);
4871 DrawPosition(TRUE, boards[currentMove]);
4875 SendBoard(cps, moveNum)
4876 ChessProgramState *cps;
4879 char message[MSG_SIZ];
4881 if (cps->useSetboard) {
4882 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4883 sprintf(message, "setboard %s\n", fen);
4884 SendToProgram(message, cps);
4890 /* Kludge to set black to move, avoiding the troublesome and now
4891 * deprecated "black" command.
4893 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4895 SendToProgram("edit\n", cps);
4896 SendToProgram("#\n", cps);
4897 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4898 bp = &boards[moveNum][i][BOARD_LEFT];
4899 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4900 if ((int) *bp < (int) BlackPawn) {
4901 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4903 if(message[0] == '+' || message[0] == '~') {
4904 sprintf(message, "%c%c%c+\n",
4905 PieceToChar((ChessSquare)(DEMOTED *bp)),
4908 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4909 message[1] = BOARD_RGHT - 1 - j + '1';
4910 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4912 SendToProgram(message, cps);
4917 SendToProgram("c\n", cps);
4918 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4919 bp = &boards[moveNum][i][BOARD_LEFT];
4920 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4921 if (((int) *bp != (int) EmptySquare)
4922 && ((int) *bp >= (int) BlackPawn)) {
4923 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4925 if(message[0] == '+' || message[0] == '~') {
4926 sprintf(message, "%c%c%c+\n",
4927 PieceToChar((ChessSquare)(DEMOTED *bp)),
4930 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4931 message[1] = BOARD_RGHT - 1 - j + '1';
4932 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4934 SendToProgram(message, cps);
4939 SendToProgram(".\n", cps);
4941 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4945 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4947 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4948 /* [HGM] add Shogi promotions */
4949 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4954 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4955 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4957 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4958 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4961 piece = boards[currentMove][fromY][fromX];
4962 if(gameInfo.variant == VariantShogi) {
4963 promotionZoneSize = 3;
4964 highestPromotingPiece = (int)WhiteFerz;
4967 // next weed out all moves that do not touch the promotion zone at all
4968 if((int)piece >= BlackPawn) {
4969 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4971 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4973 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4974 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4977 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4979 // weed out mandatory Shogi promotions
4980 if(gameInfo.variant == VariantShogi) {
4981 if(piece >= BlackPawn) {
4982 if(toY == 0 && piece == BlackPawn ||
4983 toY == 0 && piece == BlackQueen ||
4984 toY <= 1 && piece == BlackKnight) {
4989 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4990 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4991 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4998 // weed out obviously illegal Pawn moves
4999 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5000 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5001 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5002 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5003 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5004 // note we are not allowed to test for valid (non-)capture, due to premove
5007 // we either have a choice what to promote to, or (in Shogi) whether to promote
5008 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5009 *promoChoice = PieceToChar(BlackFerz); // no choice
5012 if(appData.alwaysPromoteToQueen) { // predetermined
5013 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5014 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5015 else *promoChoice = PieceToChar(BlackQueen);
5019 // suppress promotion popup on illegal moves that are not premoves
5020 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5021 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5022 if(appData.testLegality && !premove) {
5023 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5024 fromY, fromX, toY, toX, NULLCHAR);
5025 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5026 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5034 InPalace(row, column)
5036 { /* [HGM] for Xiangqi */
5037 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5038 column < (BOARD_WIDTH + 4)/2 &&
5039 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5044 PieceForSquare (x, y)
5048 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5051 return boards[currentMove][y][x];
5055 OKToStartUserMove(x, y)
5058 ChessSquare from_piece;
5061 if (matchMode) return FALSE;
5062 if (gameMode == EditPosition) return TRUE;
5064 if (x >= 0 && y >= 0)
5065 from_piece = boards[currentMove][y][x];
5067 from_piece = EmptySquare;
5069 if (from_piece == EmptySquare) return FALSE;
5071 white_piece = (int)from_piece >= (int)WhitePawn &&
5072 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5075 case PlayFromGameFile:
5077 case TwoMachinesPlay:
5085 case MachinePlaysWhite:
5086 case IcsPlayingBlack:
5087 if (appData.zippyPlay) return FALSE;
5089 DisplayMoveError(_("You are playing Black"));
5094 case MachinePlaysBlack:
5095 case IcsPlayingWhite:
5096 if (appData.zippyPlay) return FALSE;
5098 DisplayMoveError(_("You are playing White"));
5104 if (!white_piece && WhiteOnMove(currentMove)) {
5105 DisplayMoveError(_("It is White's turn"));
5108 if (white_piece && !WhiteOnMove(currentMove)) {
5109 DisplayMoveError(_("It is Black's turn"));
5112 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5113 /* Editing correspondence game history */
5114 /* Could disallow this or prompt for confirmation */
5119 case BeginningOfGame:
5120 if (appData.icsActive) return FALSE;
5121 if (!appData.noChessProgram) {
5123 DisplayMoveError(_("You are playing White"));
5130 if (!white_piece && WhiteOnMove(currentMove)) {
5131 DisplayMoveError(_("It is White's turn"));
5134 if (white_piece && !WhiteOnMove(currentMove)) {
5135 DisplayMoveError(_("It is Black's turn"));
5144 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5145 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5146 && gameMode != AnalyzeFile && gameMode != Training) {
5147 DisplayMoveError(_("Displayed position is not current"));
5153 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5154 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5155 int lastLoadGameUseList = FALSE;
5156 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5157 ChessMove lastLoadGameStart = (ChessMove) 0;
5160 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5161 int fromX, fromY, toX, toY;
5166 ChessSquare pdown, pup;
5168 /* Check if the user is playing in turn. This is complicated because we
5169 let the user "pick up" a piece before it is his turn. So the piece he
5170 tried to pick up may have been captured by the time he puts it down!
5171 Therefore we use the color the user is supposed to be playing in this
5172 test, not the color of the piece that is currently on the starting
5173 square---except in EditGame mode, where the user is playing both
5174 sides; fortunately there the capture race can't happen. (It can
5175 now happen in IcsExamining mode, but that's just too bad. The user
5176 will get a somewhat confusing message in that case.)
5180 case PlayFromGameFile:
5182 case TwoMachinesPlay:
5186 /* We switched into a game mode where moves are not accepted,
5187 perhaps while the mouse button was down. */
5188 return ImpossibleMove;
5190 case MachinePlaysWhite:
5191 /* User is moving for Black */
5192 if (WhiteOnMove(currentMove)) {
5193 DisplayMoveError(_("It is White's turn"));
5194 return ImpossibleMove;
5198 case MachinePlaysBlack:
5199 /* User is moving for White */
5200 if (!WhiteOnMove(currentMove)) {
5201 DisplayMoveError(_("It is Black's turn"));
5202 return ImpossibleMove;
5208 case BeginningOfGame:
5211 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5212 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5213 /* User is moving for Black */
5214 if (WhiteOnMove(currentMove)) {
5215 DisplayMoveError(_("It is White's turn"));
5216 return ImpossibleMove;
5219 /* User is moving for White */
5220 if (!WhiteOnMove(currentMove)) {
5221 DisplayMoveError(_("It is Black's turn"));
5222 return ImpossibleMove;
5227 case IcsPlayingBlack:
5228 /* User is moving for Black */
5229 if (WhiteOnMove(currentMove)) {
5230 if (!appData.premove) {
5231 DisplayMoveError(_("It is White's turn"));
5232 } else if (toX >= 0 && toY >= 0) {
5235 premoveFromX = fromX;
5236 premoveFromY = fromY;
5237 premovePromoChar = promoChar;
5239 if (appData.debugMode)
5240 fprintf(debugFP, "Got premove: fromX %d,"
5241 "fromY %d, toX %d, toY %d\n",
5242 fromX, fromY, toX, toY);
5244 return ImpossibleMove;
5248 case IcsPlayingWhite:
5249 /* User is moving for White */
5250 if (!WhiteOnMove(currentMove)) {
5251 if (!appData.premove) {
5252 DisplayMoveError(_("It is Black's turn"));
5253 } else if (toX >= 0 && toY >= 0) {
5256 premoveFromX = fromX;
5257 premoveFromY = fromY;
5258 premovePromoChar = promoChar;
5260 if (appData.debugMode)
5261 fprintf(debugFP, "Got premove: fromX %d,"
5262 "fromY %d, toX %d, toY %d\n",
5263 fromX, fromY, toX, toY);
5265 return ImpossibleMove;
5273 /* EditPosition, empty square, or different color piece;
5274 click-click move is possible */
5275 if (toX == -2 || toY == -2) {
5276 boards[0][fromY][fromX] = EmptySquare;
5277 return AmbiguousMove;
5278 } else if (toX >= 0 && toY >= 0) {
5279 boards[0][toY][toX] = boards[0][fromY][fromX];
5280 boards[0][fromY][fromX] = EmptySquare;
5281 return AmbiguousMove;
5283 return ImpossibleMove;
5286 if(toX < 0 || toY < 0) return ImpossibleMove;
5287 pdown = boards[currentMove][fromY][fromX];
5288 pup = boards[currentMove][toY][toX];
5290 /* [HGM] If move started in holdings, it means a drop */
5291 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5292 if( pup != EmptySquare ) return ImpossibleMove;
5293 if(appData.testLegality) {
5294 /* it would be more logical if LegalityTest() also figured out
5295 * which drops are legal. For now we forbid pawns on back rank.
5296 * Shogi is on its own here...
5298 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5299 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5300 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5302 return WhiteDrop; /* Not needed to specify white or black yet */
5305 userOfferedDraw = FALSE;
5307 /* [HGM] always test for legality, to get promotion info */
5308 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5309 fromY, fromX, toY, toX, promoChar);
5310 /* [HGM] but possibly ignore an IllegalMove result */
5311 if (appData.testLegality) {
5312 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5313 DisplayMoveError(_("Illegal move"));
5314 return ImpossibleMove;
5319 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5320 function is made into one that returns an OK move type if FinishMove
5321 should be called. This to give the calling driver routine the
5322 opportunity to finish the userMove input with a promotion popup,
5323 without bothering the user with this for invalid or illegal moves */
5325 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5328 /* Common tail of UserMoveEvent and DropMenuEvent */
5330 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5332 int fromX, fromY, toX, toY;
5333 /*char*/int promoChar;
5337 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)) {
5341 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5343 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5347 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5348 move type in caller when we know the move is a legal promotion */
5349 if(moveType == NormalMove && promoChar)
5350 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5352 /* [HGM] convert drag-and-drop piece drops to standard form */
5353 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5354 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5355 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5356 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5357 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5358 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5359 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5360 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5364 /* [HGM] <popupFix> The following if has been moved here from
5365 UserMoveEvent(). Because it seemed to belong here (why not allow
5366 piece drops in training games?), and because it can only be
5367 performed after it is known to what we promote. */
5368 if (gameMode == Training) {
5369 /* compare the move played on the board to the next move in the
5370 * game. If they match, display the move and the opponent's response.
5371 * If they don't match, display an error message.
5375 CopyBoard(testBoard, boards[currentMove]);
5376 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5378 if (CompareBoards(testBoard, boards[currentMove+1])) {
5379 ForwardInner(currentMove+1);
5381 /* Autoplay the opponent's response.
5382 * if appData.animate was TRUE when Training mode was entered,
5383 * the response will be animated.
5385 saveAnimate = appData.animate;
5386 appData.animate = animateTraining;
5387 ForwardInner(currentMove+1);
5388 appData.animate = saveAnimate;
5390 /* check for the end of the game */
5391 if (currentMove >= forwardMostMove) {
5392 gameMode = PlayFromGameFile;
5394 SetTrainingModeOff();
5395 DisplayInformation(_("End of game"));
5398 DisplayError(_("Incorrect move"), 0);
5403 /* Ok, now we know that the move is good, so we can kill
5404 the previous line in Analysis Mode */
5405 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5406 && currentMove < forwardMostMove) {
5407 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5410 /* If we need the chess program but it's dead, restart it */
5411 ResurrectChessProgram();
5413 /* A user move restarts a paused game*/
5417 thinkOutput[0] = NULLCHAR;
5419 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5421 if (gameMode == BeginningOfGame) {
5422 if (appData.noChessProgram) {
5423 gameMode = EditGame;
5427 gameMode = MachinePlaysBlack;
5430 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5432 if (first.sendName) {
5433 sprintf(buf, "name %s\n", gameInfo.white);
5434 SendToProgram(buf, &first);
5441 /* Relay move to ICS or chess engine */
5442 if (appData.icsActive) {
5443 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5444 gameMode == IcsExamining) {
5445 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5449 if (first.sendTime && (gameMode == BeginningOfGame ||
5450 gameMode == MachinePlaysWhite ||
5451 gameMode == MachinePlaysBlack)) {
5452 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5454 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5455 // [HGM] book: if program might be playing, let it use book
5456 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5457 first.maybeThinking = TRUE;
5458 } else SendMoveToProgram(forwardMostMove-1, &first);
5459 if (currentMove == cmailOldMove + 1) {
5460 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5464 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5468 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5474 if (WhiteOnMove(currentMove)) {
5475 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5477 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5481 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5486 case MachinePlaysBlack:
5487 case MachinePlaysWhite:
5488 /* disable certain menu options while machine is thinking */
5489 SetMachineThinkingEnables();
5496 if(bookHit) { // [HGM] book: simulate book reply
5497 static char bookMove[MSG_SIZ]; // a bit generous?
5499 programStats.nodes = programStats.depth = programStats.time =
5500 programStats.score = programStats.got_only_move = 0;
5501 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5503 strcpy(bookMove, "move ");
5504 strcat(bookMove, bookHit);
5505 HandleMachineMove(bookMove, &first);
5511 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5512 int fromX, fromY, toX, toY;
5515 /* [HGM] This routine was added to allow calling of its two logical
5516 parts from other modules in the old way. Before, UserMoveEvent()
5517 automatically called FinishMove() if the move was OK, and returned
5518 otherwise. I separated the two, in order to make it possible to
5519 slip a promotion popup in between. But that it always needs two
5520 calls, to the first part, (now called UserMoveTest() ), and to
5521 FinishMove if the first part succeeded. Calls that do not need
5522 to do anything in between, can call this routine the old way.
5524 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5525 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5526 if(moveType == AmbiguousMove)
5527 DrawPosition(FALSE, boards[currentMove]);
5528 else if(moveType != ImpossibleMove && moveType != Comment)
5529 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5532 void LeftClick(ClickType clickType, int xPix, int yPix)
5535 Boolean saveAnimate;
5536 static int second = 0, promotionChoice = 0;
5537 char promoChoice = NULLCHAR;
5539 if (clickType == Press) ErrorPopDown();
5541 x = EventToSquare(xPix, BOARD_WIDTH);
5542 y = EventToSquare(yPix, BOARD_HEIGHT);
5543 if (!flipView && y >= 0) {
5544 y = BOARD_HEIGHT - 1 - y;
5546 if (flipView && x >= 0) {
5547 x = BOARD_WIDTH - 1 - x;
5550 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5551 if(clickType == Release) return; // ignore upclick of click-click destination
5552 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5553 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5554 if(gameInfo.holdingsWidth &&
5555 (WhiteOnMove(currentMove)
5556 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5557 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5558 // click in right holdings, for determining promotion piece
5559 ChessSquare p = boards[currentMove][y][x];
5560 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5561 if(p != EmptySquare) {
5562 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5567 DrawPosition(FALSE, boards[currentMove]);
5571 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5572 if(clickType == Press
5573 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5574 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5575 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5579 if (clickType == Press) {
5581 if (OKToStartUserMove(x, y)) {
5585 DragPieceBegin(xPix, yPix);
5586 if (appData.highlightDragging) {
5587 SetHighlights(x, y, -1, -1);
5595 if (clickType == Press && gameMode != EditPosition) {
5600 // ignore off-board to clicks
5601 if(y < 0 || x < 0) return;
5603 /* Check if clicking again on the same color piece */
5604 fromP = boards[currentMove][fromY][fromX];
5605 toP = boards[currentMove][y][x];
5606 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5607 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5608 WhitePawn <= toP && toP <= WhiteKing &&
5609 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5610 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5611 (BlackPawn <= fromP && fromP <= BlackKing &&
5612 BlackPawn <= toP && toP <= BlackKing &&
5613 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5614 !(fromP == BlackKing && toP == BlackRook && frc))) {
5615 /* Clicked again on same color piece -- changed his mind */
5616 second = (x == fromX && y == fromY);
5617 if (appData.highlightDragging) {
5618 SetHighlights(x, y, -1, -1);
5622 if (OKToStartUserMove(x, y)) {
5625 DragPieceBegin(xPix, yPix);
5629 // ignore clicks on holdings
5630 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5633 if (clickType == Release && x == fromX && y == fromY) {
5634 DragPieceEnd(xPix, yPix);
5635 if (appData.animateDragging) {
5636 /* Undo animation damage if any */
5637 DrawPosition(FALSE, NULL);
5640 /* Second up/down in same square; just abort move */
5645 ClearPremoveHighlights();
5647 /* First upclick in same square; start click-click mode */
5648 SetHighlights(x, y, -1, -1);
5653 /* we now have a different from- and (possibly off-board) to-square */
5654 /* Completed move */
5657 saveAnimate = appData.animate;
5658 if (clickType == Press) {
5659 /* Finish clickclick move */
5660 if (appData.animate || appData.highlightLastMove) {
5661 SetHighlights(fromX, fromY, toX, toY);
5666 /* Finish drag move */
5667 if (appData.highlightLastMove) {
5668 SetHighlights(fromX, fromY, toX, toY);
5672 DragPieceEnd(xPix, yPix);
5673 /* Don't animate move and drag both */
5674 appData.animate = FALSE;
5677 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5678 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5681 DrawPosition(TRUE, NULL);
5685 // off-board moves should not be highlighted
5686 if(x < 0 || x < 0) ClearHighlights();
5688 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5689 SetHighlights(fromX, fromY, toX, toY);
5690 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5691 // [HGM] super: promotion to captured piece selected from holdings
5692 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5693 promotionChoice = TRUE;
5694 // kludge follows to temporarily execute move on display, without promoting yet
5695 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5696 boards[currentMove][toY][toX] = p;
5697 DrawPosition(FALSE, boards[currentMove]);
5698 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5699 boards[currentMove][toY][toX] = q;
5700 DisplayMessage("Click in holdings to choose piece", "");
5705 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5706 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5707 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5710 appData.animate = saveAnimate;
5711 if (appData.animate || appData.animateDragging) {
5712 /* Undo animation damage if needed */
5713 DrawPosition(FALSE, NULL);
5717 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5719 // char * hint = lastHint;
5720 FrontEndProgramStats stats;
5722 stats.which = cps == &first ? 0 : 1;
5723 stats.depth = cpstats->depth;
5724 stats.nodes = cpstats->nodes;
5725 stats.score = cpstats->score;
5726 stats.time = cpstats->time;
5727 stats.pv = cpstats->movelist;
5728 stats.hint = lastHint;
5729 stats.an_move_index = 0;
5730 stats.an_move_count = 0;
5732 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5733 stats.hint = cpstats->move_name;
5734 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5735 stats.an_move_count = cpstats->nr_moves;
5738 SetProgramStats( &stats );
5741 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5742 { // [HGM] book: this routine intercepts moves to simulate book replies
5743 char *bookHit = NULL;
5745 //first determine if the incoming move brings opponent into his book
5746 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5747 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5748 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5749 if(bookHit != NULL && !cps->bookSuspend) {
5750 // make sure opponent is not going to reply after receiving move to book position
5751 SendToProgram("force\n", cps);
5752 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5754 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5755 // now arrange restart after book miss
5757 // after a book hit we never send 'go', and the code after the call to this routine
5758 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5760 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5761 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5762 SendToProgram(buf, cps);
5763 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5764 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5765 SendToProgram("go\n", cps);
5766 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5767 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5768 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5769 SendToProgram("go\n", cps);
5770 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5772 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5776 ChessProgramState *savedState;
5777 void DeferredBookMove(void)
5779 if(savedState->lastPing != savedState->lastPong)
5780 ScheduleDelayedEvent(DeferredBookMove, 10);
5782 HandleMachineMove(savedMessage, savedState);
5786 HandleMachineMove(message, cps)
5788 ChessProgramState *cps;
5790 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5791 char realname[MSG_SIZ];
5792 int fromX, fromY, toX, toY;
5799 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5801 * Kludge to ignore BEL characters
5803 while (*message == '\007') message++;
5806 * [HGM] engine debug message: ignore lines starting with '#' character
5808 if(cps->debug && *message == '#') return;
5811 * Look for book output
5813 if (cps == &first && bookRequested) {
5814 if (message[0] == '\t' || message[0] == ' ') {
5815 /* Part of the book output is here; append it */
5816 strcat(bookOutput, message);
5817 strcat(bookOutput, " \n");
5819 } else if (bookOutput[0] != NULLCHAR) {
5820 /* All of book output has arrived; display it */
5821 char *p = bookOutput;
5822 while (*p != NULLCHAR) {
5823 if (*p == '\t') *p = ' ';
5826 DisplayInformation(bookOutput);
5827 bookRequested = FALSE;
5828 /* Fall through to parse the current output */
5833 * Look for machine move.
5835 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5836 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5838 /* This method is only useful on engines that support ping */
5839 if (cps->lastPing != cps->lastPong) {
5840 if (gameMode == BeginningOfGame) {
5841 /* Extra move from before last new; ignore */
5842 if (appData.debugMode) {
5843 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5846 if (appData.debugMode) {
5847 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5848 cps->which, gameMode);
5851 SendToProgram("undo\n", cps);
5857 case BeginningOfGame:
5858 /* Extra move from before last reset; ignore */
5859 if (appData.debugMode) {
5860 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5867 /* Extra move after we tried to stop. The mode test is
5868 not a reliable way of detecting this problem, but it's
5869 the best we can do on engines that don't support ping.
5871 if (appData.debugMode) {
5872 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5873 cps->which, gameMode);
5875 SendToProgram("undo\n", cps);
5878 case MachinePlaysWhite:
5879 case IcsPlayingWhite:
5880 machineWhite = TRUE;
5883 case MachinePlaysBlack:
5884 case IcsPlayingBlack:
5885 machineWhite = FALSE;
5888 case TwoMachinesPlay:
5889 machineWhite = (cps->twoMachinesColor[0] == 'w');
5892 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5893 if (appData.debugMode) {
5895 "Ignoring move out of turn by %s, gameMode %d"
5896 ", forwardMost %d\n",
5897 cps->which, gameMode, forwardMostMove);
5902 if (appData.debugMode) { int f = forwardMostMove;
5903 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5904 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5905 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5907 if(cps->alphaRank) AlphaRank(machineMove, 4);
5908 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5909 &fromX, &fromY, &toX, &toY, &promoChar)) {
5910 /* Machine move could not be parsed; ignore it. */
5911 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5912 machineMove, cps->which);
5913 DisplayError(buf1, 0);
5914 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5915 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5916 if (gameMode == TwoMachinesPlay) {
5917 GameEnds(machineWhite ? BlackWins : WhiteWins,
5923 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5924 /* So we have to redo legality test with true e.p. status here, */
5925 /* to make sure an illegal e.p. capture does not slip through, */
5926 /* to cause a forfeit on a justified illegal-move complaint */
5927 /* of the opponent. */
5928 if( gameMode==TwoMachinesPlay && appData.testLegality
5929 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5932 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5933 fromY, fromX, toY, toX, promoChar);
5934 if (appData.debugMode) {
5936 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5937 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5938 fprintf(debugFP, "castling rights\n");
5940 if(moveType == IllegalMove) {
5941 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5942 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5943 GameEnds(machineWhite ? BlackWins : WhiteWins,
5946 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5947 /* [HGM] Kludge to handle engines that send FRC-style castling
5948 when they shouldn't (like TSCP-Gothic) */
5950 case WhiteASideCastleFR:
5951 case BlackASideCastleFR:
5953 currentMoveString[2]++;
5955 case WhiteHSideCastleFR:
5956 case BlackHSideCastleFR:
5958 currentMoveString[2]--;
5960 default: ; // nothing to do, but suppresses warning of pedantic compilers
5963 hintRequested = FALSE;
5964 lastHint[0] = NULLCHAR;
5965 bookRequested = FALSE;
5966 /* Program may be pondering now */
5967 cps->maybeThinking = TRUE;
5968 if (cps->sendTime == 2) cps->sendTime = 1;
5969 if (cps->offeredDraw) cps->offeredDraw--;
5972 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5974 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5976 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5977 char buf[3*MSG_SIZ];
5979 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5980 programStats.score / 100.,
5982 programStats.time / 100.,
5983 (unsigned int)programStats.nodes,
5984 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5985 programStats.movelist);
5987 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5991 /* currentMoveString is set as a side-effect of ParseOneMove */
5992 strcpy(machineMove, currentMoveString);
5993 strcat(machineMove, "\n");
5994 strcpy(moveList[forwardMostMove], machineMove);
5996 /* [AS] Save move info and clear stats for next move */
5997 pvInfoList[ forwardMostMove ].score = programStats.score;
5998 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5999 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6000 ClearProgramStats();
6001 thinkOutput[0] = NULLCHAR;
6002 hiddenThinkOutputState = 0;
6004 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6006 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6007 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6010 while( count < adjudicateLossPlies ) {
6011 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6014 score = -score; /* Flip score for winning side */
6017 if( score > adjudicateLossThreshold ) {
6024 if( count >= adjudicateLossPlies ) {
6025 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6027 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6028 "Xboard adjudication",
6035 if( gameMode == TwoMachinesPlay ) {
6036 // [HGM] some adjudications useful with buggy engines
6037 int k, count = 0; static int bare = 1;
6038 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6041 if( appData.testLegality )
6042 { /* [HGM] Some more adjudications for obstinate engines */
6043 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6044 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6045 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6046 static int moveCount = 6;
6048 char *reason = NULL;
6050 /* Count what is on board. */
6051 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6052 { ChessSquare p = boards[forwardMostMove][i][j];
6056 { /* count B,N,R and other of each side */
6059 NrK++; break; // [HGM] atomic: count Kings
6063 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6064 bishopsColor |= 1 << ((i^j)&1);
6069 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6070 bishopsColor |= 1 << ((i^j)&1);
6085 PawnAdvance += m; NrPawns++;
6087 NrPieces += (p != EmptySquare);
6088 NrW += ((int)p < (int)BlackPawn);
6089 if(gameInfo.variant == VariantXiangqi &&
6090 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6091 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6092 NrW -= ((int)p < (int)BlackPawn);
6096 /* Some material-based adjudications that have to be made before stalemate test */
6097 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6098 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6099 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6100 if(appData.checkMates) {
6101 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6102 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6103 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6104 "Xboard adjudication: King destroyed", GE_XBOARD );
6109 /* Bare King in Shatranj (loses) or Losers (wins) */
6110 if( NrW == 1 || NrPieces - NrW == 1) {
6111 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6112 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6113 if(appData.checkMates) {
6114 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6115 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6117 "Xboard adjudication: Bare king", GE_XBOARD );
6121 if( gameInfo.variant == VariantShatranj && --bare < 0)
6123 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6124 if(appData.checkMates) {
6125 /* but only adjudicate if adjudication enabled */
6126 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6127 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6128 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6129 "Xboard adjudication: Bare king", GE_XBOARD );
6136 // don't wait for engine to announce game end if we can judge ourselves
6137 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6139 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6140 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6141 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6142 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6145 reason = "Xboard adjudication: 3rd check";
6146 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6156 reason = "Xboard adjudication: Stalemate";
6157 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6158 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6159 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6160 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6161 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6162 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6163 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6164 EP_CHECKMATE : EP_WINS);
6165 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6166 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6170 reason = "Xboard adjudication: Checkmate";
6171 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6175 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6177 result = GameIsDrawn; break;
6179 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6181 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6183 result = (ChessMove) 0;
6185 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6186 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6187 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6188 GameEnds( result, reason, GE_XBOARD );
6192 /* Next absolutely insufficient mating material. */
6193 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6194 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6195 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6196 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6197 { /* KBK, KNK, KK of KBKB with like Bishops */
6199 /* always flag draws, for judging claims */
6200 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6202 if(appData.materialDraws) {
6203 /* but only adjudicate them if adjudication enabled */
6204 SendToProgram("force\n", cps->other); // suppress reply
6205 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6206 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6207 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6212 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6214 ( NrWR == 1 && NrBR == 1 /* KRKR */
6215 || NrWQ==1 && NrBQ==1 /* KQKQ */
6216 || NrWN==2 || NrBN==2 /* KNNK */
6217 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6219 if(--moveCount < 0 && appData.trivialDraws)
6220 { /* if the first 3 moves do not show a tactical win, declare draw */
6221 SendToProgram("force\n", cps->other); // suppress reply
6222 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6223 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6224 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6227 } else moveCount = 6;
6231 if (appData.debugMode) { int i;
6232 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6233 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6234 appData.drawRepeats);
6235 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6236 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6240 /* Check for rep-draws */
6242 for(k = forwardMostMove-2;
6243 k>=backwardMostMove && k>=forwardMostMove-100 &&
6244 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6245 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6248 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6249 /* compare castling rights */
6250 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6251 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6252 rights++; /* King lost rights, while rook still had them */
6253 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6254 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6255 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6256 rights++; /* but at least one rook lost them */
6258 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6259 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6261 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6262 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6263 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6266 if( rights == 0 && ++count > appData.drawRepeats-2
6267 && appData.drawRepeats > 1) {
6268 /* adjudicate after user-specified nr of repeats */
6269 SendToProgram("force\n", cps->other); // suppress reply
6270 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6271 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6272 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6273 // [HGM] xiangqi: check for forbidden perpetuals
6274 int m, ourPerpetual = 1, hisPerpetual = 1;
6275 for(m=forwardMostMove; m>k; m-=2) {
6276 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6277 ourPerpetual = 0; // the current mover did not always check
6278 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6279 hisPerpetual = 0; // the opponent did not always check
6281 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6282 ourPerpetual, hisPerpetual);
6283 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6284 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6285 "Xboard adjudication: perpetual checking", GE_XBOARD );
6288 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6289 break; // (or we would have caught him before). Abort repetition-checking loop.
6290 // Now check for perpetual chases
6291 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6292 hisPerpetual = PerpetualChase(k, forwardMostMove);
6293 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6294 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6295 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6296 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6299 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6300 break; // Abort repetition-checking loop.
6302 // if neither of us is checking or chasing all the time, or both are, it is draw
6304 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6307 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6308 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6312 /* Now we test for 50-move draws. Determine ply count */
6313 count = forwardMostMove;
6314 /* look for last irreversble move */
6315 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6317 /* if we hit starting position, add initial plies */
6318 if( count == backwardMostMove )
6319 count -= initialRulePlies;
6320 count = forwardMostMove - count;
6322 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6323 /* this is used to judge if draw claims are legal */
6324 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6325 SendToProgram("force\n", cps->other); // suppress reply
6326 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6327 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6328 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6332 /* if draw offer is pending, treat it as a draw claim
6333 * when draw condition present, to allow engines a way to
6334 * claim draws before making their move to avoid a race
6335 * condition occurring after their move
6337 if( cps->other->offeredDraw || cps->offeredDraw ) {
6339 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6340 p = "Draw claim: 50-move rule";
6341 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6342 p = "Draw claim: 3-fold repetition";
6343 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6344 p = "Draw claim: insufficient mating material";
6346 SendToProgram("force\n", cps->other); // suppress reply
6347 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6348 GameEnds( GameIsDrawn, p, GE_XBOARD );
6349 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6355 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6356 SendToProgram("force\n", cps->other); // suppress reply
6357 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6358 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6360 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6367 if (gameMode == TwoMachinesPlay) {
6368 /* [HGM] relaying draw offers moved to after reception of move */
6369 /* and interpreting offer as claim if it brings draw condition */
6370 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6371 SendToProgram("draw\n", cps->other);
6373 if (cps->other->sendTime) {
6374 SendTimeRemaining(cps->other,
6375 cps->other->twoMachinesColor[0] == 'w');
6377 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6378 if (firstMove && !bookHit) {
6380 if (cps->other->useColors) {
6381 SendToProgram(cps->other->twoMachinesColor, cps->other);
6383 SendToProgram("go\n", cps->other);
6385 cps->other->maybeThinking = TRUE;
6388 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6390 if (!pausing && appData.ringBellAfterMoves) {
6395 * Reenable menu items that were disabled while
6396 * machine was thinking
6398 if (gameMode != TwoMachinesPlay)
6399 SetUserThinkingEnables();
6401 // [HGM] book: after book hit opponent has received move and is now in force mode
6402 // force the book reply into it, and then fake that it outputted this move by jumping
6403 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6405 static char bookMove[MSG_SIZ]; // a bit generous?
6407 strcpy(bookMove, "move ");
6408 strcat(bookMove, bookHit);
6411 programStats.nodes = programStats.depth = programStats.time =
6412 programStats.score = programStats.got_only_move = 0;
6413 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6415 if(cps->lastPing != cps->lastPong) {
6416 savedMessage = message; // args for deferred call
6418 ScheduleDelayedEvent(DeferredBookMove, 10);
6427 /* Set special modes for chess engines. Later something general
6428 * could be added here; for now there is just one kludge feature,
6429 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6430 * when "xboard" is given as an interactive command.
6432 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6433 cps->useSigint = FALSE;
6434 cps->useSigterm = FALSE;
6436 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6437 ParseFeatures(message+8, cps);
6438 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6441 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6442 * want this, I was asked to put it in, and obliged.
6444 if (!strncmp(message, "setboard ", 9)) {
6445 Board initial_position;
6447 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6449 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6450 DisplayError(_("Bad FEN received from engine"), 0);
6454 CopyBoard(boards[0], initial_position);
6455 initialRulePlies = FENrulePlies;
6456 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6457 else gameMode = MachinePlaysBlack;
6458 DrawPosition(FALSE, boards[currentMove]);
6464 * Look for communication commands
6466 if (!strncmp(message, "telluser ", 9)) {
6467 DisplayNote(message + 9);
6470 if (!strncmp(message, "tellusererror ", 14)) {
6471 DisplayError(message + 14, 0);
6474 if (!strncmp(message, "tellopponent ", 13)) {
6475 if (appData.icsActive) {
6477 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6481 DisplayNote(message + 13);
6485 if (!strncmp(message, "tellothers ", 11)) {
6486 if (appData.icsActive) {
6488 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6494 if (!strncmp(message, "tellall ", 8)) {
6495 if (appData.icsActive) {
6497 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6501 DisplayNote(message + 8);
6505 if (strncmp(message, "warning", 7) == 0) {
6506 /* Undocumented feature, use tellusererror in new code */
6507 DisplayError(message, 0);
6510 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6511 strcpy(realname, cps->tidy);
6512 strcat(realname, " query");
6513 AskQuestion(realname, buf2, buf1, cps->pr);
6516 /* Commands from the engine directly to ICS. We don't allow these to be
6517 * sent until we are logged on. Crafty kibitzes have been known to
6518 * interfere with the login process.
6521 if (!strncmp(message, "tellics ", 8)) {
6522 SendToICS(message + 8);
6526 if (!strncmp(message, "tellicsnoalias ", 15)) {
6527 SendToICS(ics_prefix);
6528 SendToICS(message + 15);
6532 /* The following are for backward compatibility only */
6533 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6534 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6535 SendToICS(ics_prefix);
6541 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6545 * If the move is illegal, cancel it and redraw the board.
6546 * Also deal with other error cases. Matching is rather loose
6547 * here to accommodate engines written before the spec.
6549 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6550 strncmp(message, "Error", 5) == 0) {
6551 if (StrStr(message, "name") ||
6552 StrStr(message, "rating") || StrStr(message, "?") ||
6553 StrStr(message, "result") || StrStr(message, "board") ||
6554 StrStr(message, "bk") || StrStr(message, "computer") ||
6555 StrStr(message, "variant") || StrStr(message, "hint") ||
6556 StrStr(message, "random") || StrStr(message, "depth") ||
6557 StrStr(message, "accepted")) {
6560 if (StrStr(message, "protover")) {
6561 /* Program is responding to input, so it's apparently done
6562 initializing, and this error message indicates it is
6563 protocol version 1. So we don't need to wait any longer
6564 for it to initialize and send feature commands. */
6565 FeatureDone(cps, 1);
6566 cps->protocolVersion = 1;
6569 cps->maybeThinking = FALSE;
6571 if (StrStr(message, "draw")) {
6572 /* Program doesn't have "draw" command */
6573 cps->sendDrawOffers = 0;
6576 if (cps->sendTime != 1 &&
6577 (StrStr(message, "time") || StrStr(message, "otim"))) {
6578 /* Program apparently doesn't have "time" or "otim" command */
6582 if (StrStr(message, "analyze")) {
6583 cps->analysisSupport = FALSE;
6584 cps->analyzing = FALSE;
6586 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6587 DisplayError(buf2, 0);
6590 if (StrStr(message, "(no matching move)st")) {
6591 /* Special kludge for GNU Chess 4 only */
6592 cps->stKludge = TRUE;
6593 SendTimeControl(cps, movesPerSession, timeControl,
6594 timeIncrement, appData.searchDepth,
6598 if (StrStr(message, "(no matching move)sd")) {
6599 /* Special kludge for GNU Chess 4 only */
6600 cps->sdKludge = TRUE;
6601 SendTimeControl(cps, movesPerSession, timeControl,
6602 timeIncrement, appData.searchDepth,
6606 if (!StrStr(message, "llegal")) {
6609 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6610 gameMode == IcsIdle) return;
6611 if (forwardMostMove <= backwardMostMove) return;
6612 if (pausing) PauseEvent();
6613 if(appData.forceIllegal) {
6614 // [HGM] illegal: machine refused move; force position after move into it
6615 SendToProgram("force\n", cps);
6616 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6617 // we have a real problem now, as SendBoard will use the a2a3 kludge
6618 // when black is to move, while there might be nothing on a2 or black
6619 // might already have the move. So send the board as if white has the move.
6620 // But first we must change the stm of the engine, as it refused the last move
6621 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6622 if(WhiteOnMove(forwardMostMove)) {
6623 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6624 SendBoard(cps, forwardMostMove); // kludgeless board
6626 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6627 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6628 SendBoard(cps, forwardMostMove+1); // kludgeless board
6630 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6631 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6632 gameMode == TwoMachinesPlay)
6633 SendToProgram("go\n", cps);
6636 if (gameMode == PlayFromGameFile) {
6637 /* Stop reading this game file */
6638 gameMode = EditGame;
6641 currentMove = --forwardMostMove;
6642 DisplayMove(currentMove-1); /* before DisplayMoveError */
6644 DisplayBothClocks();
6645 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6646 parseList[currentMove], cps->which);
6647 DisplayMoveError(buf1);
6648 DrawPosition(FALSE, boards[currentMove]);
6650 /* [HGM] illegal-move claim should forfeit game when Xboard */
6651 /* only passes fully legal moves */
6652 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6653 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6654 "False illegal-move claim", GE_XBOARD );
6658 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6659 /* Program has a broken "time" command that
6660 outputs a string not ending in newline.
6666 * If chess program startup fails, exit with an error message.
6667 * Attempts to recover here are futile.
6669 if ((StrStr(message, "unknown host") != NULL)
6670 || (StrStr(message, "No remote directory") != NULL)
6671 || (StrStr(message, "not found") != NULL)
6672 || (StrStr(message, "No such file") != NULL)
6673 || (StrStr(message, "can't alloc") != NULL)
6674 || (StrStr(message, "Permission denied") != NULL)) {
6676 cps->maybeThinking = FALSE;
6677 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6678 cps->which, cps->program, cps->host, message);
6679 RemoveInputSource(cps->isr);
6680 DisplayFatalError(buf1, 0, 1);
6685 * Look for hint output
6687 if (sscanf(message, "Hint: %s", buf1) == 1) {
6688 if (cps == &first && hintRequested) {
6689 hintRequested = FALSE;
6690 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6691 &fromX, &fromY, &toX, &toY, &promoChar)) {
6692 (void) CoordsToAlgebraic(boards[forwardMostMove],
6693 PosFlags(forwardMostMove),
6694 fromY, fromX, toY, toX, promoChar, buf1);
6695 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6696 DisplayInformation(buf2);
6698 /* Hint move could not be parsed!? */
6699 snprintf(buf2, sizeof(buf2),
6700 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6702 DisplayError(buf2, 0);
6705 strcpy(lastHint, buf1);
6711 * Ignore other messages if game is not in progress
6713 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6714 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6717 * look for win, lose, draw, or draw offer
6719 if (strncmp(message, "1-0", 3) == 0) {
6720 char *p, *q, *r = "";
6721 p = strchr(message, '{');
6729 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6731 } else if (strncmp(message, "0-1", 3) == 0) {
6732 char *p, *q, *r = "";
6733 p = strchr(message, '{');
6741 /* Kludge for Arasan 4.1 bug */
6742 if (strcmp(r, "Black resigns") == 0) {
6743 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6746 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6748 } else if (strncmp(message, "1/2", 3) == 0) {
6749 char *p, *q, *r = "";
6750 p = strchr(message, '{');
6759 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6762 } else if (strncmp(message, "White resign", 12) == 0) {
6763 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6765 } else if (strncmp(message, "Black resign", 12) == 0) {
6766 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6768 } else if (strncmp(message, "White matches", 13) == 0 ||
6769 strncmp(message, "Black matches", 13) == 0 ) {
6770 /* [HGM] ignore GNUShogi noises */
6772 } else if (strncmp(message, "White", 5) == 0 &&
6773 message[5] != '(' &&
6774 StrStr(message, "Black") == NULL) {
6775 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6777 } else if (strncmp(message, "Black", 5) == 0 &&
6778 message[5] != '(') {
6779 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6781 } else if (strcmp(message, "resign") == 0 ||
6782 strcmp(message, "computer resigns") == 0) {
6784 case MachinePlaysBlack:
6785 case IcsPlayingBlack:
6786 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6788 case MachinePlaysWhite:
6789 case IcsPlayingWhite:
6790 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6792 case TwoMachinesPlay:
6793 if (cps->twoMachinesColor[0] == 'w')
6794 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6796 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6803 } else if (strncmp(message, "opponent mates", 14) == 0) {
6805 case MachinePlaysBlack:
6806 case IcsPlayingBlack:
6807 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6809 case MachinePlaysWhite:
6810 case IcsPlayingWhite:
6811 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6813 case TwoMachinesPlay:
6814 if (cps->twoMachinesColor[0] == 'w')
6815 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6817 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6824 } else if (strncmp(message, "computer mates", 14) == 0) {
6826 case MachinePlaysBlack:
6827 case IcsPlayingBlack:
6828 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6830 case MachinePlaysWhite:
6831 case IcsPlayingWhite:
6832 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6834 case TwoMachinesPlay:
6835 if (cps->twoMachinesColor[0] == 'w')
6836 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6838 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6845 } else if (strncmp(message, "checkmate", 9) == 0) {
6846 if (WhiteOnMove(forwardMostMove)) {
6847 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6849 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6852 } else if (strstr(message, "Draw") != NULL ||
6853 strstr(message, "game is a draw") != NULL) {
6854 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6856 } else if (strstr(message, "offer") != NULL &&
6857 strstr(message, "draw") != NULL) {
6859 if (appData.zippyPlay && first.initDone) {
6860 /* Relay offer to ICS */
6861 SendToICS(ics_prefix);
6862 SendToICS("draw\n");
6865 cps->offeredDraw = 2; /* valid until this engine moves twice */
6866 if (gameMode == TwoMachinesPlay) {
6867 if (cps->other->offeredDraw) {
6868 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6869 /* [HGM] in two-machine mode we delay relaying draw offer */
6870 /* until after we also have move, to see if it is really claim */
6872 } else if (gameMode == MachinePlaysWhite ||
6873 gameMode == MachinePlaysBlack) {
6874 if (userOfferedDraw) {
6875 DisplayInformation(_("Machine accepts your draw offer"));
6876 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6878 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6885 * Look for thinking output
6887 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6888 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6890 int plylev, mvleft, mvtot, curscore, time;
6891 char mvname[MOVE_LEN];
6895 int prefixHint = FALSE;
6896 mvname[0] = NULLCHAR;
6899 case MachinePlaysBlack:
6900 case IcsPlayingBlack:
6901 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6903 case MachinePlaysWhite:
6904 case IcsPlayingWhite:
6905 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6910 case IcsObserving: /* [DM] icsEngineAnalyze */
6911 if (!appData.icsEngineAnalyze) ignore = TRUE;
6913 case TwoMachinesPlay:
6914 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6925 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6926 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6928 if (plyext != ' ' && plyext != '\t') {
6932 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6933 if( cps->scoreIsAbsolute &&
6934 ( gameMode == MachinePlaysBlack ||
6935 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6936 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6937 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6938 !WhiteOnMove(currentMove)
6941 curscore = -curscore;
6945 programStats.depth = plylev;
6946 programStats.nodes = nodes;
6947 programStats.time = time;
6948 programStats.score = curscore;
6949 programStats.got_only_move = 0;
6951 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6954 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6955 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6956 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6957 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6958 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6959 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6960 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6961 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6964 /* Buffer overflow protection */
6965 if (buf1[0] != NULLCHAR) {
6966 if (strlen(buf1) >= sizeof(programStats.movelist)
6967 && appData.debugMode) {
6969 "PV is too long; using the first %u bytes.\n",
6970 (unsigned) sizeof(programStats.movelist) - 1);
6973 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6975 sprintf(programStats.movelist, " no PV\n");
6978 if (programStats.seen_stat) {
6979 programStats.ok_to_send = 1;
6982 if (strchr(programStats.movelist, '(') != NULL) {
6983 programStats.line_is_book = 1;
6984 programStats.nr_moves = 0;
6985 programStats.moves_left = 0;
6987 programStats.line_is_book = 0;
6990 SendProgramStatsToFrontend( cps, &programStats );
6993 [AS] Protect the thinkOutput buffer from overflow... this
6994 is only useful if buf1 hasn't overflowed first!
6996 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6998 (gameMode == TwoMachinesPlay ?
6999 ToUpper(cps->twoMachinesColor[0]) : ' '),
7000 ((double) curscore) / 100.0,
7001 prefixHint ? lastHint : "",
7002 prefixHint ? " " : "" );
7004 if( buf1[0] != NULLCHAR ) {
7005 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7007 if( strlen(buf1) > max_len ) {
7008 if( appData.debugMode) {
7009 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7011 buf1[max_len+1] = '\0';
7014 strcat( thinkOutput, buf1 );
7017 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7018 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7019 DisplayMove(currentMove - 1);
7023 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7024 /* crafty (9.25+) says "(only move) <move>"
7025 * if there is only 1 legal move
7027 sscanf(p, "(only move) %s", buf1);
7028 sprintf(thinkOutput, "%s (only move)", buf1);
7029 sprintf(programStats.movelist, "%s (only move)", buf1);
7030 programStats.depth = 1;
7031 programStats.nr_moves = 1;
7032 programStats.moves_left = 1;
7033 programStats.nodes = 1;
7034 programStats.time = 1;
7035 programStats.got_only_move = 1;
7037 /* Not really, but we also use this member to
7038 mean "line isn't going to change" (Crafty
7039 isn't searching, so stats won't change) */
7040 programStats.line_is_book = 1;
7042 SendProgramStatsToFrontend( cps, &programStats );
7044 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7045 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7046 DisplayMove(currentMove - 1);
7049 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7050 &time, &nodes, &plylev, &mvleft,
7051 &mvtot, mvname) >= 5) {
7052 /* The stat01: line is from Crafty (9.29+) in response
7053 to the "." command */
7054 programStats.seen_stat = 1;
7055 cps->maybeThinking = TRUE;
7057 if (programStats.got_only_move || !appData.periodicUpdates)
7060 programStats.depth = plylev;
7061 programStats.time = time;
7062 programStats.nodes = nodes;
7063 programStats.moves_left = mvleft;
7064 programStats.nr_moves = mvtot;
7065 strcpy(programStats.move_name, mvname);
7066 programStats.ok_to_send = 1;
7067 programStats.movelist[0] = '\0';
7069 SendProgramStatsToFrontend( cps, &programStats );
7073 } else if (strncmp(message,"++",2) == 0) {
7074 /* Crafty 9.29+ outputs this */
7075 programStats.got_fail = 2;
7078 } else if (strncmp(message,"--",2) == 0) {
7079 /* Crafty 9.29+ outputs this */
7080 programStats.got_fail = 1;
7083 } else if (thinkOutput[0] != NULLCHAR &&
7084 strncmp(message, " ", 4) == 0) {
7085 unsigned message_len;
7088 while (*p && *p == ' ') p++;
7090 message_len = strlen( p );
7092 /* [AS] Avoid buffer overflow */
7093 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7094 strcat(thinkOutput, " ");
7095 strcat(thinkOutput, p);
7098 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7099 strcat(programStats.movelist, " ");
7100 strcat(programStats.movelist, p);
7103 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7104 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7105 DisplayMove(currentMove - 1);
7113 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7114 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7116 ChessProgramStats cpstats;
7118 if (plyext != ' ' && plyext != '\t') {
7122 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7123 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7124 curscore = -curscore;
7127 cpstats.depth = plylev;
7128 cpstats.nodes = nodes;
7129 cpstats.time = time;
7130 cpstats.score = curscore;
7131 cpstats.got_only_move = 0;
7132 cpstats.movelist[0] = '\0';
7134 if (buf1[0] != NULLCHAR) {
7135 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7138 cpstats.ok_to_send = 0;
7139 cpstats.line_is_book = 0;
7140 cpstats.nr_moves = 0;
7141 cpstats.moves_left = 0;
7143 SendProgramStatsToFrontend( cps, &cpstats );
7150 /* Parse a game score from the character string "game", and
7151 record it as the history of the current game. The game
7152 score is NOT assumed to start from the standard position.
7153 The display is not updated in any way.
7156 ParseGameHistory(game)
7160 int fromX, fromY, toX, toY, boardIndex;
7165 if (appData.debugMode)
7166 fprintf(debugFP, "Parsing game history: %s\n", game);
7168 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7169 gameInfo.site = StrSave(appData.icsHost);
7170 gameInfo.date = PGNDate();
7171 gameInfo.round = StrSave("-");
7173 /* Parse out names of players */
7174 while (*game == ' ') game++;
7176 while (*game != ' ') *p++ = *game++;
7178 gameInfo.white = StrSave(buf);
7179 while (*game == ' ') game++;
7181 while (*game != ' ' && *game != '\n') *p++ = *game++;
7183 gameInfo.black = StrSave(buf);
7186 boardIndex = blackPlaysFirst ? 1 : 0;
7189 yyboardindex = boardIndex;
7190 moveType = (ChessMove) yylex();
7192 case IllegalMove: /* maybe suicide chess, etc. */
7193 if (appData.debugMode) {
7194 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7195 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7196 setbuf(debugFP, NULL);
7198 case WhitePromotionChancellor:
7199 case BlackPromotionChancellor:
7200 case WhitePromotionArchbishop:
7201 case BlackPromotionArchbishop:
7202 case WhitePromotionQueen:
7203 case BlackPromotionQueen:
7204 case WhitePromotionRook:
7205 case BlackPromotionRook:
7206 case WhitePromotionBishop:
7207 case BlackPromotionBishop:
7208 case WhitePromotionKnight:
7209 case BlackPromotionKnight:
7210 case WhitePromotionKing:
7211 case BlackPromotionKing:
7213 case WhiteCapturesEnPassant:
7214 case BlackCapturesEnPassant:
7215 case WhiteKingSideCastle:
7216 case WhiteQueenSideCastle:
7217 case BlackKingSideCastle:
7218 case BlackQueenSideCastle:
7219 case WhiteKingSideCastleWild:
7220 case WhiteQueenSideCastleWild:
7221 case BlackKingSideCastleWild:
7222 case BlackQueenSideCastleWild:
7224 case WhiteHSideCastleFR:
7225 case WhiteASideCastleFR:
7226 case BlackHSideCastleFR:
7227 case BlackASideCastleFR:
7229 fromX = currentMoveString[0] - AAA;
7230 fromY = currentMoveString[1] - ONE;
7231 toX = currentMoveString[2] - AAA;
7232 toY = currentMoveString[3] - ONE;
7233 promoChar = currentMoveString[4];
7237 fromX = moveType == WhiteDrop ?
7238 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7239 (int) CharToPiece(ToLower(currentMoveString[0]));
7241 toX = currentMoveString[2] - AAA;
7242 toY = currentMoveString[3] - ONE;
7243 promoChar = NULLCHAR;
7247 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7248 if (appData.debugMode) {
7249 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7250 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7251 setbuf(debugFP, NULL);
7253 DisplayError(buf, 0);
7255 case ImpossibleMove:
7257 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7258 if (appData.debugMode) {
7259 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7260 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7261 setbuf(debugFP, NULL);
7263 DisplayError(buf, 0);
7265 case (ChessMove) 0: /* end of file */
7266 if (boardIndex < backwardMostMove) {
7267 /* Oops, gap. How did that happen? */
7268 DisplayError(_("Gap in move list"), 0);
7271 backwardMostMove = blackPlaysFirst ? 1 : 0;
7272 if (boardIndex > forwardMostMove) {
7273 forwardMostMove = boardIndex;
7277 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7278 strcat(parseList[boardIndex-1], " ");
7279 strcat(parseList[boardIndex-1], yy_text);
7291 case GameUnfinished:
7292 if (gameMode == IcsExamining) {
7293 if (boardIndex < backwardMostMove) {
7294 /* Oops, gap. How did that happen? */
7297 backwardMostMove = blackPlaysFirst ? 1 : 0;
7300 gameInfo.result = moveType;
7301 p = strchr(yy_text, '{');
7302 if (p == NULL) p = strchr(yy_text, '(');
7305 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7307 q = strchr(p, *p == '{' ? '}' : ')');
7308 if (q != NULL) *q = NULLCHAR;
7311 gameInfo.resultDetails = StrSave(p);
7314 if (boardIndex >= forwardMostMove &&
7315 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7316 backwardMostMove = blackPlaysFirst ? 1 : 0;
7319 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7320 fromY, fromX, toY, toX, promoChar,
7321 parseList[boardIndex]);
7322 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7323 /* currentMoveString is set as a side-effect of yylex */
7324 strcpy(moveList[boardIndex], currentMoveString);
7325 strcat(moveList[boardIndex], "\n");
7327 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7328 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7334 if(gameInfo.variant != VariantShogi)
7335 strcat(parseList[boardIndex - 1], "+");
7339 strcat(parseList[boardIndex - 1], "#");
7346 /* Apply a move to the given board */
7348 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7349 int fromX, fromY, toX, toY;
7353 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7355 /* [HGM] compute & store e.p. status and castling rights for new position */
7356 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7359 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7360 oldEP = (signed char)board[EP_STATUS];
7361 board[EP_STATUS] = EP_NONE;
7363 if( board[toY][toX] != EmptySquare )
7364 board[EP_STATUS] = EP_CAPTURE;
7366 if( board[fromY][fromX] == WhitePawn ) {
7367 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7368 board[EP_STATUS] = EP_PAWN_MOVE;
7370 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7371 gameInfo.variant != VariantBerolina || toX < fromX)
7372 board[EP_STATUS] = toX | berolina;
7373 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7374 gameInfo.variant != VariantBerolina || toX > fromX)
7375 board[EP_STATUS] = toX;
7378 if( board[fromY][fromX] == BlackPawn ) {
7379 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7380 board[EP_STATUS] = EP_PAWN_MOVE;
7381 if( toY-fromY== -2) {
7382 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7383 gameInfo.variant != VariantBerolina || toX < fromX)
7384 board[EP_STATUS] = toX | berolina;
7385 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7386 gameInfo.variant != VariantBerolina || toX > fromX)
7387 board[EP_STATUS] = toX;
7391 for(i=0; i<nrCastlingRights; i++) {
7392 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7393 board[CASTLING][i] == toX && castlingRank[i] == toY
7394 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7399 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7400 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7401 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7403 if (fromX == toX && fromY == toY) return;
7405 if (fromY == DROP_RANK) {
7407 piece = board[toY][toX] = (ChessSquare) fromX;
7409 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7410 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7411 if(gameInfo.variant == VariantKnightmate)
7412 king += (int) WhiteUnicorn - (int) WhiteKing;
7414 /* Code added by Tord: */
7415 /* FRC castling assumed when king captures friendly rook. */
7416 if (board[fromY][fromX] == WhiteKing &&
7417 board[toY][toX] == WhiteRook) {
7418 board[fromY][fromX] = EmptySquare;
7419 board[toY][toX] = EmptySquare;
7421 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7423 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7425 } else if (board[fromY][fromX] == BlackKing &&
7426 board[toY][toX] == BlackRook) {
7427 board[fromY][fromX] = EmptySquare;
7428 board[toY][toX] = EmptySquare;
7430 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7432 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7434 /* End of code added by Tord */
7436 } else if (board[fromY][fromX] == king
7437 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7438 && toY == fromY && toX > fromX+1) {
7439 board[fromY][fromX] = EmptySquare;
7440 board[toY][toX] = king;
7441 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7442 board[fromY][BOARD_RGHT-1] = EmptySquare;
7443 } else if (board[fromY][fromX] == king
7444 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7445 && toY == fromY && toX < fromX-1) {
7446 board[fromY][fromX] = EmptySquare;
7447 board[toY][toX] = king;
7448 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7449 board[fromY][BOARD_LEFT] = EmptySquare;
7450 } else if (board[fromY][fromX] == WhitePawn
7451 && toY == BOARD_HEIGHT-1
7452 && gameInfo.variant != VariantXiangqi
7454 /* white pawn promotion */
7455 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7456 if (board[toY][toX] == EmptySquare) {
7457 board[toY][toX] = WhiteQueen;
7459 if(gameInfo.variant==VariantBughouse ||
7460 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7461 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7462 board[fromY][fromX] = EmptySquare;
7463 } else if ((fromY == BOARD_HEIGHT-4)
7465 && gameInfo.variant != VariantXiangqi
7466 && gameInfo.variant != VariantBerolina
7467 && (board[fromY][fromX] == WhitePawn)
7468 && (board[toY][toX] == EmptySquare)) {
7469 board[fromY][fromX] = EmptySquare;
7470 board[toY][toX] = WhitePawn;
7471 captured = board[toY - 1][toX];
7472 board[toY - 1][toX] = EmptySquare;
7473 } else if ((fromY == BOARD_HEIGHT-4)
7475 && gameInfo.variant == VariantBerolina
7476 && (board[fromY][fromX] == WhitePawn)
7477 && (board[toY][toX] == EmptySquare)) {
7478 board[fromY][fromX] = EmptySquare;
7479 board[toY][toX] = WhitePawn;
7480 if(oldEP & EP_BEROLIN_A) {
7481 captured = board[fromY][fromX-1];
7482 board[fromY][fromX-1] = EmptySquare;
7483 }else{ captured = board[fromY][fromX+1];
7484 board[fromY][fromX+1] = EmptySquare;
7486 } else if (board[fromY][fromX] == king
7487 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7488 && toY == fromY && toX > fromX+1) {
7489 board[fromY][fromX] = EmptySquare;
7490 board[toY][toX] = king;
7491 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7492 board[fromY][BOARD_RGHT-1] = EmptySquare;
7493 } else if (board[fromY][fromX] == king
7494 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7495 && toY == fromY && toX < fromX-1) {
7496 board[fromY][fromX] = EmptySquare;
7497 board[toY][toX] = king;
7498 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7499 board[fromY][BOARD_LEFT] = EmptySquare;
7500 } else if (fromY == 7 && fromX == 3
7501 && board[fromY][fromX] == BlackKing
7502 && toY == 7 && toX == 5) {
7503 board[fromY][fromX] = EmptySquare;
7504 board[toY][toX] = BlackKing;
7505 board[fromY][7] = EmptySquare;
7506 board[toY][4] = BlackRook;
7507 } else if (fromY == 7 && fromX == 3
7508 && board[fromY][fromX] == BlackKing
7509 && toY == 7 && toX == 1) {
7510 board[fromY][fromX] = EmptySquare;
7511 board[toY][toX] = BlackKing;
7512 board[fromY][0] = EmptySquare;
7513 board[toY][2] = BlackRook;
7514 } else if (board[fromY][fromX] == BlackPawn
7516 && gameInfo.variant != VariantXiangqi
7518 /* black pawn promotion */
7519 board[0][toX] = CharToPiece(ToLower(promoChar));
7520 if (board[0][toX] == EmptySquare) {
7521 board[0][toX] = BlackQueen;
7523 if(gameInfo.variant==VariantBughouse ||
7524 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7525 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7526 board[fromY][fromX] = EmptySquare;
7527 } else if ((fromY == 3)
7529 && gameInfo.variant != VariantXiangqi
7530 && gameInfo.variant != VariantBerolina
7531 && (board[fromY][fromX] == BlackPawn)
7532 && (board[toY][toX] == EmptySquare)) {
7533 board[fromY][fromX] = EmptySquare;
7534 board[toY][toX] = BlackPawn;
7535 captured = board[toY + 1][toX];
7536 board[toY + 1][toX] = EmptySquare;
7537 } else if ((fromY == 3)
7539 && gameInfo.variant == VariantBerolina
7540 && (board[fromY][fromX] == BlackPawn)
7541 && (board[toY][toX] == EmptySquare)) {
7542 board[fromY][fromX] = EmptySquare;
7543 board[toY][toX] = BlackPawn;
7544 if(oldEP & EP_BEROLIN_A) {
7545 captured = board[fromY][fromX-1];
7546 board[fromY][fromX-1] = EmptySquare;
7547 }else{ captured = board[fromY][fromX+1];
7548 board[fromY][fromX+1] = EmptySquare;
7551 board[toY][toX] = board[fromY][fromX];
7552 board[fromY][fromX] = EmptySquare;
7555 /* [HGM] now we promote for Shogi, if needed */
7556 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7557 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7560 if (gameInfo.holdingsWidth != 0) {
7562 /* !!A lot more code needs to be written to support holdings */
7563 /* [HGM] OK, so I have written it. Holdings are stored in the */
7564 /* penultimate board files, so they are automaticlly stored */
7565 /* in the game history. */
7566 if (fromY == DROP_RANK) {
7567 /* Delete from holdings, by decreasing count */
7568 /* and erasing image if necessary */
7570 if(p < (int) BlackPawn) { /* white drop */
7571 p -= (int)WhitePawn;
7572 p = PieceToNumber((ChessSquare)p);
7573 if(p >= gameInfo.holdingsSize) p = 0;
7574 if(--board[p][BOARD_WIDTH-2] <= 0)
7575 board[p][BOARD_WIDTH-1] = EmptySquare;
7576 if((int)board[p][BOARD_WIDTH-2] < 0)
7577 board[p][BOARD_WIDTH-2] = 0;
7578 } else { /* black drop */
7579 p -= (int)BlackPawn;
7580 p = PieceToNumber((ChessSquare)p);
7581 if(p >= gameInfo.holdingsSize) p = 0;
7582 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7583 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7584 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7585 board[BOARD_HEIGHT-1-p][1] = 0;
7588 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7589 && gameInfo.variant != VariantBughouse ) {
7590 /* [HGM] holdings: Add to holdings, if holdings exist */
7591 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7592 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7593 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7596 if (p >= (int) BlackPawn) {
7597 p -= (int)BlackPawn;
7598 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7599 /* in Shogi restore piece to its original first */
7600 captured = (ChessSquare) (DEMOTED captured);
7603 p = PieceToNumber((ChessSquare)p);
7604 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7605 board[p][BOARD_WIDTH-2]++;
7606 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7608 p -= (int)WhitePawn;
7609 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7610 captured = (ChessSquare) (DEMOTED captured);
7613 p = PieceToNumber((ChessSquare)p);
7614 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7615 board[BOARD_HEIGHT-1-p][1]++;
7616 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7619 } else if (gameInfo.variant == VariantAtomic) {
7620 if (captured != EmptySquare) {
7622 for (y = toY-1; y <= toY+1; y++) {
7623 for (x = toX-1; x <= toX+1; x++) {
7624 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7625 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7626 board[y][x] = EmptySquare;
7630 board[toY][toX] = EmptySquare;
7633 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7634 /* [HGM] Shogi promotions */
7635 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7638 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7639 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7640 // [HGM] superchess: take promotion piece out of holdings
7641 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7642 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7643 if(!--board[k][BOARD_WIDTH-2])
7644 board[k][BOARD_WIDTH-1] = EmptySquare;
7646 if(!--board[BOARD_HEIGHT-1-k][1])
7647 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7653 /* Updates forwardMostMove */
7655 MakeMove(fromX, fromY, toX, toY, promoChar)
7656 int fromX, fromY, toX, toY;
7659 // forwardMostMove++; // [HGM] bare: moved downstream
7661 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7662 int timeLeft; static int lastLoadFlag=0; int king, piece;
7663 piece = boards[forwardMostMove][fromY][fromX];
7664 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7665 if(gameInfo.variant == VariantKnightmate)
7666 king += (int) WhiteUnicorn - (int) WhiteKing;
7667 if(forwardMostMove == 0) {
7669 fprintf(serverMoves, "%s;", second.tidy);
7670 fprintf(serverMoves, "%s;", first.tidy);
7671 if(!blackPlaysFirst)
7672 fprintf(serverMoves, "%s;", second.tidy);
7673 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7674 lastLoadFlag = loadFlag;
7676 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7677 // print castling suffix
7678 if( toY == fromY && piece == king ) {
7680 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7682 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7685 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7686 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7687 boards[forwardMostMove][toY][toX] == EmptySquare
7689 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7691 if(promoChar != NULLCHAR)
7692 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7694 fprintf(serverMoves, "/%d/%d",
7695 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7696 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7697 else timeLeft = blackTimeRemaining/1000;
7698 fprintf(serverMoves, "/%d", timeLeft);
7700 fflush(serverMoves);
7703 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7704 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7708 if (commentList[forwardMostMove+1] != NULL) {
7709 free(commentList[forwardMostMove+1]);
7710 commentList[forwardMostMove+1] = NULL;
7712 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7713 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7714 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7715 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7716 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7717 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7718 gameInfo.result = GameUnfinished;
7719 if (gameInfo.resultDetails != NULL) {
7720 free(gameInfo.resultDetails);
7721 gameInfo.resultDetails = NULL;
7723 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7724 moveList[forwardMostMove - 1]);
7725 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7726 PosFlags(forwardMostMove - 1),
7727 fromY, fromX, toY, toX, promoChar,
7728 parseList[forwardMostMove - 1]);
7729 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7735 if(gameInfo.variant != VariantShogi)
7736 strcat(parseList[forwardMostMove - 1], "+");
7740 strcat(parseList[forwardMostMove - 1], "#");
7743 if (appData.debugMode) {
7744 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7749 /* Updates currentMove if not pausing */
7751 ShowMove(fromX, fromY, toX, toY)
7753 int instant = (gameMode == PlayFromGameFile) ?
7754 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7755 if(appData.noGUI) return;
7756 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7758 if (forwardMostMove == currentMove + 1) {
7759 AnimateMove(boards[forwardMostMove - 1],
7760 fromX, fromY, toX, toY);
7762 if (appData.highlightLastMove) {
7763 SetHighlights(fromX, fromY, toX, toY);
7766 currentMove = forwardMostMove;
7769 if (instant) return;
7771 DisplayMove(currentMove - 1);
7772 DrawPosition(FALSE, boards[currentMove]);
7773 DisplayBothClocks();
7774 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7777 void SendEgtPath(ChessProgramState *cps)
7778 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7779 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7781 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7784 char c, *q = name+1, *r, *s;
7786 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7787 while(*p && *p != ',') *q++ = *p++;
7789 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7790 strcmp(name, ",nalimov:") == 0 ) {
7791 // take nalimov path from the menu-changeable option first, if it is defined
7792 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7793 SendToProgram(buf,cps); // send egtbpath command for nalimov
7795 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7796 (s = StrStr(appData.egtFormats, name)) != NULL) {
7797 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7798 s = r = StrStr(s, ":") + 1; // beginning of path info
7799 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7800 c = *r; *r = 0; // temporarily null-terminate path info
7801 *--q = 0; // strip of trailig ':' from name
7802 sprintf(buf, "egtpath %s %s\n", name+1, s);
7804 SendToProgram(buf,cps); // send egtbpath command for this format
7806 if(*p == ',') p++; // read away comma to position for next format name
7811 InitChessProgram(cps, setup)
7812 ChessProgramState *cps;
7813 int setup; /* [HGM] needed to setup FRC opening position */
7815 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7816 if (appData.noChessProgram) return;
7817 hintRequested = FALSE;
7818 bookRequested = FALSE;
7820 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7821 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7822 if(cps->memSize) { /* [HGM] memory */
7823 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7824 SendToProgram(buf, cps);
7826 SendEgtPath(cps); /* [HGM] EGT */
7827 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7828 sprintf(buf, "cores %d\n", appData.smpCores);
7829 SendToProgram(buf, cps);
7832 SendToProgram(cps->initString, cps);
7833 if (gameInfo.variant != VariantNormal &&
7834 gameInfo.variant != VariantLoadable
7835 /* [HGM] also send variant if board size non-standard */
7836 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7838 char *v = VariantName(gameInfo.variant);
7839 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7840 /* [HGM] in protocol 1 we have to assume all variants valid */
7841 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7842 DisplayFatalError(buf, 0, 1);
7846 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7847 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7848 if( gameInfo.variant == VariantXiangqi )
7849 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7850 if( gameInfo.variant == VariantShogi )
7851 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7852 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7853 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7854 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7855 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7856 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7857 if( gameInfo.variant == VariantCourier )
7858 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7859 if( gameInfo.variant == VariantSuper )
7860 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7861 if( gameInfo.variant == VariantGreat )
7862 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7865 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7866 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7867 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7868 if(StrStr(cps->variants, b) == NULL) {
7869 // specific sized variant not known, check if general sizing allowed
7870 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7871 if(StrStr(cps->variants, "boardsize") == NULL) {
7872 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7873 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7874 DisplayFatalError(buf, 0, 1);
7877 /* [HGM] here we really should compare with the maximum supported board size */
7880 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7881 sprintf(buf, "variant %s\n", b);
7882 SendToProgram(buf, cps);
7884 currentlyInitializedVariant = gameInfo.variant;
7886 /* [HGM] send opening position in FRC to first engine */
7888 SendToProgram("force\n", cps);
7890 /* engine is now in force mode! Set flag to wake it up after first move. */
7891 setboardSpoiledMachineBlack = 1;
7895 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7896 SendToProgram(buf, cps);
7898 cps->maybeThinking = FALSE;
7899 cps->offeredDraw = 0;
7900 if (!appData.icsActive) {
7901 SendTimeControl(cps, movesPerSession, timeControl,
7902 timeIncrement, appData.searchDepth,
7905 if (appData.showThinking
7906 // [HGM] thinking: four options require thinking output to be sent
7907 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7909 SendToProgram("post\n", cps);
7911 SendToProgram("hard\n", cps);
7912 if (!appData.ponderNextMove) {
7913 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7914 it without being sure what state we are in first. "hard"
7915 is not a toggle, so that one is OK.
7917 SendToProgram("easy\n", cps);
7920 sprintf(buf, "ping %d\n", ++cps->lastPing);
7921 SendToProgram(buf, cps);
7923 cps->initDone = TRUE;
7928 StartChessProgram(cps)
7929 ChessProgramState *cps;
7934 if (appData.noChessProgram) return;
7935 cps->initDone = FALSE;
7937 if (strcmp(cps->host, "localhost") == 0) {
7938 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7939 } else if (*appData.remoteShell == NULLCHAR) {
7940 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7942 if (*appData.remoteUser == NULLCHAR) {
7943 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7946 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7947 cps->host, appData.remoteUser, cps->program);
7949 err = StartChildProcess(buf, "", &cps->pr);
7953 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7954 DisplayFatalError(buf, err, 1);
7960 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7961 if (cps->protocolVersion > 1) {
7962 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7963 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7964 cps->comboCnt = 0; // and values of combo boxes
7965 SendToProgram(buf, cps);
7967 SendToProgram("xboard\n", cps);
7973 TwoMachinesEventIfReady P((void))
7975 if (first.lastPing != first.lastPong) {
7976 DisplayMessage("", _("Waiting for first chess program"));
7977 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7980 if (second.lastPing != second.lastPong) {
7981 DisplayMessage("", _("Waiting for second chess program"));
7982 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7990 NextMatchGame P((void))
7992 int index; /* [HGM] autoinc: step load index during match */
7994 if (*appData.loadGameFile != NULLCHAR) {
7995 index = appData.loadGameIndex;
7996 if(index < 0) { // [HGM] autoinc
7997 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7998 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8000 LoadGameFromFile(appData.loadGameFile,
8002 appData.loadGameFile, FALSE);
8003 } else if (*appData.loadPositionFile != NULLCHAR) {
8004 index = appData.loadPositionIndex;
8005 if(index < 0) { // [HGM] autoinc
8006 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8007 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8009 LoadPositionFromFile(appData.loadPositionFile,
8011 appData.loadPositionFile);
8013 TwoMachinesEventIfReady();
8016 void UserAdjudicationEvent( int result )
8018 ChessMove gameResult = GameIsDrawn;
8021 gameResult = WhiteWins;
8023 else if( result < 0 ) {
8024 gameResult = BlackWins;
8027 if( gameMode == TwoMachinesPlay ) {
8028 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8033 // [HGM] save: calculate checksum of game to make games easily identifiable
8034 int StringCheckSum(char *s)
8037 if(s==NULL) return 0;
8038 while(*s) i = i*259 + *s++;
8045 for(i=backwardMostMove; i<forwardMostMove; i++) {
8046 sum += pvInfoList[i].depth;
8047 sum += StringCheckSum(parseList[i]);
8048 sum += StringCheckSum(commentList[i]);
8051 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8052 return sum + StringCheckSum(commentList[i]);
8053 } // end of save patch
8056 GameEnds(result, resultDetails, whosays)
8058 char *resultDetails;
8061 GameMode nextGameMode;
8065 if(endingGame) return; /* [HGM] crash: forbid recursion */
8068 if (appData.debugMode) {
8069 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8070 result, resultDetails ? resultDetails : "(null)", whosays);
8073 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8074 /* If we are playing on ICS, the server decides when the
8075 game is over, but the engine can offer to draw, claim
8079 if (appData.zippyPlay && first.initDone) {
8080 if (result == GameIsDrawn) {
8081 /* In case draw still needs to be claimed */
8082 SendToICS(ics_prefix);
8083 SendToICS("draw\n");
8084 } else if (StrCaseStr(resultDetails, "resign")) {
8085 SendToICS(ics_prefix);
8086 SendToICS("resign\n");
8090 endingGame = 0; /* [HGM] crash */
8094 /* If we're loading the game from a file, stop */
8095 if (whosays == GE_FILE) {
8096 (void) StopLoadGameTimer();
8100 /* Cancel draw offers */
8101 first.offeredDraw = second.offeredDraw = 0;
8103 /* If this is an ICS game, only ICS can really say it's done;
8104 if not, anyone can. */
8105 isIcsGame = (gameMode == IcsPlayingWhite ||
8106 gameMode == IcsPlayingBlack ||
8107 gameMode == IcsObserving ||
8108 gameMode == IcsExamining);
8110 if (!isIcsGame || whosays == GE_ICS) {
8111 /* OK -- not an ICS game, or ICS said it was done */
8113 if (!isIcsGame && !appData.noChessProgram)
8114 SetUserThinkingEnables();
8116 /* [HGM] if a machine claims the game end we verify this claim */
8117 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8118 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8120 ChessMove trueResult = (ChessMove) -1;
8122 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8123 first.twoMachinesColor[0] :
8124 second.twoMachinesColor[0] ;
8126 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8127 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8128 /* [HGM] verify: engine mate claims accepted if they were flagged */
8129 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8131 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8132 /* [HGM] verify: engine mate claims accepted if they were flagged */
8133 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8135 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8136 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8139 // now verify win claims, but not in drop games, as we don't understand those yet
8140 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8141 || gameInfo.variant == VariantGreat) &&
8142 (result == WhiteWins && claimer == 'w' ||
8143 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8144 if (appData.debugMode) {
8145 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8146 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8148 if(result != trueResult) {
8149 sprintf(buf, "False win claim: '%s'", resultDetails);
8150 result = claimer == 'w' ? BlackWins : WhiteWins;
8151 resultDetails = buf;
8154 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8155 && (forwardMostMove <= backwardMostMove ||
8156 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8157 (claimer=='b')==(forwardMostMove&1))
8159 /* [HGM] verify: draws that were not flagged are false claims */
8160 sprintf(buf, "False draw claim: '%s'", resultDetails);
8161 result = claimer == 'w' ? BlackWins : WhiteWins;
8162 resultDetails = buf;
8164 /* (Claiming a loss is accepted no questions asked!) */
8166 /* [HGM] bare: don't allow bare King to win */
8167 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8168 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8169 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8170 && result != GameIsDrawn)
8171 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8172 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8173 int p = (signed char)boards[forwardMostMove][i][j] - color;
8174 if(p >= 0 && p <= (int)WhiteKing) k++;
8176 if (appData.debugMode) {
8177 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8178 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8181 result = GameIsDrawn;
8182 sprintf(buf, "%s but bare king", resultDetails);
8183 resultDetails = buf;
8189 if(serverMoves != NULL && !loadFlag) { char c = '=';
8190 if(result==WhiteWins) c = '+';
8191 if(result==BlackWins) c = '-';
8192 if(resultDetails != NULL)
8193 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8195 if (resultDetails != NULL) {
8196 gameInfo.result = result;
8197 gameInfo.resultDetails = StrSave(resultDetails);
8199 /* display last move only if game was not loaded from file */
8200 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8201 DisplayMove(currentMove - 1);
8203 if (forwardMostMove != 0) {
8204 if (gameMode != PlayFromGameFile && gameMode != EditGame
8205 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8207 if (*appData.saveGameFile != NULLCHAR) {
8208 SaveGameToFile(appData.saveGameFile, TRUE);
8209 } else if (appData.autoSaveGames) {
8212 if (*appData.savePositionFile != NULLCHAR) {
8213 SavePositionToFile(appData.savePositionFile);
8218 /* Tell program how game ended in case it is learning */
8219 /* [HGM] Moved this to after saving the PGN, just in case */
8220 /* engine died and we got here through time loss. In that */
8221 /* case we will get a fatal error writing the pipe, which */
8222 /* would otherwise lose us the PGN. */
8223 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8224 /* output during GameEnds should never be fatal anymore */
8225 if (gameMode == MachinePlaysWhite ||
8226 gameMode == MachinePlaysBlack ||
8227 gameMode == TwoMachinesPlay ||
8228 gameMode == IcsPlayingWhite ||
8229 gameMode == IcsPlayingBlack ||
8230 gameMode == BeginningOfGame) {
8232 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8234 if (first.pr != NoProc) {
8235 SendToProgram(buf, &first);
8237 if (second.pr != NoProc &&
8238 gameMode == TwoMachinesPlay) {
8239 SendToProgram(buf, &second);
8244 if (appData.icsActive) {
8245 if (appData.quietPlay &&
8246 (gameMode == IcsPlayingWhite ||
8247 gameMode == IcsPlayingBlack)) {
8248 SendToICS(ics_prefix);
8249 SendToICS("set shout 1\n");
8251 nextGameMode = IcsIdle;
8252 ics_user_moved = FALSE;
8253 /* clean up premove. It's ugly when the game has ended and the
8254 * premove highlights are still on the board.
8258 ClearPremoveHighlights();
8259 DrawPosition(FALSE, boards[currentMove]);
8261 if (whosays == GE_ICS) {
8264 if (gameMode == IcsPlayingWhite)
8266 else if(gameMode == IcsPlayingBlack)
8270 if (gameMode == IcsPlayingBlack)
8272 else if(gameMode == IcsPlayingWhite)
8279 PlayIcsUnfinishedSound();
8282 } else if (gameMode == EditGame ||
8283 gameMode == PlayFromGameFile ||
8284 gameMode == AnalyzeMode ||
8285 gameMode == AnalyzeFile) {
8286 nextGameMode = gameMode;
8288 nextGameMode = EndOfGame;
8293 nextGameMode = gameMode;
8296 if (appData.noChessProgram) {
8297 gameMode = nextGameMode;
8299 endingGame = 0; /* [HGM] crash */
8304 /* Put first chess program into idle state */
8305 if (first.pr != NoProc &&
8306 (gameMode == MachinePlaysWhite ||
8307 gameMode == MachinePlaysBlack ||
8308 gameMode == TwoMachinesPlay ||
8309 gameMode == IcsPlayingWhite ||
8310 gameMode == IcsPlayingBlack ||
8311 gameMode == BeginningOfGame)) {
8312 SendToProgram("force\n", &first);
8313 if (first.usePing) {
8315 sprintf(buf, "ping %d\n", ++first.lastPing);
8316 SendToProgram(buf, &first);
8319 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8320 /* Kill off first chess program */
8321 if (first.isr != NULL)
8322 RemoveInputSource(first.isr);
8325 if (first.pr != NoProc) {
8327 DoSleep( appData.delayBeforeQuit );
8328 SendToProgram("quit\n", &first);
8329 DoSleep( appData.delayAfterQuit );
8330 DestroyChildProcess(first.pr, first.useSigterm);
8335 /* Put second chess program into idle state */
8336 if (second.pr != NoProc &&
8337 gameMode == TwoMachinesPlay) {
8338 SendToProgram("force\n", &second);
8339 if (second.usePing) {
8341 sprintf(buf, "ping %d\n", ++second.lastPing);
8342 SendToProgram(buf, &second);
8345 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8346 /* Kill off second chess program */
8347 if (second.isr != NULL)
8348 RemoveInputSource(second.isr);
8351 if (second.pr != NoProc) {
8352 DoSleep( appData.delayBeforeQuit );
8353 SendToProgram("quit\n", &second);
8354 DoSleep( appData.delayAfterQuit );
8355 DestroyChildProcess(second.pr, second.useSigterm);
8360 if (matchMode && gameMode == TwoMachinesPlay) {
8363 if (first.twoMachinesColor[0] == 'w') {
8370 if (first.twoMachinesColor[0] == 'b') {
8379 if (matchGame < appData.matchGames) {
8381 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8382 tmp = first.twoMachinesColor;
8383 first.twoMachinesColor = second.twoMachinesColor;
8384 second.twoMachinesColor = tmp;
8386 gameMode = nextGameMode;
8388 if(appData.matchPause>10000 || appData.matchPause<10)
8389 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8390 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8391 endingGame = 0; /* [HGM] crash */
8395 gameMode = nextGameMode;
8396 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8397 first.tidy, second.tidy,
8398 first.matchWins, second.matchWins,
8399 appData.matchGames - (first.matchWins + second.matchWins));
8400 DisplayFatalError(buf, 0, 0);
8403 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8404 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8406 gameMode = nextGameMode;
8408 endingGame = 0; /* [HGM] crash */
8411 /* Assumes program was just initialized (initString sent).
8412 Leaves program in force mode. */
8414 FeedMovesToProgram(cps, upto)
8415 ChessProgramState *cps;
8420 if (appData.debugMode)
8421 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8422 startedFromSetupPosition ? "position and " : "",
8423 backwardMostMove, upto, cps->which);
8424 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8425 // [HGM] variantswitch: make engine aware of new variant
8426 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8427 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8428 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8429 SendToProgram(buf, cps);
8430 currentlyInitializedVariant = gameInfo.variant;
8432 SendToProgram("force\n", cps);
8433 if (startedFromSetupPosition) {
8434 SendBoard(cps, backwardMostMove);
8435 if (appData.debugMode) {
8436 fprintf(debugFP, "feedMoves\n");
8439 for (i = backwardMostMove; i < upto; i++) {
8440 SendMoveToProgram(i, cps);
8446 ResurrectChessProgram()
8448 /* The chess program may have exited.
8449 If so, restart it and feed it all the moves made so far. */
8451 if (appData.noChessProgram || first.pr != NoProc) return;
8453 StartChessProgram(&first);
8454 InitChessProgram(&first, FALSE);
8455 FeedMovesToProgram(&first, currentMove);
8457 if (!first.sendTime) {
8458 /* can't tell gnuchess what its clock should read,
8459 so we bow to its notion. */
8461 timeRemaining[0][currentMove] = whiteTimeRemaining;
8462 timeRemaining[1][currentMove] = blackTimeRemaining;
8465 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8466 appData.icsEngineAnalyze) && first.analysisSupport) {
8467 SendToProgram("analyze\n", &first);
8468 first.analyzing = TRUE;
8481 if (appData.debugMode) {
8482 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8483 redraw, init, gameMode);
8485 CleanupTail(); // [HGM] vari: delete any stored variations
8486 pausing = pauseExamInvalid = FALSE;
8487 startedFromSetupPosition = blackPlaysFirst = FALSE;
8489 whiteFlag = blackFlag = FALSE;
8490 userOfferedDraw = FALSE;
8491 hintRequested = bookRequested = FALSE;
8492 first.maybeThinking = FALSE;
8493 second.maybeThinking = FALSE;
8494 first.bookSuspend = FALSE; // [HGM] book
8495 second.bookSuspend = FALSE;
8496 thinkOutput[0] = NULLCHAR;
8497 lastHint[0] = NULLCHAR;
8498 ClearGameInfo(&gameInfo);
8499 gameInfo.variant = StringToVariant(appData.variant);
8500 ics_user_moved = ics_clock_paused = FALSE;
8501 ics_getting_history = H_FALSE;
8503 white_holding[0] = black_holding[0] = NULLCHAR;
8504 ClearProgramStats();
8505 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8509 flipView = appData.flipView;
8510 ClearPremoveHighlights();
8512 alarmSounded = FALSE;
8514 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8515 if(appData.serverMovesName != NULL) {
8516 /* [HGM] prepare to make moves file for broadcasting */
8517 clock_t t = clock();
8518 if(serverMoves != NULL) fclose(serverMoves);
8519 serverMoves = fopen(appData.serverMovesName, "r");
8520 if(serverMoves != NULL) {
8521 fclose(serverMoves);
8522 /* delay 15 sec before overwriting, so all clients can see end */
8523 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8525 serverMoves = fopen(appData.serverMovesName, "w");
8529 gameMode = BeginningOfGame;
8531 if(appData.icsActive) gameInfo.variant = VariantNormal;
8532 currentMove = forwardMostMove = backwardMostMove = 0;
8533 InitPosition(redraw);
8534 for (i = 0; i < MAX_MOVES; i++) {
8535 if (commentList[i] != NULL) {
8536 free(commentList[i]);
8537 commentList[i] = NULL;
8541 timeRemaining[0][0] = whiteTimeRemaining;
8542 timeRemaining[1][0] = blackTimeRemaining;
8543 if (first.pr == NULL) {
8544 StartChessProgram(&first);
8547 InitChessProgram(&first, startedFromSetupPosition);
8550 DisplayMessage("", "");
8551 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8552 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8559 if (!AutoPlayOneMove())
8561 if (matchMode || appData.timeDelay == 0)
8563 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8565 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8574 int fromX, fromY, toX, toY;
8576 if (appData.debugMode) {
8577 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8580 if (gameMode != PlayFromGameFile)
8583 if (currentMove >= forwardMostMove) {
8584 gameMode = EditGame;
8587 /* [AS] Clear current move marker at the end of a game */
8588 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8593 toX = moveList[currentMove][2] - AAA;
8594 toY = moveList[currentMove][3] - ONE;
8596 if (moveList[currentMove][1] == '@') {
8597 if (appData.highlightLastMove) {
8598 SetHighlights(-1, -1, toX, toY);
8601 fromX = moveList[currentMove][0] - AAA;
8602 fromY = moveList[currentMove][1] - ONE;
8604 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8606 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8608 if (appData.highlightLastMove) {
8609 SetHighlights(fromX, fromY, toX, toY);
8612 DisplayMove(currentMove);
8613 SendMoveToProgram(currentMove++, &first);
8614 DisplayBothClocks();
8615 DrawPosition(FALSE, boards[currentMove]);
8616 // [HGM] PV info: always display, routine tests if empty
8617 DisplayComment(currentMove - 1, commentList[currentMove]);
8623 LoadGameOneMove(readAhead)
8624 ChessMove readAhead;
8626 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8627 char promoChar = NULLCHAR;
8632 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8633 gameMode != AnalyzeMode && gameMode != Training) {
8638 yyboardindex = forwardMostMove;
8639 if (readAhead != (ChessMove)0) {
8640 moveType = readAhead;
8642 if (gameFileFP == NULL)
8644 moveType = (ChessMove) yylex();
8650 if (appData.debugMode)
8651 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8654 /* append the comment but don't display it */
8655 AppendComment(currentMove, p, FALSE);
8658 case WhiteCapturesEnPassant:
8659 case BlackCapturesEnPassant:
8660 case WhitePromotionChancellor:
8661 case BlackPromotionChancellor:
8662 case WhitePromotionArchbishop:
8663 case BlackPromotionArchbishop:
8664 case WhitePromotionCentaur:
8665 case BlackPromotionCentaur:
8666 case WhitePromotionQueen:
8667 case BlackPromotionQueen:
8668 case WhitePromotionRook:
8669 case BlackPromotionRook:
8670 case WhitePromotionBishop:
8671 case BlackPromotionBishop:
8672 case WhitePromotionKnight:
8673 case BlackPromotionKnight:
8674 case WhitePromotionKing:
8675 case BlackPromotionKing:
8677 case WhiteKingSideCastle:
8678 case WhiteQueenSideCastle:
8679 case BlackKingSideCastle:
8680 case BlackQueenSideCastle:
8681 case WhiteKingSideCastleWild:
8682 case WhiteQueenSideCastleWild:
8683 case BlackKingSideCastleWild:
8684 case BlackQueenSideCastleWild:
8686 case WhiteHSideCastleFR:
8687 case WhiteASideCastleFR:
8688 case BlackHSideCastleFR:
8689 case BlackASideCastleFR:
8691 if (appData.debugMode)
8692 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8693 fromX = currentMoveString[0] - AAA;
8694 fromY = currentMoveString[1] - ONE;
8695 toX = currentMoveString[2] - AAA;
8696 toY = currentMoveString[3] - ONE;
8697 promoChar = currentMoveString[4];
8702 if (appData.debugMode)
8703 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8704 fromX = moveType == WhiteDrop ?
8705 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8706 (int) CharToPiece(ToLower(currentMoveString[0]));
8708 toX = currentMoveString[2] - AAA;
8709 toY = currentMoveString[3] - ONE;
8715 case GameUnfinished:
8716 if (appData.debugMode)
8717 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8718 p = strchr(yy_text, '{');
8719 if (p == NULL) p = strchr(yy_text, '(');
8722 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8724 q = strchr(p, *p == '{' ? '}' : ')');
8725 if (q != NULL) *q = NULLCHAR;
8728 GameEnds(moveType, p, GE_FILE);
8730 if (cmailMsgLoaded) {
8732 flipView = WhiteOnMove(currentMove);
8733 if (moveType == GameUnfinished) flipView = !flipView;
8734 if (appData.debugMode)
8735 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8739 case (ChessMove) 0: /* end of file */
8740 if (appData.debugMode)
8741 fprintf(debugFP, "Parser hit end of file\n");
8742 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8748 if (WhiteOnMove(currentMove)) {
8749 GameEnds(BlackWins, "Black mates", GE_FILE);
8751 GameEnds(WhiteWins, "White mates", GE_FILE);
8755 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8762 if (lastLoadGameStart == GNUChessGame) {
8763 /* GNUChessGames have numbers, but they aren't move numbers */
8764 if (appData.debugMode)
8765 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8766 yy_text, (int) moveType);
8767 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8769 /* else fall thru */
8774 /* Reached start of next game in file */
8775 if (appData.debugMode)
8776 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8777 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8783 if (WhiteOnMove(currentMove)) {
8784 GameEnds(BlackWins, "Black mates", GE_FILE);
8786 GameEnds(WhiteWins, "White mates", GE_FILE);
8790 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8796 case PositionDiagram: /* should not happen; ignore */
8797 case ElapsedTime: /* ignore */
8798 case NAG: /* ignore */
8799 if (appData.debugMode)
8800 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8801 yy_text, (int) moveType);
8802 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8805 if (appData.testLegality) {
8806 if (appData.debugMode)
8807 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8808 sprintf(move, _("Illegal move: %d.%s%s"),
8809 (forwardMostMove / 2) + 1,
8810 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8811 DisplayError(move, 0);
8814 if (appData.debugMode)
8815 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8816 yy_text, currentMoveString);
8817 fromX = currentMoveString[0] - AAA;
8818 fromY = currentMoveString[1] - ONE;
8819 toX = currentMoveString[2] - AAA;
8820 toY = currentMoveString[3] - ONE;
8821 promoChar = currentMoveString[4];
8826 if (appData.debugMode)
8827 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8828 sprintf(move, _("Ambiguous move: %d.%s%s"),
8829 (forwardMostMove / 2) + 1,
8830 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8831 DisplayError(move, 0);
8836 case ImpossibleMove:
8837 if (appData.debugMode)
8838 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8839 sprintf(move, _("Illegal move: %d.%s%s"),
8840 (forwardMostMove / 2) + 1,
8841 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8842 DisplayError(move, 0);
8848 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8849 DrawPosition(FALSE, boards[currentMove]);
8850 DisplayBothClocks();
8851 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8852 DisplayComment(currentMove - 1, commentList[currentMove]);
8854 (void) StopLoadGameTimer();
8856 cmailOldMove = forwardMostMove;
8859 /* currentMoveString is set as a side-effect of yylex */
8860 strcat(currentMoveString, "\n");
8861 strcpy(moveList[forwardMostMove], currentMoveString);
8863 thinkOutput[0] = NULLCHAR;
8864 MakeMove(fromX, fromY, toX, toY, promoChar);
8865 currentMove = forwardMostMove;
8870 /* Load the nth game from the given file */
8872 LoadGameFromFile(filename, n, title, useList)
8876 /*Boolean*/ int useList;
8881 if (strcmp(filename, "-") == 0) {
8885 f = fopen(filename, "rb");
8887 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8888 DisplayError(buf, errno);
8892 if (fseek(f, 0, 0) == -1) {
8893 /* f is not seekable; probably a pipe */
8896 if (useList && n == 0) {
8897 int error = GameListBuild(f);
8899 DisplayError(_("Cannot build game list"), error);
8900 } else if (!ListEmpty(&gameList) &&
8901 ((ListGame *) gameList.tailPred)->number > 1) {
8902 GameListPopUp(f, title);
8909 return LoadGame(f, n, title, FALSE);
8914 MakeRegisteredMove()
8916 int fromX, fromY, toX, toY;
8918 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8919 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8922 if (appData.debugMode)
8923 fprintf(debugFP, "Restoring %s for game %d\n",
8924 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8926 thinkOutput[0] = NULLCHAR;
8927 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8928 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8929 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8930 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8931 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8932 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8933 MakeMove(fromX, fromY, toX, toY, promoChar);
8934 ShowMove(fromX, fromY, toX, toY);
8936 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8943 if (WhiteOnMove(currentMove)) {
8944 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8946 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8951 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8958 if (WhiteOnMove(currentMove)) {
8959 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8961 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8966 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8977 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8979 CmailLoadGame(f, gameNumber, title, useList)
8987 if (gameNumber > nCmailGames) {
8988 DisplayError(_("No more games in this message"), 0);
8991 if (f == lastLoadGameFP) {
8992 int offset = gameNumber - lastLoadGameNumber;
8994 cmailMsg[0] = NULLCHAR;
8995 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8996 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8997 nCmailMovesRegistered--;
8999 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9000 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9001 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9004 if (! RegisterMove()) return FALSE;
9008 retVal = LoadGame(f, gameNumber, title, useList);
9010 /* Make move registered during previous look at this game, if any */
9011 MakeRegisteredMove();
9013 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9014 commentList[currentMove]
9015 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9016 DisplayComment(currentMove - 1, commentList[currentMove]);
9022 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9027 int gameNumber = lastLoadGameNumber + offset;
9028 if (lastLoadGameFP == NULL) {
9029 DisplayError(_("No game has been loaded yet"), 0);
9032 if (gameNumber <= 0) {
9033 DisplayError(_("Can't back up any further"), 0);
9036 if (cmailMsgLoaded) {
9037 return CmailLoadGame(lastLoadGameFP, gameNumber,
9038 lastLoadGameTitle, lastLoadGameUseList);
9040 return LoadGame(lastLoadGameFP, gameNumber,
9041 lastLoadGameTitle, lastLoadGameUseList);
9047 /* Load the nth game from open file f */
9049 LoadGame(f, gameNumber, title, useList)
9057 int gn = gameNumber;
9058 ListGame *lg = NULL;
9061 GameMode oldGameMode;
9062 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9064 if (appData.debugMode)
9065 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9067 if (gameMode == Training )
9068 SetTrainingModeOff();
9070 oldGameMode = gameMode;
9071 if (gameMode != BeginningOfGame) {
9076 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9077 fclose(lastLoadGameFP);
9081 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9084 fseek(f, lg->offset, 0);
9085 GameListHighlight(gameNumber);
9089 DisplayError(_("Game number out of range"), 0);
9094 if (fseek(f, 0, 0) == -1) {
9095 if (f == lastLoadGameFP ?
9096 gameNumber == lastLoadGameNumber + 1 :
9100 DisplayError(_("Can't seek on game file"), 0);
9106 lastLoadGameNumber = gameNumber;
9107 strcpy(lastLoadGameTitle, title);
9108 lastLoadGameUseList = useList;
9112 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9113 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9114 lg->gameInfo.black);
9116 } else if (*title != NULLCHAR) {
9117 if (gameNumber > 1) {
9118 sprintf(buf, "%s %d", title, gameNumber);
9121 DisplayTitle(title);
9125 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9126 gameMode = PlayFromGameFile;
9130 currentMove = forwardMostMove = backwardMostMove = 0;
9131 CopyBoard(boards[0], initialPosition);
9135 * Skip the first gn-1 games in the file.
9136 * Also skip over anything that precedes an identifiable
9137 * start of game marker, to avoid being confused by
9138 * garbage at the start of the file. Currently
9139 * recognized start of game markers are the move number "1",
9140 * the pattern "gnuchess .* game", the pattern
9141 * "^[#;%] [^ ]* game file", and a PGN tag block.
9142 * A game that starts with one of the latter two patterns
9143 * will also have a move number 1, possibly
9144 * following a position diagram.
9145 * 5-4-02: Let's try being more lenient and allowing a game to
9146 * start with an unnumbered move. Does that break anything?
9148 cm = lastLoadGameStart = (ChessMove) 0;
9150 yyboardindex = forwardMostMove;
9151 cm = (ChessMove) yylex();
9154 if (cmailMsgLoaded) {
9155 nCmailGames = CMAIL_MAX_GAMES - gn;
9158 DisplayError(_("Game not found in file"), 0);
9165 lastLoadGameStart = cm;
9169 switch (lastLoadGameStart) {
9176 gn--; /* count this game */
9177 lastLoadGameStart = cm;
9186 switch (lastLoadGameStart) {
9191 gn--; /* count this game */
9192 lastLoadGameStart = cm;
9195 lastLoadGameStart = cm; /* game counted already */
9203 yyboardindex = forwardMostMove;
9204 cm = (ChessMove) yylex();
9205 } while (cm == PGNTag || cm == Comment);
9212 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9213 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9214 != CMAIL_OLD_RESULT) {
9216 cmailResult[ CMAIL_MAX_GAMES
9217 - gn - 1] = CMAIL_OLD_RESULT;
9223 /* Only a NormalMove can be at the start of a game
9224 * without a position diagram. */
9225 if (lastLoadGameStart == (ChessMove) 0) {
9227 lastLoadGameStart = MoveNumberOne;
9236 if (appData.debugMode)
9237 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9239 if (cm == XBoardGame) {
9240 /* Skip any header junk before position diagram and/or move 1 */
9242 yyboardindex = forwardMostMove;
9243 cm = (ChessMove) yylex();
9245 if (cm == (ChessMove) 0 ||
9246 cm == GNUChessGame || cm == XBoardGame) {
9247 /* Empty game; pretend end-of-file and handle later */
9252 if (cm == MoveNumberOne || cm == PositionDiagram ||
9253 cm == PGNTag || cm == Comment)
9256 } else if (cm == GNUChessGame) {
9257 if (gameInfo.event != NULL) {
9258 free(gameInfo.event);
9260 gameInfo.event = StrSave(yy_text);
9263 startedFromSetupPosition = FALSE;
9264 while (cm == PGNTag) {
9265 if (appData.debugMode)
9266 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9267 err = ParsePGNTag(yy_text, &gameInfo);
9268 if (!err) numPGNTags++;
9270 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9271 if(gameInfo.variant != oldVariant) {
9272 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9274 oldVariant = gameInfo.variant;
9275 if (appData.debugMode)
9276 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9280 if (gameInfo.fen != NULL) {
9281 Board initial_position;
9282 startedFromSetupPosition = TRUE;
9283 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9285 DisplayError(_("Bad FEN position in file"), 0);
9288 CopyBoard(boards[0], initial_position);
9289 if (blackPlaysFirst) {
9290 currentMove = forwardMostMove = backwardMostMove = 1;
9291 CopyBoard(boards[1], initial_position);
9292 strcpy(moveList[0], "");
9293 strcpy(parseList[0], "");
9294 timeRemaining[0][1] = whiteTimeRemaining;
9295 timeRemaining[1][1] = blackTimeRemaining;
9296 if (commentList[0] != NULL) {
9297 commentList[1] = commentList[0];
9298 commentList[0] = NULL;
9301 currentMove = forwardMostMove = backwardMostMove = 0;
9303 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9305 initialRulePlies = FENrulePlies;
9306 for( i=0; i< nrCastlingRights; i++ )
9307 initialRights[i] = initial_position[CASTLING][i];
9309 yyboardindex = forwardMostMove;
9311 gameInfo.fen = NULL;
9314 yyboardindex = forwardMostMove;
9315 cm = (ChessMove) yylex();
9317 /* Handle comments interspersed among the tags */
9318 while (cm == Comment) {
9320 if (appData.debugMode)
9321 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9323 AppendComment(currentMove, p, FALSE);
9324 yyboardindex = forwardMostMove;
9325 cm = (ChessMove) yylex();
9329 /* don't rely on existence of Event tag since if game was
9330 * pasted from clipboard the Event tag may not exist
9332 if (numPGNTags > 0){
9334 if (gameInfo.variant == VariantNormal) {
9335 gameInfo.variant = StringToVariant(gameInfo.event);
9338 if( appData.autoDisplayTags ) {
9339 tags = PGNTags(&gameInfo);
9340 TagsPopUp(tags, CmailMsg());
9345 /* Make something up, but don't display it now */
9350 if (cm == PositionDiagram) {
9353 Board initial_position;
9355 if (appData.debugMode)
9356 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9358 if (!startedFromSetupPosition) {
9360 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9361 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9371 initial_position[i][j++] = CharToPiece(*p);
9374 while (*p == ' ' || *p == '\t' ||
9375 *p == '\n' || *p == '\r') p++;
9377 if (strncmp(p, "black", strlen("black"))==0)
9378 blackPlaysFirst = TRUE;
9380 blackPlaysFirst = FALSE;
9381 startedFromSetupPosition = TRUE;
9383 CopyBoard(boards[0], initial_position);
9384 if (blackPlaysFirst) {
9385 currentMove = forwardMostMove = backwardMostMove = 1;
9386 CopyBoard(boards[1], initial_position);
9387 strcpy(moveList[0], "");
9388 strcpy(parseList[0], "");
9389 timeRemaining[0][1] = whiteTimeRemaining;
9390 timeRemaining[1][1] = blackTimeRemaining;
9391 if (commentList[0] != NULL) {
9392 commentList[1] = commentList[0];
9393 commentList[0] = NULL;
9396 currentMove = forwardMostMove = backwardMostMove = 0;
9399 yyboardindex = forwardMostMove;
9400 cm = (ChessMove) yylex();
9403 if (first.pr == NoProc) {
9404 StartChessProgram(&first);
9406 InitChessProgram(&first, FALSE);
9407 SendToProgram("force\n", &first);
9408 if (startedFromSetupPosition) {
9409 SendBoard(&first, forwardMostMove);
9410 if (appData.debugMode) {
9411 fprintf(debugFP, "Load Game\n");
9413 DisplayBothClocks();
9416 /* [HGM] server: flag to write setup moves in broadcast file as one */
9417 loadFlag = appData.suppressLoadMoves;
9419 while (cm == Comment) {
9421 if (appData.debugMode)
9422 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9424 AppendComment(currentMove, p, FALSE);
9425 yyboardindex = forwardMostMove;
9426 cm = (ChessMove) yylex();
9429 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9430 cm == WhiteWins || cm == BlackWins ||
9431 cm == GameIsDrawn || cm == GameUnfinished) {
9432 DisplayMessage("", _("No moves in game"));
9433 if (cmailMsgLoaded) {
9434 if (appData.debugMode)
9435 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9439 DrawPosition(FALSE, boards[currentMove]);
9440 DisplayBothClocks();
9441 gameMode = EditGame;
9448 // [HGM] PV info: routine tests if comment empty
9449 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9450 DisplayComment(currentMove - 1, commentList[currentMove]);
9452 if (!matchMode && appData.timeDelay != 0)
9453 DrawPosition(FALSE, boards[currentMove]);
9455 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9456 programStats.ok_to_send = 1;
9459 /* if the first token after the PGN tags is a move
9460 * and not move number 1, retrieve it from the parser
9462 if (cm != MoveNumberOne)
9463 LoadGameOneMove(cm);
9465 /* load the remaining moves from the file */
9466 while (LoadGameOneMove((ChessMove)0)) {
9467 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9468 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9471 /* rewind to the start of the game */
9472 currentMove = backwardMostMove;
9474 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9476 if (oldGameMode == AnalyzeFile ||
9477 oldGameMode == AnalyzeMode) {
9481 if (matchMode || appData.timeDelay == 0) {
9483 gameMode = EditGame;
9485 } else if (appData.timeDelay > 0) {
9489 if (appData.debugMode)
9490 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9492 loadFlag = 0; /* [HGM] true game starts */
9496 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9498 ReloadPosition(offset)
9501 int positionNumber = lastLoadPositionNumber + offset;
9502 if (lastLoadPositionFP == NULL) {
9503 DisplayError(_("No position has been loaded yet"), 0);
9506 if (positionNumber <= 0) {
9507 DisplayError(_("Can't back up any further"), 0);
9510 return LoadPosition(lastLoadPositionFP, positionNumber,
9511 lastLoadPositionTitle);
9514 /* Load the nth position from the given file */
9516 LoadPositionFromFile(filename, n, title)
9524 if (strcmp(filename, "-") == 0) {
9525 return LoadPosition(stdin, n, "stdin");
9527 f = fopen(filename, "rb");
9529 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9530 DisplayError(buf, errno);
9533 return LoadPosition(f, n, title);
9538 /* Load the nth position from the given open file, and close it */
9540 LoadPosition(f, positionNumber, title)
9545 char *p, line[MSG_SIZ];
9546 Board initial_position;
9547 int i, j, fenMode, pn;
9549 if (gameMode == Training )
9550 SetTrainingModeOff();
9552 if (gameMode != BeginningOfGame) {
9555 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9556 fclose(lastLoadPositionFP);
9558 if (positionNumber == 0) positionNumber = 1;
9559 lastLoadPositionFP = f;
9560 lastLoadPositionNumber = positionNumber;
9561 strcpy(lastLoadPositionTitle, title);
9562 if (first.pr == NoProc) {
9563 StartChessProgram(&first);
9564 InitChessProgram(&first, FALSE);
9566 pn = positionNumber;
9567 if (positionNumber < 0) {
9568 /* Negative position number means to seek to that byte offset */
9569 if (fseek(f, -positionNumber, 0) == -1) {
9570 DisplayError(_("Can't seek on position file"), 0);
9575 if (fseek(f, 0, 0) == -1) {
9576 if (f == lastLoadPositionFP ?
9577 positionNumber == lastLoadPositionNumber + 1 :
9578 positionNumber == 1) {
9581 DisplayError(_("Can't seek on position file"), 0);
9586 /* See if this file is FEN or old-style xboard */
9587 if (fgets(line, MSG_SIZ, f) == NULL) {
9588 DisplayError(_("Position not found in file"), 0);
9591 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9592 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9595 if (fenMode || line[0] == '#') pn--;
9597 /* skip positions before number pn */
9598 if (fgets(line, MSG_SIZ, f) == NULL) {
9600 DisplayError(_("Position not found in file"), 0);
9603 if (fenMode || line[0] == '#') pn--;
9608 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9609 DisplayError(_("Bad FEN position in file"), 0);
9613 (void) fgets(line, MSG_SIZ, f);
9614 (void) fgets(line, MSG_SIZ, f);
9616 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9617 (void) fgets(line, MSG_SIZ, f);
9618 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9621 initial_position[i][j++] = CharToPiece(*p);
9625 blackPlaysFirst = FALSE;
9627 (void) fgets(line, MSG_SIZ, f);
9628 if (strncmp(line, "black", strlen("black"))==0)
9629 blackPlaysFirst = TRUE;
9632 startedFromSetupPosition = TRUE;
9634 SendToProgram("force\n", &first);
9635 CopyBoard(boards[0], initial_position);
9636 if (blackPlaysFirst) {
9637 currentMove = forwardMostMove = backwardMostMove = 1;
9638 strcpy(moveList[0], "");
9639 strcpy(parseList[0], "");
9640 CopyBoard(boards[1], initial_position);
9641 DisplayMessage("", _("Black to play"));
9643 currentMove = forwardMostMove = backwardMostMove = 0;
9644 DisplayMessage("", _("White to play"));
9646 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9647 SendBoard(&first, forwardMostMove);
9648 if (appData.debugMode) {
9650 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9651 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9652 fprintf(debugFP, "Load Position\n");
9655 if (positionNumber > 1) {
9656 sprintf(line, "%s %d", title, positionNumber);
9659 DisplayTitle(title);
9661 gameMode = EditGame;
9664 timeRemaining[0][1] = whiteTimeRemaining;
9665 timeRemaining[1][1] = blackTimeRemaining;
9666 DrawPosition(FALSE, boards[currentMove]);
9673 CopyPlayerNameIntoFileName(dest, src)
9676 while (*src != NULLCHAR && *src != ',') {
9681 *(*dest)++ = *src++;
9686 char *DefaultFileName(ext)
9689 static char def[MSG_SIZ];
9692 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9694 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9696 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9705 /* Save the current game to the given file */
9707 SaveGameToFile(filename, append)
9714 if (strcmp(filename, "-") == 0) {
9715 return SaveGame(stdout, 0, NULL);
9717 f = fopen(filename, append ? "a" : "w");
9719 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9720 DisplayError(buf, errno);
9723 return SaveGame(f, 0, NULL);
9732 static char buf[MSG_SIZ];
9735 p = strchr(str, ' ');
9736 if (p == NULL) return str;
9737 strncpy(buf, str, p - str);
9738 buf[p - str] = NULLCHAR;
9742 #define PGN_MAX_LINE 75
9744 #define PGN_SIDE_WHITE 0
9745 #define PGN_SIDE_BLACK 1
9748 static int FindFirstMoveOutOfBook( int side )
9752 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9753 int index = backwardMostMove;
9754 int has_book_hit = 0;
9756 if( (index % 2) != side ) {
9760 while( index < forwardMostMove ) {
9761 /* Check to see if engine is in book */
9762 int depth = pvInfoList[index].depth;
9763 int score = pvInfoList[index].score;
9769 else if( score == 0 && depth == 63 ) {
9770 in_book = 1; /* Zappa */
9772 else if( score == 2 && depth == 99 ) {
9773 in_book = 1; /* Abrok */
9776 has_book_hit += in_book;
9792 void GetOutOfBookInfo( char * buf )
9796 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9798 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9799 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9803 if( oob[0] >= 0 || oob[1] >= 0 ) {
9804 for( i=0; i<2; i++ ) {
9808 if( i > 0 && oob[0] >= 0 ) {
9812 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9813 sprintf( buf+strlen(buf), "%s%.2f",
9814 pvInfoList[idx].score >= 0 ? "+" : "",
9815 pvInfoList[idx].score / 100.0 );
9821 /* Save game in PGN style and close the file */
9826 int i, offset, linelen, newblock;
9830 int movelen, numlen, blank;
9831 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9833 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9835 tm = time((time_t *) NULL);
9837 PrintPGNTags(f, &gameInfo);
9839 if (backwardMostMove > 0 || startedFromSetupPosition) {
9840 char *fen = PositionToFEN(backwardMostMove, NULL);
9841 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9842 fprintf(f, "\n{--------------\n");
9843 PrintPosition(f, backwardMostMove);
9844 fprintf(f, "--------------}\n");
9848 /* [AS] Out of book annotation */
9849 if( appData.saveOutOfBookInfo ) {
9852 GetOutOfBookInfo( buf );
9854 if( buf[0] != '\0' ) {
9855 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9862 i = backwardMostMove;
9866 while (i < forwardMostMove) {
9867 /* Print comments preceding this move */
9868 if (commentList[i] != NULL) {
9869 if (linelen > 0) fprintf(f, "\n");
9870 fprintf(f, "%s", commentList[i]);
9875 /* Format move number */
9877 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9880 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9882 numtext[0] = NULLCHAR;
9885 numlen = strlen(numtext);
9888 /* Print move number */
9889 blank = linelen > 0 && numlen > 0;
9890 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9899 fprintf(f, "%s", numtext);
9903 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9904 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9907 blank = linelen > 0 && movelen > 0;
9908 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9917 fprintf(f, "%s", move_buffer);
9920 /* [AS] Add PV info if present */
9921 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9922 /* [HGM] add time */
9923 char buf[MSG_SIZ]; int seconds;
9925 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9927 if( seconds <= 0) buf[0] = 0; else
9928 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9929 seconds = (seconds + 4)/10; // round to full seconds
9930 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9931 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9934 sprintf( move_buffer, "{%s%.2f/%d%s}",
9935 pvInfoList[i].score >= 0 ? "+" : "",
9936 pvInfoList[i].score / 100.0,
9937 pvInfoList[i].depth,
9940 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9942 /* Print score/depth */
9943 blank = linelen > 0 && movelen > 0;
9944 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9953 fprintf(f, "%s", move_buffer);
9960 /* Start a new line */
9961 if (linelen > 0) fprintf(f, "\n");
9963 /* Print comments after last move */
9964 if (commentList[i] != NULL) {
9965 fprintf(f, "%s\n", commentList[i]);
9969 if (gameInfo.resultDetails != NULL &&
9970 gameInfo.resultDetails[0] != NULLCHAR) {
9971 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9972 PGNResult(gameInfo.result));
9974 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9978 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9982 /* Save game in old style and close the file */
9990 tm = time((time_t *) NULL);
9992 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9995 if (backwardMostMove > 0 || startedFromSetupPosition) {
9996 fprintf(f, "\n[--------------\n");
9997 PrintPosition(f, backwardMostMove);
9998 fprintf(f, "--------------]\n");
10003 i = backwardMostMove;
10004 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10006 while (i < forwardMostMove) {
10007 if (commentList[i] != NULL) {
10008 fprintf(f, "[%s]\n", commentList[i]);
10011 if ((i % 2) == 1) {
10012 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10015 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10017 if (commentList[i] != NULL) {
10021 if (i >= forwardMostMove) {
10025 fprintf(f, "%s\n", parseList[i]);
10030 if (commentList[i] != NULL) {
10031 fprintf(f, "[%s]\n", commentList[i]);
10034 /* This isn't really the old style, but it's close enough */
10035 if (gameInfo.resultDetails != NULL &&
10036 gameInfo.resultDetails[0] != NULLCHAR) {
10037 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10038 gameInfo.resultDetails);
10040 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10047 /* Save the current game to open file f and close the file */
10049 SaveGame(f, dummy, dummy2)
10054 if (gameMode == EditPosition) EditPositionDone(TRUE);
10055 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10056 if (appData.oldSaveStyle)
10057 return SaveGameOldStyle(f);
10059 return SaveGamePGN(f);
10062 /* Save the current position to the given file */
10064 SavePositionToFile(filename)
10070 if (strcmp(filename, "-") == 0) {
10071 return SavePosition(stdout, 0, NULL);
10073 f = fopen(filename, "a");
10075 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10076 DisplayError(buf, errno);
10079 SavePosition(f, 0, NULL);
10085 /* Save the current position to the given open file and close the file */
10087 SavePosition(f, dummy, dummy2)
10095 if (gameMode == EditPosition) EditPositionDone(TRUE);
10096 if (appData.oldSaveStyle) {
10097 tm = time((time_t *) NULL);
10099 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10101 fprintf(f, "[--------------\n");
10102 PrintPosition(f, currentMove);
10103 fprintf(f, "--------------]\n");
10105 fen = PositionToFEN(currentMove, NULL);
10106 fprintf(f, "%s\n", fen);
10114 ReloadCmailMsgEvent(unregister)
10118 static char *inFilename = NULL;
10119 static char *outFilename;
10121 struct stat inbuf, outbuf;
10124 /* Any registered moves are unregistered if unregister is set, */
10125 /* i.e. invoked by the signal handler */
10127 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10128 cmailMoveRegistered[i] = FALSE;
10129 if (cmailCommentList[i] != NULL) {
10130 free(cmailCommentList[i]);
10131 cmailCommentList[i] = NULL;
10134 nCmailMovesRegistered = 0;
10137 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10138 cmailResult[i] = CMAIL_NOT_RESULT;
10142 if (inFilename == NULL) {
10143 /* Because the filenames are static they only get malloced once */
10144 /* and they never get freed */
10145 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10146 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10148 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10149 sprintf(outFilename, "%s.out", appData.cmailGameName);
10152 status = stat(outFilename, &outbuf);
10154 cmailMailedMove = FALSE;
10156 status = stat(inFilename, &inbuf);
10157 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10160 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10161 counts the games, notes how each one terminated, etc.
10163 It would be nice to remove this kludge and instead gather all
10164 the information while building the game list. (And to keep it
10165 in the game list nodes instead of having a bunch of fixed-size
10166 parallel arrays.) Note this will require getting each game's
10167 termination from the PGN tags, as the game list builder does
10168 not process the game moves. --mann
10170 cmailMsgLoaded = TRUE;
10171 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10173 /* Load first game in the file or popup game menu */
10174 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10176 #endif /* !WIN32 */
10184 char string[MSG_SIZ];
10186 if ( cmailMailedMove
10187 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10188 return TRUE; /* Allow free viewing */
10191 /* Unregister move to ensure that we don't leave RegisterMove */
10192 /* with the move registered when the conditions for registering no */
10194 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10195 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10196 nCmailMovesRegistered --;
10198 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10200 free(cmailCommentList[lastLoadGameNumber - 1]);
10201 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10205 if (cmailOldMove == -1) {
10206 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10210 if (currentMove > cmailOldMove + 1) {
10211 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10215 if (currentMove < cmailOldMove) {
10216 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10220 if (forwardMostMove > currentMove) {
10221 /* Silently truncate extra moves */
10225 if ( (currentMove == cmailOldMove + 1)
10226 || ( (currentMove == cmailOldMove)
10227 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10228 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10229 if (gameInfo.result != GameUnfinished) {
10230 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10233 if (commentList[currentMove] != NULL) {
10234 cmailCommentList[lastLoadGameNumber - 1]
10235 = StrSave(commentList[currentMove]);
10237 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10239 if (appData.debugMode)
10240 fprintf(debugFP, "Saving %s for game %d\n",
10241 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10244 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10246 f = fopen(string, "w");
10247 if (appData.oldSaveStyle) {
10248 SaveGameOldStyle(f); /* also closes the file */
10250 sprintf(string, "%s.pos.out", appData.cmailGameName);
10251 f = fopen(string, "w");
10252 SavePosition(f, 0, NULL); /* also closes the file */
10254 fprintf(f, "{--------------\n");
10255 PrintPosition(f, currentMove);
10256 fprintf(f, "--------------}\n\n");
10258 SaveGame(f, 0, NULL); /* also closes the file*/
10261 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10262 nCmailMovesRegistered ++;
10263 } else if (nCmailGames == 1) {
10264 DisplayError(_("You have not made a move yet"), 0);
10275 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10276 FILE *commandOutput;
10277 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10278 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10284 if (! cmailMsgLoaded) {
10285 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10289 if (nCmailGames == nCmailResults) {
10290 DisplayError(_("No unfinished games"), 0);
10294 #if CMAIL_PROHIBIT_REMAIL
10295 if (cmailMailedMove) {
10296 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);
10297 DisplayError(msg, 0);
10302 if (! (cmailMailedMove || RegisterMove())) return;
10304 if ( cmailMailedMove
10305 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10306 sprintf(string, partCommandString,
10307 appData.debugMode ? " -v" : "", appData.cmailGameName);
10308 commandOutput = popen(string, "r");
10310 if (commandOutput == NULL) {
10311 DisplayError(_("Failed to invoke cmail"), 0);
10313 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10314 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10316 if (nBuffers > 1) {
10317 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10318 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10319 nBytes = MSG_SIZ - 1;
10321 (void) memcpy(msg, buffer, nBytes);
10323 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10325 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10326 cmailMailedMove = TRUE; /* Prevent >1 moves */
10329 for (i = 0; i < nCmailGames; i ++) {
10330 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10335 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10337 sprintf(buffer, "%s/%s.%s.archive",
10339 appData.cmailGameName,
10341 LoadGameFromFile(buffer, 1, buffer, FALSE);
10342 cmailMsgLoaded = FALSE;
10346 DisplayInformation(msg);
10347 pclose(commandOutput);
10350 if ((*cmailMsg) != '\0') {
10351 DisplayInformation(cmailMsg);
10356 #endif /* !WIN32 */
10365 int prependComma = 0;
10367 char string[MSG_SIZ]; /* Space for game-list */
10370 if (!cmailMsgLoaded) return "";
10372 if (cmailMailedMove) {
10373 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10375 /* Create a list of games left */
10376 sprintf(string, "[");
10377 for (i = 0; i < nCmailGames; i ++) {
10378 if (! ( cmailMoveRegistered[i]
10379 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10380 if (prependComma) {
10381 sprintf(number, ",%d", i + 1);
10383 sprintf(number, "%d", i + 1);
10387 strcat(string, number);
10390 strcat(string, "]");
10392 if (nCmailMovesRegistered + nCmailResults == 0) {
10393 switch (nCmailGames) {
10396 _("Still need to make move for game\n"));
10401 _("Still need to make moves for both games\n"));
10406 _("Still need to make moves for all %d games\n"),
10411 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10414 _("Still need to make a move for game %s\n"),
10419 if (nCmailResults == nCmailGames) {
10420 sprintf(cmailMsg, _("No unfinished games\n"));
10422 sprintf(cmailMsg, _("Ready to send mail\n"));
10428 _("Still need to make moves for games %s\n"),
10440 if (gameMode == Training)
10441 SetTrainingModeOff();
10444 cmailMsgLoaded = FALSE;
10445 if (appData.icsActive) {
10446 SendToICS(ics_prefix);
10447 SendToICS("refresh\n");
10457 /* Give up on clean exit */
10461 /* Keep trying for clean exit */
10465 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10467 if (telnetISR != NULL) {
10468 RemoveInputSource(telnetISR);
10470 if (icsPR != NoProc) {
10471 DestroyChildProcess(icsPR, TRUE);
10474 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10475 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10477 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10478 /* make sure this other one finishes before killing it! */
10479 if(endingGame) { int count = 0;
10480 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10481 while(endingGame && count++ < 10) DoSleep(1);
10482 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10485 /* Kill off chess programs */
10486 if (first.pr != NoProc) {
10489 DoSleep( appData.delayBeforeQuit );
10490 SendToProgram("quit\n", &first);
10491 DoSleep( appData.delayAfterQuit );
10492 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10494 if (second.pr != NoProc) {
10495 DoSleep( appData.delayBeforeQuit );
10496 SendToProgram("quit\n", &second);
10497 DoSleep( appData.delayAfterQuit );
10498 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10500 if (first.isr != NULL) {
10501 RemoveInputSource(first.isr);
10503 if (second.isr != NULL) {
10504 RemoveInputSource(second.isr);
10507 ShutDownFrontEnd();
10514 if (appData.debugMode)
10515 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10519 if (gameMode == MachinePlaysWhite ||
10520 gameMode == MachinePlaysBlack) {
10523 DisplayBothClocks();
10525 if (gameMode == PlayFromGameFile) {
10526 if (appData.timeDelay >= 0)
10527 AutoPlayGameLoop();
10528 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10529 Reset(FALSE, TRUE);
10530 SendToICS(ics_prefix);
10531 SendToICS("refresh\n");
10532 } else if (currentMove < forwardMostMove) {
10533 ForwardInner(forwardMostMove);
10535 pauseExamInvalid = FALSE;
10537 switch (gameMode) {
10541 pauseExamForwardMostMove = forwardMostMove;
10542 pauseExamInvalid = FALSE;
10545 case IcsPlayingWhite:
10546 case IcsPlayingBlack:
10550 case PlayFromGameFile:
10551 (void) StopLoadGameTimer();
10555 case BeginningOfGame:
10556 if (appData.icsActive) return;
10557 /* else fall through */
10558 case MachinePlaysWhite:
10559 case MachinePlaysBlack:
10560 case TwoMachinesPlay:
10561 if (forwardMostMove == 0)
10562 return; /* don't pause if no one has moved */
10563 if ((gameMode == MachinePlaysWhite &&
10564 !WhiteOnMove(forwardMostMove)) ||
10565 (gameMode == MachinePlaysBlack &&
10566 WhiteOnMove(forwardMostMove))) {
10579 char title[MSG_SIZ];
10581 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10582 strcpy(title, _("Edit comment"));
10584 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10585 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10586 parseList[currentMove - 1]);
10589 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10596 char *tags = PGNTags(&gameInfo);
10597 EditTagsPopUp(tags);
10604 if (appData.noChessProgram || gameMode == AnalyzeMode)
10607 if (gameMode != AnalyzeFile) {
10608 if (!appData.icsEngineAnalyze) {
10610 if (gameMode != EditGame) return;
10612 ResurrectChessProgram();
10613 SendToProgram("analyze\n", &first);
10614 first.analyzing = TRUE;
10615 /*first.maybeThinking = TRUE;*/
10616 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10617 EngineOutputPopUp();
10619 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10624 StartAnalysisClock();
10625 GetTimeMark(&lastNodeCountTime);
10632 if (appData.noChessProgram || gameMode == AnalyzeFile)
10635 if (gameMode != AnalyzeMode) {
10637 if (gameMode != EditGame) return;
10638 ResurrectChessProgram();
10639 SendToProgram("analyze\n", &first);
10640 first.analyzing = TRUE;
10641 /*first.maybeThinking = TRUE;*/
10642 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10643 EngineOutputPopUp();
10645 gameMode = AnalyzeFile;
10650 StartAnalysisClock();
10651 GetTimeMark(&lastNodeCountTime);
10656 MachineWhiteEvent()
10659 char *bookHit = NULL;
10661 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10665 if (gameMode == PlayFromGameFile ||
10666 gameMode == TwoMachinesPlay ||
10667 gameMode == Training ||
10668 gameMode == AnalyzeMode ||
10669 gameMode == EndOfGame)
10672 if (gameMode == EditPosition)
10673 EditPositionDone(TRUE);
10675 if (!WhiteOnMove(currentMove)) {
10676 DisplayError(_("It is not White's turn"), 0);
10680 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10683 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10684 gameMode == AnalyzeFile)
10687 ResurrectChessProgram(); /* in case it isn't running */
10688 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10689 gameMode = MachinePlaysWhite;
10692 gameMode = MachinePlaysWhite;
10696 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10698 if (first.sendName) {
10699 sprintf(buf, "name %s\n", gameInfo.black);
10700 SendToProgram(buf, &first);
10702 if (first.sendTime) {
10703 if (first.useColors) {
10704 SendToProgram("black\n", &first); /*gnu kludge*/
10706 SendTimeRemaining(&first, TRUE);
10708 if (first.useColors) {
10709 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10711 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10712 SetMachineThinkingEnables();
10713 first.maybeThinking = TRUE;
10717 if (appData.autoFlipView && !flipView) {
10718 flipView = !flipView;
10719 DrawPosition(FALSE, NULL);
10720 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10723 if(bookHit) { // [HGM] book: simulate book reply
10724 static char bookMove[MSG_SIZ]; // a bit generous?
10726 programStats.nodes = programStats.depth = programStats.time =
10727 programStats.score = programStats.got_only_move = 0;
10728 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10730 strcpy(bookMove, "move ");
10731 strcat(bookMove, bookHit);
10732 HandleMachineMove(bookMove, &first);
10737 MachineBlackEvent()
10740 char *bookHit = NULL;
10742 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10746 if (gameMode == PlayFromGameFile ||
10747 gameMode == TwoMachinesPlay ||
10748 gameMode == Training ||
10749 gameMode == AnalyzeMode ||
10750 gameMode == EndOfGame)
10753 if (gameMode == EditPosition)
10754 EditPositionDone(TRUE);
10756 if (WhiteOnMove(currentMove)) {
10757 DisplayError(_("It is not Black's turn"), 0);
10761 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10764 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10765 gameMode == AnalyzeFile)
10768 ResurrectChessProgram(); /* in case it isn't running */
10769 gameMode = MachinePlaysBlack;
10773 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10775 if (first.sendName) {
10776 sprintf(buf, "name %s\n", gameInfo.white);
10777 SendToProgram(buf, &first);
10779 if (first.sendTime) {
10780 if (first.useColors) {
10781 SendToProgram("white\n", &first); /*gnu kludge*/
10783 SendTimeRemaining(&first, FALSE);
10785 if (first.useColors) {
10786 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10788 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10789 SetMachineThinkingEnables();
10790 first.maybeThinking = TRUE;
10793 if (appData.autoFlipView && flipView) {
10794 flipView = !flipView;
10795 DrawPosition(FALSE, NULL);
10796 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10798 if(bookHit) { // [HGM] book: simulate book reply
10799 static char bookMove[MSG_SIZ]; // a bit generous?
10801 programStats.nodes = programStats.depth = programStats.time =
10802 programStats.score = programStats.got_only_move = 0;
10803 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10805 strcpy(bookMove, "move ");
10806 strcat(bookMove, bookHit);
10807 HandleMachineMove(bookMove, &first);
10813 DisplayTwoMachinesTitle()
10816 if (appData.matchGames > 0) {
10817 if (first.twoMachinesColor[0] == 'w') {
10818 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10819 gameInfo.white, gameInfo.black,
10820 first.matchWins, second.matchWins,
10821 matchGame - 1 - (first.matchWins + second.matchWins));
10823 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10824 gameInfo.white, gameInfo.black,
10825 second.matchWins, first.matchWins,
10826 matchGame - 1 - (first.matchWins + second.matchWins));
10829 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10835 TwoMachinesEvent P((void))
10839 ChessProgramState *onmove;
10840 char *bookHit = NULL;
10842 if (appData.noChessProgram) return;
10844 switch (gameMode) {
10845 case TwoMachinesPlay:
10847 case MachinePlaysWhite:
10848 case MachinePlaysBlack:
10849 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10850 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10854 case BeginningOfGame:
10855 case PlayFromGameFile:
10858 if (gameMode != EditGame) return;
10861 EditPositionDone(TRUE);
10872 // forwardMostMove = currentMove;
10873 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10874 ResurrectChessProgram(); /* in case first program isn't running */
10876 if (second.pr == NULL) {
10877 StartChessProgram(&second);
10878 if (second.protocolVersion == 1) {
10879 TwoMachinesEventIfReady();
10881 /* kludge: allow timeout for initial "feature" command */
10883 DisplayMessage("", _("Starting second chess program"));
10884 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10888 DisplayMessage("", "");
10889 InitChessProgram(&second, FALSE);
10890 SendToProgram("force\n", &second);
10891 if (startedFromSetupPosition) {
10892 SendBoard(&second, backwardMostMove);
10893 if (appData.debugMode) {
10894 fprintf(debugFP, "Two Machines\n");
10897 for (i = backwardMostMove; i < forwardMostMove; i++) {
10898 SendMoveToProgram(i, &second);
10901 gameMode = TwoMachinesPlay;
10905 DisplayTwoMachinesTitle();
10907 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10913 SendToProgram(first.computerString, &first);
10914 if (first.sendName) {
10915 sprintf(buf, "name %s\n", second.tidy);
10916 SendToProgram(buf, &first);
10918 SendToProgram(second.computerString, &second);
10919 if (second.sendName) {
10920 sprintf(buf, "name %s\n", first.tidy);
10921 SendToProgram(buf, &second);
10925 if (!first.sendTime || !second.sendTime) {
10926 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10927 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10929 if (onmove->sendTime) {
10930 if (onmove->useColors) {
10931 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10933 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10935 if (onmove->useColors) {
10936 SendToProgram(onmove->twoMachinesColor, onmove);
10938 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10939 // SendToProgram("go\n", onmove);
10940 onmove->maybeThinking = TRUE;
10941 SetMachineThinkingEnables();
10945 if(bookHit) { // [HGM] book: simulate book reply
10946 static char bookMove[MSG_SIZ]; // a bit generous?
10948 programStats.nodes = programStats.depth = programStats.time =
10949 programStats.score = programStats.got_only_move = 0;
10950 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10952 strcpy(bookMove, "move ");
10953 strcat(bookMove, bookHit);
10954 savedMessage = bookMove; // args for deferred call
10955 savedState = onmove;
10956 ScheduleDelayedEvent(DeferredBookMove, 1);
10963 if (gameMode == Training) {
10964 SetTrainingModeOff();
10965 gameMode = PlayFromGameFile;
10966 DisplayMessage("", _("Training mode off"));
10968 gameMode = Training;
10969 animateTraining = appData.animate;
10971 /* make sure we are not already at the end of the game */
10972 if (currentMove < forwardMostMove) {
10973 SetTrainingModeOn();
10974 DisplayMessage("", _("Training mode on"));
10976 gameMode = PlayFromGameFile;
10977 DisplayError(_("Already at end of game"), 0);
10986 if (!appData.icsActive) return;
10987 switch (gameMode) {
10988 case IcsPlayingWhite:
10989 case IcsPlayingBlack:
10992 case BeginningOfGame:
11000 EditPositionDone(TRUE);
11013 gameMode = IcsIdle;
11024 switch (gameMode) {
11026 SetTrainingModeOff();
11028 case MachinePlaysWhite:
11029 case MachinePlaysBlack:
11030 case BeginningOfGame:
11031 SendToProgram("force\n", &first);
11032 SetUserThinkingEnables();
11034 case PlayFromGameFile:
11035 (void) StopLoadGameTimer();
11036 if (gameFileFP != NULL) {
11041 EditPositionDone(TRUE);
11046 SendToProgram("force\n", &first);
11048 case TwoMachinesPlay:
11049 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11050 ResurrectChessProgram();
11051 SetUserThinkingEnables();
11054 ResurrectChessProgram();
11056 case IcsPlayingBlack:
11057 case IcsPlayingWhite:
11058 DisplayError(_("Warning: You are still playing a game"), 0);
11061 DisplayError(_("Warning: You are still observing a game"), 0);
11064 DisplayError(_("Warning: You are still examining a game"), 0);
11075 first.offeredDraw = second.offeredDraw = 0;
11077 if (gameMode == PlayFromGameFile) {
11078 whiteTimeRemaining = timeRemaining[0][currentMove];
11079 blackTimeRemaining = timeRemaining[1][currentMove];
11083 if (gameMode == MachinePlaysWhite ||
11084 gameMode == MachinePlaysBlack ||
11085 gameMode == TwoMachinesPlay ||
11086 gameMode == EndOfGame) {
11087 i = forwardMostMove;
11088 while (i > currentMove) {
11089 SendToProgram("undo\n", &first);
11092 whiteTimeRemaining = timeRemaining[0][currentMove];
11093 blackTimeRemaining = timeRemaining[1][currentMove];
11094 DisplayBothClocks();
11095 if (whiteFlag || blackFlag) {
11096 whiteFlag = blackFlag = 0;
11101 gameMode = EditGame;
11108 EditPositionEvent()
11110 if (gameMode == EditPosition) {
11116 if (gameMode != EditGame) return;
11118 gameMode = EditPosition;
11121 if (currentMove > 0)
11122 CopyBoard(boards[0], boards[currentMove]);
11124 blackPlaysFirst = !WhiteOnMove(currentMove);
11126 currentMove = forwardMostMove = backwardMostMove = 0;
11127 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11134 /* [DM] icsEngineAnalyze - possible call from other functions */
11135 if (appData.icsEngineAnalyze) {
11136 appData.icsEngineAnalyze = FALSE;
11138 DisplayMessage("",_("Close ICS engine analyze..."));
11140 if (first.analysisSupport && first.analyzing) {
11141 SendToProgram("exit\n", &first);
11142 first.analyzing = FALSE;
11144 thinkOutput[0] = NULLCHAR;
11148 EditPositionDone(Boolean fakeRights)
11150 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11152 startedFromSetupPosition = TRUE;
11153 InitChessProgram(&first, FALSE);
11154 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11155 boards[0][EP_STATUS] = EP_NONE;
11156 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11157 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11158 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11159 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11160 } else boards[0][CASTLING][2] = NoRights;
11161 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11162 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11163 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11164 } else boards[0][CASTLING][5] = NoRights;
11166 SendToProgram("force\n", &first);
11167 if (blackPlaysFirst) {
11168 strcpy(moveList[0], "");
11169 strcpy(parseList[0], "");
11170 currentMove = forwardMostMove = backwardMostMove = 1;
11171 CopyBoard(boards[1], boards[0]);
11173 currentMove = forwardMostMove = backwardMostMove = 0;
11175 SendBoard(&first, forwardMostMove);
11176 if (appData.debugMode) {
11177 fprintf(debugFP, "EditPosDone\n");
11180 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11181 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11182 gameMode = EditGame;
11184 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11185 ClearHighlights(); /* [AS] */
11188 /* Pause for `ms' milliseconds */
11189 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11199 } while (SubtractTimeMarks(&m2, &m1) < ms);
11202 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11204 SendMultiLineToICS(buf)
11207 char temp[MSG_SIZ+1], *p;
11214 strncpy(temp, buf, len);
11219 if (*p == '\n' || *p == '\r')
11224 strcat(temp, "\n");
11226 SendToPlayer(temp, strlen(temp));
11230 SetWhiteToPlayEvent()
11232 if (gameMode == EditPosition) {
11233 blackPlaysFirst = FALSE;
11234 DisplayBothClocks(); /* works because currentMove is 0 */
11235 } else if (gameMode == IcsExamining) {
11236 SendToICS(ics_prefix);
11237 SendToICS("tomove white\n");
11242 SetBlackToPlayEvent()
11244 if (gameMode == EditPosition) {
11245 blackPlaysFirst = TRUE;
11246 currentMove = 1; /* kludge */
11247 DisplayBothClocks();
11249 } else if (gameMode == IcsExamining) {
11250 SendToICS(ics_prefix);
11251 SendToICS("tomove black\n");
11256 EditPositionMenuEvent(selection, x, y)
11257 ChessSquare selection;
11261 ChessSquare piece = boards[0][y][x];
11263 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11265 switch (selection) {
11267 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11268 SendToICS(ics_prefix);
11269 SendToICS("bsetup clear\n");
11270 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11271 SendToICS(ics_prefix);
11272 SendToICS("clearboard\n");
11274 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11275 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11276 for (y = 0; y < BOARD_HEIGHT; y++) {
11277 if (gameMode == IcsExamining) {
11278 if (boards[currentMove][y][x] != EmptySquare) {
11279 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11284 boards[0][y][x] = p;
11289 if (gameMode == EditPosition) {
11290 DrawPosition(FALSE, boards[0]);
11295 SetWhiteToPlayEvent();
11299 SetBlackToPlayEvent();
11303 if (gameMode == IcsExamining) {
11304 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11307 boards[0][y][x] = EmptySquare;
11308 DrawPosition(FALSE, boards[0]);
11313 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11314 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11315 selection = (ChessSquare) (PROMOTED piece);
11316 } else if(piece == EmptySquare) selection = WhiteSilver;
11317 else selection = (ChessSquare)((int)piece - 1);
11321 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11322 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11323 selection = (ChessSquare) (DEMOTED piece);
11324 } else if(piece == EmptySquare) selection = BlackSilver;
11325 else selection = (ChessSquare)((int)piece + 1);
11330 if(gameInfo.variant == VariantShatranj ||
11331 gameInfo.variant == VariantXiangqi ||
11332 gameInfo.variant == VariantCourier )
11333 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11338 if(gameInfo.variant == VariantXiangqi)
11339 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11340 if(gameInfo.variant == VariantKnightmate)
11341 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11344 if (gameMode == IcsExamining) {
11345 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11346 PieceToChar(selection), AAA + x, ONE + y);
11349 boards[0][y][x] = selection;
11350 DrawPosition(FALSE, boards[0]);
11358 DropMenuEvent(selection, x, y)
11359 ChessSquare selection;
11362 ChessMove moveType;
11364 switch (gameMode) {
11365 case IcsPlayingWhite:
11366 case MachinePlaysBlack:
11367 if (!WhiteOnMove(currentMove)) {
11368 DisplayMoveError(_("It is Black's turn"));
11371 moveType = WhiteDrop;
11373 case IcsPlayingBlack:
11374 case MachinePlaysWhite:
11375 if (WhiteOnMove(currentMove)) {
11376 DisplayMoveError(_("It is White's turn"));
11379 moveType = BlackDrop;
11382 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11388 if (moveType == BlackDrop && selection < BlackPawn) {
11389 selection = (ChessSquare) ((int) selection
11390 + (int) BlackPawn - (int) WhitePawn);
11392 if (boards[currentMove][y][x] != EmptySquare) {
11393 DisplayMoveError(_("That square is occupied"));
11397 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11403 /* Accept a pending offer of any kind from opponent */
11405 if (appData.icsActive) {
11406 SendToICS(ics_prefix);
11407 SendToICS("accept\n");
11408 } else if (cmailMsgLoaded) {
11409 if (currentMove == cmailOldMove &&
11410 commentList[cmailOldMove] != NULL &&
11411 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11412 "Black offers a draw" : "White offers a draw")) {
11414 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11415 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11417 DisplayError(_("There is no pending offer on this move"), 0);
11418 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11421 /* Not used for offers from chess program */
11428 /* Decline a pending offer of any kind from opponent */
11430 if (appData.icsActive) {
11431 SendToICS(ics_prefix);
11432 SendToICS("decline\n");
11433 } else if (cmailMsgLoaded) {
11434 if (currentMove == cmailOldMove &&
11435 commentList[cmailOldMove] != NULL &&
11436 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11437 "Black offers a draw" : "White offers a draw")) {
11439 AppendComment(cmailOldMove, "Draw declined", TRUE);
11440 DisplayComment(cmailOldMove - 1, "Draw declined");
11443 DisplayError(_("There is no pending offer on this move"), 0);
11446 /* Not used for offers from chess program */
11453 /* Issue ICS rematch command */
11454 if (appData.icsActive) {
11455 SendToICS(ics_prefix);
11456 SendToICS("rematch\n");
11463 /* Call your opponent's flag (claim a win on time) */
11464 if (appData.icsActive) {
11465 SendToICS(ics_prefix);
11466 SendToICS("flag\n");
11468 switch (gameMode) {
11471 case MachinePlaysWhite:
11474 GameEnds(GameIsDrawn, "Both players ran out of time",
11477 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11479 DisplayError(_("Your opponent is not out of time"), 0);
11482 case MachinePlaysBlack:
11485 GameEnds(GameIsDrawn, "Both players ran out of time",
11488 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11490 DisplayError(_("Your opponent is not out of time"), 0);
11500 /* Offer draw or accept pending draw offer from opponent */
11502 if (appData.icsActive) {
11503 /* Note: tournament rules require draw offers to be
11504 made after you make your move but before you punch
11505 your clock. Currently ICS doesn't let you do that;
11506 instead, you immediately punch your clock after making
11507 a move, but you can offer a draw at any time. */
11509 SendToICS(ics_prefix);
11510 SendToICS("draw\n");
11511 } else if (cmailMsgLoaded) {
11512 if (currentMove == cmailOldMove &&
11513 commentList[cmailOldMove] != NULL &&
11514 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11515 "Black offers a draw" : "White offers a draw")) {
11516 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11517 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11518 } else if (currentMove == cmailOldMove + 1) {
11519 char *offer = WhiteOnMove(cmailOldMove) ?
11520 "White offers a draw" : "Black offers a draw";
11521 AppendComment(currentMove, offer, TRUE);
11522 DisplayComment(currentMove - 1, offer);
11523 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11525 DisplayError(_("You must make your move before offering a draw"), 0);
11526 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11528 } else if (first.offeredDraw) {
11529 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11531 if (first.sendDrawOffers) {
11532 SendToProgram("draw\n", &first);
11533 userOfferedDraw = TRUE;
11541 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11543 if (appData.icsActive) {
11544 SendToICS(ics_prefix);
11545 SendToICS("adjourn\n");
11547 /* Currently GNU Chess doesn't offer or accept Adjourns */
11555 /* Offer Abort or accept pending Abort offer from opponent */
11557 if (appData.icsActive) {
11558 SendToICS(ics_prefix);
11559 SendToICS("abort\n");
11561 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11568 /* Resign. You can do this even if it's not your turn. */
11570 if (appData.icsActive) {
11571 SendToICS(ics_prefix);
11572 SendToICS("resign\n");
11574 switch (gameMode) {
11575 case MachinePlaysWhite:
11576 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11578 case MachinePlaysBlack:
11579 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11582 if (cmailMsgLoaded) {
11584 if (WhiteOnMove(cmailOldMove)) {
11585 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11587 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11589 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11600 StopObservingEvent()
11602 /* Stop observing current games */
11603 SendToICS(ics_prefix);
11604 SendToICS("unobserve\n");
11608 StopExaminingEvent()
11610 /* Stop observing current game */
11611 SendToICS(ics_prefix);
11612 SendToICS("unexamine\n");
11616 ForwardInner(target)
11621 if (appData.debugMode)
11622 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11623 target, currentMove, forwardMostMove);
11625 if (gameMode == EditPosition)
11628 if (gameMode == PlayFromGameFile && !pausing)
11631 if (gameMode == IcsExamining && pausing)
11632 limit = pauseExamForwardMostMove;
11634 limit = forwardMostMove;
11636 if (target > limit) target = limit;
11638 if (target > 0 && moveList[target - 1][0]) {
11639 int fromX, fromY, toX, toY;
11640 toX = moveList[target - 1][2] - AAA;
11641 toY = moveList[target - 1][3] - ONE;
11642 if (moveList[target - 1][1] == '@') {
11643 if (appData.highlightLastMove) {
11644 SetHighlights(-1, -1, toX, toY);
11647 fromX = moveList[target - 1][0] - AAA;
11648 fromY = moveList[target - 1][1] - ONE;
11649 if (target == currentMove + 1) {
11650 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11652 if (appData.highlightLastMove) {
11653 SetHighlights(fromX, fromY, toX, toY);
11657 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11658 gameMode == Training || gameMode == PlayFromGameFile ||
11659 gameMode == AnalyzeFile) {
11660 while (currentMove < target) {
11661 SendMoveToProgram(currentMove++, &first);
11664 currentMove = target;
11667 if (gameMode == EditGame || gameMode == EndOfGame) {
11668 whiteTimeRemaining = timeRemaining[0][currentMove];
11669 blackTimeRemaining = timeRemaining[1][currentMove];
11671 DisplayBothClocks();
11672 DisplayMove(currentMove - 1);
11673 DrawPosition(FALSE, boards[currentMove]);
11674 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11675 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11676 DisplayComment(currentMove - 1, commentList[currentMove]);
11684 if (gameMode == IcsExamining && !pausing) {
11685 SendToICS(ics_prefix);
11686 SendToICS("forward\n");
11688 ForwardInner(currentMove + 1);
11695 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11696 /* to optimze, we temporarily turn off analysis mode while we feed
11697 * the remaining moves to the engine. Otherwise we get analysis output
11700 if (first.analysisSupport) {
11701 SendToProgram("exit\nforce\n", &first);
11702 first.analyzing = FALSE;
11706 if (gameMode == IcsExamining && !pausing) {
11707 SendToICS(ics_prefix);
11708 SendToICS("forward 999999\n");
11710 ForwardInner(forwardMostMove);
11713 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11714 /* we have fed all the moves, so reactivate analysis mode */
11715 SendToProgram("analyze\n", &first);
11716 first.analyzing = TRUE;
11717 /*first.maybeThinking = TRUE;*/
11718 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11723 BackwardInner(target)
11726 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11728 if (appData.debugMode)
11729 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11730 target, currentMove, forwardMostMove);
11732 if (gameMode == EditPosition) return;
11733 if (currentMove <= backwardMostMove) {
11735 DrawPosition(full_redraw, boards[currentMove]);
11738 if (gameMode == PlayFromGameFile && !pausing)
11741 if (moveList[target][0]) {
11742 int fromX, fromY, toX, toY;
11743 toX = moveList[target][2] - AAA;
11744 toY = moveList[target][3] - ONE;
11745 if (moveList[target][1] == '@') {
11746 if (appData.highlightLastMove) {
11747 SetHighlights(-1, -1, toX, toY);
11750 fromX = moveList[target][0] - AAA;
11751 fromY = moveList[target][1] - ONE;
11752 if (target == currentMove - 1) {
11753 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11755 if (appData.highlightLastMove) {
11756 SetHighlights(fromX, fromY, toX, toY);
11760 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11761 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11762 while (currentMove > target) {
11763 SendToProgram("undo\n", &first);
11767 currentMove = target;
11770 if (gameMode == EditGame || gameMode == EndOfGame) {
11771 whiteTimeRemaining = timeRemaining[0][currentMove];
11772 blackTimeRemaining = timeRemaining[1][currentMove];
11774 DisplayBothClocks();
11775 DisplayMove(currentMove - 1);
11776 DrawPosition(full_redraw, boards[currentMove]);
11777 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11778 // [HGM] PV info: routine tests if comment empty
11779 DisplayComment(currentMove - 1, commentList[currentMove]);
11785 if (gameMode == IcsExamining && !pausing) {
11786 SendToICS(ics_prefix);
11787 SendToICS("backward\n");
11789 BackwardInner(currentMove - 1);
11796 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11797 /* to optimize, we temporarily turn off analysis mode while we undo
11798 * all the moves. Otherwise we get analysis output after each undo.
11800 if (first.analysisSupport) {
11801 SendToProgram("exit\nforce\n", &first);
11802 first.analyzing = FALSE;
11806 if (gameMode == IcsExamining && !pausing) {
11807 SendToICS(ics_prefix);
11808 SendToICS("backward 999999\n");
11810 BackwardInner(backwardMostMove);
11813 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11814 /* we have fed all the moves, so reactivate analysis mode */
11815 SendToProgram("analyze\n", &first);
11816 first.analyzing = TRUE;
11817 /*first.maybeThinking = TRUE;*/
11818 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11825 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11826 if (to >= forwardMostMove) to = forwardMostMove;
11827 if (to <= backwardMostMove) to = backwardMostMove;
11828 if (to < currentMove) {
11838 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11841 if (gameMode != IcsExamining) {
11842 DisplayError(_("You are not examining a game"), 0);
11846 DisplayError(_("You can't revert while pausing"), 0);
11849 SendToICS(ics_prefix);
11850 SendToICS("revert\n");
11856 switch (gameMode) {
11857 case MachinePlaysWhite:
11858 case MachinePlaysBlack:
11859 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11860 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11863 if (forwardMostMove < 2) return;
11864 currentMove = forwardMostMove = forwardMostMove - 2;
11865 whiteTimeRemaining = timeRemaining[0][currentMove];
11866 blackTimeRemaining = timeRemaining[1][currentMove];
11867 DisplayBothClocks();
11868 DisplayMove(currentMove - 1);
11869 ClearHighlights();/*!! could figure this out*/
11870 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11871 SendToProgram("remove\n", &first);
11872 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11875 case BeginningOfGame:
11879 case IcsPlayingWhite:
11880 case IcsPlayingBlack:
11881 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11882 SendToICS(ics_prefix);
11883 SendToICS("takeback 2\n");
11885 SendToICS(ics_prefix);
11886 SendToICS("takeback 1\n");
11895 ChessProgramState *cps;
11897 switch (gameMode) {
11898 case MachinePlaysWhite:
11899 if (!WhiteOnMove(forwardMostMove)) {
11900 DisplayError(_("It is your turn"), 0);
11905 case MachinePlaysBlack:
11906 if (WhiteOnMove(forwardMostMove)) {
11907 DisplayError(_("It is your turn"), 0);
11912 case TwoMachinesPlay:
11913 if (WhiteOnMove(forwardMostMove) ==
11914 (first.twoMachinesColor[0] == 'w')) {
11920 case BeginningOfGame:
11924 SendToProgram("?\n", cps);
11928 TruncateGameEvent()
11931 if (gameMode != EditGame) return;
11938 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11939 if (forwardMostMove > currentMove) {
11940 if (gameInfo.resultDetails != NULL) {
11941 free(gameInfo.resultDetails);
11942 gameInfo.resultDetails = NULL;
11943 gameInfo.result = GameUnfinished;
11945 forwardMostMove = currentMove;
11946 HistorySet(parseList, backwardMostMove, forwardMostMove,
11954 if (appData.noChessProgram) return;
11955 switch (gameMode) {
11956 case MachinePlaysWhite:
11957 if (WhiteOnMove(forwardMostMove)) {
11958 DisplayError(_("Wait until your turn"), 0);
11962 case BeginningOfGame:
11963 case MachinePlaysBlack:
11964 if (!WhiteOnMove(forwardMostMove)) {
11965 DisplayError(_("Wait until your turn"), 0);
11970 DisplayError(_("No hint available"), 0);
11973 SendToProgram("hint\n", &first);
11974 hintRequested = TRUE;
11980 if (appData.noChessProgram) return;
11981 switch (gameMode) {
11982 case MachinePlaysWhite:
11983 if (WhiteOnMove(forwardMostMove)) {
11984 DisplayError(_("Wait until your turn"), 0);
11988 case BeginningOfGame:
11989 case MachinePlaysBlack:
11990 if (!WhiteOnMove(forwardMostMove)) {
11991 DisplayError(_("Wait until your turn"), 0);
11996 EditPositionDone(TRUE);
11998 case TwoMachinesPlay:
12003 SendToProgram("bk\n", &first);
12004 bookOutput[0] = NULLCHAR;
12005 bookRequested = TRUE;
12011 char *tags = PGNTags(&gameInfo);
12012 TagsPopUp(tags, CmailMsg());
12016 /* end button procedures */
12019 PrintPosition(fp, move)
12025 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12026 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12027 char c = PieceToChar(boards[move][i][j]);
12028 fputc(c == 'x' ? '.' : c, fp);
12029 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12032 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12033 fprintf(fp, "white to play\n");
12035 fprintf(fp, "black to play\n");
12042 if (gameInfo.white != NULL) {
12043 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12049 /* Find last component of program's own name, using some heuristics */
12051 TidyProgramName(prog, host, buf)
12052 char *prog, *host, buf[MSG_SIZ];
12055 int local = (strcmp(host, "localhost") == 0);
12056 while (!local && (p = strchr(prog, ';')) != NULL) {
12058 while (*p == ' ') p++;
12061 if (*prog == '"' || *prog == '\'') {
12062 q = strchr(prog + 1, *prog);
12064 q = strchr(prog, ' ');
12066 if (q == NULL) q = prog + strlen(prog);
12068 while (p >= prog && *p != '/' && *p != '\\') p--;
12070 if(p == prog && *p == '"') p++;
12071 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12072 memcpy(buf, p, q - p);
12073 buf[q - p] = NULLCHAR;
12081 TimeControlTagValue()
12084 if (!appData.clockMode) {
12086 } else if (movesPerSession > 0) {
12087 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12088 } else if (timeIncrement == 0) {
12089 sprintf(buf, "%ld", timeControl/1000);
12091 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12093 return StrSave(buf);
12099 /* This routine is used only for certain modes */
12100 VariantClass v = gameInfo.variant;
12101 ChessMove r = GameUnfinished;
12104 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12105 r = gameInfo.result;
12106 p = gameInfo.resultDetails;
12107 gameInfo.resultDetails = NULL;
12109 ClearGameInfo(&gameInfo);
12110 gameInfo.variant = v;
12112 switch (gameMode) {
12113 case MachinePlaysWhite:
12114 gameInfo.event = StrSave( appData.pgnEventHeader );
12115 gameInfo.site = StrSave(HostName());
12116 gameInfo.date = PGNDate();
12117 gameInfo.round = StrSave("-");
12118 gameInfo.white = StrSave(first.tidy);
12119 gameInfo.black = StrSave(UserName());
12120 gameInfo.timeControl = TimeControlTagValue();
12123 case MachinePlaysBlack:
12124 gameInfo.event = StrSave( appData.pgnEventHeader );
12125 gameInfo.site = StrSave(HostName());
12126 gameInfo.date = PGNDate();
12127 gameInfo.round = StrSave("-");
12128 gameInfo.white = StrSave(UserName());
12129 gameInfo.black = StrSave(first.tidy);
12130 gameInfo.timeControl = TimeControlTagValue();
12133 case TwoMachinesPlay:
12134 gameInfo.event = StrSave( appData.pgnEventHeader );
12135 gameInfo.site = StrSave(HostName());
12136 gameInfo.date = PGNDate();
12137 if (matchGame > 0) {
12139 sprintf(buf, "%d", matchGame);
12140 gameInfo.round = StrSave(buf);
12142 gameInfo.round = StrSave("-");
12144 if (first.twoMachinesColor[0] == 'w') {
12145 gameInfo.white = StrSave(first.tidy);
12146 gameInfo.black = StrSave(second.tidy);
12148 gameInfo.white = StrSave(second.tidy);
12149 gameInfo.black = StrSave(first.tidy);
12151 gameInfo.timeControl = TimeControlTagValue();
12155 gameInfo.event = StrSave("Edited game");
12156 gameInfo.site = StrSave(HostName());
12157 gameInfo.date = PGNDate();
12158 gameInfo.round = StrSave("-");
12159 gameInfo.white = StrSave("-");
12160 gameInfo.black = StrSave("-");
12161 gameInfo.result = r;
12162 gameInfo.resultDetails = p;
12166 gameInfo.event = StrSave("Edited position");
12167 gameInfo.site = StrSave(HostName());
12168 gameInfo.date = PGNDate();
12169 gameInfo.round = StrSave("-");
12170 gameInfo.white = StrSave("-");
12171 gameInfo.black = StrSave("-");
12174 case IcsPlayingWhite:
12175 case IcsPlayingBlack:
12180 case PlayFromGameFile:
12181 gameInfo.event = StrSave("Game from non-PGN file");
12182 gameInfo.site = StrSave(HostName());
12183 gameInfo.date = PGNDate();
12184 gameInfo.round = StrSave("-");
12185 gameInfo.white = StrSave("?");
12186 gameInfo.black = StrSave("?");
12195 ReplaceComment(index, text)
12201 while (*text == '\n') text++;
12202 len = strlen(text);
12203 while (len > 0 && text[len - 1] == '\n') len--;
12205 if (commentList[index] != NULL)
12206 free(commentList[index]);
12209 commentList[index] = NULL;
12212 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12213 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12214 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12215 commentList[index] = (char *) malloc(len + 2);
12216 strncpy(commentList[index], text, len);
12217 commentList[index][len] = '\n';
12218 commentList[index][len + 1] = NULLCHAR;
12220 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12222 commentList[index] = (char *) malloc(len + 6);
12223 strcpy(commentList[index], "{\n");
12224 strncpy(commentList[index]+2, text, len);
12225 commentList[index][len+2] = NULLCHAR;
12226 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12227 strcat(commentList[index], "\n}\n");
12241 if (ch == '\r') continue;
12243 } while (ch != '\0');
12247 AppendComment(index, text, addBraces)
12250 Boolean addBraces; // [HGM] braces: tells if we should add {}
12255 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12256 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12259 while (*text == '\n') text++;
12260 len = strlen(text);
12261 while (len > 0 && text[len - 1] == '\n') len--;
12263 if (len == 0) return;
12265 if (commentList[index] != NULL) {
12266 old = commentList[index];
12267 oldlen = strlen(old);
12268 while(commentList[index][oldlen-1] == '\n')
12269 commentList[index][--oldlen] = NULLCHAR;
12270 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12271 strcpy(commentList[index], old);
12273 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12274 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12275 if(addBraces) addBraces = FALSE; else { text++; len--; }
12276 while (*text == '\n') { text++; len--; }
12277 commentList[index][--oldlen] = NULLCHAR;
12279 if(addBraces) strcat(commentList[index], "\n{\n");
12280 else strcat(commentList[index], "\n");
12281 strcat(commentList[index], text);
12282 if(addBraces) strcat(commentList[index], "\n}\n");
12283 else strcat(commentList[index], "\n");
12285 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12287 strcpy(commentList[index], "{\n");
12288 else commentList[index][0] = NULLCHAR;
12289 strcat(commentList[index], text);
12290 strcat(commentList[index], "\n");
12291 if(addBraces) strcat(commentList[index], "}\n");
12295 static char * FindStr( char * text, char * sub_text )
12297 char * result = strstr( text, sub_text );
12299 if( result != NULL ) {
12300 result += strlen( sub_text );
12306 /* [AS] Try to extract PV info from PGN comment */
12307 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12308 char *GetInfoFromComment( int index, char * text )
12312 if( text != NULL && index > 0 ) {
12315 int time = -1, sec = 0, deci;
12316 char * s_eval = FindStr( text, "[%eval " );
12317 char * s_emt = FindStr( text, "[%emt " );
12319 if( s_eval != NULL || s_emt != NULL ) {
12323 if( s_eval != NULL ) {
12324 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12328 if( delim != ']' ) {
12333 if( s_emt != NULL ) {
12338 /* We expect something like: [+|-]nnn.nn/dd */
12341 if(*text != '{') return text; // [HGM] braces: must be normal comment
12343 sep = strchr( text, '/' );
12344 if( sep == NULL || sep < (text+4) ) {
12348 time = -1; sec = -1; deci = -1;
12349 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12350 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12351 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12352 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12356 if( score_lo < 0 || score_lo >= 100 ) {
12360 if(sec >= 0) time = 600*time + 10*sec; else
12361 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12363 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12365 /* [HGM] PV time: now locate end of PV info */
12366 while( *++sep >= '0' && *sep <= '9'); // strip depth
12368 while( *++sep >= '0' && *sep <= '9'); // strip time
12370 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12372 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12373 while(*sep == ' ') sep++;
12384 pvInfoList[index-1].depth = depth;
12385 pvInfoList[index-1].score = score;
12386 pvInfoList[index-1].time = 10*time; // centi-sec
12387 if(*sep == '}') *sep = 0; else *--sep = '{';
12393 SendToProgram(message, cps)
12395 ChessProgramState *cps;
12397 int count, outCount, error;
12400 if (cps->pr == NULL) return;
12403 if (appData.debugMode) {
12406 fprintf(debugFP, "%ld >%-6s: %s",
12407 SubtractTimeMarks(&now, &programStartTime),
12408 cps->which, message);
12411 count = strlen(message);
12412 outCount = OutputToProcess(cps->pr, message, count, &error);
12413 if (outCount < count && !exiting
12414 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12415 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12416 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12417 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12418 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12419 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12421 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12423 gameInfo.resultDetails = StrSave(buf);
12425 DisplayFatalError(buf, error, 1);
12430 ReceiveFromProgram(isr, closure, message, count, error)
12431 InputSourceRef isr;
12439 ChessProgramState *cps = (ChessProgramState *)closure;
12441 if (isr != cps->isr) return; /* Killed intentionally */
12445 _("Error: %s chess program (%s) exited unexpectedly"),
12446 cps->which, cps->program);
12447 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12448 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12449 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12450 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12452 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12454 gameInfo.resultDetails = StrSave(buf);
12456 RemoveInputSource(cps->isr);
12457 DisplayFatalError(buf, 0, 1);
12460 _("Error reading from %s chess program (%s)"),
12461 cps->which, cps->program);
12462 RemoveInputSource(cps->isr);
12464 /* [AS] Program is misbehaving badly... kill it */
12465 if( count == -2 ) {
12466 DestroyChildProcess( cps->pr, 9 );
12470 DisplayFatalError(buf, error, 1);
12475 if ((end_str = strchr(message, '\r')) != NULL)
12476 *end_str = NULLCHAR;
12477 if ((end_str = strchr(message, '\n')) != NULL)
12478 *end_str = NULLCHAR;
12480 if (appData.debugMode) {
12481 TimeMark now; int print = 1;
12482 char *quote = ""; char c; int i;
12484 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12485 char start = message[0];
12486 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12487 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12488 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12489 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12490 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12491 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12492 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12493 sscanf(message, "pong %c", &c)!=1 && start != '#')
12494 { quote = "# "; print = (appData.engineComments == 2); }
12495 message[0] = start; // restore original message
12499 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12500 SubtractTimeMarks(&now, &programStartTime), cps->which,
12506 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12507 if (appData.icsEngineAnalyze) {
12508 if (strstr(message, "whisper") != NULL ||
12509 strstr(message, "kibitz") != NULL ||
12510 strstr(message, "tellics") != NULL) return;
12513 HandleMachineMove(message, cps);
12518 SendTimeControl(cps, mps, tc, inc, sd, st)
12519 ChessProgramState *cps;
12520 int mps, inc, sd, st;
12526 if( timeControl_2 > 0 ) {
12527 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12528 tc = timeControl_2;
12531 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12532 inc /= cps->timeOdds;
12533 st /= cps->timeOdds;
12535 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12538 /* Set exact time per move, normally using st command */
12539 if (cps->stKludge) {
12540 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12542 if (seconds == 0) {
12543 sprintf(buf, "level 1 %d\n", st/60);
12545 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12548 sprintf(buf, "st %d\n", st);
12551 /* Set conventional or incremental time control, using level command */
12552 if (seconds == 0) {
12553 /* Note old gnuchess bug -- minutes:seconds used to not work.
12554 Fixed in later versions, but still avoid :seconds
12555 when seconds is 0. */
12556 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12558 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12559 seconds, inc/1000);
12562 SendToProgram(buf, cps);
12564 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12565 /* Orthogonally, limit search to given depth */
12567 if (cps->sdKludge) {
12568 sprintf(buf, "depth\n%d\n", sd);
12570 sprintf(buf, "sd %d\n", sd);
12572 SendToProgram(buf, cps);
12575 if(cps->nps > 0) { /* [HGM] nps */
12576 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12578 sprintf(buf, "nps %d\n", cps->nps);
12579 SendToProgram(buf, cps);
12584 ChessProgramState *WhitePlayer()
12585 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12587 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12588 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12594 SendTimeRemaining(cps, machineWhite)
12595 ChessProgramState *cps;
12596 int /*boolean*/ machineWhite;
12598 char message[MSG_SIZ];
12601 /* Note: this routine must be called when the clocks are stopped
12602 or when they have *just* been set or switched; otherwise
12603 it will be off by the time since the current tick started.
12605 if (machineWhite) {
12606 time = whiteTimeRemaining / 10;
12607 otime = blackTimeRemaining / 10;
12609 time = blackTimeRemaining / 10;
12610 otime = whiteTimeRemaining / 10;
12612 /* [HGM] translate opponent's time by time-odds factor */
12613 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12614 if (appData.debugMode) {
12615 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12618 if (time <= 0) time = 1;
12619 if (otime <= 0) otime = 1;
12621 sprintf(message, "time %ld\n", time);
12622 SendToProgram(message, cps);
12624 sprintf(message, "otim %ld\n", otime);
12625 SendToProgram(message, cps);
12629 BoolFeature(p, name, loc, cps)
12633 ChessProgramState *cps;
12636 int len = strlen(name);
12638 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12640 sscanf(*p, "%d", &val);
12642 while (**p && **p != ' ') (*p)++;
12643 sprintf(buf, "accepted %s\n", name);
12644 SendToProgram(buf, cps);
12651 IntFeature(p, name, loc, cps)
12655 ChessProgramState *cps;
12658 int len = strlen(name);
12659 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12661 sscanf(*p, "%d", loc);
12662 while (**p && **p != ' ') (*p)++;
12663 sprintf(buf, "accepted %s\n", name);
12664 SendToProgram(buf, cps);
12671 StringFeature(p, name, loc, cps)
12675 ChessProgramState *cps;
12678 int len = strlen(name);
12679 if (strncmp((*p), name, len) == 0
12680 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12682 sscanf(*p, "%[^\"]", loc);
12683 while (**p && **p != '\"') (*p)++;
12684 if (**p == '\"') (*p)++;
12685 sprintf(buf, "accepted %s\n", name);
12686 SendToProgram(buf, cps);
12693 ParseOption(Option *opt, ChessProgramState *cps)
12694 // [HGM] options: process the string that defines an engine option, and determine
12695 // name, type, default value, and allowed value range
12697 char *p, *q, buf[MSG_SIZ];
12698 int n, min = (-1)<<31, max = 1<<31, def;
12700 if(p = strstr(opt->name, " -spin ")) {
12701 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12702 if(max < min) max = min; // enforce consistency
12703 if(def < min) def = min;
12704 if(def > max) def = max;
12709 } else if((p = strstr(opt->name, " -slider "))) {
12710 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12711 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12712 if(max < min) max = min; // enforce consistency
12713 if(def < min) def = min;
12714 if(def > max) def = max;
12718 opt->type = Spin; // Slider;
12719 } else if((p = strstr(opt->name, " -string "))) {
12720 opt->textValue = p+9;
12721 opt->type = TextBox;
12722 } else if((p = strstr(opt->name, " -file "))) {
12723 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12724 opt->textValue = p+7;
12725 opt->type = TextBox; // FileName;
12726 } else if((p = strstr(opt->name, " -path "))) {
12727 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12728 opt->textValue = p+7;
12729 opt->type = TextBox; // PathName;
12730 } else if(p = strstr(opt->name, " -check ")) {
12731 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12732 opt->value = (def != 0);
12733 opt->type = CheckBox;
12734 } else if(p = strstr(opt->name, " -combo ")) {
12735 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12736 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12737 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12738 opt->value = n = 0;
12739 while(q = StrStr(q, " /// ")) {
12740 n++; *q = 0; // count choices, and null-terminate each of them
12742 if(*q == '*') { // remember default, which is marked with * prefix
12746 cps->comboList[cps->comboCnt++] = q;
12748 cps->comboList[cps->comboCnt++] = NULL;
12750 opt->type = ComboBox;
12751 } else if(p = strstr(opt->name, " -button")) {
12752 opt->type = Button;
12753 } else if(p = strstr(opt->name, " -save")) {
12754 opt->type = SaveButton;
12755 } else return FALSE;
12756 *p = 0; // terminate option name
12757 // now look if the command-line options define a setting for this engine option.
12758 if(cps->optionSettings && cps->optionSettings[0])
12759 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12760 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12761 sprintf(buf, "option %s", p);
12762 if(p = strstr(buf, ",")) *p = 0;
12764 SendToProgram(buf, cps);
12770 FeatureDone(cps, val)
12771 ChessProgramState* cps;
12774 DelayedEventCallback cb = GetDelayedEvent();
12775 if ((cb == InitBackEnd3 && cps == &first) ||
12776 (cb == TwoMachinesEventIfReady && cps == &second)) {
12777 CancelDelayedEvent();
12778 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12780 cps->initDone = val;
12783 /* Parse feature command from engine */
12785 ParseFeatures(args, cps)
12787 ChessProgramState *cps;
12795 while (*p == ' ') p++;
12796 if (*p == NULLCHAR) return;
12798 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12799 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12800 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12801 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12802 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12803 if (BoolFeature(&p, "reuse", &val, cps)) {
12804 /* Engine can disable reuse, but can't enable it if user said no */
12805 if (!val) cps->reuse = FALSE;
12808 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12809 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12810 if (gameMode == TwoMachinesPlay) {
12811 DisplayTwoMachinesTitle();
12817 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12818 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12819 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12820 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12821 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12822 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12823 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12824 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12825 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12826 if (IntFeature(&p, "done", &val, cps)) {
12827 FeatureDone(cps, val);
12830 /* Added by Tord: */
12831 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12832 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12833 /* End of additions by Tord */
12835 /* [HGM] added features: */
12836 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12837 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12838 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12839 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12840 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12841 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12842 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12843 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12844 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12845 SendToProgram(buf, cps);
12848 if(cps->nrOptions >= MAX_OPTIONS) {
12850 sprintf(buf, "%s engine has too many options\n", cps->which);
12851 DisplayError(buf, 0);
12855 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12856 /* End of additions by HGM */
12858 /* unknown feature: complain and skip */
12860 while (*q && *q != '=') q++;
12861 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12862 SendToProgram(buf, cps);
12868 while (*p && *p != '\"') p++;
12869 if (*p == '\"') p++;
12871 while (*p && *p != ' ') p++;
12879 PeriodicUpdatesEvent(newState)
12882 if (newState == appData.periodicUpdates)
12885 appData.periodicUpdates=newState;
12887 /* Display type changes, so update it now */
12888 // DisplayAnalysis();
12890 /* Get the ball rolling again... */
12892 AnalysisPeriodicEvent(1);
12893 StartAnalysisClock();
12898 PonderNextMoveEvent(newState)
12901 if (newState == appData.ponderNextMove) return;
12902 if (gameMode == EditPosition) EditPositionDone(TRUE);
12904 SendToProgram("hard\n", &first);
12905 if (gameMode == TwoMachinesPlay) {
12906 SendToProgram("hard\n", &second);
12909 SendToProgram("easy\n", &first);
12910 thinkOutput[0] = NULLCHAR;
12911 if (gameMode == TwoMachinesPlay) {
12912 SendToProgram("easy\n", &second);
12915 appData.ponderNextMove = newState;
12919 NewSettingEvent(option, command, value)
12925 if (gameMode == EditPosition) EditPositionDone(TRUE);
12926 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12927 SendToProgram(buf, &first);
12928 if (gameMode == TwoMachinesPlay) {
12929 SendToProgram(buf, &second);
12934 ShowThinkingEvent()
12935 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12937 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12938 int newState = appData.showThinking
12939 // [HGM] thinking: other features now need thinking output as well
12940 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12942 if (oldState == newState) return;
12943 oldState = newState;
12944 if (gameMode == EditPosition) EditPositionDone(TRUE);
12946 SendToProgram("post\n", &first);
12947 if (gameMode == TwoMachinesPlay) {
12948 SendToProgram("post\n", &second);
12951 SendToProgram("nopost\n", &first);
12952 thinkOutput[0] = NULLCHAR;
12953 if (gameMode == TwoMachinesPlay) {
12954 SendToProgram("nopost\n", &second);
12957 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12961 AskQuestionEvent(title, question, replyPrefix, which)
12962 char *title; char *question; char *replyPrefix; char *which;
12964 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12965 if (pr == NoProc) return;
12966 AskQuestion(title, question, replyPrefix, pr);
12970 DisplayMove(moveNumber)
12973 char message[MSG_SIZ];
12975 char cpThinkOutput[MSG_SIZ];
12977 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12979 if (moveNumber == forwardMostMove - 1 ||
12980 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12982 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12984 if (strchr(cpThinkOutput, '\n')) {
12985 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12988 *cpThinkOutput = NULLCHAR;
12991 /* [AS] Hide thinking from human user */
12992 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12993 *cpThinkOutput = NULLCHAR;
12994 if( thinkOutput[0] != NULLCHAR ) {
12997 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12998 cpThinkOutput[i] = '.';
13000 cpThinkOutput[i] = NULLCHAR;
13001 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13005 if (moveNumber == forwardMostMove - 1 &&
13006 gameInfo.resultDetails != NULL) {
13007 if (gameInfo.resultDetails[0] == NULLCHAR) {
13008 sprintf(res, " %s", PGNResult(gameInfo.result));
13010 sprintf(res, " {%s} %s",
13011 gameInfo.resultDetails, PGNResult(gameInfo.result));
13017 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13018 DisplayMessage(res, cpThinkOutput);
13020 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13021 WhiteOnMove(moveNumber) ? " " : ".. ",
13022 parseList[moveNumber], res);
13023 DisplayMessage(message, cpThinkOutput);
13028 DisplayComment(moveNumber, text)
13032 char title[MSG_SIZ];
13033 char buf[8000]; // comment can be long!
13036 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13037 strcpy(title, "Comment");
13039 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13040 WhiteOnMove(moveNumber) ? " " : ".. ",
13041 parseList[moveNumber]);
13043 // [HGM] PV info: display PV info together with (or as) comment
13044 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13045 if(text == NULL) text = "";
13046 score = pvInfoList[moveNumber].score;
13047 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13048 depth, (pvInfoList[moveNumber].time+50)/100, text);
13051 if (text != NULL && (appData.autoDisplayComment || commentUp))
13052 CommentPopUp(title, text);
13055 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13056 * might be busy thinking or pondering. It can be omitted if your
13057 * gnuchess is configured to stop thinking immediately on any user
13058 * input. However, that gnuchess feature depends on the FIONREAD
13059 * ioctl, which does not work properly on some flavors of Unix.
13063 ChessProgramState *cps;
13066 if (!cps->useSigint) return;
13067 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13068 switch (gameMode) {
13069 case MachinePlaysWhite:
13070 case MachinePlaysBlack:
13071 case TwoMachinesPlay:
13072 case IcsPlayingWhite:
13073 case IcsPlayingBlack:
13076 /* Skip if we know it isn't thinking */
13077 if (!cps->maybeThinking) return;
13078 if (appData.debugMode)
13079 fprintf(debugFP, "Interrupting %s\n", cps->which);
13080 InterruptChildProcess(cps->pr);
13081 cps->maybeThinking = FALSE;
13086 #endif /*ATTENTION*/
13092 if (whiteTimeRemaining <= 0) {
13095 if (appData.icsActive) {
13096 if (appData.autoCallFlag &&
13097 gameMode == IcsPlayingBlack && !blackFlag) {
13098 SendToICS(ics_prefix);
13099 SendToICS("flag\n");
13103 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13105 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13106 if (appData.autoCallFlag) {
13107 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13114 if (blackTimeRemaining <= 0) {
13117 if (appData.icsActive) {
13118 if (appData.autoCallFlag &&
13119 gameMode == IcsPlayingWhite && !whiteFlag) {
13120 SendToICS(ics_prefix);
13121 SendToICS("flag\n");
13125 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13127 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13128 if (appData.autoCallFlag) {
13129 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13142 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13143 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13146 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13148 if ( !WhiteOnMove(forwardMostMove) )
13149 /* White made time control */
13150 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13151 /* [HGM] time odds: correct new time quota for time odds! */
13152 / WhitePlayer()->timeOdds;
13154 /* Black made time control */
13155 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13156 / WhitePlayer()->other->timeOdds;
13160 DisplayBothClocks()
13162 int wom = gameMode == EditPosition ?
13163 !blackPlaysFirst : WhiteOnMove(currentMove);
13164 DisplayWhiteClock(whiteTimeRemaining, wom);
13165 DisplayBlackClock(blackTimeRemaining, !wom);
13169 /* Timekeeping seems to be a portability nightmare. I think everyone
13170 has ftime(), but I'm really not sure, so I'm including some ifdefs
13171 to use other calls if you don't. Clocks will be less accurate if
13172 you have neither ftime nor gettimeofday.
13175 /* VS 2008 requires the #include outside of the function */
13176 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13177 #include <sys/timeb.h>
13180 /* Get the current time as a TimeMark */
13185 #if HAVE_GETTIMEOFDAY
13187 struct timeval timeVal;
13188 struct timezone timeZone;
13190 gettimeofday(&timeVal, &timeZone);
13191 tm->sec = (long) timeVal.tv_sec;
13192 tm->ms = (int) (timeVal.tv_usec / 1000L);
13194 #else /*!HAVE_GETTIMEOFDAY*/
13197 // include <sys/timeb.h> / moved to just above start of function
13198 struct timeb timeB;
13201 tm->sec = (long) timeB.time;
13202 tm->ms = (int) timeB.millitm;
13204 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13205 tm->sec = (long) time(NULL);
13211 /* Return the difference in milliseconds between two
13212 time marks. We assume the difference will fit in a long!
13215 SubtractTimeMarks(tm2, tm1)
13216 TimeMark *tm2, *tm1;
13218 return 1000L*(tm2->sec - tm1->sec) +
13219 (long) (tm2->ms - tm1->ms);
13224 * Code to manage the game clocks.
13226 * In tournament play, black starts the clock and then white makes a move.
13227 * We give the human user a slight advantage if he is playing white---the
13228 * clocks don't run until he makes his first move, so it takes zero time.
13229 * Also, we don't account for network lag, so we could get out of sync
13230 * with GNU Chess's clock -- but then, referees are always right.
13233 static TimeMark tickStartTM;
13234 static long intendedTickLength;
13237 NextTickLength(timeRemaining)
13238 long timeRemaining;
13240 long nominalTickLength, nextTickLength;
13242 if (timeRemaining > 0L && timeRemaining <= 10000L)
13243 nominalTickLength = 100L;
13245 nominalTickLength = 1000L;
13246 nextTickLength = timeRemaining % nominalTickLength;
13247 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13249 return nextTickLength;
13252 /* Adjust clock one minute up or down */
13254 AdjustClock(Boolean which, int dir)
13256 if(which) blackTimeRemaining += 60000*dir;
13257 else whiteTimeRemaining += 60000*dir;
13258 DisplayBothClocks();
13261 /* Stop clocks and reset to a fresh time control */
13265 (void) StopClockTimer();
13266 if (appData.icsActive) {
13267 whiteTimeRemaining = blackTimeRemaining = 0;
13268 } else if (searchTime) {
13269 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13270 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13271 } else { /* [HGM] correct new time quote for time odds */
13272 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13273 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13275 if (whiteFlag || blackFlag) {
13277 whiteFlag = blackFlag = FALSE;
13279 DisplayBothClocks();
13282 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13284 /* Decrement running clock by amount of time that has passed */
13288 long timeRemaining;
13289 long lastTickLength, fudge;
13292 if (!appData.clockMode) return;
13293 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13297 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13299 /* Fudge if we woke up a little too soon */
13300 fudge = intendedTickLength - lastTickLength;
13301 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13303 if (WhiteOnMove(forwardMostMove)) {
13304 if(whiteNPS >= 0) lastTickLength = 0;
13305 timeRemaining = whiteTimeRemaining -= lastTickLength;
13306 DisplayWhiteClock(whiteTimeRemaining - fudge,
13307 WhiteOnMove(currentMove));
13309 if(blackNPS >= 0) lastTickLength = 0;
13310 timeRemaining = blackTimeRemaining -= lastTickLength;
13311 DisplayBlackClock(blackTimeRemaining - fudge,
13312 !WhiteOnMove(currentMove));
13315 if (CheckFlags()) return;
13318 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13319 StartClockTimer(intendedTickLength);
13321 /* if the time remaining has fallen below the alarm threshold, sound the
13322 * alarm. if the alarm has sounded and (due to a takeback or time control
13323 * with increment) the time remaining has increased to a level above the
13324 * threshold, reset the alarm so it can sound again.
13327 if (appData.icsActive && appData.icsAlarm) {
13329 /* make sure we are dealing with the user's clock */
13330 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13331 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13334 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13335 alarmSounded = FALSE;
13336 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13338 alarmSounded = TRUE;
13344 /* A player has just moved, so stop the previously running
13345 clock and (if in clock mode) start the other one.
13346 We redisplay both clocks in case we're in ICS mode, because
13347 ICS gives us an update to both clocks after every move.
13348 Note that this routine is called *after* forwardMostMove
13349 is updated, so the last fractional tick must be subtracted
13350 from the color that is *not* on move now.
13355 long lastTickLength;
13357 int flagged = FALSE;
13361 if (StopClockTimer() && appData.clockMode) {
13362 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13363 if (WhiteOnMove(forwardMostMove)) {
13364 if(blackNPS >= 0) lastTickLength = 0;
13365 blackTimeRemaining -= lastTickLength;
13366 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13367 // if(pvInfoList[forwardMostMove-1].time == -1)
13368 pvInfoList[forwardMostMove-1].time = // use GUI time
13369 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13371 if(whiteNPS >= 0) lastTickLength = 0;
13372 whiteTimeRemaining -= lastTickLength;
13373 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13374 // if(pvInfoList[forwardMostMove-1].time == -1)
13375 pvInfoList[forwardMostMove-1].time =
13376 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13378 flagged = CheckFlags();
13380 CheckTimeControl();
13382 if (flagged || !appData.clockMode) return;
13384 switch (gameMode) {
13385 case MachinePlaysBlack:
13386 case MachinePlaysWhite:
13387 case BeginningOfGame:
13388 if (pausing) return;
13392 case PlayFromGameFile:
13400 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13401 if(WhiteOnMove(forwardMostMove))
13402 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13403 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13407 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13408 whiteTimeRemaining : blackTimeRemaining);
13409 StartClockTimer(intendedTickLength);
13413 /* Stop both clocks */
13417 long lastTickLength;
13420 if (!StopClockTimer()) return;
13421 if (!appData.clockMode) return;
13425 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13426 if (WhiteOnMove(forwardMostMove)) {
13427 if(whiteNPS >= 0) lastTickLength = 0;
13428 whiteTimeRemaining -= lastTickLength;
13429 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13431 if(blackNPS >= 0) lastTickLength = 0;
13432 blackTimeRemaining -= lastTickLength;
13433 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13438 /* Start clock of player on move. Time may have been reset, so
13439 if clock is already running, stop and restart it. */
13443 (void) StopClockTimer(); /* in case it was running already */
13444 DisplayBothClocks();
13445 if (CheckFlags()) return;
13447 if (!appData.clockMode) return;
13448 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13450 GetTimeMark(&tickStartTM);
13451 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13452 whiteTimeRemaining : blackTimeRemaining);
13454 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13455 whiteNPS = blackNPS = -1;
13456 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13457 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13458 whiteNPS = first.nps;
13459 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13460 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13461 blackNPS = first.nps;
13462 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13463 whiteNPS = second.nps;
13464 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13465 blackNPS = second.nps;
13466 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13468 StartClockTimer(intendedTickLength);
13475 long second, minute, hour, day;
13477 static char buf[32];
13479 if (ms > 0 && ms <= 9900) {
13480 /* convert milliseconds to tenths, rounding up */
13481 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13483 sprintf(buf, " %03.1f ", tenths/10.0);
13487 /* convert milliseconds to seconds, rounding up */
13488 /* use floating point to avoid strangeness of integer division
13489 with negative dividends on many machines */
13490 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13497 day = second / (60 * 60 * 24);
13498 second = second % (60 * 60 * 24);
13499 hour = second / (60 * 60);
13500 second = second % (60 * 60);
13501 minute = second / 60;
13502 second = second % 60;
13505 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13506 sign, day, hour, minute, second);
13508 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13510 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13517 * This is necessary because some C libraries aren't ANSI C compliant yet.
13520 StrStr(string, match)
13521 char *string, *match;
13525 length = strlen(match);
13527 for (i = strlen(string) - length; i >= 0; i--, string++)
13528 if (!strncmp(match, string, length))
13535 StrCaseStr(string, match)
13536 char *string, *match;
13540 length = strlen(match);
13542 for (i = strlen(string) - length; i >= 0; i--, string++) {
13543 for (j = 0; j < length; j++) {
13544 if (ToLower(match[j]) != ToLower(string[j]))
13547 if (j == length) return string;
13561 c1 = ToLower(*s1++);
13562 c2 = ToLower(*s2++);
13563 if (c1 > c2) return 1;
13564 if (c1 < c2) return -1;
13565 if (c1 == NULLCHAR) return 0;
13574 return isupper(c) ? tolower(c) : c;
13582 return islower(c) ? toupper(c) : c;
13584 #endif /* !_amigados */
13592 if ((ret = (char *) malloc(strlen(s) + 1))) {
13599 StrSavePtr(s, savePtr)
13600 char *s, **savePtr;
13605 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13606 strcpy(*savePtr, s);
13618 clock = time((time_t *)NULL);
13619 tm = localtime(&clock);
13620 sprintf(buf, "%04d.%02d.%02d",
13621 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13622 return StrSave(buf);
13627 PositionToFEN(move, overrideCastling)
13629 char *overrideCastling;
13631 int i, j, fromX, fromY, toX, toY;
13638 whiteToPlay = (gameMode == EditPosition) ?
13639 !blackPlaysFirst : (move % 2 == 0);
13642 /* Piece placement data */
13643 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13645 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13646 if (boards[move][i][j] == EmptySquare) {
13648 } else { ChessSquare piece = boards[move][i][j];
13649 if (emptycount > 0) {
13650 if(emptycount<10) /* [HGM] can be >= 10 */
13651 *p++ = '0' + emptycount;
13652 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13655 if(PieceToChar(piece) == '+') {
13656 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13658 piece = (ChessSquare)(DEMOTED piece);
13660 *p++ = PieceToChar(piece);
13662 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13663 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13668 if (emptycount > 0) {
13669 if(emptycount<10) /* [HGM] can be >= 10 */
13670 *p++ = '0' + emptycount;
13671 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13678 /* [HGM] print Crazyhouse or Shogi holdings */
13679 if( gameInfo.holdingsWidth ) {
13680 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13682 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13683 piece = boards[move][i][BOARD_WIDTH-1];
13684 if( piece != EmptySquare )
13685 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13686 *p++ = PieceToChar(piece);
13688 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13689 piece = boards[move][BOARD_HEIGHT-i-1][0];
13690 if( piece != EmptySquare )
13691 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13692 *p++ = PieceToChar(piece);
13695 if( q == p ) *p++ = '-';
13701 *p++ = whiteToPlay ? 'w' : 'b';
13704 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13705 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13707 if(nrCastlingRights) {
13709 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13710 /* [HGM] write directly from rights */
13711 if(boards[move][CASTLING][2] != NoRights &&
13712 boards[move][CASTLING][0] != NoRights )
13713 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13714 if(boards[move][CASTLING][2] != NoRights &&
13715 boards[move][CASTLING][1] != NoRights )
13716 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13717 if(boards[move][CASTLING][5] != NoRights &&
13718 boards[move][CASTLING][3] != NoRights )
13719 *p++ = boards[move][CASTLING][3] + AAA;
13720 if(boards[move][CASTLING][5] != NoRights &&
13721 boards[move][CASTLING][4] != NoRights )
13722 *p++ = boards[move][CASTLING][4] + AAA;
13725 /* [HGM] write true castling rights */
13726 if( nrCastlingRights == 6 ) {
13727 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13728 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13729 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13730 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13731 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13732 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13733 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13734 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13737 if (q == p) *p++ = '-'; /* No castling rights */
13741 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13742 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13743 /* En passant target square */
13744 if (move > backwardMostMove) {
13745 fromX = moveList[move - 1][0] - AAA;
13746 fromY = moveList[move - 1][1] - ONE;
13747 toX = moveList[move - 1][2] - AAA;
13748 toY = moveList[move - 1][3] - ONE;
13749 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13750 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13751 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13753 /* 2-square pawn move just happened */
13755 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13759 } else if(move == backwardMostMove) {
13760 // [HGM] perhaps we should always do it like this, and forget the above?
13761 if((signed char)boards[move][EP_STATUS] >= 0) {
13762 *p++ = boards[move][EP_STATUS] + AAA;
13763 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13774 /* [HGM] find reversible plies */
13775 { int i = 0, j=move;
13777 if (appData.debugMode) { int k;
13778 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13779 for(k=backwardMostMove; k<=forwardMostMove; k++)
13780 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13784 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13785 if( j == backwardMostMove ) i += initialRulePlies;
13786 sprintf(p, "%d ", i);
13787 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13789 /* Fullmove number */
13790 sprintf(p, "%d", (move / 2) + 1);
13792 return StrSave(buf);
13796 ParseFEN(board, blackPlaysFirst, fen)
13798 int *blackPlaysFirst;
13808 /* [HGM] by default clear Crazyhouse holdings, if present */
13809 if(gameInfo.holdingsWidth) {
13810 for(i=0; i<BOARD_HEIGHT; i++) {
13811 board[i][0] = EmptySquare; /* black holdings */
13812 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13813 board[i][1] = (ChessSquare) 0; /* black counts */
13814 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13818 /* Piece placement data */
13819 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13822 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13823 if (*p == '/') p++;
13824 emptycount = gameInfo.boardWidth - j;
13825 while (emptycount--)
13826 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13828 #if(BOARD_FILES >= 10)
13829 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13830 p++; emptycount=10;
13831 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13832 while (emptycount--)
13833 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13835 } else if (isdigit(*p)) {
13836 emptycount = *p++ - '0';
13837 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13838 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13839 while (emptycount--)
13840 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13841 } else if (*p == '+' || isalpha(*p)) {
13842 if (j >= gameInfo.boardWidth) return FALSE;
13844 piece = CharToPiece(*++p);
13845 if(piece == EmptySquare) return FALSE; /* unknown piece */
13846 piece = (ChessSquare) (PROMOTED piece ); p++;
13847 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13848 } else piece = CharToPiece(*p++);
13850 if(piece==EmptySquare) return FALSE; /* unknown piece */
13851 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13852 piece = (ChessSquare) (PROMOTED piece);
13853 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13856 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13862 while (*p == '/' || *p == ' ') p++;
13864 /* [HGM] look for Crazyhouse holdings here */
13865 while(*p==' ') p++;
13866 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13868 if(*p == '-' ) *p++; /* empty holdings */ else {
13869 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13870 /* if we would allow FEN reading to set board size, we would */
13871 /* have to add holdings and shift the board read so far here */
13872 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13874 if((int) piece >= (int) BlackPawn ) {
13875 i = (int)piece - (int)BlackPawn;
13876 i = PieceToNumber((ChessSquare)i);
13877 if( i >= gameInfo.holdingsSize ) return FALSE;
13878 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13879 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13881 i = (int)piece - (int)WhitePawn;
13882 i = PieceToNumber((ChessSquare)i);
13883 if( i >= gameInfo.holdingsSize ) return FALSE;
13884 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13885 board[i][BOARD_WIDTH-2]++; /* black holdings */
13889 if(*p == ']') *p++;
13892 while(*p == ' ') p++;
13897 *blackPlaysFirst = FALSE;
13900 *blackPlaysFirst = TRUE;
13906 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13907 /* return the extra info in global variiables */
13909 /* set defaults in case FEN is incomplete */
13910 board[EP_STATUS] = EP_UNKNOWN;
13911 for(i=0; i<nrCastlingRights; i++ ) {
13912 board[CASTLING][i] =
13913 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13914 } /* assume possible unless obviously impossible */
13915 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13916 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13917 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13918 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13919 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13920 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13923 while(*p==' ') p++;
13924 if(nrCastlingRights) {
13925 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13926 /* castling indicator present, so default becomes no castlings */
13927 for(i=0; i<nrCastlingRights; i++ ) {
13928 board[CASTLING][i] = NoRights;
13931 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13932 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13933 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13934 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13935 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13937 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13938 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13939 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13943 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13944 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13945 board[CASTLING][2] = whiteKingFile;
13948 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13949 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13950 board[CASTLING][2] = whiteKingFile;
13953 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13954 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13955 board[CASTLING][5] = blackKingFile;
13958 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13959 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13960 board[CASTLING][5] = blackKingFile;
13963 default: /* FRC castlings */
13964 if(c >= 'a') { /* black rights */
13965 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13966 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13967 if(i == BOARD_RGHT) break;
13968 board[CASTLING][5] = i;
13970 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13971 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13973 board[CASTLING][3] = c;
13975 board[CASTLING][4] = c;
13976 } else { /* white rights */
13977 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13978 if(board[0][i] == WhiteKing) break;
13979 if(i == BOARD_RGHT) break;
13980 board[CASTLING][2] = i;
13981 c -= AAA - 'a' + 'A';
13982 if(board[0][c] >= WhiteKing) break;
13984 board[CASTLING][0] = c;
13986 board[CASTLING][1] = c;
13990 if (appData.debugMode) {
13991 fprintf(debugFP, "FEN castling rights:");
13992 for(i=0; i<nrCastlingRights; i++)
13993 fprintf(debugFP, " %d", board[CASTLING][i]);
13994 fprintf(debugFP, "\n");
13997 while(*p==' ') p++;
14000 /* read e.p. field in games that know e.p. capture */
14001 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14002 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14004 p++; board[EP_STATUS] = EP_NONE;
14006 char c = *p++ - AAA;
14008 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14009 if(*p >= '0' && *p <='9') *p++;
14010 board[EP_STATUS] = c;
14015 if(sscanf(p, "%d", &i) == 1) {
14016 FENrulePlies = i; /* 50-move ply counter */
14017 /* (The move number is still ignored) */
14024 EditPositionPasteFEN(char *fen)
14027 Board initial_position;
14029 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14030 DisplayError(_("Bad FEN position in clipboard"), 0);
14033 int savedBlackPlaysFirst = blackPlaysFirst;
14034 EditPositionEvent();
14035 blackPlaysFirst = savedBlackPlaysFirst;
14036 CopyBoard(boards[0], initial_position);
14037 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14038 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14039 DisplayBothClocks();
14040 DrawPosition(FALSE, boards[currentMove]);
14045 static char cseq[12] = "\\ ";
14047 Boolean set_cont_sequence(char *new_seq)
14052 // handle bad attempts to set the sequence
14054 return 0; // acceptable error - no debug
14056 len = strlen(new_seq);
14057 ret = (len > 0) && (len < sizeof(cseq));
14059 strcpy(cseq, new_seq);
14060 else if (appData.debugMode)
14061 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14066 reformat a source message so words don't cross the width boundary. internal
14067 newlines are not removed. returns the wrapped size (no null character unless
14068 included in source message). If dest is NULL, only calculate the size required
14069 for the dest buffer. lp argument indicats line position upon entry, and it's
14070 passed back upon exit.
14072 int wrap(char *dest, char *src, int count, int width, int *lp)
14074 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14076 cseq_len = strlen(cseq);
14077 old_line = line = *lp;
14078 ansi = len = clen = 0;
14080 for (i=0; i < count; i++)
14082 if (src[i] == '\033')
14085 // if we hit the width, back up
14086 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14088 // store i & len in case the word is too long
14089 old_i = i, old_len = len;
14091 // find the end of the last word
14092 while (i && src[i] != ' ' && src[i] != '\n')
14098 // word too long? restore i & len before splitting it
14099 if ((old_i-i+clen) >= width)
14106 if (i && src[i-1] == ' ')
14109 if (src[i] != ' ' && src[i] != '\n')
14116 // now append the newline and continuation sequence
14121 strncpy(dest+len, cseq, cseq_len);
14129 dest[len] = src[i];
14133 if (src[i] == '\n')
14138 if (dest && appData.debugMode)
14140 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14141 count, width, line, len, *lp);
14142 show_bytes(debugFP, src, count);
14143 fprintf(debugFP, "\ndest: ");
14144 show_bytes(debugFP, dest, len);
14145 fprintf(debugFP, "\n");
14147 *lp = dest ? line : old_line;
14152 // [HGM] vari: routines for shelving variations
14155 PushTail(int firstMove, int lastMove)
14157 int i, j, nrMoves = lastMove - firstMove;
14159 if(appData.icsActive) { // only in local mode
14160 forwardMostMove = currentMove; // mimic old ICS behavior
14163 if(storedGames >= MAX_VARIATIONS-1) return;
14165 // push current tail of game on stack
14166 savedResult[storedGames] = gameInfo.result;
14167 savedDetails[storedGames] = gameInfo.resultDetails;
14168 gameInfo.resultDetails = NULL;
14169 savedFirst[storedGames] = firstMove;
14170 savedLast [storedGames] = lastMove;
14171 savedFramePtr[storedGames] = framePtr;
14172 framePtr -= nrMoves; // reserve space for the boards
14173 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14174 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14175 for(j=0; j<MOVE_LEN; j++)
14176 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14177 for(j=0; j<2*MOVE_LEN; j++)
14178 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14179 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14180 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14181 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14182 pvInfoList[firstMove+i-1].depth = 0;
14183 commentList[framePtr+i] = commentList[firstMove+i];
14184 commentList[firstMove+i] = NULL;
14188 forwardMostMove = currentMove; // truncte game so we can start variation
14189 if(storedGames == 1) GreyRevert(FALSE);
14193 PopTail(Boolean annotate)
14196 char buf[8000], moveBuf[20];
14198 if(appData.icsActive) return FALSE; // only in local mode
14199 if(!storedGames) return FALSE; // sanity
14202 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14203 nrMoves = savedLast[storedGames] - currentMove;
14206 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14207 else strcpy(buf, "(");
14208 for(i=currentMove; i<forwardMostMove; i++) {
14210 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14211 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14212 strcat(buf, moveBuf);
14213 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14217 for(i=1; i<nrMoves; i++) { // copy last variation back
14218 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14219 for(j=0; j<MOVE_LEN; j++)
14220 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14221 for(j=0; j<2*MOVE_LEN; j++)
14222 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14223 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14224 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14225 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14226 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14227 commentList[currentMove+i] = commentList[framePtr+i];
14228 commentList[framePtr+i] = NULL;
14230 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14231 framePtr = savedFramePtr[storedGames];
14232 gameInfo.result = savedResult[storedGames];
14233 if(gameInfo.resultDetails != NULL) {
14234 free(gameInfo.resultDetails);
14236 gameInfo.resultDetails = savedDetails[storedGames];
14237 forwardMostMove = currentMove + nrMoves;
14238 if(storedGames == 0) GreyRevert(TRUE);
14244 { // remove all shelved variations
14246 for(i=0; i<storedGames; i++) {
14247 if(savedDetails[i])
14248 free(savedDetails[i]);
14249 savedDetails[i] = NULL;
14251 for(i=framePtr; i<MAX_MOVES; i++) {
14252 if(commentList[i]) free(commentList[i]);
14253 commentList[i] = NULL;
14255 framePtr = MAX_MOVES-1;