2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char initialRights[BOARD_FILES];
446 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int initialRulePlies, FENrulePlies;
448 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
451 int mute; // mute all sounds
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((Boolean annotate));
465 void CleanupTail P((void));
467 ChessSquare FIDEArray[2][BOARD_FILES] = {
468 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471 BlackKing, BlackBishop, BlackKnight, BlackRook }
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackKing, BlackKnight, BlackRook }
481 ChessSquare KnightmateArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484 { BlackRook, BlackMan, BlackBishop, BlackQueen,
485 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492 BlackKing, BlackBishop, BlackKnight, BlackRook }
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
522 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
527 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
529 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
534 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
536 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
542 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
544 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
547 #define GothicArray CapablancaArray
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
553 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
555 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
558 #define FalconArray CapablancaArray
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
580 Board initialPosition;
583 /* Convert str to a rating. Checks for special cases of "----",
585 "++++", etc. Also strips ()'s */
587 string_to_rating(str)
590 while(*str && !isdigit(*str)) ++str;
592 return 0; /* One of the special "no rating" cases */
600 /* Init programStats */
601 programStats.movelist[0] = 0;
602 programStats.depth = 0;
603 programStats.nr_moves = 0;
604 programStats.moves_left = 0;
605 programStats.nodes = 0;
606 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
607 programStats.score = 0;
608 programStats.got_only_move = 0;
609 programStats.got_fail = 0;
610 programStats.line_is_book = 0;
616 int matched, min, sec;
618 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
620 GetTimeMark(&programStartTime);
621 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
624 programStats.ok_to_send = 1;
625 programStats.seen_stat = 0;
628 * Initialize game list
634 * Internet chess server status
636 if (appData.icsActive) {
637 appData.matchMode = FALSE;
638 appData.matchGames = 0;
640 appData.noChessProgram = !appData.zippyPlay;
642 appData.zippyPlay = FALSE;
643 appData.zippyTalk = FALSE;
644 appData.noChessProgram = TRUE;
646 if (*appData.icsHelper != NULLCHAR) {
647 appData.useTelnet = TRUE;
648 appData.telnetProgram = appData.icsHelper;
651 appData.zippyTalk = appData.zippyPlay = FALSE;
654 /* [AS] Initialize pv info list [HGM] and game state */
658 for( i=0; i<=framePtr; i++ ) {
659 pvInfoList[i].depth = -1;
660 boards[i][EP_STATUS] = EP_NONE;
661 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
666 * Parse timeControl resource
668 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669 appData.movesPerSession)) {
671 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672 DisplayFatalError(buf, 0, 2);
676 * Parse searchTime resource
678 if (*appData.searchTime != NULLCHAR) {
679 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
681 searchTime = min * 60;
682 } else if (matched == 2) {
683 searchTime = min * 60 + sec;
686 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687 DisplayFatalError(buf, 0, 2);
691 /* [AS] Adjudication threshold */
692 adjudicateLossThreshold = appData.adjudicateLossThreshold;
694 first.which = "first";
695 second.which = "second";
696 first.maybeThinking = second.maybeThinking = FALSE;
697 first.pr = second.pr = NoProc;
698 first.isr = second.isr = NULL;
699 first.sendTime = second.sendTime = 2;
700 first.sendDrawOffers = 1;
701 if (appData.firstPlaysBlack) {
702 first.twoMachinesColor = "black\n";
703 second.twoMachinesColor = "white\n";
705 first.twoMachinesColor = "white\n";
706 second.twoMachinesColor = "black\n";
708 first.program = appData.firstChessProgram;
709 second.program = appData.secondChessProgram;
710 first.host = appData.firstHost;
711 second.host = appData.secondHost;
712 first.dir = appData.firstDirectory;
713 second.dir = appData.secondDirectory;
714 first.other = &second;
715 second.other = &first;
716 first.initString = appData.initString;
717 second.initString = appData.secondInitString;
718 first.computerString = appData.firstComputerString;
719 second.computerString = appData.secondComputerString;
720 first.useSigint = second.useSigint = TRUE;
721 first.useSigterm = second.useSigterm = TRUE;
722 first.reuse = appData.reuseFirst;
723 second.reuse = appData.reuseSecond;
724 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
725 second.nps = appData.secondNPS;
726 first.useSetboard = second.useSetboard = FALSE;
727 first.useSAN = second.useSAN = FALSE;
728 first.usePing = second.usePing = FALSE;
729 first.lastPing = second.lastPing = 0;
730 first.lastPong = second.lastPong = 0;
731 first.usePlayother = second.usePlayother = FALSE;
732 first.useColors = second.useColors = TRUE;
733 first.useUsermove = second.useUsermove = FALSE;
734 first.sendICS = second.sendICS = FALSE;
735 first.sendName = second.sendName = appData.icsActive;
736 first.sdKludge = second.sdKludge = FALSE;
737 first.stKludge = second.stKludge = FALSE;
738 TidyProgramName(first.program, first.host, first.tidy);
739 TidyProgramName(second.program, second.host, second.tidy);
740 first.matchWins = second.matchWins = 0;
741 strcpy(first.variants, appData.variant);
742 strcpy(second.variants, appData.variant);
743 first.analysisSupport = second.analysisSupport = 2; /* detect */
744 first.analyzing = second.analyzing = FALSE;
745 first.initDone = second.initDone = FALSE;
747 /* New features added by Tord: */
748 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750 /* End of new features added by Tord. */
751 first.fenOverride = appData.fenOverride1;
752 second.fenOverride = appData.fenOverride2;
754 /* [HGM] time odds: set factor for each machine */
755 first.timeOdds = appData.firstTimeOdds;
756 second.timeOdds = appData.secondTimeOdds;
758 if(appData.timeOddsMode) {
759 norm = first.timeOdds;
760 if(norm > second.timeOdds) norm = second.timeOdds;
762 first.timeOdds /= norm;
763 second.timeOdds /= norm;
766 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767 first.accumulateTC = appData.firstAccumulateTC;
768 second.accumulateTC = appData.secondAccumulateTC;
769 first.maxNrOfSessions = second.maxNrOfSessions = 1;
772 first.debug = second.debug = FALSE;
773 first.supportsNPS = second.supportsNPS = UNKNOWN;
776 first.optionSettings = appData.firstOptions;
777 second.optionSettings = appData.secondOptions;
779 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781 first.isUCI = appData.firstIsUCI; /* [AS] */
782 second.isUCI = appData.secondIsUCI; /* [AS] */
783 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
786 if (appData.firstProtocolVersion > PROTOVER ||
787 appData.firstProtocolVersion < 1) {
789 sprintf(buf, _("protocol version %d not supported"),
790 appData.firstProtocolVersion);
791 DisplayFatalError(buf, 0, 2);
793 first.protocolVersion = appData.firstProtocolVersion;
796 if (appData.secondProtocolVersion > PROTOVER ||
797 appData.secondProtocolVersion < 1) {
799 sprintf(buf, _("protocol version %d not supported"),
800 appData.secondProtocolVersion);
801 DisplayFatalError(buf, 0, 2);
803 second.protocolVersion = appData.secondProtocolVersion;
806 if (appData.icsActive) {
807 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
808 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810 appData.clockMode = FALSE;
811 first.sendTime = second.sendTime = 0;
815 /* Override some settings from environment variables, for backward
816 compatibility. Unfortunately it's not feasible to have the env
817 vars just set defaults, at least in xboard. Ugh.
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
824 if (appData.noChessProgram) {
825 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826 sprintf(programVersion, "%s", PACKAGE_STRING);
828 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
833 if (!appData.icsActive) {
835 /* Check for variants that are supported only in ICS mode,
836 or not at all. Some that are accepted here nevertheless
837 have bugs; see comments below.
839 VariantClass variant = StringToVariant(appData.variant);
841 case VariantBughouse: /* need four players and two boards */
842 case VariantKriegspiel: /* need to hide pieces and move details */
843 /* case VariantFischeRandom: (Fabien: moved below) */
844 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845 DisplayFatalError(buf, 0, 2);
849 case VariantLoadable:
859 sprintf(buf, _("Unknown variant name %s"), appData.variant);
860 DisplayFatalError(buf, 0, 2);
863 case VariantXiangqi: /* [HGM] repetition rules not implemented */
864 case VariantFairy: /* [HGM] TestLegality definitely off! */
865 case VariantGothic: /* [HGM] should work */
866 case VariantCapablanca: /* [HGM] should work */
867 case VariantCourier: /* [HGM] initial forced moves not implemented */
868 case VariantShogi: /* [HGM] drops not tested for legality */
869 case VariantKnightmate: /* [HGM] should work */
870 case VariantCylinder: /* [HGM] untested */
871 case VariantFalcon: /* [HGM] untested */
872 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873 offboard interposition not understood */
874 case VariantNormal: /* definitely works! */
875 case VariantWildCastle: /* pieces not automatically shuffled */
876 case VariantNoCastle: /* pieces not automatically shuffled */
877 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878 case VariantLosers: /* should work except for win condition,
879 and doesn't know captures are mandatory */
880 case VariantSuicide: /* should work except for win condition,
881 and doesn't know captures are mandatory */
882 case VariantGiveaway: /* should work except for win condition,
883 and doesn't know captures are mandatory */
884 case VariantTwoKings: /* should work */
885 case VariantAtomic: /* should work except for win condition */
886 case Variant3Check: /* should work except for win condition */
887 case VariantShatranj: /* should work except for all win conditions */
888 case VariantBerolina: /* might work if TestLegality is off */
889 case VariantCapaRandom: /* should work */
890 case VariantJanus: /* should work */
891 case VariantSuper: /* experimental */
892 case VariantGreat: /* experimental, requires legality testing to be off */
897 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
898 InitEngineUCI( installDir, &second );
901 int NextIntegerFromString( char ** str, long * value )
906 while( *s == ' ' || *s == '\t' ) {
912 if( *s >= '0' && *s <= '9' ) {
913 while( *s >= '0' && *s <= '9' ) {
914 *value = *value * 10 + (*s - '0');
926 int NextTimeControlFromString( char ** str, long * value )
929 int result = NextIntegerFromString( str, &temp );
932 *value = temp * 60; /* Minutes */
935 result = NextIntegerFromString( str, &temp );
936 *value += temp; /* Seconds */
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 { /* [HGM] routine added to read '+moves/time' for secondary time control */
945 int result = -1; long temp, temp2;
947 if(**str != '+') return -1; // old params remain in force!
949 if( NextTimeControlFromString( str, &temp ) ) return -1;
952 /* time only: incremental or sudden-death time control */
953 if(**str == '+') { /* increment follows; read it */
955 if(result = NextIntegerFromString( str, &temp2)) return -1;
958 *moves = 0; *tc = temp * 1000;
960 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
962 (*str)++; /* classical time control */
963 result = NextTimeControlFromString( str, &temp2);
972 int GetTimeQuota(int movenr)
973 { /* [HGM] get time to add from the multi-session time-control string */
974 int moves=1; /* kludge to force reading of first session */
975 long time, increment;
976 char *s = fullTimeControlString;
978 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
980 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982 if(movenr == -1) return time; /* last move before new session */
983 if(!moves) return increment; /* current session is incremental */
984 if(movenr >= 0) movenr -= moves; /* we already finished this session */
985 } while(movenr >= -1); /* try again for next session */
987 return 0; // no new time quota on this move
991 ParseTimeControl(tc, ti, mps)
1000 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1003 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004 else sprintf(buf, "+%s+%d", tc, ti);
1007 sprintf(buf, "+%d/%s", mps, tc);
1008 else sprintf(buf, "+%s", tc);
1010 fullTimeControlString = StrSave(buf);
1012 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1017 /* Parse second time control */
1020 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1028 timeControl_2 = tc2 * 1000;
1038 timeControl = tc1 * 1000;
1041 timeIncrement = ti * 1000; /* convert to ms */
1042 movesPerSession = 0;
1045 movesPerSession = mps;
1053 if (appData.debugMode) {
1054 fprintf(debugFP, "%s\n", programVersion);
1057 set_cont_sequence(appData.wrapContSeq);
1058 if (appData.matchGames > 0) {
1059 appData.matchMode = TRUE;
1060 } else if (appData.matchMode) {
1061 appData.matchGames = 1;
1063 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064 appData.matchGames = appData.sameColorGames;
1065 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1070 if (appData.noChessProgram || first.protocolVersion == 1) {
1073 /* kludge: allow timeout for initial "feature" commands */
1075 DisplayMessage("", _("Starting chess program"));
1076 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1081 InitBackEnd3 P((void))
1083 GameMode initialMode;
1087 InitChessProgram(&first, startedFromSetupPosition);
1090 if (appData.icsActive) {
1092 /* [DM] Make a console window if needed [HGM] merged ifs */
1097 if (*appData.icsCommPort != NULLCHAR) {
1098 sprintf(buf, _("Could not open comm port %s"),
1099 appData.icsCommPort);
1101 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1102 appData.icsHost, appData.icsPort);
1104 DisplayFatalError(buf, err, 1);
1109 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1111 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112 } else if (appData.noChessProgram) {
1118 if (*appData.cmailGameName != NULLCHAR) {
1120 OpenLoopback(&cmailPR);
1122 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1126 DisplayMessage("", "");
1127 if (StrCaseCmp(appData.initialMode, "") == 0) {
1128 initialMode = BeginningOfGame;
1129 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130 initialMode = TwoMachinesPlay;
1131 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132 initialMode = AnalyzeFile;
1133 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134 initialMode = AnalyzeMode;
1135 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136 initialMode = MachinePlaysWhite;
1137 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138 initialMode = MachinePlaysBlack;
1139 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140 initialMode = EditGame;
1141 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142 initialMode = EditPosition;
1143 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144 initialMode = Training;
1146 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147 DisplayFatalError(buf, 0, 2);
1151 if (appData.matchMode) {
1152 /* Set up machine vs. machine match */
1153 if (appData.noChessProgram) {
1154 DisplayFatalError(_("Can't have a match with no chess programs"),
1160 if (*appData.loadGameFile != NULLCHAR) {
1161 int index = appData.loadGameIndex; // [HGM] autoinc
1162 if(index<0) lastIndex = index = 1;
1163 if (!LoadGameFromFile(appData.loadGameFile,
1165 appData.loadGameFile, FALSE)) {
1166 DisplayFatalError(_("Bad game file"), 0, 1);
1169 } else if (*appData.loadPositionFile != NULLCHAR) {
1170 int index = appData.loadPositionIndex; // [HGM] autoinc
1171 if(index<0) lastIndex = index = 1;
1172 if (!LoadPositionFromFile(appData.loadPositionFile,
1174 appData.loadPositionFile)) {
1175 DisplayFatalError(_("Bad position file"), 0, 1);
1180 } else if (*appData.cmailGameName != NULLCHAR) {
1181 /* Set up cmail mode */
1182 ReloadCmailMsgEvent(TRUE);
1184 /* Set up other modes */
1185 if (initialMode == AnalyzeFile) {
1186 if (*appData.loadGameFile == NULLCHAR) {
1187 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1191 if (*appData.loadGameFile != NULLCHAR) {
1192 (void) LoadGameFromFile(appData.loadGameFile,
1193 appData.loadGameIndex,
1194 appData.loadGameFile, TRUE);
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
1196 (void) LoadPositionFromFile(appData.loadPositionFile,
1197 appData.loadPositionIndex,
1198 appData.loadPositionFile);
1199 /* [HGM] try to make self-starting even after FEN load */
1200 /* to allow automatic setup of fairy variants with wtm */
1201 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202 gameMode = BeginningOfGame;
1203 setboardSpoiledMachineBlack = 1;
1205 /* [HGM] loadPos: make that every new game uses the setup */
1206 /* from file as long as we do not switch variant */
1207 if(!blackPlaysFirst) {
1208 startedFromPositionFile = TRUE;
1209 CopyBoard(filePosition, boards[0]);
1212 if (initialMode == AnalyzeMode) {
1213 if (appData.noChessProgram) {
1214 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217 if (appData.icsActive) {
1218 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1222 } else if (initialMode == AnalyzeFile) {
1223 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224 ShowThinkingEvent();
1226 AnalysisPeriodicEvent(1);
1227 } else if (initialMode == MachinePlaysWhite) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1238 MachineWhiteEvent();
1239 } else if (initialMode == MachinePlaysBlack) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1250 MachineBlackEvent();
1251 } else if (initialMode == TwoMachinesPlay) {
1252 if (appData.noChessProgram) {
1253 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1257 if (appData.icsActive) {
1258 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1263 } else if (initialMode == EditGame) {
1265 } else if (initialMode == EditPosition) {
1266 EditPositionEvent();
1267 } else if (initialMode == Training) {
1268 if (*appData.loadGameFile == NULLCHAR) {
1269 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1278 * Establish will establish a contact to a remote host.port.
1279 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280 * used to talk to the host.
1281 * Returns 0 if okay, error code if not.
1288 if (*appData.icsCommPort != NULLCHAR) {
1289 /* Talk to the host through a serial comm port */
1290 return OpenCommPort(appData.icsCommPort, &icsPR);
1292 } else if (*appData.gateway != NULLCHAR) {
1293 if (*appData.remoteShell == NULLCHAR) {
1294 /* Use the rcmd protocol to run telnet program on a gateway host */
1295 snprintf(buf, sizeof(buf), "%s %s %s",
1296 appData.telnetProgram, appData.icsHost, appData.icsPort);
1297 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300 /* Use the rsh program to run telnet program on a gateway host */
1301 if (*appData.remoteUser == NULLCHAR) {
1302 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303 appData.gateway, appData.telnetProgram,
1304 appData.icsHost, appData.icsPort);
1306 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307 appData.remoteShell, appData.gateway,
1308 appData.remoteUser, appData.telnetProgram,
1309 appData.icsHost, appData.icsPort);
1311 return StartChildProcess(buf, "", &icsPR);
1314 } else if (appData.useTelnet) {
1315 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318 /* TCP socket interface differs somewhat between
1319 Unix and NT; handle details in the front end.
1321 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1326 show_bytes(fp, buf, count)
1332 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333 fprintf(fp, "\\%03o", *buf & 0xff);
1342 /* Returns an errno value */
1344 OutputMaybeTelnet(pr, message, count, outError)
1350 char buf[8192], *p, *q, *buflim;
1351 int left, newcount, outcount;
1353 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354 *appData.gateway != NULLCHAR) {
1355 if (appData.debugMode) {
1356 fprintf(debugFP, ">ICS: ");
1357 show_bytes(debugFP, message, count);
1358 fprintf(debugFP, "\n");
1360 return OutputToProcess(pr, message, count, outError);
1363 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1370 if (appData.debugMode) {
1371 fprintf(debugFP, ">ICS: ");
1372 show_bytes(debugFP, buf, newcount);
1373 fprintf(debugFP, "\n");
1375 outcount = OutputToProcess(pr, buf, newcount, outError);
1376 if (outcount < newcount) return -1; /* to be sure */
1383 } else if (((unsigned char) *p) == TN_IAC) {
1384 *q++ = (char) TN_IAC;
1391 if (appData.debugMode) {
1392 fprintf(debugFP, ">ICS: ");
1393 show_bytes(debugFP, buf, newcount);
1394 fprintf(debugFP, "\n");
1396 outcount = OutputToProcess(pr, buf, newcount, outError);
1397 if (outcount < newcount) return -1; /* to be sure */
1402 read_from_player(isr, closure, message, count, error)
1409 int outError, outCount;
1410 static int gotEof = 0;
1412 /* Pass data read from player on to ICS */
1415 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416 if (outCount < count) {
1417 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1419 } else if (count < 0) {
1420 RemoveInputSource(isr);
1421 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422 } else if (gotEof++ > 0) {
1423 RemoveInputSource(isr);
1424 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1430 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431 SendToICS("date\n");
1432 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1438 char buffer[MSG_SIZ];
1441 va_start(args, format);
1442 vsnprintf(buffer, sizeof(buffer), format, args);
1443 buffer[sizeof(buffer)-1] = '\0';
1452 int count, outCount, outError;
1454 if (icsPR == NULL) return;
1457 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458 if (outCount < count) {
1459 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463 /* This is used for sending logon scripts to the ICS. Sending
1464 without a delay causes problems when using timestamp on ICC
1465 (at least on my machine). */
1467 SendToICSDelayed(s,msdelay)
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 if (appData.debugMode) {
1477 fprintf(debugFP, ">ICS: ");
1478 show_bytes(debugFP, s, count);
1479 fprintf(debugFP, "\n");
1481 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1483 if (outCount < count) {
1484 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1489 /* Remove all highlighting escape sequences in s
1490 Also deletes any suffix starting with '('
1493 StripHighlightAndTitle(s)
1496 static char retbuf[MSG_SIZ];
1499 while (*s != NULLCHAR) {
1500 while (*s == '\033') {
1501 while (*s != NULLCHAR && !isalpha(*s)) s++;
1502 if (*s != NULLCHAR) s++;
1504 while (*s != NULLCHAR && *s != '\033') {
1505 if (*s == '(' || *s == '[') {
1516 /* Remove all highlighting escape sequences in s */
1521 static char retbuf[MSG_SIZ];
1524 while (*s != NULLCHAR) {
1525 while (*s == '\033') {
1526 while (*s != NULLCHAR && !isalpha(*s)) s++;
1527 if (*s != NULLCHAR) s++;
1529 while (*s != NULLCHAR && *s != '\033') {
1537 char *variantNames[] = VARIANT_NAMES;
1542 return variantNames[v];
1546 /* Identify a variant from the strings the chess servers use or the
1547 PGN Variant tag names we use. */
1554 VariantClass v = VariantNormal;
1555 int i, found = FALSE;
1560 /* [HGM] skip over optional board-size prefixes */
1561 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563 while( *e++ != '_');
1566 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1570 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571 if (StrCaseStr(e, variantNames[i])) {
1572 v = (VariantClass) i;
1579 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580 || StrCaseStr(e, "wild/fr")
1581 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582 v = VariantFischeRandom;
1583 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584 (i = 1, p = StrCaseStr(e, "w"))) {
1586 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1593 case 0: /* FICS only, actually */
1595 /* Castling legal even if K starts on d-file */
1596 v = VariantWildCastle;
1601 /* Castling illegal even if K & R happen to start in
1602 normal positions. */
1603 v = VariantNoCastle;
1616 /* Castling legal iff K & R start in normal positions */
1622 /* Special wilds for position setup; unclear what to do here */
1623 v = VariantLoadable;
1626 /* Bizarre ICC game */
1627 v = VariantTwoKings;
1630 v = VariantKriegspiel;
1636 v = VariantFischeRandom;
1639 v = VariantCrazyhouse;
1642 v = VariantBughouse;
1648 /* Not quite the same as FICS suicide! */
1649 v = VariantGiveaway;
1655 v = VariantShatranj;
1658 /* Temporary names for future ICC types. The name *will* change in
1659 the next xboard/WinBoard release after ICC defines it. */
1697 v = VariantCapablanca;
1700 v = VariantKnightmate;
1706 v = VariantCylinder;
1712 v = VariantCapaRandom;
1715 v = VariantBerolina;
1727 /* Found "wild" or "w" in the string but no number;
1728 must assume it's normal chess. */
1732 sprintf(buf, _("Unknown wild type %d"), wnum);
1733 DisplayError(buf, 0);
1739 if (appData.debugMode) {
1740 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741 e, wnum, VariantName(v));
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750 advance *index beyond it, and set leftover_start to the new value of
1751 *index; else return FALSE. If pattern contains the character '*', it
1752 matches any sequence of characters not containing '\r', '\n', or the
1753 character following the '*' (if any), and the matched sequence(s) are
1754 copied into star_match.
1757 looking_at(buf, index, pattern)
1762 char *bufp = &buf[*index], *patternp = pattern;
1764 char *matchp = star_match[0];
1767 if (*patternp == NULLCHAR) {
1768 *index = leftover_start = bufp - buf;
1772 if (*bufp == NULLCHAR) return FALSE;
1773 if (*patternp == '*') {
1774 if (*bufp == *(patternp + 1)) {
1776 matchp = star_match[++star_count];
1780 } else if (*bufp == '\n' || *bufp == '\r') {
1782 if (*patternp == NULLCHAR)
1787 *matchp++ = *bufp++;
1791 if (*patternp != *bufp) return FALSE;
1798 SendToPlayer(data, length)
1802 int error, outCount;
1803 outCount = OutputToProcess(NoProc, data, length, &error);
1804 if (outCount < length) {
1805 DisplayFatalError(_("Error writing to display"), error, 1);
1810 PackHolding(packed, holding)
1822 switch (runlength) {
1833 sprintf(q, "%d", runlength);
1845 /* Telnet protocol requests from the front end */
1847 TelnetRequest(ddww, option)
1848 unsigned char ddww, option;
1850 unsigned char msg[3];
1851 int outCount, outError;
1853 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1855 if (appData.debugMode) {
1856 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1872 sprintf(buf1, "%d", ddww);
1881 sprintf(buf2, "%d", option);
1884 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1889 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1891 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898 if (!appData.icsActive) return;
1899 TelnetRequest(TN_DO, TN_ECHO);
1905 if (!appData.icsActive) return;
1906 TelnetRequest(TN_DONT, TN_ECHO);
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1912 /* put the holdings sent to us by the server on the board holdings area */
1913 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917 if(gameInfo.holdingsWidth < 2) return;
1918 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919 return; // prevent overwriting by pre-board holdings
1921 if( (int)lowestPiece >= BlackPawn ) {
1924 holdingsStartRow = BOARD_HEIGHT-1;
1927 holdingsColumn = BOARD_WIDTH-1;
1928 countsColumn = BOARD_WIDTH-2;
1929 holdingsStartRow = 0;
1933 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934 board[i][holdingsColumn] = EmptySquare;
1935 board[i][countsColumn] = (ChessSquare) 0;
1937 while( (p=*holdings++) != NULLCHAR ) {
1938 piece = CharToPiece( ToUpper(p) );
1939 if(piece == EmptySquare) continue;
1940 /*j = (int) piece - (int) WhitePawn;*/
1941 j = PieceToNumber(piece);
1942 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943 if(j < 0) continue; /* should not happen */
1944 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946 board[holdingsStartRow+j*direction][countsColumn]++;
1952 VariantSwitch(Board board, VariantClass newVariant)
1954 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957 startedFromPositionFile = FALSE;
1958 if(gameInfo.variant == newVariant) return;
1960 /* [HGM] This routine is called each time an assignment is made to
1961 * gameInfo.variant during a game, to make sure the board sizes
1962 * are set to match the new variant. If that means adding or deleting
1963 * holdings, we shift the playing board accordingly
1964 * This kludge is needed because in ICS observe mode, we get boards
1965 * of an ongoing game without knowing the variant, and learn about the
1966 * latter only later. This can be because of the move list we requested,
1967 * in which case the game history is refilled from the beginning anyway,
1968 * but also when receiving holdings of a crazyhouse game. In the latter
1969 * case we want to add those holdings to the already received position.
1972 if (appData.debugMode) {
1973 fprintf(debugFP, "Switch board from %s to %s\n",
1974 VariantName(gameInfo.variant), VariantName(newVariant));
1975 setbuf(debugFP, NULL);
1977 shuffleOpenings = 0; /* [HGM] shuffle */
1978 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982 newWidth = 9; newHeight = 9;
1983 gameInfo.holdingsSize = 7;
1984 case VariantBughouse:
1985 case VariantCrazyhouse:
1986 newHoldingsWidth = 2; break;
1990 newHoldingsWidth = 2;
1991 gameInfo.holdingsSize = 8;
1994 case VariantCapablanca:
1995 case VariantCapaRandom:
1998 newHoldingsWidth = gameInfo.holdingsSize = 0;
2001 if(newWidth != gameInfo.boardWidth ||
2002 newHeight != gameInfo.boardHeight ||
2003 newHoldingsWidth != gameInfo.holdingsWidth ) {
2005 /* shift position to new playing area, if needed */
2006 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2007 for(i=0; i<BOARD_HEIGHT; i++)
2008 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2009 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011 for(i=0; i<newHeight; i++) {
2012 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2013 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2015 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2016 for(i=0; i<BOARD_HEIGHT; i++)
2017 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2018 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2021 gameInfo.boardWidth = newWidth;
2022 gameInfo.boardHeight = newHeight;
2023 gameInfo.holdingsWidth = newHoldingsWidth;
2024 gameInfo.variant = newVariant;
2025 InitDrawingSizes(-2, 0);
2026 } else gameInfo.variant = newVariant;
2027 CopyBoard(oldBoard, board); // remember correctly formatted board
2028 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2029 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2032 static int loggedOn = FALSE;
2034 /*-- Game start info cache: --*/
2036 char gs_kind[MSG_SIZ];
2037 static char player1Name[128] = "";
2038 static char player2Name[128] = "";
2039 static char cont_seq[] = "\n\\ ";
2040 static int player1Rating = -1;
2041 static int player2Rating = -1;
2042 /*----------------------------*/
2044 ColorClass curColor = ColorNormal;
2045 int suppressKibitz = 0;
2048 read_from_ics(isr, closure, data, count, error)
2055 #define BUF_SIZE 8192
2056 #define STARTED_NONE 0
2057 #define STARTED_MOVES 1
2058 #define STARTED_BOARD 2
2059 #define STARTED_OBSERVE 3
2060 #define STARTED_HOLDINGS 4
2061 #define STARTED_CHATTER 5
2062 #define STARTED_COMMENT 6
2063 #define STARTED_MOVES_NOHIDE 7
2065 static int started = STARTED_NONE;
2066 static char parse[20000];
2067 static int parse_pos = 0;
2068 static char buf[BUF_SIZE + 1];
2069 static int firstTime = TRUE, intfSet = FALSE;
2070 static ColorClass prevColor = ColorNormal;
2071 static int savingComment = FALSE;
2072 static int cmatch = 0; // continuation sequence match
2079 int backup; /* [DM] For zippy color lines */
2081 char talker[MSG_SIZ]; // [HGM] chat
2084 if (appData.debugMode) {
2086 fprintf(debugFP, "<ICS: ");
2087 show_bytes(debugFP, data, count);
2088 fprintf(debugFP, "\n");
2092 if (appData.debugMode) { int f = forwardMostMove;
2093 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2094 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2095 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2098 /* If last read ended with a partial line that we couldn't parse,
2099 prepend it to the new read and try again. */
2100 if (leftover_len > 0) {
2101 for (i=0; i<leftover_len; i++)
2102 buf[i] = buf[leftover_start + i];
2105 /* copy new characters into the buffer */
2106 bp = buf + leftover_len;
2107 buf_len=leftover_len;
2108 for (i=0; i<count; i++)
2111 if (data[i] == '\r')
2114 // join lines split by ICS?
2115 if (!appData.noJoin)
2118 Joining just consists of finding matches against the
2119 continuation sequence, and discarding that sequence
2120 if found instead of copying it. So, until a match
2121 fails, there's nothing to do since it might be the
2122 complete sequence, and thus, something we don't want
2125 if (data[i] == cont_seq[cmatch])
2128 if (cmatch == strlen(cont_seq))
2130 cmatch = 0; // complete match. just reset the counter
2133 it's possible for the ICS to not include the space
2134 at the end of the last word, making our [correct]
2135 join operation fuse two separate words. the server
2136 does this when the space occurs at the width setting.
2138 if (!buf_len || buf[buf_len-1] != ' ')
2149 match failed, so we have to copy what matched before
2150 falling through and copying this character. In reality,
2151 this will only ever be just the newline character, but
2152 it doesn't hurt to be precise.
2154 strncpy(bp, cont_seq, cmatch);
2166 buf[buf_len] = NULLCHAR;
2167 next_out = leftover_len;
2171 while (i < buf_len) {
2172 /* Deal with part of the TELNET option negotiation
2173 protocol. We refuse to do anything beyond the
2174 defaults, except that we allow the WILL ECHO option,
2175 which ICS uses to turn off password echoing when we are
2176 directly connected to it. We reject this option
2177 if localLineEditing mode is on (always on in xboard)
2178 and we are talking to port 23, which might be a real
2179 telnet server that will try to keep WILL ECHO on permanently.
2181 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2182 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2183 unsigned char option;
2185 switch ((unsigned char) buf[++i]) {
2187 if (appData.debugMode)
2188 fprintf(debugFP, "\n<WILL ");
2189 switch (option = (unsigned char) buf[++i]) {
2191 if (appData.debugMode)
2192 fprintf(debugFP, "ECHO ");
2193 /* Reply only if this is a change, according
2194 to the protocol rules. */
2195 if (remoteEchoOption) break;
2196 if (appData.localLineEditing &&
2197 atoi(appData.icsPort) == TN_PORT) {
2198 TelnetRequest(TN_DONT, TN_ECHO);
2201 TelnetRequest(TN_DO, TN_ECHO);
2202 remoteEchoOption = TRUE;
2206 if (appData.debugMode)
2207 fprintf(debugFP, "%d ", option);
2208 /* Whatever this is, we don't want it. */
2209 TelnetRequest(TN_DONT, option);
2214 if (appData.debugMode)
2215 fprintf(debugFP, "\n<WONT ");
2216 switch (option = (unsigned char) buf[++i]) {
2218 if (appData.debugMode)
2219 fprintf(debugFP, "ECHO ");
2220 /* Reply only if this is a change, according
2221 to the protocol rules. */
2222 if (!remoteEchoOption) break;
2224 TelnetRequest(TN_DONT, TN_ECHO);
2225 remoteEchoOption = FALSE;
2228 if (appData.debugMode)
2229 fprintf(debugFP, "%d ", (unsigned char) option);
2230 /* Whatever this is, it must already be turned
2231 off, because we never agree to turn on
2232 anything non-default, so according to the
2233 protocol rules, we don't reply. */
2238 if (appData.debugMode)
2239 fprintf(debugFP, "\n<DO ");
2240 switch (option = (unsigned char) buf[++i]) {
2242 /* Whatever this is, we refuse to do it. */
2243 if (appData.debugMode)
2244 fprintf(debugFP, "%d ", option);
2245 TelnetRequest(TN_WONT, option);
2250 if (appData.debugMode)
2251 fprintf(debugFP, "\n<DONT ");
2252 switch (option = (unsigned char) buf[++i]) {
2254 if (appData.debugMode)
2255 fprintf(debugFP, "%d ", option);
2256 /* Whatever this is, we are already not doing
2257 it, because we never agree to do anything
2258 non-default, so according to the protocol
2259 rules, we don't reply. */
2264 if (appData.debugMode)
2265 fprintf(debugFP, "\n<IAC ");
2266 /* Doubled IAC; pass it through */
2270 if (appData.debugMode)
2271 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2272 /* Drop all other telnet commands on the floor */
2275 if (oldi > next_out)
2276 SendToPlayer(&buf[next_out], oldi - next_out);
2282 /* OK, this at least will *usually* work */
2283 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2287 if (loggedOn && !intfSet) {
2288 if (ics_type == ICS_ICC) {
2290 "/set-quietly interface %s\n/set-quietly style 12\n",
2292 } else if (ics_type == ICS_CHESSNET) {
2293 sprintf(str, "/style 12\n");
2295 strcpy(str, "alias $ @\n$set interface ");
2296 strcat(str, programVersion);
2297 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2299 strcat(str, "$iset nohighlight 1\n");
2301 strcat(str, "$iset lock 1\n$style 12\n");
2304 NotifyFrontendLogin();
2308 if (started == STARTED_COMMENT) {
2309 /* Accumulate characters in comment */
2310 parse[parse_pos++] = buf[i];
2311 if (buf[i] == '\n') {
2312 parse[parse_pos] = NULLCHAR;
2313 if(chattingPartner>=0) {
2315 sprintf(mess, "%s%s", talker, parse);
2316 OutputChatMessage(chattingPartner, mess);
2317 chattingPartner = -1;
2319 if(!suppressKibitz) // [HGM] kibitz
2320 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2321 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2322 int nrDigit = 0, nrAlph = 0, i;
2323 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2324 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2325 parse[parse_pos] = NULLCHAR;
2326 // try to be smart: if it does not look like search info, it should go to
2327 // ICS interaction window after all, not to engine-output window.
2328 for(i=0; i<parse_pos; i++) { // count letters and digits
2329 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2330 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2331 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2333 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2334 int depth=0; float score;
2335 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2336 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2337 pvInfoList[forwardMostMove-1].depth = depth;
2338 pvInfoList[forwardMostMove-1].score = 100*score;
2340 OutputKibitz(suppressKibitz, parse);
2343 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2344 SendToPlayer(tmp, strlen(tmp));
2347 started = STARTED_NONE;
2349 /* Don't match patterns against characters in chatter */
2354 if (started == STARTED_CHATTER) {
2355 if (buf[i] != '\n') {
2356 /* Don't match patterns against characters in chatter */
2360 started = STARTED_NONE;
2363 /* Kludge to deal with rcmd protocol */
2364 if (firstTime && looking_at(buf, &i, "\001*")) {
2365 DisplayFatalError(&buf[1], 0, 1);
2371 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2374 if (appData.debugMode)
2375 fprintf(debugFP, "ics_type %d\n", ics_type);
2378 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2379 ics_type = ICS_FICS;
2381 if (appData.debugMode)
2382 fprintf(debugFP, "ics_type %d\n", ics_type);
2385 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2386 ics_type = ICS_CHESSNET;
2388 if (appData.debugMode)
2389 fprintf(debugFP, "ics_type %d\n", ics_type);
2394 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2395 looking_at(buf, &i, "Logging you in as \"*\"") ||
2396 looking_at(buf, &i, "will be \"*\""))) {
2397 strcpy(ics_handle, star_match[0]);
2401 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2403 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2404 DisplayIcsInteractionTitle(buf);
2405 have_set_title = TRUE;
2408 /* skip finger notes */
2409 if (started == STARTED_NONE &&
2410 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2411 (buf[i] == '1' && buf[i+1] == '0')) &&
2412 buf[i+2] == ':' && buf[i+3] == ' ') {
2413 started = STARTED_CHATTER;
2418 /* skip formula vars */
2419 if (started == STARTED_NONE &&
2420 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2421 started = STARTED_CHATTER;
2427 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2428 if (appData.autoKibitz && started == STARTED_NONE &&
2429 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2430 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2431 if(looking_at(buf, &i, "* kibitzes: ") &&
2432 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2433 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2434 suppressKibitz = TRUE;
2435 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2436 && (gameMode == IcsPlayingWhite)) ||
2437 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2438 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2439 started = STARTED_CHATTER; // own kibitz we simply discard
2441 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2442 parse_pos = 0; parse[0] = NULLCHAR;
2443 savingComment = TRUE;
2444 suppressKibitz = gameMode != IcsObserving ? 2 :
2445 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2449 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2450 started = STARTED_CHATTER;
2451 suppressKibitz = TRUE;
2453 } // [HGM] kibitz: end of patch
2455 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2457 // [HGM] chat: intercept tells by users for which we have an open chat window
2459 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2460 looking_at(buf, &i, "* whispers:") ||
2461 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2462 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2464 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2465 chattingPartner = -1;
2467 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2468 for(p=0; p<MAX_CHAT; p++) {
2469 if(channel == atoi(chatPartner[p])) {
2470 talker[0] = '['; strcat(talker, "]");
2471 chattingPartner = p; break;
2474 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2475 for(p=0; p<MAX_CHAT; p++) {
2476 if(!strcmp("WHISPER", chatPartner[p])) {
2477 talker[0] = '['; strcat(talker, "]");
2478 chattingPartner = p; break;
2481 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2482 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2484 chattingPartner = p; break;
2486 if(chattingPartner<0) i = oldi; else {
2487 started = STARTED_COMMENT;
2488 parse_pos = 0; parse[0] = NULLCHAR;
2489 savingComment = TRUE;
2490 suppressKibitz = TRUE;
2492 } // [HGM] chat: end of patch
2494 if (appData.zippyTalk || appData.zippyPlay) {
2495 /* [DM] Backup address for color zippy lines */
2499 if (loggedOn == TRUE)
2500 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2501 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2503 if (ZippyControl(buf, &i) ||
2504 ZippyConverse(buf, &i) ||
2505 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2507 if (!appData.colorize) continue;
2511 } // [DM] 'else { ' deleted
2513 /* Regular tells and says */
2514 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2515 looking_at(buf, &i, "* (your partner) tells you: ") ||
2516 looking_at(buf, &i, "* says: ") ||
2517 /* Don't color "message" or "messages" output */
2518 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2519 looking_at(buf, &i, "*. * at *:*: ") ||
2520 looking_at(buf, &i, "--* (*:*): ") ||
2521 /* Message notifications (same color as tells) */
2522 looking_at(buf, &i, "* has left a message ") ||
2523 looking_at(buf, &i, "* just sent you a message:\n") ||
2524 /* Whispers and kibitzes */
2525 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2526 looking_at(buf, &i, "* kibitzes: ") ||
2528 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2530 if (tkind == 1 && strchr(star_match[0], ':')) {
2531 /* Avoid "tells you:" spoofs in channels */
2534 if (star_match[0][0] == NULLCHAR ||
2535 strchr(star_match[0], ' ') ||
2536 (tkind == 3 && strchr(star_match[1], ' '))) {
2537 /* Reject bogus matches */
2540 if (appData.colorize) {
2541 if (oldi > next_out) {
2542 SendToPlayer(&buf[next_out], oldi - next_out);
2547 Colorize(ColorTell, FALSE);
2548 curColor = ColorTell;
2551 Colorize(ColorKibitz, FALSE);
2552 curColor = ColorKibitz;
2555 p = strrchr(star_match[1], '(');
2562 Colorize(ColorChannel1, FALSE);
2563 curColor = ColorChannel1;
2565 Colorize(ColorChannel, FALSE);
2566 curColor = ColorChannel;
2570 curColor = ColorNormal;
2574 if (started == STARTED_NONE && appData.autoComment &&
2575 (gameMode == IcsObserving ||
2576 gameMode == IcsPlayingWhite ||
2577 gameMode == IcsPlayingBlack)) {
2578 parse_pos = i - oldi;
2579 memcpy(parse, &buf[oldi], parse_pos);
2580 parse[parse_pos] = NULLCHAR;
2581 started = STARTED_COMMENT;
2582 savingComment = TRUE;
2584 started = STARTED_CHATTER;
2585 savingComment = FALSE;
2592 if (looking_at(buf, &i, "* s-shouts: ") ||
2593 looking_at(buf, &i, "* c-shouts: ")) {
2594 if (appData.colorize) {
2595 if (oldi > next_out) {
2596 SendToPlayer(&buf[next_out], oldi - next_out);
2599 Colorize(ColorSShout, FALSE);
2600 curColor = ColorSShout;
2603 started = STARTED_CHATTER;
2607 if (looking_at(buf, &i, "--->")) {
2612 if (looking_at(buf, &i, "* shouts: ") ||
2613 looking_at(buf, &i, "--> ")) {
2614 if (appData.colorize) {
2615 if (oldi > next_out) {
2616 SendToPlayer(&buf[next_out], oldi - next_out);
2619 Colorize(ColorShout, FALSE);
2620 curColor = ColorShout;
2623 started = STARTED_CHATTER;
2627 if (looking_at( buf, &i, "Challenge:")) {
2628 if (appData.colorize) {
2629 if (oldi > next_out) {
2630 SendToPlayer(&buf[next_out], oldi - next_out);
2633 Colorize(ColorChallenge, FALSE);
2634 curColor = ColorChallenge;
2640 if (looking_at(buf, &i, "* offers you") ||
2641 looking_at(buf, &i, "* offers to be") ||
2642 looking_at(buf, &i, "* would like to") ||
2643 looking_at(buf, &i, "* requests to") ||
2644 looking_at(buf, &i, "Your opponent offers") ||
2645 looking_at(buf, &i, "Your opponent requests")) {
2647 if (appData.colorize) {
2648 if (oldi > next_out) {
2649 SendToPlayer(&buf[next_out], oldi - next_out);
2652 Colorize(ColorRequest, FALSE);
2653 curColor = ColorRequest;
2658 if (looking_at(buf, &i, "* (*) seeking")) {
2659 if (appData.colorize) {
2660 if (oldi > next_out) {
2661 SendToPlayer(&buf[next_out], oldi - next_out);
2664 Colorize(ColorSeek, FALSE);
2665 curColor = ColorSeek;
2670 if (looking_at(buf, &i, "\\ ")) {
2671 if (prevColor != ColorNormal) {
2672 if (oldi > next_out) {
2673 SendToPlayer(&buf[next_out], oldi - next_out);
2676 Colorize(prevColor, TRUE);
2677 curColor = prevColor;
2679 if (savingComment) {
2680 parse_pos = i - oldi;
2681 memcpy(parse, &buf[oldi], parse_pos);
2682 parse[parse_pos] = NULLCHAR;
2683 started = STARTED_COMMENT;
2685 started = STARTED_CHATTER;
2690 if (looking_at(buf, &i, "Black Strength :") ||
2691 looking_at(buf, &i, "<<< style 10 board >>>") ||
2692 looking_at(buf, &i, "<10>") ||
2693 looking_at(buf, &i, "#@#")) {
2694 /* Wrong board style */
2696 SendToICS(ics_prefix);
2697 SendToICS("set style 12\n");
2698 SendToICS(ics_prefix);
2699 SendToICS("refresh\n");
2703 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2705 have_sent_ICS_logon = 1;
2709 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2710 (looking_at(buf, &i, "\n<12> ") ||
2711 looking_at(buf, &i, "<12> "))) {
2713 if (oldi > next_out) {
2714 SendToPlayer(&buf[next_out], oldi - next_out);
2717 started = STARTED_BOARD;
2722 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2723 looking_at(buf, &i, "<b1> ")) {
2724 if (oldi > next_out) {
2725 SendToPlayer(&buf[next_out], oldi - next_out);
2728 started = STARTED_HOLDINGS;
2733 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2735 /* Header for a move list -- first line */
2737 switch (ics_getting_history) {
2741 case BeginningOfGame:
2742 /* User typed "moves" or "oldmoves" while we
2743 were idle. Pretend we asked for these
2744 moves and soak them up so user can step
2745 through them and/or save them.
2748 gameMode = IcsObserving;
2751 ics_getting_history = H_GOT_UNREQ_HEADER;
2753 case EditGame: /*?*/
2754 case EditPosition: /*?*/
2755 /* Should above feature work in these modes too? */
2756 /* For now it doesn't */
2757 ics_getting_history = H_GOT_UNWANTED_HEADER;
2760 ics_getting_history = H_GOT_UNWANTED_HEADER;
2765 /* Is this the right one? */
2766 if (gameInfo.white && gameInfo.black &&
2767 strcmp(gameInfo.white, star_match[0]) == 0 &&
2768 strcmp(gameInfo.black, star_match[2]) == 0) {
2770 ics_getting_history = H_GOT_REQ_HEADER;
2773 case H_GOT_REQ_HEADER:
2774 case H_GOT_UNREQ_HEADER:
2775 case H_GOT_UNWANTED_HEADER:
2776 case H_GETTING_MOVES:
2777 /* Should not happen */
2778 DisplayError(_("Error gathering move list: two headers"), 0);
2779 ics_getting_history = H_FALSE;
2783 /* Save player ratings into gameInfo if needed */
2784 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2785 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2786 (gameInfo.whiteRating == -1 ||
2787 gameInfo.blackRating == -1)) {
2789 gameInfo.whiteRating = string_to_rating(star_match[1]);
2790 gameInfo.blackRating = string_to_rating(star_match[3]);
2791 if (appData.debugMode)
2792 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2793 gameInfo.whiteRating, gameInfo.blackRating);
2798 if (looking_at(buf, &i,
2799 "* * match, initial time: * minute*, increment: * second")) {
2800 /* Header for a move list -- second line */
2801 /* Initial board will follow if this is a wild game */
2802 if (gameInfo.event != NULL) free(gameInfo.event);
2803 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2804 gameInfo.event = StrSave(str);
2805 /* [HGM] we switched variant. Translate boards if needed. */
2806 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2810 if (looking_at(buf, &i, "Move ")) {
2811 /* Beginning of a move list */
2812 switch (ics_getting_history) {
2814 /* Normally should not happen */
2815 /* Maybe user hit reset while we were parsing */
2818 /* Happens if we are ignoring a move list that is not
2819 * the one we just requested. Common if the user
2820 * tries to observe two games without turning off
2823 case H_GETTING_MOVES:
2824 /* Should not happen */
2825 DisplayError(_("Error gathering move list: nested"), 0);
2826 ics_getting_history = H_FALSE;
2828 case H_GOT_REQ_HEADER:
2829 ics_getting_history = H_GETTING_MOVES;
2830 started = STARTED_MOVES;
2832 if (oldi > next_out) {
2833 SendToPlayer(&buf[next_out], oldi - next_out);
2836 case H_GOT_UNREQ_HEADER:
2837 ics_getting_history = H_GETTING_MOVES;
2838 started = STARTED_MOVES_NOHIDE;
2841 case H_GOT_UNWANTED_HEADER:
2842 ics_getting_history = H_FALSE;
2848 if (looking_at(buf, &i, "% ") ||
2849 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2850 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2851 savingComment = FALSE;
2854 case STARTED_MOVES_NOHIDE:
2855 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2856 parse[parse_pos + i - oldi] = NULLCHAR;
2857 ParseGameHistory(parse);
2859 if (appData.zippyPlay && first.initDone) {
2860 FeedMovesToProgram(&first, forwardMostMove);
2861 if (gameMode == IcsPlayingWhite) {
2862 if (WhiteOnMove(forwardMostMove)) {
2863 if (first.sendTime) {
2864 if (first.useColors) {
2865 SendToProgram("black\n", &first);
2867 SendTimeRemaining(&first, TRUE);
2869 if (first.useColors) {
2870 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2872 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2873 first.maybeThinking = TRUE;
2875 if (first.usePlayother) {
2876 if (first.sendTime) {
2877 SendTimeRemaining(&first, TRUE);
2879 SendToProgram("playother\n", &first);
2885 } else if (gameMode == IcsPlayingBlack) {
2886 if (!WhiteOnMove(forwardMostMove)) {
2887 if (first.sendTime) {
2888 if (first.useColors) {
2889 SendToProgram("white\n", &first);
2891 SendTimeRemaining(&first, FALSE);
2893 if (first.useColors) {
2894 SendToProgram("black\n", &first);
2896 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2897 first.maybeThinking = TRUE;
2899 if (first.usePlayother) {
2900 if (first.sendTime) {
2901 SendTimeRemaining(&first, FALSE);
2903 SendToProgram("playother\n", &first);
2912 if (gameMode == IcsObserving && ics_gamenum == -1) {
2913 /* Moves came from oldmoves or moves command
2914 while we weren't doing anything else.
2916 currentMove = forwardMostMove;
2917 ClearHighlights();/*!!could figure this out*/
2918 flipView = appData.flipView;
2919 DrawPosition(TRUE, boards[currentMove]);
2920 DisplayBothClocks();
2921 sprintf(str, "%s vs. %s",
2922 gameInfo.white, gameInfo.black);
2926 /* Moves were history of an active game */
2927 if (gameInfo.resultDetails != NULL) {
2928 free(gameInfo.resultDetails);
2929 gameInfo.resultDetails = NULL;
2932 HistorySet(parseList, backwardMostMove,
2933 forwardMostMove, currentMove-1);
2934 DisplayMove(currentMove - 1);
2935 if (started == STARTED_MOVES) next_out = i;
2936 started = STARTED_NONE;
2937 ics_getting_history = H_FALSE;
2940 case STARTED_OBSERVE:
2941 started = STARTED_NONE;
2942 SendToICS(ics_prefix);
2943 SendToICS("refresh\n");
2949 if(bookHit) { // [HGM] book: simulate book reply
2950 static char bookMove[MSG_SIZ]; // a bit generous?
2952 programStats.nodes = programStats.depth = programStats.time =
2953 programStats.score = programStats.got_only_move = 0;
2954 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2956 strcpy(bookMove, "move ");
2957 strcat(bookMove, bookHit);
2958 HandleMachineMove(bookMove, &first);
2963 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2964 started == STARTED_HOLDINGS ||
2965 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2966 /* Accumulate characters in move list or board */
2967 parse[parse_pos++] = buf[i];
2970 /* Start of game messages. Mostly we detect start of game
2971 when the first board image arrives. On some versions
2972 of the ICS, though, we need to do a "refresh" after starting
2973 to observe in order to get the current board right away. */
2974 if (looking_at(buf, &i, "Adding game * to observation list")) {
2975 started = STARTED_OBSERVE;
2979 /* Handle auto-observe */
2980 if (appData.autoObserve &&
2981 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2982 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2984 /* Choose the player that was highlighted, if any. */
2985 if (star_match[0][0] == '\033' ||
2986 star_match[1][0] != '\033') {
2987 player = star_match[0];
2989 player = star_match[2];
2991 sprintf(str, "%sobserve %s\n",
2992 ics_prefix, StripHighlightAndTitle(player));
2995 /* Save ratings from notify string */
2996 strcpy(player1Name, star_match[0]);
2997 player1Rating = string_to_rating(star_match[1]);
2998 strcpy(player2Name, star_match[2]);
2999 player2Rating = string_to_rating(star_match[3]);
3001 if (appData.debugMode)
3003 "Ratings from 'Game notification:' %s %d, %s %d\n",
3004 player1Name, player1Rating,
3005 player2Name, player2Rating);
3010 /* Deal with automatic examine mode after a game,
3011 and with IcsObserving -> IcsExamining transition */
3012 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3013 looking_at(buf, &i, "has made you an examiner of game *")) {
3015 int gamenum = atoi(star_match[0]);
3016 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3017 gamenum == ics_gamenum) {
3018 /* We were already playing or observing this game;
3019 no need to refetch history */
3020 gameMode = IcsExamining;
3022 pauseExamForwardMostMove = forwardMostMove;
3023 } else if (currentMove < forwardMostMove) {
3024 ForwardInner(forwardMostMove);
3027 /* I don't think this case really can happen */
3028 SendToICS(ics_prefix);
3029 SendToICS("refresh\n");
3034 /* Error messages */
3035 // if (ics_user_moved) {
3036 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3037 if (looking_at(buf, &i, "Illegal move") ||
3038 looking_at(buf, &i, "Not a legal move") ||
3039 looking_at(buf, &i, "Your king is in check") ||
3040 looking_at(buf, &i, "It isn't your turn") ||
3041 looking_at(buf, &i, "It is not your move")) {
3043 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3044 currentMove = --forwardMostMove;
3045 DisplayMove(currentMove - 1); /* before DMError */
3046 DrawPosition(FALSE, boards[currentMove]);
3048 DisplayBothClocks();
3050 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3056 if (looking_at(buf, &i, "still have time") ||
3057 looking_at(buf, &i, "not out of time") ||
3058 looking_at(buf, &i, "either player is out of time") ||
3059 looking_at(buf, &i, "has timeseal; checking")) {
3060 /* We must have called his flag a little too soon */
3061 whiteFlag = blackFlag = FALSE;
3065 if (looking_at(buf, &i, "added * seconds to") ||
3066 looking_at(buf, &i, "seconds were added to")) {
3067 /* Update the clocks */
3068 SendToICS(ics_prefix);
3069 SendToICS("refresh\n");
3073 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3074 ics_clock_paused = TRUE;
3079 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3080 ics_clock_paused = FALSE;
3085 /* Grab player ratings from the Creating: message.
3086 Note we have to check for the special case when
3087 the ICS inserts things like [white] or [black]. */
3088 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3089 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3091 0 player 1 name (not necessarily white)
3093 2 empty, white, or black (IGNORED)
3094 3 player 2 name (not necessarily black)
3097 The names/ratings are sorted out when the game
3098 actually starts (below).
3100 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3101 player1Rating = string_to_rating(star_match[1]);
3102 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3103 player2Rating = string_to_rating(star_match[4]);
3105 if (appData.debugMode)
3107 "Ratings from 'Creating:' %s %d, %s %d\n",
3108 player1Name, player1Rating,
3109 player2Name, player2Rating);
3114 /* Improved generic start/end-of-game messages */
3115 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3116 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3117 /* If tkind == 0: */
3118 /* star_match[0] is the game number */
3119 /* [1] is the white player's name */
3120 /* [2] is the black player's name */
3121 /* For end-of-game: */
3122 /* [3] is the reason for the game end */
3123 /* [4] is a PGN end game-token, preceded by " " */
3124 /* For start-of-game: */
3125 /* [3] begins with "Creating" or "Continuing" */
3126 /* [4] is " *" or empty (don't care). */
3127 int gamenum = atoi(star_match[0]);
3128 char *whitename, *blackname, *why, *endtoken;
3129 ChessMove endtype = (ChessMove) 0;
3132 whitename = star_match[1];
3133 blackname = star_match[2];
3134 why = star_match[3];
3135 endtoken = star_match[4];
3137 whitename = star_match[1];
3138 blackname = star_match[3];
3139 why = star_match[5];
3140 endtoken = star_match[6];
3143 /* Game start messages */
3144 if (strncmp(why, "Creating ", 9) == 0 ||
3145 strncmp(why, "Continuing ", 11) == 0) {
3146 gs_gamenum = gamenum;
3147 strcpy(gs_kind, strchr(why, ' ') + 1);
3149 if (appData.zippyPlay) {
3150 ZippyGameStart(whitename, blackname);
3156 /* Game end messages */
3157 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3158 ics_gamenum != gamenum) {
3161 while (endtoken[0] == ' ') endtoken++;
3162 switch (endtoken[0]) {
3165 endtype = GameUnfinished;
3168 endtype = BlackWins;
3171 if (endtoken[1] == '/')
3172 endtype = GameIsDrawn;
3174 endtype = WhiteWins;
3177 GameEnds(endtype, why, GE_ICS);
3179 if (appData.zippyPlay && first.initDone) {
3180 ZippyGameEnd(endtype, why);
3181 if (first.pr == NULL) {
3182 /* Start the next process early so that we'll
3183 be ready for the next challenge */
3184 StartChessProgram(&first);
3186 /* Send "new" early, in case this command takes
3187 a long time to finish, so that we'll be ready
3188 for the next challenge. */
3189 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3196 if (looking_at(buf, &i, "Removing game * from observation") ||
3197 looking_at(buf, &i, "no longer observing game *") ||
3198 looking_at(buf, &i, "Game * (*) has no examiners")) {
3199 if (gameMode == IcsObserving &&
3200 atoi(star_match[0]) == ics_gamenum)
3202 /* icsEngineAnalyze */
3203 if (appData.icsEngineAnalyze) {
3210 ics_user_moved = FALSE;
3215 if (looking_at(buf, &i, "no longer examining game *")) {
3216 if (gameMode == IcsExamining &&
3217 atoi(star_match[0]) == ics_gamenum)
3221 ics_user_moved = FALSE;
3226 /* Advance leftover_start past any newlines we find,
3227 so only partial lines can get reparsed */
3228 if (looking_at(buf, &i, "\n")) {
3229 prevColor = curColor;
3230 if (curColor != ColorNormal) {
3231 if (oldi > next_out) {
3232 SendToPlayer(&buf[next_out], oldi - next_out);
3235 Colorize(ColorNormal, FALSE);
3236 curColor = ColorNormal;
3238 if (started == STARTED_BOARD) {
3239 started = STARTED_NONE;
3240 parse[parse_pos] = NULLCHAR;
3241 ParseBoard12(parse);
3244 /* Send premove here */
3245 if (appData.premove) {
3247 if (currentMove == 0 &&
3248 gameMode == IcsPlayingWhite &&
3249 appData.premoveWhite) {
3250 sprintf(str, "%s\n", appData.premoveWhiteText);
3251 if (appData.debugMode)
3252 fprintf(debugFP, "Sending premove:\n");
3254 } else if (currentMove == 1 &&
3255 gameMode == IcsPlayingBlack &&
3256 appData.premoveBlack) {
3257 sprintf(str, "%s\n", appData.premoveBlackText);
3258 if (appData.debugMode)
3259 fprintf(debugFP, "Sending premove:\n");
3261 } else if (gotPremove) {
3263 ClearPremoveHighlights();
3264 if (appData.debugMode)
3265 fprintf(debugFP, "Sending premove:\n");
3266 UserMoveEvent(premoveFromX, premoveFromY,
3267 premoveToX, premoveToY,
3272 /* Usually suppress following prompt */
3273 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3274 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3275 if (looking_at(buf, &i, "*% ")) {
3276 savingComment = FALSE;
3280 } else if (started == STARTED_HOLDINGS) {
3282 char new_piece[MSG_SIZ];
3283 started = STARTED_NONE;
3284 parse[parse_pos] = NULLCHAR;
3285 if (appData.debugMode)
3286 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3287 parse, currentMove);
3288 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3289 gamenum == ics_gamenum) {
3290 if (gameInfo.variant == VariantNormal) {
3291 /* [HGM] We seem to switch variant during a game!
3292 * Presumably no holdings were displayed, so we have
3293 * to move the position two files to the right to
3294 * create room for them!
3296 VariantClass newVariant;
3297 switch(gameInfo.boardWidth) { // base guess on board width
3298 case 9: newVariant = VariantShogi; break;
3299 case 10: newVariant = VariantGreat; break;
3300 default: newVariant = VariantCrazyhouse; break;
3302 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3303 /* Get a move list just to see the header, which
3304 will tell us whether this is really bug or zh */
3305 if (ics_getting_history == H_FALSE) {
3306 ics_getting_history = H_REQUESTED;
3307 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3311 new_piece[0] = NULLCHAR;
3312 sscanf(parse, "game %d white [%s black [%s <- %s",
3313 &gamenum, white_holding, black_holding,
3315 white_holding[strlen(white_holding)-1] = NULLCHAR;
3316 black_holding[strlen(black_holding)-1] = NULLCHAR;
3317 /* [HGM] copy holdings to board holdings area */
3318 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3319 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3320 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3322 if (appData.zippyPlay && first.initDone) {
3323 ZippyHoldings(white_holding, black_holding,
3327 if (tinyLayout || smallLayout) {
3328 char wh[16], bh[16];
3329 PackHolding(wh, white_holding);
3330 PackHolding(bh, black_holding);
3331 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3332 gameInfo.white, gameInfo.black);
3334 sprintf(str, "%s [%s] vs. %s [%s]",
3335 gameInfo.white, white_holding,
3336 gameInfo.black, black_holding);
3339 DrawPosition(FALSE, boards[currentMove]);
3342 /* Suppress following prompt */
3343 if (looking_at(buf, &i, "*% ")) {
3344 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3345 savingComment = FALSE;
3352 i++; /* skip unparsed character and loop back */
3355 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3356 started != STARTED_HOLDINGS && i > next_out) {
3357 SendToPlayer(&buf[next_out], i - next_out);
3360 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3362 leftover_len = buf_len - leftover_start;
3363 /* if buffer ends with something we couldn't parse,
3364 reparse it after appending the next read */
3366 } else if (count == 0) {
3367 RemoveInputSource(isr);
3368 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3370 DisplayFatalError(_("Error reading from ICS"), error, 1);
3375 /* Board style 12 looks like this:
3377 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3379 * The "<12> " is stripped before it gets to this routine. The two
3380 * trailing 0's (flip state and clock ticking) are later addition, and
3381 * some chess servers may not have them, or may have only the first.
3382 * Additional trailing fields may be added in the future.
3385 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3387 #define RELATION_OBSERVING_PLAYED 0
3388 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3389 #define RELATION_PLAYING_MYMOVE 1
3390 #define RELATION_PLAYING_NOTMYMOVE -1
3391 #define RELATION_EXAMINING 2
3392 #define RELATION_ISOLATED_BOARD -3
3393 #define RELATION_STARTING_POSITION -4 /* FICS only */
3396 ParseBoard12(string)
3399 GameMode newGameMode;
3400 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3401 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3402 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3403 char to_play, board_chars[200];
3404 char move_str[500], str[500], elapsed_time[500];
3405 char black[32], white[32];
3407 int prevMove = currentMove;
3410 int fromX, fromY, toX, toY;
3412 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3413 char *bookHit = NULL; // [HGM] book
3414 Boolean weird = FALSE, reqFlag = FALSE;
3416 fromX = fromY = toX = toY = -1;
3420 if (appData.debugMode)
3421 fprintf(debugFP, _("Parsing board: %s\n"), string);
3423 move_str[0] = NULLCHAR;
3424 elapsed_time[0] = NULLCHAR;
3425 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3427 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3428 if(string[i] == ' ') { ranks++; files = 0; }
3430 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3433 for(j = 0; j <i; j++) board_chars[j] = string[j];
3434 board_chars[i] = '\0';
3437 n = sscanf(string, PATTERN, &to_play, &double_push,
3438 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3439 &gamenum, white, black, &relation, &basetime, &increment,
3440 &white_stren, &black_stren, &white_time, &black_time,
3441 &moveNum, str, elapsed_time, move_str, &ics_flip,
3445 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3446 DisplayError(str, 0);
3450 /* Convert the move number to internal form */
3451 moveNum = (moveNum - 1) * 2;
3452 if (to_play == 'B') moveNum++;
3453 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3454 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3460 case RELATION_OBSERVING_PLAYED:
3461 case RELATION_OBSERVING_STATIC:
3462 if (gamenum == -1) {
3463 /* Old ICC buglet */
3464 relation = RELATION_OBSERVING_STATIC;
3466 newGameMode = IcsObserving;
3468 case RELATION_PLAYING_MYMOVE:
3469 case RELATION_PLAYING_NOTMYMOVE:
3471 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3472 IcsPlayingWhite : IcsPlayingBlack;
3474 case RELATION_EXAMINING:
3475 newGameMode = IcsExamining;
3477 case RELATION_ISOLATED_BOARD:
3479 /* Just display this board. If user was doing something else,
3480 we will forget about it until the next board comes. */
3481 newGameMode = IcsIdle;
3483 case RELATION_STARTING_POSITION:
3484 newGameMode = gameMode;
3488 /* Modify behavior for initial board display on move listing
3491 switch (ics_getting_history) {
3495 case H_GOT_REQ_HEADER:
3496 case H_GOT_UNREQ_HEADER:
3497 /* This is the initial position of the current game */
3498 gamenum = ics_gamenum;
3499 moveNum = 0; /* old ICS bug workaround */
3500 if (to_play == 'B') {
3501 startedFromSetupPosition = TRUE;
3502 blackPlaysFirst = TRUE;
3504 if (forwardMostMove == 0) forwardMostMove = 1;
3505 if (backwardMostMove == 0) backwardMostMove = 1;
3506 if (currentMove == 0) currentMove = 1;
3508 newGameMode = gameMode;
3509 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3511 case H_GOT_UNWANTED_HEADER:
3512 /* This is an initial board that we don't want */
3514 case H_GETTING_MOVES:
3515 /* Should not happen */
3516 DisplayError(_("Error gathering move list: extra board"), 0);
3517 ics_getting_history = H_FALSE;
3521 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3522 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3523 /* [HGM] We seem to have switched variant unexpectedly
3524 * Try to guess new variant from board size
3526 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3527 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3528 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3529 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3530 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3531 if(!weird) newVariant = VariantNormal;
3532 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3533 /* Get a move list just to see the header, which
3534 will tell us whether this is really bug or zh */
3535 if (ics_getting_history == H_FALSE) {
3536 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3537 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3542 /* Take action if this is the first board of a new game, or of a
3543 different game than is currently being displayed. */
3544 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3545 relation == RELATION_ISOLATED_BOARD) {
3547 /* Forget the old game and get the history (if any) of the new one */
3548 if (gameMode != BeginningOfGame) {
3552 if (appData.autoRaiseBoard) BoardToTop();
3554 if (gamenum == -1) {
3555 newGameMode = IcsIdle;
3556 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3557 appData.getMoveList && !reqFlag) {
3558 /* Need to get game history */
3559 ics_getting_history = H_REQUESTED;
3560 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3564 /* Initially flip the board to have black on the bottom if playing
3565 black or if the ICS flip flag is set, but let the user change
3566 it with the Flip View button. */
3567 flipView = appData.autoFlipView ?
3568 (newGameMode == IcsPlayingBlack) || ics_flip :
3571 /* Done with values from previous mode; copy in new ones */
3572 gameMode = newGameMode;
3574 ics_gamenum = gamenum;
3575 if (gamenum == gs_gamenum) {
3576 int klen = strlen(gs_kind);
3577 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3578 sprintf(str, "ICS %s", gs_kind);
3579 gameInfo.event = StrSave(str);
3581 gameInfo.event = StrSave("ICS game");
3583 gameInfo.site = StrSave(appData.icsHost);
3584 gameInfo.date = PGNDate();
3585 gameInfo.round = StrSave("-");
3586 gameInfo.white = StrSave(white);
3587 gameInfo.black = StrSave(black);
3588 timeControl = basetime * 60 * 1000;
3590 timeIncrement = increment * 1000;
3591 movesPerSession = 0;
3592 gameInfo.timeControl = TimeControlTagValue();
3593 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3594 if (appData.debugMode) {
3595 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3596 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3597 setbuf(debugFP, NULL);
3600 gameInfo.outOfBook = NULL;
3602 /* Do we have the ratings? */
3603 if (strcmp(player1Name, white) == 0 &&
3604 strcmp(player2Name, black) == 0) {
3605 if (appData.debugMode)
3606 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3607 player1Rating, player2Rating);
3608 gameInfo.whiteRating = player1Rating;
3609 gameInfo.blackRating = player2Rating;
3610 } else if (strcmp(player2Name, white) == 0 &&
3611 strcmp(player1Name, black) == 0) {
3612 if (appData.debugMode)
3613 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3614 player2Rating, player1Rating);
3615 gameInfo.whiteRating = player2Rating;
3616 gameInfo.blackRating = player1Rating;
3618 player1Name[0] = player2Name[0] = NULLCHAR;
3620 /* Silence shouts if requested */
3621 if (appData.quietPlay &&
3622 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3623 SendToICS(ics_prefix);
3624 SendToICS("set shout 0\n");
3628 /* Deal with midgame name changes */
3630 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3631 if (gameInfo.white) free(gameInfo.white);
3632 gameInfo.white = StrSave(white);
3634 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3635 if (gameInfo.black) free(gameInfo.black);
3636 gameInfo.black = StrSave(black);
3640 /* Throw away game result if anything actually changes in examine mode */
3641 if (gameMode == IcsExamining && !newGame) {
3642 gameInfo.result = GameUnfinished;
3643 if (gameInfo.resultDetails != NULL) {
3644 free(gameInfo.resultDetails);
3645 gameInfo.resultDetails = NULL;
3649 /* In pausing && IcsExamining mode, we ignore boards coming
3650 in if they are in a different variation than we are. */
3651 if (pauseExamInvalid) return;
3652 if (pausing && gameMode == IcsExamining) {
3653 if (moveNum <= pauseExamForwardMostMove) {
3654 pauseExamInvalid = TRUE;
3655 forwardMostMove = pauseExamForwardMostMove;
3660 if (appData.debugMode) {
3661 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3663 /* Parse the board */
3664 for (k = 0; k < ranks; k++) {
3665 for (j = 0; j < files; j++)
3666 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3667 if(gameInfo.holdingsWidth > 1) {
3668 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3669 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3672 CopyBoard(boards[moveNum], board);
3673 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3675 startedFromSetupPosition =
3676 !CompareBoards(board, initialPosition);
3677 if(startedFromSetupPosition)
3678 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3681 /* [HGM] Set castling rights. Take the outermost Rooks,
3682 to make it also work for FRC opening positions. Note that board12
3683 is really defective for later FRC positions, as it has no way to
3684 indicate which Rook can castle if they are on the same side of King.
3685 For the initial position we grant rights to the outermost Rooks,
3686 and remember thos rights, and we then copy them on positions
3687 later in an FRC game. This means WB might not recognize castlings with
3688 Rooks that have moved back to their original position as illegal,
3689 but in ICS mode that is not its job anyway.
3691 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3692 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3694 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3695 if(board[0][i] == WhiteRook) j = i;
3696 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3697 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3698 if(board[0][i] == WhiteRook) j = i;
3699 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3700 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3701 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3702 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3703 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3704 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3705 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3708 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3709 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3710 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3711 if(board[BOARD_HEIGHT-1][k] == bKing)
3712 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3714 r = boards[moveNum][CASTLING][0] = initialRights[0];
3715 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3716 r = boards[moveNum][CASTLING][1] = initialRights[1];
3717 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3718 r = boards[moveNum][CASTLING][3] = initialRights[3];
3719 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3720 r = boards[moveNum][CASTLING][4] = initialRights[4];
3721 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3722 /* wildcastle kludge: always assume King has rights */
3723 r = boards[moveNum][CASTLING][2] = initialRights[2];
3724 r = boards[moveNum][CASTLING][5] = initialRights[5];
3726 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3727 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3730 if (ics_getting_history == H_GOT_REQ_HEADER ||
3731 ics_getting_history == H_GOT_UNREQ_HEADER) {
3732 /* This was an initial position from a move list, not
3733 the current position */
3737 /* Update currentMove and known move number limits */
3738 newMove = newGame || moveNum > forwardMostMove;
3741 forwardMostMove = backwardMostMove = currentMove = moveNum;
3742 if (gameMode == IcsExamining && moveNum == 0) {
3743 /* Workaround for ICS limitation: we are not told the wild
3744 type when starting to examine a game. But if we ask for
3745 the move list, the move list header will tell us */
3746 ics_getting_history = H_REQUESTED;
3747 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3750 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3751 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3753 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3754 /* [HGM] applied this also to an engine that is silently watching */
3755 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3756 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3757 gameInfo.variant == currentlyInitializedVariant) {
3758 takeback = forwardMostMove - moveNum;
3759 for (i = 0; i < takeback; i++) {
3760 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3761 SendToProgram("undo\n", &first);
3766 forwardMostMove = moveNum;
3767 if (!pausing || currentMove > forwardMostMove)
3768 currentMove = forwardMostMove;
3770 /* New part of history that is not contiguous with old part */
3771 if (pausing && gameMode == IcsExamining) {
3772 pauseExamInvalid = TRUE;
3773 forwardMostMove = pauseExamForwardMostMove;
3776 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3778 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3779 // [HGM] when we will receive the move list we now request, it will be
3780 // fed to the engine from the first move on. So if the engine is not
3781 // in the initial position now, bring it there.
3782 InitChessProgram(&first, 0);
3785 ics_getting_history = H_REQUESTED;
3786 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3789 forwardMostMove = backwardMostMove = currentMove = moveNum;
3792 /* Update the clocks */
3793 if (strchr(elapsed_time, '.')) {
3795 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3796 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3798 /* Time is in seconds */
3799 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3800 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3805 if (appData.zippyPlay && newGame &&
3806 gameMode != IcsObserving && gameMode != IcsIdle &&
3807 gameMode != IcsExamining)
3808 ZippyFirstBoard(moveNum, basetime, increment);
3811 /* Put the move on the move list, first converting
3812 to canonical algebraic form. */
3814 if (appData.debugMode) {
3815 if (appData.debugMode) { int f = forwardMostMove;
3816 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3817 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3818 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3820 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3821 fprintf(debugFP, "moveNum = %d\n", moveNum);
3822 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3823 setbuf(debugFP, NULL);
3825 if (moveNum <= backwardMostMove) {
3826 /* We don't know what the board looked like before
3828 strcpy(parseList[moveNum - 1], move_str);
3829 strcat(parseList[moveNum - 1], " ");
3830 strcat(parseList[moveNum - 1], elapsed_time);
3831 moveList[moveNum - 1][0] = NULLCHAR;
3832 } else if (strcmp(move_str, "none") == 0) {
3833 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3834 /* Again, we don't know what the board looked like;
3835 this is really the start of the game. */
3836 parseList[moveNum - 1][0] = NULLCHAR;
3837 moveList[moveNum - 1][0] = NULLCHAR;
3838 backwardMostMove = moveNum;
3839 startedFromSetupPosition = TRUE;
3840 fromX = fromY = toX = toY = -1;
3842 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3843 // So we parse the long-algebraic move string in stead of the SAN move
3844 int valid; char buf[MSG_SIZ], *prom;
3846 // str looks something like "Q/a1-a2"; kill the slash
3848 sprintf(buf, "%c%s", str[0], str+2);
3849 else strcpy(buf, str); // might be castling
3850 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3851 strcat(buf, prom); // long move lacks promo specification!
3852 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3853 if(appData.debugMode)
3854 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3855 strcpy(move_str, buf);
3857 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3858 &fromX, &fromY, &toX, &toY, &promoChar)
3859 || ParseOneMove(buf, moveNum - 1, &moveType,
3860 &fromX, &fromY, &toX, &toY, &promoChar);
3861 // end of long SAN patch
3863 (void) CoordsToAlgebraic(boards[moveNum - 1],
3864 PosFlags(moveNum - 1),
3865 fromY, fromX, toY, toX, promoChar,
3866 parseList[moveNum-1]);
3867 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3873 if(gameInfo.variant != VariantShogi)
3874 strcat(parseList[moveNum - 1], "+");
3877 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3878 strcat(parseList[moveNum - 1], "#");
3881 strcat(parseList[moveNum - 1], " ");
3882 strcat(parseList[moveNum - 1], elapsed_time);
3883 /* currentMoveString is set as a side-effect of ParseOneMove */
3884 strcpy(moveList[moveNum - 1], currentMoveString);
3885 strcat(moveList[moveNum - 1], "\n");
3887 /* Move from ICS was illegal!? Punt. */
3888 if (appData.debugMode) {
3889 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3890 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3892 strcpy(parseList[moveNum - 1], move_str);
3893 strcat(parseList[moveNum - 1], " ");
3894 strcat(parseList[moveNum - 1], elapsed_time);
3895 moveList[moveNum - 1][0] = NULLCHAR;
3896 fromX = fromY = toX = toY = -1;
3899 if (appData.debugMode) {
3900 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3901 setbuf(debugFP, NULL);
3905 /* Send move to chess program (BEFORE animating it). */
3906 if (appData.zippyPlay && !newGame && newMove &&
3907 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3909 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3910 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3911 if (moveList[moveNum - 1][0] == NULLCHAR) {
3912 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3914 DisplayError(str, 0);
3916 if (first.sendTime) {
3917 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3919 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3920 if (firstMove && !bookHit) {
3922 if (first.useColors) {
3923 SendToProgram(gameMode == IcsPlayingWhite ?
3925 "black\ngo\n", &first);
3927 SendToProgram("go\n", &first);
3929 first.maybeThinking = TRUE;
3932 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3933 if (moveList[moveNum - 1][0] == NULLCHAR) {
3934 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3935 DisplayError(str, 0);
3937 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3938 SendMoveToProgram(moveNum - 1, &first);
3945 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3946 /* If move comes from a remote source, animate it. If it
3947 isn't remote, it will have already been animated. */
3948 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3949 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3951 if (!pausing && appData.highlightLastMove) {
3952 SetHighlights(fromX, fromY, toX, toY);
3956 /* Start the clocks */
3957 whiteFlag = blackFlag = FALSE;
3958 appData.clockMode = !(basetime == 0 && increment == 0);
3960 ics_clock_paused = TRUE;
3962 } else if (ticking == 1) {
3963 ics_clock_paused = FALSE;
3965 if (gameMode == IcsIdle ||
3966 relation == RELATION_OBSERVING_STATIC ||
3967 relation == RELATION_EXAMINING ||
3969 DisplayBothClocks();
3973 /* Display opponents and material strengths */
3974 if (gameInfo.variant != VariantBughouse &&
3975 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3976 if (tinyLayout || smallLayout) {
3977 if(gameInfo.variant == VariantNormal)
3978 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3979 gameInfo.white, white_stren, gameInfo.black, black_stren,
3980 basetime, increment);
3982 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3983 gameInfo.white, white_stren, gameInfo.black, black_stren,
3984 basetime, increment, (int) gameInfo.variant);
3986 if(gameInfo.variant == VariantNormal)
3987 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3988 gameInfo.white, white_stren, gameInfo.black, black_stren,
3989 basetime, increment);
3991 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3992 gameInfo.white, white_stren, gameInfo.black, black_stren,
3993 basetime, increment, VariantName(gameInfo.variant));
3996 if (appData.debugMode) {
3997 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4002 /* Display the board */
4003 if (!pausing && !appData.noGUI) {
4004 if (appData.premove)
4006 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4007 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4008 ClearPremoveHighlights();
4010 DrawPosition(FALSE, boards[currentMove]);
4011 DisplayMove(moveNum - 1);
4012 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4013 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4014 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4015 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4019 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4021 if(bookHit) { // [HGM] book: simulate book reply
4022 static char bookMove[MSG_SIZ]; // a bit generous?
4024 programStats.nodes = programStats.depth = programStats.time =
4025 programStats.score = programStats.got_only_move = 0;
4026 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4028 strcpy(bookMove, "move ");
4029 strcat(bookMove, bookHit);
4030 HandleMachineMove(bookMove, &first);
4039 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4040 ics_getting_history = H_REQUESTED;
4041 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4047 AnalysisPeriodicEvent(force)
4050 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4051 && !force) || !appData.periodicUpdates)
4054 /* Send . command to Crafty to collect stats */
4055 SendToProgram(".\n", &first);
4057 /* Don't send another until we get a response (this makes
4058 us stop sending to old Crafty's which don't understand
4059 the "." command (sending illegal cmds resets node count & time,
4060 which looks bad)) */
4061 programStats.ok_to_send = 0;
4064 void ics_update_width(new_width)
4067 ics_printf("set width %d\n", new_width);
4071 SendMoveToProgram(moveNum, cps)
4073 ChessProgramState *cps;
4077 if (cps->useUsermove) {
4078 SendToProgram("usermove ", cps);
4082 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4083 int len = space - parseList[moveNum];
4084 memcpy(buf, parseList[moveNum], len);
4086 buf[len] = NULLCHAR;
4088 sprintf(buf, "%s\n", parseList[moveNum]);
4090 SendToProgram(buf, cps);
4092 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4093 AlphaRank(moveList[moveNum], 4);
4094 SendToProgram(moveList[moveNum], cps);
4095 AlphaRank(moveList[moveNum], 4); // and back
4097 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4098 * the engine. It would be nice to have a better way to identify castle
4100 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4101 && cps->useOOCastle) {
4102 int fromX = moveList[moveNum][0] - AAA;
4103 int fromY = moveList[moveNum][1] - ONE;
4104 int toX = moveList[moveNum][2] - AAA;
4105 int toY = moveList[moveNum][3] - ONE;
4106 if((boards[moveNum][fromY][fromX] == WhiteKing
4107 && boards[moveNum][toY][toX] == WhiteRook)
4108 || (boards[moveNum][fromY][fromX] == BlackKing
4109 && boards[moveNum][toY][toX] == BlackRook)) {
4110 if(toX > fromX) SendToProgram("O-O\n", cps);
4111 else SendToProgram("O-O-O\n", cps);
4113 else SendToProgram(moveList[moveNum], cps);
4115 else SendToProgram(moveList[moveNum], cps);
4116 /* End of additions by Tord */
4119 /* [HGM] setting up the opening has brought engine in force mode! */
4120 /* Send 'go' if we are in a mode where machine should play. */
4121 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4122 (gameMode == TwoMachinesPlay ||
4124 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4126 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4127 SendToProgram("go\n", cps);
4128 if (appData.debugMode) {
4129 fprintf(debugFP, "(extra)\n");
4132 setboardSpoiledMachineBlack = 0;
4136 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4138 int fromX, fromY, toX, toY;
4140 char user_move[MSG_SIZ];
4144 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4145 (int)moveType, fromX, fromY, toX, toY);
4146 DisplayError(user_move + strlen("say "), 0);
4148 case WhiteKingSideCastle:
4149 case BlackKingSideCastle:
4150 case WhiteQueenSideCastleWild:
4151 case BlackQueenSideCastleWild:
4153 case WhiteHSideCastleFR:
4154 case BlackHSideCastleFR:
4156 sprintf(user_move, "o-o\n");
4158 case WhiteQueenSideCastle:
4159 case BlackQueenSideCastle:
4160 case WhiteKingSideCastleWild:
4161 case BlackKingSideCastleWild:
4163 case WhiteASideCastleFR:
4164 case BlackASideCastleFR:
4166 sprintf(user_move, "o-o-o\n");
4168 case WhitePromotionQueen:
4169 case BlackPromotionQueen:
4170 case WhitePromotionRook:
4171 case BlackPromotionRook:
4172 case WhitePromotionBishop:
4173 case BlackPromotionBishop:
4174 case WhitePromotionKnight:
4175 case BlackPromotionKnight:
4176 case WhitePromotionKing:
4177 case BlackPromotionKing:
4178 case WhitePromotionChancellor:
4179 case BlackPromotionChancellor:
4180 case WhitePromotionArchbishop:
4181 case BlackPromotionArchbishop:
4182 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4183 sprintf(user_move, "%c%c%c%c=%c\n",
4184 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4185 PieceToChar(WhiteFerz));
4186 else if(gameInfo.variant == VariantGreat)
4187 sprintf(user_move, "%c%c%c%c=%c\n",
4188 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189 PieceToChar(WhiteMan));
4191 sprintf(user_move, "%c%c%c%c=%c\n",
4192 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193 PieceToChar(PromoPiece(moveType)));
4197 sprintf(user_move, "%c@%c%c\n",
4198 ToUpper(PieceToChar((ChessSquare) fromX)),
4199 AAA + toX, ONE + toY);
4202 case WhiteCapturesEnPassant:
4203 case BlackCapturesEnPassant:
4204 case IllegalMove: /* could be a variant we don't quite understand */
4205 sprintf(user_move, "%c%c%c%c\n",
4206 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4209 SendToICS(user_move);
4210 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4211 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4215 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4220 if (rf == DROP_RANK) {
4221 sprintf(move, "%c@%c%c\n",
4222 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4224 if (promoChar == 'x' || promoChar == NULLCHAR) {
4225 sprintf(move, "%c%c%c%c\n",
4226 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4228 sprintf(move, "%c%c%c%c%c\n",
4229 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4235 ProcessICSInitScript(f)
4240 while (fgets(buf, MSG_SIZ, f)) {
4241 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4248 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4250 AlphaRank(char *move, int n)
4252 // char *p = move, c; int x, y;
4254 if (appData.debugMode) {
4255 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4259 move[2]>='0' && move[2]<='9' &&
4260 move[3]>='a' && move[3]<='x' ) {
4262 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4263 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4265 if(move[0]>='0' && move[0]<='9' &&
4266 move[1]>='a' && move[1]<='x' &&
4267 move[2]>='0' && move[2]<='9' &&
4268 move[3]>='a' && move[3]<='x' ) {
4269 /* input move, Shogi -> normal */
4270 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4271 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4272 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4273 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4276 move[3]>='0' && move[3]<='9' &&
4277 move[2]>='a' && move[2]<='x' ) {
4279 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4280 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4283 move[0]>='a' && move[0]<='x' &&
4284 move[3]>='0' && move[3]<='9' &&
4285 move[2]>='a' && move[2]<='x' ) {
4286 /* output move, normal -> Shogi */
4287 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4288 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4289 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4290 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4291 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4293 if (appData.debugMode) {
4294 fprintf(debugFP, " out = '%s'\n", move);
4298 /* Parser for moves from gnuchess, ICS, or user typein box */
4300 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4303 ChessMove *moveType;
4304 int *fromX, *fromY, *toX, *toY;
4307 if (appData.debugMode) {
4308 fprintf(debugFP, "move to parse: %s\n", move);
4310 *moveType = yylexstr(moveNum, move);
4312 switch (*moveType) {
4313 case WhitePromotionChancellor:
4314 case BlackPromotionChancellor:
4315 case WhitePromotionArchbishop:
4316 case BlackPromotionArchbishop:
4317 case WhitePromotionQueen:
4318 case BlackPromotionQueen:
4319 case WhitePromotionRook:
4320 case BlackPromotionRook:
4321 case WhitePromotionBishop:
4322 case BlackPromotionBishop:
4323 case WhitePromotionKnight:
4324 case BlackPromotionKnight:
4325 case WhitePromotionKing:
4326 case BlackPromotionKing:
4328 case WhiteCapturesEnPassant:
4329 case BlackCapturesEnPassant:
4330 case WhiteKingSideCastle:
4331 case WhiteQueenSideCastle:
4332 case BlackKingSideCastle:
4333 case BlackQueenSideCastle:
4334 case WhiteKingSideCastleWild:
4335 case WhiteQueenSideCastleWild:
4336 case BlackKingSideCastleWild:
4337 case BlackQueenSideCastleWild:
4338 /* Code added by Tord: */
4339 case WhiteHSideCastleFR:
4340 case WhiteASideCastleFR:
4341 case BlackHSideCastleFR:
4342 case BlackASideCastleFR:
4343 /* End of code added by Tord */
4344 case IllegalMove: /* bug or odd chess variant */
4345 *fromX = currentMoveString[0] - AAA;
4346 *fromY = currentMoveString[1] - ONE;
4347 *toX = currentMoveString[2] - AAA;
4348 *toY = currentMoveString[3] - ONE;
4349 *promoChar = currentMoveString[4];
4350 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4351 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4352 if (appData.debugMode) {
4353 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4355 *fromX = *fromY = *toX = *toY = 0;
4358 if (appData.testLegality) {
4359 return (*moveType != IllegalMove);
4361 return !(fromX == fromY && toX == toY);
4366 *fromX = *moveType == WhiteDrop ?
4367 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4368 (int) CharToPiece(ToLower(currentMoveString[0]));
4370 *toX = currentMoveString[2] - AAA;
4371 *toY = currentMoveString[3] - ONE;
4372 *promoChar = NULLCHAR;
4376 case ImpossibleMove:
4377 case (ChessMove) 0: /* end of file */
4386 if (appData.debugMode) {
4387 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4390 *fromX = *fromY = *toX = *toY = 0;
4391 *promoChar = NULLCHAR;
4396 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4397 // All positions will have equal probability, but the current method will not provide a unique
4398 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4404 int piecesLeft[(int)BlackPawn];
4405 int seed, nrOfShuffles;
4407 void GetPositionNumber()
4408 { // sets global variable seed
4411 seed = appData.defaultFrcPosition;
4412 if(seed < 0) { // randomize based on time for negative FRC position numbers
4413 for(i=0; i<50; i++) seed += random();
4414 seed = random() ^ random() >> 8 ^ random() << 8;
4415 if(seed<0) seed = -seed;
4419 int put(Board board, int pieceType, int rank, int n, int shade)
4420 // put the piece on the (n-1)-th empty squares of the given shade
4424 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4425 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4426 board[rank][i] = (ChessSquare) pieceType;
4427 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4429 piecesLeft[pieceType]--;
4437 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4438 // calculate where the next piece goes, (any empty square), and put it there
4442 i = seed % squaresLeft[shade];
4443 nrOfShuffles *= squaresLeft[shade];
4444 seed /= squaresLeft[shade];
4445 put(board, pieceType, rank, i, shade);
4448 void AddTwoPieces(Board board, int pieceType, int rank)
4449 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4451 int i, n=squaresLeft[ANY], j=n-1, k;
4453 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4454 i = seed % k; // pick one
4457 while(i >= j) i -= j--;
4458 j = n - 1 - j; i += j;
4459 put(board, pieceType, rank, j, ANY);
4460 put(board, pieceType, rank, i, ANY);
4463 void SetUpShuffle(Board board, int number)
4467 GetPositionNumber(); nrOfShuffles = 1;
4469 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4470 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4471 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4473 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4475 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4476 p = (int) board[0][i];
4477 if(p < (int) BlackPawn) piecesLeft[p] ++;
4478 board[0][i] = EmptySquare;
4481 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4482 // shuffles restricted to allow normal castling put KRR first
4483 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4484 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4485 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4486 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4487 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4488 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4489 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4490 put(board, WhiteRook, 0, 0, ANY);
4491 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4494 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4495 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4496 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4497 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4498 while(piecesLeft[p] >= 2) {
4499 AddOnePiece(board, p, 0, LITE);
4500 AddOnePiece(board, p, 0, DARK);
4502 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4505 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4506 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4507 // but we leave King and Rooks for last, to possibly obey FRC restriction
4508 if(p == (int)WhiteRook) continue;
4509 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4510 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4513 // now everything is placed, except perhaps King (Unicorn) and Rooks
4515 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4516 // Last King gets castling rights
4517 while(piecesLeft[(int)WhiteUnicorn]) {
4518 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4519 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4522 while(piecesLeft[(int)WhiteKing]) {
4523 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4524 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4529 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4530 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4533 // Only Rooks can be left; simply place them all
4534 while(piecesLeft[(int)WhiteRook]) {
4535 i = put(board, WhiteRook, 0, 0, ANY);
4536 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4539 initialRights[1] = initialRights[4] = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4541 initialRights[0] = initialRights[3] = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4544 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4545 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4548 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4551 int SetCharTable( char *table, const char * map )
4552 /* [HGM] moved here from winboard.c because of its general usefulness */
4553 /* Basically a safe strcpy that uses the last character as King */
4555 int result = FALSE; int NrPieces;
4557 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4558 && NrPieces >= 12 && !(NrPieces&1)) {
4559 int i; /* [HGM] Accept even length from 12 to 34 */
4561 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4562 for( i=0; i<NrPieces/2-1; i++ ) {
4564 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4566 table[(int) WhiteKing] = map[NrPieces/2-1];
4567 table[(int) BlackKing] = map[NrPieces-1];
4575 void Prelude(Board board)
4576 { // [HGM] superchess: random selection of exo-pieces
4577 int i, j, k; ChessSquare p;
4578 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4580 GetPositionNumber(); // use FRC position number
4582 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4583 SetCharTable(pieceToChar, appData.pieceToCharTable);
4584 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4585 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4588 j = seed%4; seed /= 4;
4589 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4590 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4591 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4592 j = seed%3 + (seed%3 >= j); seed /= 3;
4593 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4594 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4595 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4596 j = seed%3; seed /= 3;
4597 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4598 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4599 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4600 j = seed%2 + (seed%2 >= j); seed /= 2;
4601 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4602 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4603 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4604 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4605 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4606 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4607 put(board, exoPieces[0], 0, 0, ANY);
4608 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4612 InitPosition(redraw)
4615 ChessSquare (* pieces)[BOARD_FILES];
4616 int i, j, pawnRow, overrule,
4617 oldx = gameInfo.boardWidth,
4618 oldy = gameInfo.boardHeight,
4619 oldh = gameInfo.holdingsWidth,
4620 oldv = gameInfo.variant;
4622 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4624 /* [AS] Initialize pv info list [HGM] and game status */
4626 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4627 pvInfoList[i].depth = 0;
4628 boards[i][EP_STATUS] = EP_NONE;
4629 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4632 initialRulePlies = 0; /* 50-move counter start */
4634 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4635 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4639 /* [HGM] logic here is completely changed. In stead of full positions */
4640 /* the initialized data only consist of the two backranks. The switch */
4641 /* selects which one we will use, which is than copied to the Board */
4642 /* initialPosition, which for the rest is initialized by Pawns and */
4643 /* empty squares. This initial position is then copied to boards[0], */
4644 /* possibly after shuffling, so that it remains available. */
4646 gameInfo.holdingsWidth = 0; /* default board sizes */
4647 gameInfo.boardWidth = 8;
4648 gameInfo.boardHeight = 8;
4649 gameInfo.holdingsSize = 0;
4650 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4651 for(i=0; i<BOARD_FILES-2; i++)
4652 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4653 initialPosition[EP_STATUS] = EP_NONE;
4654 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4656 switch (gameInfo.variant) {
4657 case VariantFischeRandom:
4658 shuffleOpenings = TRUE;
4662 case VariantShatranj:
4663 pieces = ShatranjArray;
4664 nrCastlingRights = 0;
4665 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4667 case VariantTwoKings:
4668 pieces = twoKingsArray;
4670 case VariantCapaRandom:
4671 shuffleOpenings = TRUE;
4672 case VariantCapablanca:
4673 pieces = CapablancaArray;
4674 gameInfo.boardWidth = 10;
4675 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4678 pieces = GothicArray;
4679 gameInfo.boardWidth = 10;
4680 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4683 pieces = JanusArray;
4684 gameInfo.boardWidth = 10;
4685 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4686 nrCastlingRights = 6;
4687 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4688 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4689 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4690 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4691 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4692 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4695 pieces = FalconArray;
4696 gameInfo.boardWidth = 10;
4697 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4699 case VariantXiangqi:
4700 pieces = XiangqiArray;
4701 gameInfo.boardWidth = 9;
4702 gameInfo.boardHeight = 10;
4703 nrCastlingRights = 0;
4704 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4707 pieces = ShogiArray;
4708 gameInfo.boardWidth = 9;
4709 gameInfo.boardHeight = 9;
4710 gameInfo.holdingsSize = 7;
4711 nrCastlingRights = 0;
4712 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4714 case VariantCourier:
4715 pieces = CourierArray;
4716 gameInfo.boardWidth = 12;
4717 nrCastlingRights = 0;
4718 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4720 case VariantKnightmate:
4721 pieces = KnightmateArray;
4722 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4725 pieces = fairyArray;
4726 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4729 pieces = GreatArray;
4730 gameInfo.boardWidth = 10;
4731 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4732 gameInfo.holdingsSize = 8;
4736 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4737 gameInfo.holdingsSize = 8;
4738 startedFromSetupPosition = TRUE;
4740 case VariantCrazyhouse:
4741 case VariantBughouse:
4743 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4744 gameInfo.holdingsSize = 5;
4746 case VariantWildCastle:
4748 /* !!?shuffle with kings guaranteed to be on d or e file */
4749 shuffleOpenings = 1;
4751 case VariantNoCastle:
4753 nrCastlingRights = 0;
4754 /* !!?unconstrained back-rank shuffle */
4755 shuffleOpenings = 1;
4760 if(appData.NrFiles >= 0) {
4761 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4762 gameInfo.boardWidth = appData.NrFiles;
4764 if(appData.NrRanks >= 0) {
4765 gameInfo.boardHeight = appData.NrRanks;
4767 if(appData.holdingsSize >= 0) {
4768 i = appData.holdingsSize;
4769 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4770 gameInfo.holdingsSize = i;
4772 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4773 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4774 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4776 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4777 if(pawnRow < 1) pawnRow = 1;
4779 /* User pieceToChar list overrules defaults */
4780 if(appData.pieceToCharTable != NULL)
4781 SetCharTable(pieceToChar, appData.pieceToCharTable);
4783 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4785 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4786 s = (ChessSquare) 0; /* account holding counts in guard band */
4787 for( i=0; i<BOARD_HEIGHT; i++ )
4788 initialPosition[i][j] = s;
4790 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4791 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4792 initialPosition[pawnRow][j] = WhitePawn;
4793 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4794 if(gameInfo.variant == VariantXiangqi) {
4796 initialPosition[pawnRow][j] =
4797 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4798 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4799 initialPosition[2][j] = WhiteCannon;
4800 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4804 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4806 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4809 initialPosition[1][j] = WhiteBishop;
4810 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4812 initialPosition[1][j] = WhiteRook;
4813 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4816 if( nrCastlingRights == -1) {
4817 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4818 /* This sets default castling rights from none to normal corners */
4819 /* Variants with other castling rights must set them themselves above */
4820 nrCastlingRights = 6;
4821 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4822 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4823 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4824 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4825 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4826 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4829 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4830 if(gameInfo.variant == VariantGreat) { // promotion commoners
4831 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4832 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4833 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4834 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4836 if (appData.debugMode) {
4837 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4839 if(shuffleOpenings) {
4840 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4841 startedFromSetupPosition = TRUE;
4843 if(startedFromPositionFile) {
4844 /* [HGM] loadPos: use PositionFile for every new game */
4845 CopyBoard(initialPosition, filePosition);
4846 for(i=0; i<nrCastlingRights; i++)
4847 initialRights[i] = filePosition[CASTLING][i];
4848 startedFromSetupPosition = TRUE;
4851 CopyBoard(boards[0], initialPosition);
4852 if(oldx != gameInfo.boardWidth ||
4853 oldy != gameInfo.boardHeight ||
4854 oldh != gameInfo.holdingsWidth
4856 || oldv == VariantGothic || // For licensing popups
4857 gameInfo.variant == VariantGothic
4860 || oldv == VariantFalcon ||
4861 gameInfo.variant == VariantFalcon
4865 InitDrawingSizes(-2 ,0);
4869 DrawPosition(TRUE, boards[currentMove]);
4874 SendBoard(cps, moveNum)
4875 ChessProgramState *cps;
4878 char message[MSG_SIZ];
4880 if (cps->useSetboard) {
4881 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4882 sprintf(message, "setboard %s\n", fen);
4883 SendToProgram(message, cps);
4889 /* Kludge to set black to move, avoiding the troublesome and now
4890 * deprecated "black" command.
4892 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4894 SendToProgram("edit\n", cps);
4895 SendToProgram("#\n", cps);
4896 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4897 bp = &boards[moveNum][i][BOARD_LEFT];
4898 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4899 if ((int) *bp < (int) BlackPawn) {
4900 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4902 if(message[0] == '+' || message[0] == '~') {
4903 sprintf(message, "%c%c%c+\n",
4904 PieceToChar((ChessSquare)(DEMOTED *bp)),
4907 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4908 message[1] = BOARD_RGHT - 1 - j + '1';
4909 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4911 SendToProgram(message, cps);
4916 SendToProgram("c\n", cps);
4917 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4918 bp = &boards[moveNum][i][BOARD_LEFT];
4919 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4920 if (((int) *bp != (int) EmptySquare)
4921 && ((int) *bp >= (int) BlackPawn)) {
4922 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4924 if(message[0] == '+' || message[0] == '~') {
4925 sprintf(message, "%c%c%c+\n",
4926 PieceToChar((ChessSquare)(DEMOTED *bp)),
4929 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4930 message[1] = BOARD_RGHT - 1 - j + '1';
4931 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4933 SendToProgram(message, cps);
4938 SendToProgram(".\n", cps);
4940 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4944 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4946 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4947 /* [HGM] add Shogi promotions */
4948 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4953 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4954 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4956 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4957 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4960 piece = boards[currentMove][fromY][fromX];
4961 if(gameInfo.variant == VariantShogi) {
4962 promotionZoneSize = 3;
4963 highestPromotingPiece = (int)WhiteFerz;
4966 // next weed out all moves that do not touch the promotion zone at all
4967 if((int)piece >= BlackPawn) {
4968 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4970 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4972 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4973 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4976 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4978 // weed out mandatory Shogi promotions
4979 if(gameInfo.variant == VariantShogi) {
4980 if(piece >= BlackPawn) {
4981 if(toY == 0 && piece == BlackPawn ||
4982 toY == 0 && piece == BlackQueen ||
4983 toY <= 1 && piece == BlackKnight) {
4988 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4989 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4990 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4997 // weed out obviously illegal Pawn moves
4998 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4999 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5000 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5001 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5002 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5003 // note we are not allowed to test for valid (non-)capture, due to premove
5006 // we either have a choice what to promote to, or (in Shogi) whether to promote
5007 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5008 *promoChoice = PieceToChar(BlackFerz); // no choice
5011 if(appData.alwaysPromoteToQueen) { // predetermined
5012 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5013 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5014 else *promoChoice = PieceToChar(BlackQueen);
5018 // suppress promotion popup on illegal moves that are not premoves
5019 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5020 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5021 if(appData.testLegality && !premove) {
5022 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5023 fromY, fromX, toY, toX, NULLCHAR);
5024 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5025 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5033 InPalace(row, column)
5035 { /* [HGM] for Xiangqi */
5036 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5037 column < (BOARD_WIDTH + 4)/2 &&
5038 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5043 PieceForSquare (x, y)
5047 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5050 return boards[currentMove][y][x];
5054 OKToStartUserMove(x, y)
5057 ChessSquare from_piece;
5060 if (matchMode) return FALSE;
5061 if (gameMode == EditPosition) return TRUE;
5063 if (x >= 0 && y >= 0)
5064 from_piece = boards[currentMove][y][x];
5066 from_piece = EmptySquare;
5068 if (from_piece == EmptySquare) return FALSE;
5070 white_piece = (int)from_piece >= (int)WhitePawn &&
5071 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5074 case PlayFromGameFile:
5076 case TwoMachinesPlay:
5084 case MachinePlaysWhite:
5085 case IcsPlayingBlack:
5086 if (appData.zippyPlay) return FALSE;
5088 DisplayMoveError(_("You are playing Black"));
5093 case MachinePlaysBlack:
5094 case IcsPlayingWhite:
5095 if (appData.zippyPlay) return FALSE;
5097 DisplayMoveError(_("You are playing White"));
5103 if (!white_piece && WhiteOnMove(currentMove)) {
5104 DisplayMoveError(_("It is White's turn"));
5107 if (white_piece && !WhiteOnMove(currentMove)) {
5108 DisplayMoveError(_("It is Black's turn"));
5111 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5112 /* Editing correspondence game history */
5113 /* Could disallow this or prompt for confirmation */
5118 case BeginningOfGame:
5119 if (appData.icsActive) return FALSE;
5120 if (!appData.noChessProgram) {
5122 DisplayMoveError(_("You are playing White"));
5129 if (!white_piece && WhiteOnMove(currentMove)) {
5130 DisplayMoveError(_("It is White's turn"));
5133 if (white_piece && !WhiteOnMove(currentMove)) {
5134 DisplayMoveError(_("It is Black's turn"));
5143 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5144 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5145 && gameMode != AnalyzeFile && gameMode != Training) {
5146 DisplayMoveError(_("Displayed position is not current"));
5152 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5153 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5154 int lastLoadGameUseList = FALSE;
5155 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5156 ChessMove lastLoadGameStart = (ChessMove) 0;
5159 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5160 int fromX, fromY, toX, toY;
5165 ChessSquare pdown, pup;
5167 /* Check if the user is playing in turn. This is complicated because we
5168 let the user "pick up" a piece before it is his turn. So the piece he
5169 tried to pick up may have been captured by the time he puts it down!
5170 Therefore we use the color the user is supposed to be playing in this
5171 test, not the color of the piece that is currently on the starting
5172 square---except in EditGame mode, where the user is playing both
5173 sides; fortunately there the capture race can't happen. (It can
5174 now happen in IcsExamining mode, but that's just too bad. The user
5175 will get a somewhat confusing message in that case.)
5179 case PlayFromGameFile:
5181 case TwoMachinesPlay:
5185 /* We switched into a game mode where moves are not accepted,
5186 perhaps while the mouse button was down. */
5187 return ImpossibleMove;
5189 case MachinePlaysWhite:
5190 /* User is moving for Black */
5191 if (WhiteOnMove(currentMove)) {
5192 DisplayMoveError(_("It is White's turn"));
5193 return ImpossibleMove;
5197 case MachinePlaysBlack:
5198 /* User is moving for White */
5199 if (!WhiteOnMove(currentMove)) {
5200 DisplayMoveError(_("It is Black's turn"));
5201 return ImpossibleMove;
5207 case BeginningOfGame:
5210 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5211 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5212 /* User is moving for Black */
5213 if (WhiteOnMove(currentMove)) {
5214 DisplayMoveError(_("It is White's turn"));
5215 return ImpossibleMove;
5218 /* User is moving for White */
5219 if (!WhiteOnMove(currentMove)) {
5220 DisplayMoveError(_("It is Black's turn"));
5221 return ImpossibleMove;
5226 case IcsPlayingBlack:
5227 /* User is moving for Black */
5228 if (WhiteOnMove(currentMove)) {
5229 if (!appData.premove) {
5230 DisplayMoveError(_("It is White's turn"));
5231 } else if (toX >= 0 && toY >= 0) {
5234 premoveFromX = fromX;
5235 premoveFromY = fromY;
5236 premovePromoChar = promoChar;
5238 if (appData.debugMode)
5239 fprintf(debugFP, "Got premove: fromX %d,"
5240 "fromY %d, toX %d, toY %d\n",
5241 fromX, fromY, toX, toY);
5243 return ImpossibleMove;
5247 case IcsPlayingWhite:
5248 /* User is moving for White */
5249 if (!WhiteOnMove(currentMove)) {
5250 if (!appData.premove) {
5251 DisplayMoveError(_("It is Black's turn"));
5252 } else if (toX >= 0 && toY >= 0) {
5255 premoveFromX = fromX;
5256 premoveFromY = fromY;
5257 premovePromoChar = promoChar;
5259 if (appData.debugMode)
5260 fprintf(debugFP, "Got premove: fromX %d,"
5261 "fromY %d, toX %d, toY %d\n",
5262 fromX, fromY, toX, toY);
5264 return ImpossibleMove;
5272 /* EditPosition, empty square, or different color piece;
5273 click-click move is possible */
5274 if (toX == -2 || toY == -2) {
5275 boards[0][fromY][fromX] = EmptySquare;
5276 return AmbiguousMove;
5277 } else if (toX >= 0 && toY >= 0) {
5278 boards[0][toY][toX] = boards[0][fromY][fromX];
5279 boards[0][fromY][fromX] = EmptySquare;
5280 return AmbiguousMove;
5282 return ImpossibleMove;
5285 if(toX < 0 || toY < 0) return ImpossibleMove;
5286 pdown = boards[currentMove][fromY][fromX];
5287 pup = boards[currentMove][toY][toX];
5289 /* [HGM] If move started in holdings, it means a drop */
5290 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5291 if( pup != EmptySquare ) return ImpossibleMove;
5292 if(appData.testLegality) {
5293 /* it would be more logical if LegalityTest() also figured out
5294 * which drops are legal. For now we forbid pawns on back rank.
5295 * Shogi is on its own here...
5297 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5298 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5299 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5301 return WhiteDrop; /* Not needed to specify white or black yet */
5304 userOfferedDraw = FALSE;
5306 /* [HGM] always test for legality, to get promotion info */
5307 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5308 fromY, fromX, toY, toX, promoChar);
5309 /* [HGM] but possibly ignore an IllegalMove result */
5310 if (appData.testLegality) {
5311 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5312 DisplayMoveError(_("Illegal move"));
5313 return ImpossibleMove;
5318 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5319 function is made into one that returns an OK move type if FinishMove
5320 should be called. This to give the calling driver routine the
5321 opportunity to finish the userMove input with a promotion popup,
5322 without bothering the user with this for invalid or illegal moves */
5324 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5327 /* Common tail of UserMoveEvent and DropMenuEvent */
5329 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5331 int fromX, fromY, toX, toY;
5332 /*char*/int promoChar;
5336 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5338 // [HGM] superchess: suppress promotions to non-available piece
5339 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340 if(WhiteOnMove(currentMove))
5342 if(!boards[currentMove][k][BOARD_WIDTH-2])
5347 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5352 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5353 move type in caller when we know the move is a legal promotion */
5354 if(moveType == NormalMove && promoChar)
5355 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5357 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5358 move type in caller when we know the move is a legal promotion */
5359 if(moveType == NormalMove && promoChar)
5360 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5362 /* [HGM] convert drag-and-drop piece drops to standard form */
5363 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5365 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5366 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5367 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5368 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5369 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5370 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5371 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5375 /* [HGM] <popupFix> The following if has been moved here from
5376 UserMoveEvent(). Because it seemed to belong here (why not allow
5377 piece drops in training games?), and because it can only be
5378 performed after it is known to what we promote. */
5379 if (gameMode == Training)
5381 /* compare the move played on the board to the next move in the
5382 * game. If they match, display the move and the opponent's response.
5383 * If they don't match, display an error message.
5387 CopyBoard(testBoard, boards[currentMove]);
5388 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5390 if (CompareBoards(testBoard, boards[currentMove+1]))
5392 ForwardInner(currentMove+1);
5394 /* Autoplay the opponent's response.
5395 * if appData.animate was TRUE when Training mode was entered,
5396 * the response will be animated.
5398 saveAnimate = appData.animate;
5399 appData.animate = animateTraining;
5400 ForwardInner(currentMove+1);
5401 appData.animate = saveAnimate;
5403 /* check for the end of the game */
5404 if (currentMove >= forwardMostMove)
5406 gameMode = PlayFromGameFile;
5408 SetTrainingModeOff();
5409 DisplayInformation(_("End of game"));
5414 DisplayError(_("Incorrect move"), 0);
5419 /* Ok, now we know that the move is good, so we can kill
5420 the previous line in Analysis Mode */
5421 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5422 && currentMove < forwardMostMove) {
5423 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5426 /* If we need the chess program but it's dead, restart it */
5427 ResurrectChessProgram();
5429 /* A user move restarts a paused game*/
5433 thinkOutput[0] = NULLCHAR;
5435 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5437 if (gameMode == BeginningOfGame)
5439 if (appData.noChessProgram)
5441 gameMode = EditGame;
5447 gameMode = MachinePlaysBlack;
5450 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5454 sprintf(buf, "name %s\n", gameInfo.white);
5455 SendToProgram(buf, &first);
5462 /* Relay move to ICS or chess engine */
5463 if (appData.icsActive)
5465 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5466 gameMode == IcsExamining)
5468 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5474 if (first.sendTime && (gameMode == BeginningOfGame ||
5475 gameMode == MachinePlaysWhite ||
5476 gameMode == MachinePlaysBlack))
5478 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5480 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5482 // [HGM] book: if program might be playing, let it use book
5483 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5484 first.maybeThinking = TRUE;
5487 SendMoveToProgram(forwardMostMove-1, &first);
5488 if (currentMove == cmailOldMove + 1)
5490 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5494 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5499 switch (MateTest(boards[currentMove], PosFlags(currentMove)) )
5506 if (WhiteOnMove(currentMove)) {
5507 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5509 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5513 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5518 case MachinePlaysBlack:
5519 case MachinePlaysWhite:
5520 /* disable certain menu options while machine is thinking */
5521 SetMachineThinkingEnables();
5529 { // [HGM] book: simulate book reply
5530 static char bookMove[MSG_SIZ]; // a bit generous?
5532 programStats.nodes = programStats.depth = programStats.time =
5533 programStats.score = programStats.got_only_move = 0;
5534 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5536 strcpy(bookMove, "move ");
5537 strcat(bookMove, bookHit);
5538 HandleMachineMove(bookMove, &first);
5545 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5546 int fromX, fromY, toX, toY;
5549 /* [HGM] This routine was added to allow calling of its two logical
5550 parts from other modules in the old way. Before, UserMoveEvent()
5551 automatically called FinishMove() if the move was OK, and returned
5552 otherwise. I separated the two, in order to make it possible to
5553 slip a promotion popup in between. But that it always needs two
5554 calls, to the first part, (now called UserMoveTest() ), and to
5555 FinishMove if the first part succeeded. Calls that do not need
5556 to do anything in between, can call this routine the old way.
5558 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5559 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5560 if(moveType == AmbiguousMove)
5561 DrawPosition(FALSE, boards[currentMove]);
5562 else if(moveType != ImpossibleMove && moveType != Comment)
5563 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5566 void LeftClick(ClickType clickType, int xPix, int yPix)
5569 Boolean saveAnimate;
5570 static int second = 0, promotionChoice = 0;
5571 char promoChoice = NULLCHAR;
5573 if (clickType == Press) ErrorPopDown();
5575 x = EventToSquare(xPix, BOARD_WIDTH);
5576 y = EventToSquare(yPix, BOARD_HEIGHT);
5577 if (!flipView && y >= 0) {
5578 y = BOARD_HEIGHT - 1 - y;
5580 if (flipView && x >= 0) {
5581 x = BOARD_WIDTH - 1 - x;
5584 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5585 if(clickType == Release) return; // ignore upclick of click-click destination
5586 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5587 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5588 if(gameInfo.holdingsWidth &&
5589 (WhiteOnMove(currentMove)
5590 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5591 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5592 // click in right holdings, for determining promotion piece
5593 ChessSquare p = boards[currentMove][y][x];
5594 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5595 if(p != EmptySquare) {
5596 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5601 DrawPosition(FALSE, boards[currentMove]);
5605 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5606 if(clickType == Press
5607 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5608 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5609 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5613 if (clickType == Press) {
5615 if (OKToStartUserMove(x, y)) {
5619 DragPieceBegin(xPix, yPix);
5620 if (appData.highlightDragging) {
5621 SetHighlights(x, y, -1, -1);
5629 if (clickType == Press && gameMode != EditPosition) {
5634 // ignore off-board to clicks
5635 if(y < 0 || x < 0) return;
5637 /* Check if clicking again on the same color piece */
5638 fromP = boards[currentMove][fromY][fromX];
5639 toP = boards[currentMove][y][x];
5640 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5641 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5642 WhitePawn <= toP && toP <= WhiteKing &&
5643 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5644 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5645 (BlackPawn <= fromP && fromP <= BlackKing &&
5646 BlackPawn <= toP && toP <= BlackKing &&
5647 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5648 !(fromP == BlackKing && toP == BlackRook && frc))) {
5649 /* Clicked again on same color piece -- changed his mind */
5650 second = (x == fromX && y == fromY);
5651 if (appData.highlightDragging) {
5652 SetHighlights(x, y, -1, -1);
5656 if (OKToStartUserMove(x, y)) {
5659 DragPieceBegin(xPix, yPix);
5663 // ignore clicks on holdings
5664 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5667 if (clickType == Release && x == fromX && y == fromY) {
5668 DragPieceEnd(xPix, yPix);
5669 if (appData.animateDragging) {
5670 /* Undo animation damage if any */
5671 DrawPosition(FALSE, NULL);
5674 /* Second up/down in same square; just abort move */
5679 ClearPremoveHighlights();
5681 /* First upclick in same square; start click-click mode */
5682 SetHighlights(x, y, -1, -1);
5687 /* we now have a different from- and (possibly off-board) to-square */
5688 /* Completed move */
5691 saveAnimate = appData.animate;
5692 if (clickType == Press) {
5693 /* Finish clickclick move */
5694 if (appData.animate || appData.highlightLastMove) {
5695 SetHighlights(fromX, fromY, toX, toY);
5700 /* Finish drag move */
5701 if (appData.highlightLastMove) {
5702 SetHighlights(fromX, fromY, toX, toY);
5706 DragPieceEnd(xPix, yPix);
5707 /* Don't animate move and drag both */
5708 appData.animate = FALSE;
5711 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5712 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5715 DrawPosition(TRUE, NULL);
5719 // off-board moves should not be highlighted
5720 if(x < 0 || x < 0) ClearHighlights();
5722 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5723 SetHighlights(fromX, fromY, toX, toY);
5724 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5725 // [HGM] super: promotion to captured piece selected from holdings
5726 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5727 promotionChoice = TRUE;
5728 // kludge follows to temporarily execute move on display, without promoting yet
5729 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5730 boards[currentMove][toY][toX] = p;
5731 DrawPosition(FALSE, boards[currentMove]);
5732 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5733 boards[currentMove][toY][toX] = q;
5734 DisplayMessage("Click in holdings to choose piece", "");
5739 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5740 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5741 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5744 appData.animate = saveAnimate;
5745 if (appData.animate || appData.animateDragging) {
5746 /* Undo animation damage if needed */
5747 DrawPosition(FALSE, NULL);
5751 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5753 // char * hint = lastHint;
5754 FrontEndProgramStats stats;
5756 stats.which = cps == &first ? 0 : 1;
5757 stats.depth = cpstats->depth;
5758 stats.nodes = cpstats->nodes;
5759 stats.score = cpstats->score;
5760 stats.time = cpstats->time;
5761 stats.pv = cpstats->movelist;
5762 stats.hint = lastHint;
5763 stats.an_move_index = 0;
5764 stats.an_move_count = 0;
5766 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5767 stats.hint = cpstats->move_name;
5768 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5769 stats.an_move_count = cpstats->nr_moves;
5772 SetProgramStats( &stats );
5775 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5776 { // [HGM] book: this routine intercepts moves to simulate book replies
5777 char *bookHit = NULL;
5779 //first determine if the incoming move brings opponent into his book
5780 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5781 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5782 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5783 if(bookHit != NULL && !cps->bookSuspend) {
5784 // make sure opponent is not going to reply after receiving move to book position
5785 SendToProgram("force\n", cps);
5786 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5788 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5789 // now arrange restart after book miss
5791 // after a book hit we never send 'go', and the code after the call to this routine
5792 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5794 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5795 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5796 SendToProgram(buf, cps);
5797 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5798 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5799 SendToProgram("go\n", cps);
5800 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5801 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5802 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5803 SendToProgram("go\n", cps);
5804 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5806 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5810 ChessProgramState *savedState;
5811 void DeferredBookMove(void)
5813 if(savedState->lastPing != savedState->lastPong)
5814 ScheduleDelayedEvent(DeferredBookMove, 10);
5816 HandleMachineMove(savedMessage, savedState);
5820 HandleMachineMove(message, cps)
5822 ChessProgramState *cps;
5824 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5825 char realname[MSG_SIZ];
5826 int fromX, fromY, toX, toY;
5833 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5835 * Kludge to ignore BEL characters
5837 while (*message == '\007') message++;
5840 * [HGM] engine debug message: ignore lines starting with '#' character
5842 if(cps->debug && *message == '#') return;
5845 * Look for book output
5847 if (cps == &first && bookRequested) {
5848 if (message[0] == '\t' || message[0] == ' ') {
5849 /* Part of the book output is here; append it */
5850 strcat(bookOutput, message);
5851 strcat(bookOutput, " \n");
5853 } else if (bookOutput[0] != NULLCHAR) {
5854 /* All of book output has arrived; display it */
5855 char *p = bookOutput;
5856 while (*p != NULLCHAR) {
5857 if (*p == '\t') *p = ' ';
5860 DisplayInformation(bookOutput);
5861 bookRequested = FALSE;
5862 /* Fall through to parse the current output */
5867 * Look for machine move.
5869 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5870 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5872 /* This method is only useful on engines that support ping */
5873 if (cps->lastPing != cps->lastPong) {
5874 if (gameMode == BeginningOfGame) {
5875 /* Extra move from before last new; ignore */
5876 if (appData.debugMode) {
5877 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5880 if (appData.debugMode) {
5881 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5882 cps->which, gameMode);
5885 SendToProgram("undo\n", cps);
5891 case BeginningOfGame:
5892 /* Extra move from before last reset; ignore */
5893 if (appData.debugMode) {
5894 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5901 /* Extra move after we tried to stop. The mode test is
5902 not a reliable way of detecting this problem, but it's
5903 the best we can do on engines that don't support ping.
5905 if (appData.debugMode) {
5906 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5907 cps->which, gameMode);
5909 SendToProgram("undo\n", cps);
5912 case MachinePlaysWhite:
5913 case IcsPlayingWhite:
5914 machineWhite = TRUE;
5917 case MachinePlaysBlack:
5918 case IcsPlayingBlack:
5919 machineWhite = FALSE;
5922 case TwoMachinesPlay:
5923 machineWhite = (cps->twoMachinesColor[0] == 'w');
5926 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5927 if (appData.debugMode) {
5929 "Ignoring move out of turn by %s, gameMode %d"
5930 ", forwardMost %d\n",
5931 cps->which, gameMode, forwardMostMove);
5936 if (appData.debugMode) { int f = forwardMostMove;
5937 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5938 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5939 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5941 if(cps->alphaRank) AlphaRank(machineMove, 4);
5942 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5943 &fromX, &fromY, &toX, &toY, &promoChar)) {
5944 /* Machine move could not be parsed; ignore it. */
5945 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5946 machineMove, cps->which);
5947 DisplayError(buf1, 0);
5948 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5949 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5950 if (gameMode == TwoMachinesPlay) {
5951 GameEnds(machineWhite ? BlackWins : WhiteWins,
5957 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5958 /* So we have to redo legality test with true e.p. status here, */
5959 /* to make sure an illegal e.p. capture does not slip through, */
5960 /* to cause a forfeit on a justified illegal-move complaint */
5961 /* of the opponent. */
5962 if( gameMode==TwoMachinesPlay && appData.testLegality
5963 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5966 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5967 fromY, fromX, toY, toX, promoChar);
5968 if (appData.debugMode) {
5970 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5971 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5972 fprintf(debugFP, "castling rights\n");
5974 if(moveType == IllegalMove) {
5975 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5976 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5977 GameEnds(machineWhite ? BlackWins : WhiteWins,
5980 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5981 /* [HGM] Kludge to handle engines that send FRC-style castling
5982 when they shouldn't (like TSCP-Gothic) */
5984 case WhiteASideCastleFR:
5985 case BlackASideCastleFR:
5987 currentMoveString[2]++;
5989 case WhiteHSideCastleFR:
5990 case BlackHSideCastleFR:
5992 currentMoveString[2]--;
5994 default: ; // nothing to do, but suppresses warning of pedantic compilers
5997 hintRequested = FALSE;
5998 lastHint[0] = NULLCHAR;
5999 bookRequested = FALSE;
6000 /* Program may be pondering now */
6001 cps->maybeThinking = TRUE;
6002 if (cps->sendTime == 2) cps->sendTime = 1;
6003 if (cps->offeredDraw) cps->offeredDraw--;
6006 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6008 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6010 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6011 char buf[3*MSG_SIZ];
6013 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6014 programStats.score / 100.,
6016 programStats.time / 100.,
6017 (unsigned int)programStats.nodes,
6018 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6019 programStats.movelist);
6021 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6025 /* currentMoveString is set as a side-effect of ParseOneMove */
6026 strcpy(machineMove, currentMoveString);
6027 strcat(machineMove, "\n");
6028 strcpy(moveList[forwardMostMove], machineMove);
6030 /* [AS] Save move info and clear stats for next move */
6031 pvInfoList[ forwardMostMove ].score = programStats.score;
6032 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6033 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6034 ClearProgramStats();
6035 thinkOutput[0] = NULLCHAR;
6036 hiddenThinkOutputState = 0;
6038 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6040 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6041 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6044 while( count < adjudicateLossPlies ) {
6045 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6048 score = -score; /* Flip score for winning side */
6051 if( score > adjudicateLossThreshold ) {
6058 if( count >= adjudicateLossPlies ) {
6059 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6061 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6062 "Xboard adjudication",
6069 if( gameMode == TwoMachinesPlay ) {
6070 // [HGM] some adjudications useful with buggy engines
6071 int k, count = 0; static int bare = 1;
6072 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6075 if( appData.testLegality )
6076 { /* [HGM] Some more adjudications for obstinate engines */
6077 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6078 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6079 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6080 static int moveCount = 6;
6082 char *reason = NULL;
6084 /* Count what is on board. */
6085 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6086 { ChessSquare p = boards[forwardMostMove][i][j];
6090 { /* count B,N,R and other of each side */
6093 NrK++; break; // [HGM] atomic: count Kings
6097 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6098 bishopsColor |= 1 << ((i^j)&1);
6103 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6104 bishopsColor |= 1 << ((i^j)&1);
6119 PawnAdvance += m; NrPawns++;
6121 NrPieces += (p != EmptySquare);
6122 NrW += ((int)p < (int)BlackPawn);
6123 if(gameInfo.variant == VariantXiangqi &&
6124 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6125 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6126 NrW -= ((int)p < (int)BlackPawn);
6130 /* Some material-based adjudications that have to be made before stalemate test */
6131 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6132 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6133 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6134 if(appData.checkMates) {
6135 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6136 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6137 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6138 "Xboard adjudication: King destroyed", GE_XBOARD );
6143 /* Bare King in Shatranj (loses) or Losers (wins) */
6144 if( NrW == 1 || NrPieces - NrW == 1) {
6145 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6146 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6147 if(appData.checkMates) {
6148 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6149 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6151 "Xboard adjudication: Bare king", GE_XBOARD );
6155 if( gameInfo.variant == VariantShatranj && --bare < 0)
6157 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6158 if(appData.checkMates) {
6159 /* but only adjudicate if adjudication enabled */
6160 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6161 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6162 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6163 "Xboard adjudication: Bare king", GE_XBOARD );
6170 // don't wait for engine to announce game end if we can judge ourselves
6171 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6173 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6174 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6175 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6176 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6179 reason = "Xboard adjudication: 3rd check";
6180 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6190 reason = "Xboard adjudication: Stalemate";
6191 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6192 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6193 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6194 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6195 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6196 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6197 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6198 EP_CHECKMATE : EP_WINS);
6199 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6200 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6204 reason = "Xboard adjudication: Checkmate";
6205 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6209 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6211 result = GameIsDrawn; break;
6213 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6215 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6217 result = (ChessMove) 0;
6219 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6220 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6221 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6222 GameEnds( result, reason, GE_XBOARD );
6226 /* Next absolutely insufficient mating material. */
6227 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6228 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6229 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6230 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6231 { /* KBK, KNK, KK of KBKB with like Bishops */
6233 /* always flag draws, for judging claims */
6234 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6236 if(appData.materialDraws) {
6237 /* but only adjudicate them if adjudication enabled */
6238 SendToProgram("force\n", cps->other); // suppress reply
6239 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6240 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6241 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6246 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6248 ( NrWR == 1 && NrBR == 1 /* KRKR */
6249 || NrWQ==1 && NrBQ==1 /* KQKQ */
6250 || NrWN==2 || NrBN==2 /* KNNK */
6251 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6253 if(--moveCount < 0 && appData.trivialDraws)
6254 { /* if the first 3 moves do not show a tactical win, declare draw */
6255 SendToProgram("force\n", cps->other); // suppress reply
6256 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6257 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6258 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6261 } else moveCount = 6;
6265 if (appData.debugMode) { int i;
6266 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6267 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6268 appData.drawRepeats);
6269 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6270 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6274 /* Check for rep-draws */
6276 for(k = forwardMostMove-2;
6277 k>=backwardMostMove && k>=forwardMostMove-100 &&
6278 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6279 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6282 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6283 /* compare castling rights */
6284 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6285 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6286 rights++; /* King lost rights, while rook still had them */
6287 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6288 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6289 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6290 rights++; /* but at least one rook lost them */
6292 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6293 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6295 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6296 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6297 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6300 if( rights == 0 && ++count > appData.drawRepeats-2
6301 && appData.drawRepeats > 1) {
6302 /* adjudicate after user-specified nr of repeats */
6303 SendToProgram("force\n", cps->other); // suppress reply
6304 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6305 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6306 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6307 // [HGM] xiangqi: check for forbidden perpetuals
6308 int m, ourPerpetual = 1, hisPerpetual = 1;
6309 for(m=forwardMostMove; m>k; m-=2) {
6310 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6311 ourPerpetual = 0; // the current mover did not always check
6312 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6313 hisPerpetual = 0; // the opponent did not always check
6315 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6316 ourPerpetual, hisPerpetual);
6317 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6318 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6319 "Xboard adjudication: perpetual checking", GE_XBOARD );
6322 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6323 break; // (or we would have caught him before). Abort repetition-checking loop.
6324 // Now check for perpetual chases
6325 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6326 hisPerpetual = PerpetualChase(k, forwardMostMove);
6327 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6328 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6329 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6330 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6333 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6334 break; // Abort repetition-checking loop.
6336 // if neither of us is checking or chasing all the time, or both are, it is draw
6338 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6341 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6342 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6346 /* Now we test for 50-move draws. Determine ply count */
6347 count = forwardMostMove;
6348 /* look for last irreversble move */
6349 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6351 /* if we hit starting position, add initial plies */
6352 if( count == backwardMostMove )
6353 count -= initialRulePlies;
6354 count = forwardMostMove - count;
6356 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6357 /* this is used to judge if draw claims are legal */
6358 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6359 SendToProgram("force\n", cps->other); // suppress reply
6360 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6361 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6362 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6366 /* if draw offer is pending, treat it as a draw claim
6367 * when draw condition present, to allow engines a way to
6368 * claim draws before making their move to avoid a race
6369 * condition occurring after their move
6371 if( cps->other->offeredDraw || cps->offeredDraw ) {
6373 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6374 p = "Draw claim: 50-move rule";
6375 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6376 p = "Draw claim: 3-fold repetition";
6377 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6378 p = "Draw claim: insufficient mating material";
6380 SendToProgram("force\n", cps->other); // suppress reply
6381 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6382 GameEnds( GameIsDrawn, p, GE_XBOARD );
6383 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6389 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6390 SendToProgram("force\n", cps->other); // suppress reply
6391 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6392 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6394 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6401 if (gameMode == TwoMachinesPlay) {
6402 /* [HGM] relaying draw offers moved to after reception of move */
6403 /* and interpreting offer as claim if it brings draw condition */
6404 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6405 SendToProgram("draw\n", cps->other);
6407 if (cps->other->sendTime) {
6408 SendTimeRemaining(cps->other,
6409 cps->other->twoMachinesColor[0] == 'w');
6411 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6412 if (firstMove && !bookHit) {
6414 if (cps->other->useColors) {
6415 SendToProgram(cps->other->twoMachinesColor, cps->other);
6417 SendToProgram("go\n", cps->other);
6419 cps->other->maybeThinking = TRUE;
6422 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6424 if (!pausing && appData.ringBellAfterMoves) {
6429 * Reenable menu items that were disabled while
6430 * machine was thinking
6432 if (gameMode != TwoMachinesPlay)
6433 SetUserThinkingEnables();
6435 // [HGM] book: after book hit opponent has received move and is now in force mode
6436 // force the book reply into it, and then fake that it outputted this move by jumping
6437 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6439 static char bookMove[MSG_SIZ]; // a bit generous?
6441 strcpy(bookMove, "move ");
6442 strcat(bookMove, bookHit);
6445 programStats.nodes = programStats.depth = programStats.time =
6446 programStats.score = programStats.got_only_move = 0;
6447 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6449 if(cps->lastPing != cps->lastPong) {
6450 savedMessage = message; // args for deferred call
6452 ScheduleDelayedEvent(DeferredBookMove, 10);
6461 /* Set special modes for chess engines. Later something general
6462 * could be added here; for now there is just one kludge feature,
6463 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6464 * when "xboard" is given as an interactive command.
6466 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6467 cps->useSigint = FALSE;
6468 cps->useSigterm = FALSE;
6470 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6471 ParseFeatures(message+8, cps);
6472 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6475 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6476 * want this, I was asked to put it in, and obliged.
6478 if (!strncmp(message, "setboard ", 9)) {
6479 Board initial_position;
6481 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6483 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6484 DisplayError(_("Bad FEN received from engine"), 0);
6488 CopyBoard(boards[0], initial_position);
6489 initialRulePlies = FENrulePlies;
6490 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6491 else gameMode = MachinePlaysBlack;
6492 DrawPosition(FALSE, boards[currentMove]);
6498 * Look for communication commands
6500 if (!strncmp(message, "telluser ", 9)) {
6501 DisplayNote(message + 9);
6504 if (!strncmp(message, "tellusererror ", 14)) {
6505 DisplayError(message + 14, 0);
6508 if (!strncmp(message, "tellopponent ", 13)) {
6509 if (appData.icsActive) {
6511 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6515 DisplayNote(message + 13);
6519 if (!strncmp(message, "tellothers ", 11)) {
6520 if (appData.icsActive) {
6522 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6528 if (!strncmp(message, "tellall ", 8)) {
6529 if (appData.icsActive) {
6531 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6535 DisplayNote(message + 8);
6539 if (strncmp(message, "warning", 7) == 0) {
6540 /* Undocumented feature, use tellusererror in new code */
6541 DisplayError(message, 0);
6544 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6545 strcpy(realname, cps->tidy);
6546 strcat(realname, " query");
6547 AskQuestion(realname, buf2, buf1, cps->pr);
6550 /* Commands from the engine directly to ICS. We don't allow these to be
6551 * sent until we are logged on. Crafty kibitzes have been known to
6552 * interfere with the login process.
6555 if (!strncmp(message, "tellics ", 8)) {
6556 SendToICS(message + 8);
6560 if (!strncmp(message, "tellicsnoalias ", 15)) {
6561 SendToICS(ics_prefix);
6562 SendToICS(message + 15);
6566 /* The following are for backward compatibility only */
6567 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6568 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6569 SendToICS(ics_prefix);
6575 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6579 * If the move is illegal, cancel it and redraw the board.
6580 * Also deal with other error cases. Matching is rather loose
6581 * here to accommodate engines written before the spec.
6583 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6584 strncmp(message, "Error", 5) == 0) {
6585 if (StrStr(message, "name") ||
6586 StrStr(message, "rating") || StrStr(message, "?") ||
6587 StrStr(message, "result") || StrStr(message, "board") ||
6588 StrStr(message, "bk") || StrStr(message, "computer") ||
6589 StrStr(message, "variant") || StrStr(message, "hint") ||
6590 StrStr(message, "random") || StrStr(message, "depth") ||
6591 StrStr(message, "accepted")) {
6594 if (StrStr(message, "protover")) {
6595 /* Program is responding to input, so it's apparently done
6596 initializing, and this error message indicates it is
6597 protocol version 1. So we don't need to wait any longer
6598 for it to initialize and send feature commands. */
6599 FeatureDone(cps, 1);
6600 cps->protocolVersion = 1;
6603 cps->maybeThinking = FALSE;
6605 if (StrStr(message, "draw")) {
6606 /* Program doesn't have "draw" command */
6607 cps->sendDrawOffers = 0;
6610 if (cps->sendTime != 1 &&
6611 (StrStr(message, "time") || StrStr(message, "otim"))) {
6612 /* Program apparently doesn't have "time" or "otim" command */
6616 if (StrStr(message, "analyze")) {
6617 cps->analysisSupport = FALSE;
6618 cps->analyzing = FALSE;
6620 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6621 DisplayError(buf2, 0);
6624 if (StrStr(message, "(no matching move)st")) {
6625 /* Special kludge for GNU Chess 4 only */
6626 cps->stKludge = TRUE;
6627 SendTimeControl(cps, movesPerSession, timeControl,
6628 timeIncrement, appData.searchDepth,
6632 if (StrStr(message, "(no matching move)sd")) {
6633 /* Special kludge for GNU Chess 4 only */
6634 cps->sdKludge = TRUE;
6635 SendTimeControl(cps, movesPerSession, timeControl,
6636 timeIncrement, appData.searchDepth,
6640 if (!StrStr(message, "llegal")) {
6643 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6644 gameMode == IcsIdle) return;
6645 if (forwardMostMove <= backwardMostMove) return;
6646 if (pausing) PauseEvent();
6647 if(appData.forceIllegal) {
6648 // [HGM] illegal: machine refused move; force position after move into it
6649 SendToProgram("force\n", cps);
6650 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6651 // we have a real problem now, as SendBoard will use the a2a3 kludge
6652 // when black is to move, while there might be nothing on a2 or black
6653 // might already have the move. So send the board as if white has the move.
6654 // But first we must change the stm of the engine, as it refused the last move
6655 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6656 if(WhiteOnMove(forwardMostMove)) {
6657 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6658 SendBoard(cps, forwardMostMove); // kludgeless board
6660 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6661 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6662 SendBoard(cps, forwardMostMove+1); // kludgeless board
6664 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6665 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6666 gameMode == TwoMachinesPlay)
6667 SendToProgram("go\n", cps);
6670 if (gameMode == PlayFromGameFile) {
6671 /* Stop reading this game file */
6672 gameMode = EditGame;
6675 currentMove = --forwardMostMove;
6676 DisplayMove(currentMove-1); /* before DisplayMoveError */
6678 DisplayBothClocks();
6679 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6680 parseList[currentMove], cps->which);
6681 DisplayMoveError(buf1);
6682 DrawPosition(FALSE, boards[currentMove]);
6684 /* [HGM] illegal-move claim should forfeit game when Xboard */
6685 /* only passes fully legal moves */
6686 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6687 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6688 "False illegal-move claim", GE_XBOARD );
6692 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6693 /* Program has a broken "time" command that
6694 outputs a string not ending in newline.
6700 * If chess program startup fails, exit with an error message.
6701 * Attempts to recover here are futile.
6703 if ((StrStr(message, "unknown host") != NULL)
6704 || (StrStr(message, "No remote directory") != NULL)
6705 || (StrStr(message, "not found") != NULL)
6706 || (StrStr(message, "No such file") != NULL)
6707 || (StrStr(message, "can't alloc") != NULL)
6708 || (StrStr(message, "Permission denied") != NULL)) {
6710 cps->maybeThinking = FALSE;
6711 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6712 cps->which, cps->program, cps->host, message);
6713 RemoveInputSource(cps->isr);
6714 DisplayFatalError(buf1, 0, 1);
6719 * Look for hint output
6721 if (sscanf(message, "Hint: %s", buf1) == 1) {
6722 if (cps == &first && hintRequested) {
6723 hintRequested = FALSE;
6724 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6725 &fromX, &fromY, &toX, &toY, &promoChar)) {
6726 (void) CoordsToAlgebraic(boards[forwardMostMove],
6727 PosFlags(forwardMostMove),
6728 fromY, fromX, toY, toX, promoChar, buf1);
6729 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6730 DisplayInformation(buf2);
6732 /* Hint move could not be parsed!? */
6733 snprintf(buf2, sizeof(buf2),
6734 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6736 DisplayError(buf2, 0);
6739 strcpy(lastHint, buf1);
6745 * Ignore other messages if game is not in progress
6747 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6748 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6751 * look for win, lose, draw, or draw offer
6753 if (strncmp(message, "1-0", 3) == 0) {
6754 char *p, *q, *r = "";
6755 p = strchr(message, '{');
6763 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6765 } else if (strncmp(message, "0-1", 3) == 0) {
6766 char *p, *q, *r = "";
6767 p = strchr(message, '{');
6775 /* Kludge for Arasan 4.1 bug */
6776 if (strcmp(r, "Black resigns") == 0) {
6777 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6780 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6782 } else if (strncmp(message, "1/2", 3) == 0) {
6783 char *p, *q, *r = "";
6784 p = strchr(message, '{');
6793 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6796 } else if (strncmp(message, "White resign", 12) == 0) {
6797 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6799 } else if (strncmp(message, "Black resign", 12) == 0) {
6800 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6802 } else if (strncmp(message, "White matches", 13) == 0 ||
6803 strncmp(message, "Black matches", 13) == 0 ) {
6804 /* [HGM] ignore GNUShogi noises */
6806 } else if (strncmp(message, "White", 5) == 0 &&
6807 message[5] != '(' &&
6808 StrStr(message, "Black") == NULL) {
6809 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6811 } else if (strncmp(message, "Black", 5) == 0 &&
6812 message[5] != '(') {
6813 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6815 } else if (strcmp(message, "resign") == 0 ||
6816 strcmp(message, "computer resigns") == 0) {
6818 case MachinePlaysBlack:
6819 case IcsPlayingBlack:
6820 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6822 case MachinePlaysWhite:
6823 case IcsPlayingWhite:
6824 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6826 case TwoMachinesPlay:
6827 if (cps->twoMachinesColor[0] == 'w')
6828 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6830 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6837 } else if (strncmp(message, "opponent mates", 14) == 0) {
6839 case MachinePlaysBlack:
6840 case IcsPlayingBlack:
6841 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6843 case MachinePlaysWhite:
6844 case IcsPlayingWhite:
6845 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6847 case TwoMachinesPlay:
6848 if (cps->twoMachinesColor[0] == 'w')
6849 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6851 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6858 } else if (strncmp(message, "computer mates", 14) == 0) {
6860 case MachinePlaysBlack:
6861 case IcsPlayingBlack:
6862 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6864 case MachinePlaysWhite:
6865 case IcsPlayingWhite:
6866 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6868 case TwoMachinesPlay:
6869 if (cps->twoMachinesColor[0] == 'w')
6870 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6872 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6879 } else if (strncmp(message, "checkmate", 9) == 0) {
6880 if (WhiteOnMove(forwardMostMove)) {
6881 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6883 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6886 } else if (strstr(message, "Draw") != NULL ||
6887 strstr(message, "game is a draw") != NULL) {
6888 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6890 } else if (strstr(message, "offer") != NULL &&
6891 strstr(message, "draw") != NULL) {
6893 if (appData.zippyPlay && first.initDone) {
6894 /* Relay offer to ICS */
6895 SendToICS(ics_prefix);
6896 SendToICS("draw\n");
6899 cps->offeredDraw = 2; /* valid until this engine moves twice */
6900 if (gameMode == TwoMachinesPlay) {
6901 if (cps->other->offeredDraw) {
6902 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6903 /* [HGM] in two-machine mode we delay relaying draw offer */
6904 /* until after we also have move, to see if it is really claim */
6906 } else if (gameMode == MachinePlaysWhite ||
6907 gameMode == MachinePlaysBlack) {
6908 if (userOfferedDraw) {
6909 DisplayInformation(_("Machine accepts your draw offer"));
6910 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6912 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6919 * Look for thinking output
6921 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6922 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6924 int plylev, mvleft, mvtot, curscore, time;
6925 char mvname[MOVE_LEN];
6929 int prefixHint = FALSE;
6930 mvname[0] = NULLCHAR;
6933 case MachinePlaysBlack:
6934 case IcsPlayingBlack:
6935 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6937 case MachinePlaysWhite:
6938 case IcsPlayingWhite:
6939 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6944 case IcsObserving: /* [DM] icsEngineAnalyze */
6945 if (!appData.icsEngineAnalyze) ignore = TRUE;
6947 case TwoMachinesPlay:
6948 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6959 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6960 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6962 if (plyext != ' ' && plyext != '\t') {
6966 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6967 if( cps->scoreIsAbsolute &&
6968 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6970 curscore = -curscore;
6974 programStats.depth = plylev;
6975 programStats.nodes = nodes;
6976 programStats.time = time;
6977 programStats.score = curscore;
6978 programStats.got_only_move = 0;
6980 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6983 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6984 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6985 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6986 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6987 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6988 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6989 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6990 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6993 /* Buffer overflow protection */
6994 if (buf1[0] != NULLCHAR) {
6995 if (strlen(buf1) >= sizeof(programStats.movelist)
6996 && appData.debugMode) {
6998 "PV is too long; using the first %u bytes.\n",
6999 (unsigned) sizeof(programStats.movelist) - 1);
7002 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7004 sprintf(programStats.movelist, " no PV\n");
7007 if (programStats.seen_stat) {
7008 programStats.ok_to_send = 1;
7011 if (strchr(programStats.movelist, '(') != NULL) {
7012 programStats.line_is_book = 1;
7013 programStats.nr_moves = 0;
7014 programStats.moves_left = 0;
7016 programStats.line_is_book = 0;
7019 SendProgramStatsToFrontend( cps, &programStats );
7022 [AS] Protect the thinkOutput buffer from overflow... this
7023 is only useful if buf1 hasn't overflowed first!
7025 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7027 (gameMode == TwoMachinesPlay ?
7028 ToUpper(cps->twoMachinesColor[0]) : ' '),
7029 ((double) curscore) / 100.0,
7030 prefixHint ? lastHint : "",
7031 prefixHint ? " " : "" );
7033 if( buf1[0] != NULLCHAR ) {
7034 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7036 if( strlen(buf1) > max_len ) {
7037 if( appData.debugMode) {
7038 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7040 buf1[max_len+1] = '\0';
7043 strcat( thinkOutput, buf1 );
7046 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7047 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7048 DisplayMove(currentMove - 1);
7052 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7053 /* crafty (9.25+) says "(only move) <move>"
7054 * if there is only 1 legal move
7056 sscanf(p, "(only move) %s", buf1);
7057 sprintf(thinkOutput, "%s (only move)", buf1);
7058 sprintf(programStats.movelist, "%s (only move)", buf1);
7059 programStats.depth = 1;
7060 programStats.nr_moves = 1;
7061 programStats.moves_left = 1;
7062 programStats.nodes = 1;
7063 programStats.time = 1;
7064 programStats.got_only_move = 1;
7066 /* Not really, but we also use this member to
7067 mean "line isn't going to change" (Crafty
7068 isn't searching, so stats won't change) */
7069 programStats.line_is_book = 1;
7071 SendProgramStatsToFrontend( cps, &programStats );
7073 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7074 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7075 DisplayMove(currentMove - 1);
7078 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7079 &time, &nodes, &plylev, &mvleft,
7080 &mvtot, mvname) >= 5) {
7081 /* The stat01: line is from Crafty (9.29+) in response
7082 to the "." command */
7083 programStats.seen_stat = 1;
7084 cps->maybeThinking = TRUE;
7086 if (programStats.got_only_move || !appData.periodicUpdates)
7089 programStats.depth = plylev;
7090 programStats.time = time;
7091 programStats.nodes = nodes;
7092 programStats.moves_left = mvleft;
7093 programStats.nr_moves = mvtot;
7094 strcpy(programStats.move_name, mvname);
7095 programStats.ok_to_send = 1;
7096 programStats.movelist[0] = '\0';
7098 SendProgramStatsToFrontend( cps, &programStats );
7102 } else if (strncmp(message,"++",2) == 0) {
7103 /* Crafty 9.29+ outputs this */
7104 programStats.got_fail = 2;
7107 } else if (strncmp(message,"--",2) == 0) {
7108 /* Crafty 9.29+ outputs this */
7109 programStats.got_fail = 1;
7112 } else if (thinkOutput[0] != NULLCHAR &&
7113 strncmp(message, " ", 4) == 0) {
7114 unsigned message_len;
7117 while (*p && *p == ' ') p++;
7119 message_len = strlen( p );
7121 /* [AS] Avoid buffer overflow */
7122 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7123 strcat(thinkOutput, " ");
7124 strcat(thinkOutput, p);
7127 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7128 strcat(programStats.movelist, " ");
7129 strcat(programStats.movelist, p);
7132 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7133 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7134 DisplayMove(currentMove - 1);
7142 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7143 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7145 ChessProgramStats cpstats;
7147 if (plyext != ' ' && plyext != '\t') {
7151 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7152 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7153 curscore = -curscore;
7156 cpstats.depth = plylev;
7157 cpstats.nodes = nodes;
7158 cpstats.time = time;
7159 cpstats.score = curscore;
7160 cpstats.got_only_move = 0;
7161 cpstats.movelist[0] = '\0';
7163 if (buf1[0] != NULLCHAR) {
7164 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7167 cpstats.ok_to_send = 0;
7168 cpstats.line_is_book = 0;
7169 cpstats.nr_moves = 0;
7170 cpstats.moves_left = 0;
7172 SendProgramStatsToFrontend( cps, &cpstats );
7179 /* Parse a game score from the character string "game", and
7180 record it as the history of the current game. The game
7181 score is NOT assumed to start from the standard position.
7182 The display is not updated in any way.
7185 ParseGameHistory(game)
7189 int fromX, fromY, toX, toY, boardIndex;
7194 if (appData.debugMode)
7195 fprintf(debugFP, "Parsing game history: %s\n", game);
7197 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7198 gameInfo.site = StrSave(appData.icsHost);
7199 gameInfo.date = PGNDate();
7200 gameInfo.round = StrSave("-");
7202 /* Parse out names of players */
7203 while (*game == ' ') game++;
7205 while (*game != ' ') *p++ = *game++;
7207 gameInfo.white = StrSave(buf);
7208 while (*game == ' ') game++;
7210 while (*game != ' ' && *game != '\n') *p++ = *game++;
7212 gameInfo.black = StrSave(buf);
7215 boardIndex = blackPlaysFirst ? 1 : 0;
7218 yyboardindex = boardIndex;
7219 moveType = (ChessMove) yylex();
7221 case IllegalMove: /* maybe suicide chess, etc. */
7222 if (appData.debugMode) {
7223 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7224 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7225 setbuf(debugFP, NULL);
7227 case WhitePromotionChancellor:
7228 case BlackPromotionChancellor:
7229 case WhitePromotionArchbishop:
7230 case BlackPromotionArchbishop:
7231 case WhitePromotionQueen:
7232 case BlackPromotionQueen:
7233 case WhitePromotionRook:
7234 case BlackPromotionRook:
7235 case WhitePromotionBishop:
7236 case BlackPromotionBishop:
7237 case WhitePromotionKnight:
7238 case BlackPromotionKnight:
7239 case WhitePromotionKing:
7240 case BlackPromotionKing:
7242 case WhiteCapturesEnPassant:
7243 case BlackCapturesEnPassant:
7244 case WhiteKingSideCastle:
7245 case WhiteQueenSideCastle:
7246 case BlackKingSideCastle:
7247 case BlackQueenSideCastle:
7248 case WhiteKingSideCastleWild:
7249 case WhiteQueenSideCastleWild:
7250 case BlackKingSideCastleWild:
7251 case BlackQueenSideCastleWild:
7253 case WhiteHSideCastleFR:
7254 case WhiteASideCastleFR:
7255 case BlackHSideCastleFR:
7256 case BlackASideCastleFR:
7258 fromX = currentMoveString[0] - AAA;
7259 fromY = currentMoveString[1] - ONE;
7260 toX = currentMoveString[2] - AAA;
7261 toY = currentMoveString[3] - ONE;
7262 promoChar = currentMoveString[4];
7266 fromX = moveType == WhiteDrop ?
7267 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7268 (int) CharToPiece(ToLower(currentMoveString[0]));
7270 toX = currentMoveString[2] - AAA;
7271 toY = currentMoveString[3] - ONE;
7272 promoChar = NULLCHAR;
7276 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7277 if (appData.debugMode) {
7278 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7279 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7280 setbuf(debugFP, NULL);
7282 DisplayError(buf, 0);
7284 case ImpossibleMove:
7286 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7287 if (appData.debugMode) {
7288 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7289 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7290 setbuf(debugFP, NULL);
7292 DisplayError(buf, 0);
7294 case (ChessMove) 0: /* end of file */
7295 if (boardIndex < backwardMostMove) {
7296 /* Oops, gap. How did that happen? */
7297 DisplayError(_("Gap in move list"), 0);
7300 backwardMostMove = blackPlaysFirst ? 1 : 0;
7301 if (boardIndex > forwardMostMove) {
7302 forwardMostMove = boardIndex;
7306 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7307 strcat(parseList[boardIndex-1], " ");
7308 strcat(parseList[boardIndex-1], yy_text);
7320 case GameUnfinished:
7321 if (gameMode == IcsExamining) {
7322 if (boardIndex < backwardMostMove) {
7323 /* Oops, gap. How did that happen? */
7326 backwardMostMove = blackPlaysFirst ? 1 : 0;
7329 gameInfo.result = moveType;
7330 p = strchr(yy_text, '{');
7331 if (p == NULL) p = strchr(yy_text, '(');
7334 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7336 q = strchr(p, *p == '{' ? '}' : ')');
7337 if (q != NULL) *q = NULLCHAR;
7340 gameInfo.resultDetails = StrSave(p);
7343 if (boardIndex >= forwardMostMove &&
7344 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7345 backwardMostMove = blackPlaysFirst ? 1 : 0;
7348 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7349 fromY, fromX, toY, toX, promoChar,
7350 parseList[boardIndex]);
7351 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7352 /* currentMoveString is set as a side-effect of yylex */
7353 strcpy(moveList[boardIndex], currentMoveString);
7354 strcat(moveList[boardIndex], "\n");
7356 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7357 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7363 if(gameInfo.variant != VariantShogi)
7364 strcat(parseList[boardIndex - 1], "+");
7368 strcat(parseList[boardIndex - 1], "#");
7375 /* Apply a move to the given board */
7377 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7378 int fromX, fromY, toX, toY;
7382 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7384 /* [HGM] compute & store e.p. status and castling rights for new position */
7385 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7388 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7389 oldEP = (signed char)board[EP_STATUS];
7390 board[EP_STATUS] = EP_NONE;
7392 if( board[toY][toX] != EmptySquare )
7393 board[EP_STATUS] = EP_CAPTURE;
7395 if( board[fromY][fromX] == WhitePawn ) {
7396 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7397 board[EP_STATUS] = EP_PAWN_MOVE;
7399 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7400 gameInfo.variant != VariantBerolina || toX < fromX)
7401 board[EP_STATUS] = toX | berolina;
7402 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7403 gameInfo.variant != VariantBerolina || toX > fromX)
7404 board[EP_STATUS] = toX;
7407 if( board[fromY][fromX] == BlackPawn ) {
7408 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7409 board[EP_STATUS] = EP_PAWN_MOVE;
7410 if( toY-fromY== -2) {
7411 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7412 gameInfo.variant != VariantBerolina || toX < fromX)
7413 board[EP_STATUS] = toX | berolina;
7414 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7415 gameInfo.variant != VariantBerolina || toX > fromX)
7416 board[EP_STATUS] = toX;
7420 for(i=0; i<nrCastlingRights; i++) {
7421 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7422 board[CASTLING][i] == toX && castlingRank[i] == toY
7423 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7428 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7429 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7430 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7432 if (fromX == toX && fromY == toY) return;
7434 if (fromY == DROP_RANK) {
7436 piece = board[toY][toX] = (ChessSquare) fromX;
7438 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7439 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7440 if(gameInfo.variant == VariantKnightmate)
7441 king += (int) WhiteUnicorn - (int) WhiteKing;
7443 /* Code added by Tord: */
7444 /* FRC castling assumed when king captures friendly rook. */
7445 if (board[fromY][fromX] == WhiteKing &&
7446 board[toY][toX] == WhiteRook) {
7447 board[fromY][fromX] = EmptySquare;
7448 board[toY][toX] = EmptySquare;
7450 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7452 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7454 } else if (board[fromY][fromX] == BlackKing &&
7455 board[toY][toX] == BlackRook) {
7456 board[fromY][fromX] = EmptySquare;
7457 board[toY][toX] = EmptySquare;
7459 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7461 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7463 /* End of code added by Tord */
7465 } else if (board[fromY][fromX] == king
7466 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7467 && toY == fromY && toX > fromX+1) {
7468 board[fromY][fromX] = EmptySquare;
7469 board[toY][toX] = king;
7470 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7471 board[fromY][BOARD_RGHT-1] = EmptySquare;
7472 } else if (board[fromY][fromX] == king
7473 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7474 && toY == fromY && toX < fromX-1) {
7475 board[fromY][fromX] = EmptySquare;
7476 board[toY][toX] = king;
7477 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7478 board[fromY][BOARD_LEFT] = EmptySquare;
7479 } else if (board[fromY][fromX] == WhitePawn
7480 && toY == BOARD_HEIGHT-1
7481 && gameInfo.variant != VariantXiangqi
7483 /* white pawn promotion */
7484 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7485 if (board[toY][toX] == EmptySquare) {
7486 board[toY][toX] = WhiteQueen;
7488 if(gameInfo.variant==VariantBughouse ||
7489 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7490 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7491 board[fromY][fromX] = EmptySquare;
7492 } else if ((fromY == BOARD_HEIGHT-4)
7494 && gameInfo.variant != VariantXiangqi
7495 && gameInfo.variant != VariantBerolina
7496 && (board[fromY][fromX] == WhitePawn)
7497 && (board[toY][toX] == EmptySquare)) {
7498 board[fromY][fromX] = EmptySquare;
7499 board[toY][toX] = WhitePawn;
7500 captured = board[toY - 1][toX];
7501 board[toY - 1][toX] = EmptySquare;
7502 } else if ((fromY == BOARD_HEIGHT-4)
7504 && gameInfo.variant == VariantBerolina
7505 && (board[fromY][fromX] == WhitePawn)
7506 && (board[toY][toX] == EmptySquare)) {
7507 board[fromY][fromX] = EmptySquare;
7508 board[toY][toX] = WhitePawn;
7509 if(oldEP & EP_BEROLIN_A) {
7510 captured = board[fromY][fromX-1];
7511 board[fromY][fromX-1] = EmptySquare;
7512 }else{ captured = board[fromY][fromX+1];
7513 board[fromY][fromX+1] = EmptySquare;
7515 } else if (board[fromY][fromX] == king
7516 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7517 && toY == fromY && toX > fromX+1) {
7518 board[fromY][fromX] = EmptySquare;
7519 board[toY][toX] = king;
7520 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7521 board[fromY][BOARD_RGHT-1] = EmptySquare;
7522 } else if (board[fromY][fromX] == king
7523 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7524 && toY == fromY && toX < fromX-1) {
7525 board[fromY][fromX] = EmptySquare;
7526 board[toY][toX] = king;
7527 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7528 board[fromY][BOARD_LEFT] = EmptySquare;
7529 } else if (fromY == 7 && fromX == 3
7530 && board[fromY][fromX] == BlackKing
7531 && toY == 7 && toX == 5) {
7532 board[fromY][fromX] = EmptySquare;
7533 board[toY][toX] = BlackKing;
7534 board[fromY][7] = EmptySquare;
7535 board[toY][4] = BlackRook;
7536 } else if (fromY == 7 && fromX == 3
7537 && board[fromY][fromX] == BlackKing
7538 && toY == 7 && toX == 1) {
7539 board[fromY][fromX] = EmptySquare;
7540 board[toY][toX] = BlackKing;
7541 board[fromY][0] = EmptySquare;
7542 board[toY][2] = BlackRook;
7543 } else if (board[fromY][fromX] == BlackPawn
7545 && gameInfo.variant != VariantXiangqi
7547 /* black pawn promotion */
7548 board[0][toX] = CharToPiece(ToLower(promoChar));
7549 if (board[0][toX] == EmptySquare) {
7550 board[0][toX] = BlackQueen;
7552 if(gameInfo.variant==VariantBughouse ||
7553 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7554 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7555 board[fromY][fromX] = EmptySquare;
7556 } else if ((fromY == 3)
7558 && gameInfo.variant != VariantXiangqi
7559 && gameInfo.variant != VariantBerolina
7560 && (board[fromY][fromX] == BlackPawn)
7561 && (board[toY][toX] == EmptySquare)) {
7562 board[fromY][fromX] = EmptySquare;
7563 board[toY][toX] = BlackPawn;
7564 captured = board[toY + 1][toX];
7565 board[toY + 1][toX] = EmptySquare;
7566 } else if ((fromY == 3)
7568 && gameInfo.variant == VariantBerolina
7569 && (board[fromY][fromX] == BlackPawn)
7570 && (board[toY][toX] == EmptySquare)) {
7571 board[fromY][fromX] = EmptySquare;
7572 board[toY][toX] = BlackPawn;
7573 if(oldEP & EP_BEROLIN_A) {
7574 captured = board[fromY][fromX-1];
7575 board[fromY][fromX-1] = EmptySquare;
7576 }else{ captured = board[fromY][fromX+1];
7577 board[fromY][fromX+1] = EmptySquare;
7580 board[toY][toX] = board[fromY][fromX];
7581 board[fromY][fromX] = EmptySquare;
7584 /* [HGM] now we promote for Shogi, if needed */
7585 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7586 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7589 if (gameInfo.holdingsWidth != 0) {
7591 /* !!A lot more code needs to be written to support holdings */
7592 /* [HGM] OK, so I have written it. Holdings are stored in the */
7593 /* penultimate board files, so they are automaticlly stored */
7594 /* in the game history. */
7595 if (fromY == DROP_RANK) {
7596 /* Delete from holdings, by decreasing count */
7597 /* and erasing image if necessary */
7599 if(p < (int) BlackPawn) { /* white drop */
7600 p -= (int)WhitePawn;
7601 p = PieceToNumber((ChessSquare)p);
7602 if(p >= gameInfo.holdingsSize) p = 0;
7603 if(--board[p][BOARD_WIDTH-2] <= 0)
7604 board[p][BOARD_WIDTH-1] = EmptySquare;
7605 if((int)board[p][BOARD_WIDTH-2] < 0)
7606 board[p][BOARD_WIDTH-2] = 0;
7607 } else { /* black drop */
7608 p -= (int)BlackPawn;
7609 p = PieceToNumber((ChessSquare)p);
7610 if(p >= gameInfo.holdingsSize) p = 0;
7611 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7612 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7613 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7614 board[BOARD_HEIGHT-1-p][1] = 0;
7617 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7618 && gameInfo.variant != VariantBughouse ) {
7619 /* [HGM] holdings: Add to holdings, if holdings exist */
7620 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7621 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7622 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7625 if (p >= (int) BlackPawn) {
7626 p -= (int)BlackPawn;
7627 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7628 /* in Shogi restore piece to its original first */
7629 captured = (ChessSquare) (DEMOTED captured);
7632 p = PieceToNumber((ChessSquare)p);
7633 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7634 board[p][BOARD_WIDTH-2]++;
7635 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7637 p -= (int)WhitePawn;
7638 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7639 captured = (ChessSquare) (DEMOTED captured);
7642 p = PieceToNumber((ChessSquare)p);
7643 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7644 board[BOARD_HEIGHT-1-p][1]++;
7645 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7648 } else if (gameInfo.variant == VariantAtomic) {
7649 if (captured != EmptySquare) {
7651 for (y = toY-1; y <= toY+1; y++) {
7652 for (x = toX-1; x <= toX+1; x++) {
7653 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7654 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7655 board[y][x] = EmptySquare;
7659 board[toY][toX] = EmptySquare;
7662 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7663 /* [HGM] Shogi promotions */
7664 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7667 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7668 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7669 // [HGM] superchess: take promotion piece out of holdings
7670 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7671 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7672 if(!--board[k][BOARD_WIDTH-2])
7673 board[k][BOARD_WIDTH-1] = EmptySquare;
7675 if(!--board[BOARD_HEIGHT-1-k][1])
7676 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7682 /* Updates forwardMostMove */
7684 MakeMove(fromX, fromY, toX, toY, promoChar)
7685 int fromX, fromY, toX, toY;
7688 // forwardMostMove++; // [HGM] bare: moved downstream
7690 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7691 int timeLeft; static int lastLoadFlag=0; int king, piece;
7692 piece = boards[forwardMostMove][fromY][fromX];
7693 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7694 if(gameInfo.variant == VariantKnightmate)
7695 king += (int) WhiteUnicorn - (int) WhiteKing;
7696 if(forwardMostMove == 0) {
7698 fprintf(serverMoves, "%s;", second.tidy);
7699 fprintf(serverMoves, "%s;", first.tidy);
7700 if(!blackPlaysFirst)
7701 fprintf(serverMoves, "%s;", second.tidy);
7702 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7703 lastLoadFlag = loadFlag;
7705 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7706 // print castling suffix
7707 if( toY == fromY && piece == king ) {
7709 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7711 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7714 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7715 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7716 boards[forwardMostMove][toY][toX] == EmptySquare
7718 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7720 if(promoChar != NULLCHAR)
7721 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7723 fprintf(serverMoves, "/%d/%d",
7724 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7725 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7726 else timeLeft = blackTimeRemaining/1000;
7727 fprintf(serverMoves, "/%d", timeLeft);
7729 fflush(serverMoves);
7732 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7733 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7737 if (commentList[forwardMostMove+1] != NULL) {
7738 free(commentList[forwardMostMove+1]);
7739 commentList[forwardMostMove+1] = NULL;
7741 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7742 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7743 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7744 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7745 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7746 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7747 gameInfo.result = GameUnfinished;
7748 if (gameInfo.resultDetails != NULL) {
7749 free(gameInfo.resultDetails);
7750 gameInfo.resultDetails = NULL;
7752 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7753 moveList[forwardMostMove - 1]);
7754 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7755 PosFlags(forwardMostMove - 1),
7756 fromY, fromX, toY, toX, promoChar,
7757 parseList[forwardMostMove - 1]);
7758 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7764 if(gameInfo.variant != VariantShogi)
7765 strcat(parseList[forwardMostMove - 1], "+");
7769 strcat(parseList[forwardMostMove - 1], "#");
7772 if (appData.debugMode) {
7773 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7778 /* Updates currentMove if not pausing */
7780 ShowMove(fromX, fromY, toX, toY)
7782 int instant = (gameMode == PlayFromGameFile) ?
7783 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7785 if(appData.noGUI) return;
7787 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7791 if (forwardMostMove == currentMove + 1)
7794 // AnimateMove(boards[forwardMostMove - 1],
7795 // fromX, fromY, toX, toY);
7797 if (appData.highlightLastMove)
7799 SetHighlights(fromX, fromY, toX, toY);
7802 currentMove = forwardMostMove;
7805 if (instant) return;
7807 DisplayMove(currentMove - 1);
7808 DrawPosition(FALSE, boards[currentMove]);
7809 DisplayBothClocks();
7810 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7815 void SendEgtPath(ChessProgramState *cps)
7816 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7817 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7819 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7822 char c, *q = name+1, *r, *s;
7824 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7825 while(*p && *p != ',') *q++ = *p++;
7827 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7828 strcmp(name, ",nalimov:") == 0 ) {
7829 // take nalimov path from the menu-changeable option first, if it is defined
7830 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7831 SendToProgram(buf,cps); // send egtbpath command for nalimov
7833 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7834 (s = StrStr(appData.egtFormats, name)) != NULL) {
7835 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7836 s = r = StrStr(s, ":") + 1; // beginning of path info
7837 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7838 c = *r; *r = 0; // temporarily null-terminate path info
7839 *--q = 0; // strip of trailig ':' from name
7840 sprintf(buf, "egtpath %s %s\n", name+1, s);
7842 SendToProgram(buf,cps); // send egtbpath command for this format
7844 if(*p == ',') p++; // read away comma to position for next format name
7849 InitChessProgram(cps, setup)
7850 ChessProgramState *cps;
7851 int setup; /* [HGM] needed to setup FRC opening position */
7853 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7854 if (appData.noChessProgram) return;
7855 hintRequested = FALSE;
7856 bookRequested = FALSE;
7858 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7859 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7860 if(cps->memSize) { /* [HGM] memory */
7861 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7862 SendToProgram(buf, cps);
7864 SendEgtPath(cps); /* [HGM] EGT */
7865 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7866 sprintf(buf, "cores %d\n", appData.smpCores);
7867 SendToProgram(buf, cps);
7870 SendToProgram(cps->initString, cps);
7871 if (gameInfo.variant != VariantNormal &&
7872 gameInfo.variant != VariantLoadable
7873 /* [HGM] also send variant if board size non-standard */
7874 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7876 char *v = VariantName(gameInfo.variant);
7877 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7878 /* [HGM] in protocol 1 we have to assume all variants valid */
7879 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7880 DisplayFatalError(buf, 0, 1);
7884 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7885 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7886 if( gameInfo.variant == VariantXiangqi )
7887 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7888 if( gameInfo.variant == VariantShogi )
7889 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7890 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7891 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7892 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7893 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7894 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7895 if( gameInfo.variant == VariantCourier )
7896 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7897 if( gameInfo.variant == VariantSuper )
7898 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7899 if( gameInfo.variant == VariantGreat )
7900 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7903 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7904 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7905 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7906 if(StrStr(cps->variants, b) == NULL) {
7907 // specific sized variant not known, check if general sizing allowed
7908 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7909 if(StrStr(cps->variants, "boardsize") == NULL) {
7910 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7911 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7912 DisplayFatalError(buf, 0, 1);
7915 /* [HGM] here we really should compare with the maximum supported board size */
7918 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7919 sprintf(buf, "variant %s\n", b);
7920 SendToProgram(buf, cps);
7922 currentlyInitializedVariant = gameInfo.variant;
7924 /* [HGM] send opening position in FRC to first engine */
7926 SendToProgram("force\n", cps);
7928 /* engine is now in force mode! Set flag to wake it up after first move. */
7929 setboardSpoiledMachineBlack = 1;
7933 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7934 SendToProgram(buf, cps);
7936 cps->maybeThinking = FALSE;
7937 cps->offeredDraw = 0;
7938 if (!appData.icsActive) {
7939 SendTimeControl(cps, movesPerSession, timeControl,
7940 timeIncrement, appData.searchDepth,
7943 if (appData.showThinking
7944 // [HGM] thinking: four options require thinking output to be sent
7945 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7947 SendToProgram("post\n", cps);
7949 SendToProgram("hard\n", cps);
7950 if (!appData.ponderNextMove) {
7951 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7952 it without being sure what state we are in first. "hard"
7953 is not a toggle, so that one is OK.
7955 SendToProgram("easy\n", cps);
7958 sprintf(buf, "ping %d\n", ++cps->lastPing);
7959 SendToProgram(buf, cps);
7961 cps->initDone = TRUE;
7966 StartChessProgram(cps)
7967 ChessProgramState *cps;
7972 if (appData.noChessProgram) return;
7973 cps->initDone = FALSE;
7975 if (strcmp(cps->host, "localhost") == 0) {
7976 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7977 } else if (*appData.remoteShell == NULLCHAR) {
7978 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7980 if (*appData.remoteUser == NULLCHAR) {
7981 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7984 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7985 cps->host, appData.remoteUser, cps->program);
7987 err = StartChildProcess(buf, "", &cps->pr);
7991 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7992 DisplayFatalError(buf, err, 1);
7998 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7999 if (cps->protocolVersion > 1) {
8000 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8001 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8002 cps->comboCnt = 0; // and values of combo boxes
8003 SendToProgram(buf, cps);
8005 SendToProgram("xboard\n", cps);
8011 TwoMachinesEventIfReady P((void))
8013 if (first.lastPing != first.lastPong) {
8014 DisplayMessage("", _("Waiting for first chess program"));
8015 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8018 if (second.lastPing != second.lastPong) {
8019 DisplayMessage("", _("Waiting for second chess program"));
8020 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8028 NextMatchGame P((void))
8030 int index; /* [HGM] autoinc: step load index during match */
8032 if (*appData.loadGameFile != NULLCHAR) {
8033 index = appData.loadGameIndex;
8034 if(index < 0) { // [HGM] autoinc
8035 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8036 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8038 LoadGameFromFile(appData.loadGameFile,
8040 appData.loadGameFile, FALSE);
8041 } else if (*appData.loadPositionFile != NULLCHAR) {
8042 index = appData.loadPositionIndex;
8043 if(index < 0) { // [HGM] autoinc
8044 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8045 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8047 LoadPositionFromFile(appData.loadPositionFile,
8049 appData.loadPositionFile);
8051 TwoMachinesEventIfReady();
8054 void UserAdjudicationEvent( int result )
8056 ChessMove gameResult = GameIsDrawn;
8059 gameResult = WhiteWins;
8061 else if( result < 0 ) {
8062 gameResult = BlackWins;
8065 if( gameMode == TwoMachinesPlay ) {
8066 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8071 // [HGM] save: calculate checksum of game to make games easily identifiable
8072 int StringCheckSum(char *s)
8075 if(s==NULL) return 0;
8076 while(*s) i = i*259 + *s++;
8083 for(i=backwardMostMove; i<forwardMostMove; i++) {
8084 sum += pvInfoList[i].depth;
8085 sum += StringCheckSum(parseList[i]);
8086 sum += StringCheckSum(commentList[i]);
8089 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8090 return sum + StringCheckSum(commentList[i]);
8091 } // end of save patch
8094 GameEnds(result, resultDetails, whosays)
8096 char *resultDetails;
8099 GameMode nextGameMode;
8103 if(endingGame) return; /* [HGM] crash: forbid recursion */
8106 if (appData.debugMode) {
8107 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8108 result, resultDetails ? resultDetails : "(null)", whosays);
8111 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8112 /* If we are playing on ICS, the server decides when the
8113 game is over, but the engine can offer to draw, claim
8117 if (appData.zippyPlay && first.initDone) {
8118 if (result == GameIsDrawn) {
8119 /* In case draw still needs to be claimed */
8120 SendToICS(ics_prefix);
8121 SendToICS("draw\n");
8122 } else if (StrCaseStr(resultDetails, "resign")) {
8123 SendToICS(ics_prefix);
8124 SendToICS("resign\n");
8128 endingGame = 0; /* [HGM] crash */
8132 /* If we're loading the game from a file, stop */
8133 if (whosays == GE_FILE) {
8134 (void) StopLoadGameTimer();
8138 /* Cancel draw offers */
8139 first.offeredDraw = second.offeredDraw = 0;
8141 /* If this is an ICS game, only ICS can really say it's done;
8142 if not, anyone can. */
8143 isIcsGame = (gameMode == IcsPlayingWhite ||
8144 gameMode == IcsPlayingBlack ||
8145 gameMode == IcsObserving ||
8146 gameMode == IcsExamining);
8148 if (!isIcsGame || whosays == GE_ICS) {
8149 /* OK -- not an ICS game, or ICS said it was done */
8151 if (!isIcsGame && !appData.noChessProgram)
8152 SetUserThinkingEnables();
8154 /* [HGM] if a machine claims the game end we verify this claim */
8155 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8156 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8158 ChessMove trueResult = (ChessMove) -1;
8160 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8161 first.twoMachinesColor[0] :
8162 second.twoMachinesColor[0] ;
8164 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8165 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8166 /* [HGM] verify: engine mate claims accepted if they were flagged */
8167 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8169 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8170 /* [HGM] verify: engine mate claims accepted if they were flagged */
8171 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8173 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8174 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8177 // now verify win claims, but not in drop games, as we don't understand those yet
8178 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8179 || gameInfo.variant == VariantGreat) &&
8180 (result == WhiteWins && claimer == 'w' ||
8181 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8182 if (appData.debugMode) {
8183 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8184 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8186 if(result != trueResult) {
8187 sprintf(buf, "False win claim: '%s'", resultDetails);
8188 result = claimer == 'w' ? BlackWins : WhiteWins;
8189 resultDetails = buf;
8192 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8193 && (forwardMostMove <= backwardMostMove ||
8194 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8195 (claimer=='b')==(forwardMostMove&1))
8197 /* [HGM] verify: draws that were not flagged are false claims */
8198 sprintf(buf, "False draw claim: '%s'", resultDetails);
8199 result = claimer == 'w' ? BlackWins : WhiteWins;
8200 resultDetails = buf;
8202 /* (Claiming a loss is accepted no questions asked!) */
8205 /* [HGM] bare: don't allow bare King to win */
8206 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8207 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8208 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8209 && result != GameIsDrawn)
8210 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8211 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8212 int p = (signed char)boards[forwardMostMove][i][j] - color;
8213 if(p >= 0 && p <= (int)WhiteKing) k++;
8215 if (appData.debugMode) {
8216 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8217 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8220 result = GameIsDrawn;
8221 sprintf(buf, "%s but bare king", resultDetails);
8222 resultDetails = buf;
8227 if(serverMoves != NULL && !loadFlag) { char c = '=';
8228 if(result==WhiteWins) c = '+';
8229 if(result==BlackWins) c = '-';
8230 if(resultDetails != NULL)
8231 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8233 if (resultDetails != NULL) {
8234 gameInfo.result = result;
8235 gameInfo.resultDetails = StrSave(resultDetails);
8237 /* display last move only if game was not loaded from file */
8238 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8239 DisplayMove(currentMove - 1);
8241 if (forwardMostMove != 0) {
8242 if (gameMode != PlayFromGameFile && gameMode != EditGame
8243 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8245 if (*appData.saveGameFile != NULLCHAR) {
8246 SaveGameToFile(appData.saveGameFile, TRUE);
8247 } else if (appData.autoSaveGames) {
8250 if (*appData.savePositionFile != NULLCHAR) {
8251 SavePositionToFile(appData.savePositionFile);
8256 /* Tell program how game ended in case it is learning */
8257 /* [HGM] Moved this to after saving the PGN, just in case */
8258 /* engine died and we got here through time loss. In that */
8259 /* case we will get a fatal error writing the pipe, which */
8260 /* would otherwise lose us the PGN. */
8261 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8262 /* output during GameEnds should never be fatal anymore */
8263 if (gameMode == MachinePlaysWhite ||
8264 gameMode == MachinePlaysBlack ||
8265 gameMode == TwoMachinesPlay ||
8266 gameMode == IcsPlayingWhite ||
8267 gameMode == IcsPlayingBlack ||
8268 gameMode == BeginningOfGame) {
8270 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8272 if (first.pr != NoProc) {
8273 SendToProgram(buf, &first);
8275 if (second.pr != NoProc &&
8276 gameMode == TwoMachinesPlay) {
8277 SendToProgram(buf, &second);
8282 if (appData.icsActive) {
8283 if (appData.quietPlay &&
8284 (gameMode == IcsPlayingWhite ||
8285 gameMode == IcsPlayingBlack)) {
8286 SendToICS(ics_prefix);
8287 SendToICS("set shout 1\n");
8289 nextGameMode = IcsIdle;
8290 ics_user_moved = FALSE;
8291 /* clean up premove. It's ugly when the game has ended and the
8292 * premove highlights are still on the board.
8296 ClearPremoveHighlights();
8297 DrawPosition(FALSE, boards[currentMove]);
8299 if (whosays == GE_ICS) {
8302 if (gameMode == IcsPlayingWhite)
8304 else if(gameMode == IcsPlayingBlack)
8308 if (gameMode == IcsPlayingBlack)
8310 else if(gameMode == IcsPlayingWhite)
8317 PlayIcsUnfinishedSound();
8320 } else if (gameMode == EditGame ||
8321 gameMode == PlayFromGameFile ||
8322 gameMode == AnalyzeMode ||
8323 gameMode == AnalyzeFile) {
8324 nextGameMode = gameMode;
8326 nextGameMode = EndOfGame;
8331 nextGameMode = gameMode;
8334 if (appData.noChessProgram) {
8335 gameMode = nextGameMode;
8337 endingGame = 0; /* [HGM] crash */
8342 /* Put first chess program into idle state */
8343 if (first.pr != NoProc &&
8344 (gameMode == MachinePlaysWhite ||
8345 gameMode == MachinePlaysBlack ||
8346 gameMode == TwoMachinesPlay ||
8347 gameMode == IcsPlayingWhite ||
8348 gameMode == IcsPlayingBlack ||
8349 gameMode == BeginningOfGame)) {
8350 SendToProgram("force\n", &first);
8351 if (first.usePing) {
8353 sprintf(buf, "ping %d\n", ++first.lastPing);
8354 SendToProgram(buf, &first);
8357 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8358 /* Kill off first chess program */
8359 if (first.isr != NULL)
8360 RemoveInputSource(first.isr);
8363 if (first.pr != NoProc) {
8365 DoSleep( appData.delayBeforeQuit );
8366 SendToProgram("quit\n", &first);
8367 DoSleep( appData.delayAfterQuit );
8368 DestroyChildProcess(first.pr, first.useSigterm);
8373 /* Put second chess program into idle state */
8374 if (second.pr != NoProc &&
8375 gameMode == TwoMachinesPlay) {
8376 SendToProgram("force\n", &second);
8377 if (second.usePing) {
8379 sprintf(buf, "ping %d\n", ++second.lastPing);
8380 SendToProgram(buf, &second);
8383 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8384 /* Kill off second chess program */
8385 if (second.isr != NULL)
8386 RemoveInputSource(second.isr);
8389 if (second.pr != NoProc) {
8390 DoSleep( appData.delayBeforeQuit );
8391 SendToProgram("quit\n", &second);
8392 DoSleep( appData.delayAfterQuit );
8393 DestroyChildProcess(second.pr, second.useSigterm);
8398 if (matchMode && gameMode == TwoMachinesPlay) {
8401 if (first.twoMachinesColor[0] == 'w') {
8408 if (first.twoMachinesColor[0] == 'b') {
8417 if (matchGame < appData.matchGames) {
8419 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8420 tmp = first.twoMachinesColor;
8421 first.twoMachinesColor = second.twoMachinesColor;
8422 second.twoMachinesColor = tmp;
8424 gameMode = nextGameMode;
8426 if(appData.matchPause>10000 || appData.matchPause<10)
8427 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8428 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8429 endingGame = 0; /* [HGM] crash */
8433 gameMode = nextGameMode;
8434 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8435 first.tidy, second.tidy,
8436 first.matchWins, second.matchWins,
8437 appData.matchGames - (first.matchWins + second.matchWins));
8438 DisplayFatalError(buf, 0, 0);
8441 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8442 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8444 gameMode = nextGameMode;
8446 endingGame = 0; /* [HGM] crash */
8449 /* Assumes program was just initialized (initString sent).
8450 Leaves program in force mode. */
8452 FeedMovesToProgram(cps, upto)
8453 ChessProgramState *cps;
8458 if (appData.debugMode)
8459 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8460 startedFromSetupPosition ? "position and " : "",
8461 backwardMostMove, upto, cps->which);
8462 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8463 // [HGM] variantswitch: make engine aware of new variant
8464 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8465 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8466 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8467 SendToProgram(buf, cps);
8468 currentlyInitializedVariant = gameInfo.variant;
8470 SendToProgram("force\n", cps);
8471 if (startedFromSetupPosition) {
8472 SendBoard(cps, backwardMostMove);
8473 if (appData.debugMode) {
8474 fprintf(debugFP, "feedMoves\n");
8477 for (i = backwardMostMove; i < upto; i++) {
8478 SendMoveToProgram(i, cps);
8484 ResurrectChessProgram()
8486 /* The chess program may have exited.
8487 If so, restart it and feed it all the moves made so far. */
8489 if (appData.noChessProgram || first.pr != NoProc) return;
8491 StartChessProgram(&first);
8492 InitChessProgram(&first, FALSE);
8493 FeedMovesToProgram(&first, currentMove);
8495 if (!first.sendTime) {
8496 /* can't tell gnuchess what its clock should read,
8497 so we bow to its notion. */
8499 timeRemaining[0][currentMove] = whiteTimeRemaining;
8500 timeRemaining[1][currentMove] = blackTimeRemaining;
8503 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8504 appData.icsEngineAnalyze) && first.analysisSupport) {
8505 SendToProgram("analyze\n", &first);
8506 first.analyzing = TRUE;
8519 if (appData.debugMode) {
8520 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8521 redraw, init, gameMode);
8523 CleanupTail(); // [HGM] vari: delete any stored variations
8524 pausing = pauseExamInvalid = FALSE;
8525 startedFromSetupPosition = blackPlaysFirst = FALSE;
8527 whiteFlag = blackFlag = FALSE;
8528 userOfferedDraw = FALSE;
8529 hintRequested = bookRequested = FALSE;
8530 first.maybeThinking = FALSE;
8531 second.maybeThinking = FALSE;
8532 first.bookSuspend = FALSE; // [HGM] book
8533 second.bookSuspend = FALSE;
8534 thinkOutput[0] = NULLCHAR;
8535 lastHint[0] = NULLCHAR;
8536 ClearGameInfo(&gameInfo);
8537 gameInfo.variant = StringToVariant(appData.variant);
8538 ics_user_moved = ics_clock_paused = FALSE;
8539 ics_getting_history = H_FALSE;
8541 white_holding[0] = black_holding[0] = NULLCHAR;
8542 ClearProgramStats();
8543 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8547 flipView = appData.flipView;
8548 ClearPremoveHighlights();
8550 alarmSounded = FALSE;
8552 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8553 if(appData.serverMovesName != NULL) {
8554 /* [HGM] prepare to make moves file for broadcasting */
8555 clock_t t = clock();
8556 if(serverMoves != NULL) fclose(serverMoves);
8557 serverMoves = fopen(appData.serverMovesName, "r");
8558 if(serverMoves != NULL) {
8559 fclose(serverMoves);
8560 /* delay 15 sec before overwriting, so all clients can see end */
8561 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8563 serverMoves = fopen(appData.serverMovesName, "w");
8567 gameMode = BeginningOfGame;
8570 if(appData.icsActive) gameInfo.variant = VariantNormal;
8571 currentMove = forwardMostMove = backwardMostMove = 0;
8572 InitPosition(redraw);
8573 for (i = 0; i < MAX_MOVES; i++) {
8574 if (commentList[i] != NULL) {
8575 free(commentList[i]);
8576 commentList[i] = NULL;
8581 timeRemaining[0][0] = whiteTimeRemaining;
8582 timeRemaining[1][0] = blackTimeRemaining;
8583 if (first.pr == NULL) {
8584 StartChessProgram(&first);
8587 InitChessProgram(&first, startedFromSetupPosition);
8591 DisplayMessage("", "");
8592 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8593 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8601 if (!AutoPlayOneMove())
8603 if (matchMode || appData.timeDelay == 0)
8605 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8607 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8616 int fromX, fromY, toX, toY;
8618 if (appData.debugMode) {
8619 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8622 if (gameMode != PlayFromGameFile)
8625 if (currentMove >= forwardMostMove) {
8626 gameMode = EditGame;
8629 /* [AS] Clear current move marker at the end of a game */
8630 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8635 toX = moveList[currentMove][2] - AAA;
8636 toY = moveList[currentMove][3] - ONE;
8638 if (moveList[currentMove][1] == '@') {
8639 if (appData.highlightLastMove) {
8640 SetHighlights(-1, -1, toX, toY);
8643 fromX = moveList[currentMove][0] - AAA;
8644 fromY = moveList[currentMove][1] - ONE;
8646 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8648 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8650 if (appData.highlightLastMove) {
8651 SetHighlights(fromX, fromY, toX, toY);
8654 DisplayMove(currentMove);
8655 SendMoveToProgram(currentMove++, &first);
8656 DisplayBothClocks();
8657 DrawPosition(FALSE, boards[currentMove]);
8658 // [HGM] PV info: always display, routine tests if empty
8659 DisplayComment(currentMove - 1, commentList[currentMove]);
8665 LoadGameOneMove(readAhead)
8666 ChessMove readAhead;
8668 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8669 char promoChar = NULLCHAR;
8674 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8675 gameMode != AnalyzeMode && gameMode != Training) {
8680 yyboardindex = forwardMostMove;
8681 if (readAhead != (ChessMove)0) {
8682 moveType = readAhead;
8684 if (gameFileFP == NULL)
8686 moveType = (ChessMove) yylex();
8692 if (appData.debugMode)
8693 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8696 /* append the comment but don't display it */
8697 AppendComment(currentMove, p, FALSE);
8700 case WhiteCapturesEnPassant:
8701 case BlackCapturesEnPassant:
8702 case WhitePromotionChancellor:
8703 case BlackPromotionChancellor:
8704 case WhitePromotionArchbishop:
8705 case BlackPromotionArchbishop:
8706 case WhitePromotionCentaur:
8707 case BlackPromotionCentaur:
8708 case WhitePromotionQueen:
8709 case BlackPromotionQueen:
8710 case WhitePromotionRook:
8711 case BlackPromotionRook:
8712 case WhitePromotionBishop:
8713 case BlackPromotionBishop:
8714 case WhitePromotionKnight:
8715 case BlackPromotionKnight:
8716 case WhitePromotionKing:
8717 case BlackPromotionKing:
8719 case WhiteKingSideCastle:
8720 case WhiteQueenSideCastle:
8721 case BlackKingSideCastle:
8722 case BlackQueenSideCastle:
8723 case WhiteKingSideCastleWild:
8724 case WhiteQueenSideCastleWild:
8725 case BlackKingSideCastleWild:
8726 case BlackQueenSideCastleWild:
8728 case WhiteHSideCastleFR:
8729 case WhiteASideCastleFR:
8730 case BlackHSideCastleFR:
8731 case BlackASideCastleFR:
8733 if (appData.debugMode)
8734 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8735 fromX = currentMoveString[0] - AAA;
8736 fromY = currentMoveString[1] - ONE;
8737 toX = currentMoveString[2] - AAA;
8738 toY = currentMoveString[3] - ONE;
8739 promoChar = currentMoveString[4];
8744 if (appData.debugMode)
8745 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8746 fromX = moveType == WhiteDrop ?
8747 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8748 (int) CharToPiece(ToLower(currentMoveString[0]));
8750 toX = currentMoveString[2] - AAA;
8751 toY = currentMoveString[3] - ONE;
8757 case GameUnfinished:
8758 if (appData.debugMode)
8759 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8760 p = strchr(yy_text, '{');
8761 if (p == NULL) p = strchr(yy_text, '(');
8764 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8766 q = strchr(p, *p == '{' ? '}' : ')');
8767 if (q != NULL) *q = NULLCHAR;
8770 GameEnds(moveType, p, GE_FILE);
8772 if (cmailMsgLoaded) {
8774 flipView = WhiteOnMove(currentMove);
8775 if (moveType == GameUnfinished) flipView = !flipView;
8776 if (appData.debugMode)
8777 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8781 case (ChessMove) 0: /* end of file */
8782 if (appData.debugMode)
8783 fprintf(debugFP, "Parser hit end of file\n");
8784 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8790 if (WhiteOnMove(currentMove)) {
8791 GameEnds(BlackWins, "Black mates", GE_FILE);
8793 GameEnds(WhiteWins, "White mates", GE_FILE);
8797 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8804 if (lastLoadGameStart == GNUChessGame) {
8805 /* GNUChessGames have numbers, but they aren't move numbers */
8806 if (appData.debugMode)
8807 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8808 yy_text, (int) moveType);
8809 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8811 /* else fall thru */
8816 /* Reached start of next game in file */
8817 if (appData.debugMode)
8818 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8819 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8825 if (WhiteOnMove(currentMove)) {
8826 GameEnds(BlackWins, "Black mates", GE_FILE);
8828 GameEnds(WhiteWins, "White mates", GE_FILE);
8832 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8838 case PositionDiagram: /* should not happen; ignore */
8839 case ElapsedTime: /* ignore */
8840 case NAG: /* ignore */
8841 if (appData.debugMode)
8842 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8843 yy_text, (int) moveType);
8844 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8847 if (appData.testLegality) {
8848 if (appData.debugMode)
8849 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8850 sprintf(move, _("Illegal move: %d.%s%s"),
8851 (forwardMostMove / 2) + 1,
8852 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8853 DisplayError(move, 0);
8856 if (appData.debugMode)
8857 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8858 yy_text, currentMoveString);
8859 fromX = currentMoveString[0] - AAA;
8860 fromY = currentMoveString[1] - ONE;
8861 toX = currentMoveString[2] - AAA;
8862 toY = currentMoveString[3] - ONE;
8863 promoChar = currentMoveString[4];
8868 if (appData.debugMode)
8869 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8870 sprintf(move, _("Ambiguous move: %d.%s%s"),
8871 (forwardMostMove / 2) + 1,
8872 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8873 DisplayError(move, 0);
8878 case ImpossibleMove:
8879 if (appData.debugMode)
8880 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8881 sprintf(move, _("Illegal move: %d.%s%s"),
8882 (forwardMostMove / 2) + 1,
8883 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8884 DisplayError(move, 0);
8890 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8891 DrawPosition(FALSE, boards[currentMove]);
8892 DisplayBothClocks();
8893 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8894 DisplayComment(currentMove - 1, commentList[currentMove]);
8896 (void) StopLoadGameTimer();
8898 cmailOldMove = forwardMostMove;
8901 /* currentMoveString is set as a side-effect of yylex */
8902 strcat(currentMoveString, "\n");
8903 strcpy(moveList[forwardMostMove], currentMoveString);
8905 thinkOutput[0] = NULLCHAR;
8906 MakeMove(fromX, fromY, toX, toY, promoChar);
8907 currentMove = forwardMostMove;
8912 /* Load the nth game from the given file */
8914 LoadGameFromFile(filename, n, title, useList)
8918 /*Boolean*/ int useList;
8923 if (strcmp(filename, "-") == 0) {
8927 f = fopen(filename, "rb");
8929 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8930 DisplayError(buf, errno);
8934 if (fseek(f, 0, 0) == -1) {
8935 /* f is not seekable; probably a pipe */
8938 if (useList && n == 0) {
8939 int error = GameListBuild(f);
8941 DisplayError(_("Cannot build game list"), error);
8942 } else if (!ListEmpty(&gameList) &&
8943 ((ListGame *) gameList.tailPred)->number > 1) {
8944 // TODO convert to GTK
8945 // GameListPopUp(f, title);
8952 return LoadGame(f, n, title, FALSE);
8957 MakeRegisteredMove()
8959 int fromX, fromY, toX, toY;
8961 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8962 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8965 if (appData.debugMode)
8966 fprintf(debugFP, "Restoring %s for game %d\n",
8967 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8969 thinkOutput[0] = NULLCHAR;
8970 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8971 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8972 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8973 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8974 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8975 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8976 MakeMove(fromX, fromY, toX, toY, promoChar);
8977 ShowMove(fromX, fromY, toX, toY);
8978 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8985 if (WhiteOnMove(currentMove)) {
8986 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8988 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8993 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9000 if (WhiteOnMove(currentMove)) {
9001 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9003 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9008 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9019 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9021 CmailLoadGame(f, gameNumber, title, useList)
9029 if (gameNumber > nCmailGames) {
9030 DisplayError(_("No more games in this message"), 0);
9033 if (f == lastLoadGameFP) {
9034 int offset = gameNumber - lastLoadGameNumber;
9036 cmailMsg[0] = NULLCHAR;
9037 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9038 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9039 nCmailMovesRegistered--;
9041 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9042 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9043 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9046 if (! RegisterMove()) return FALSE;
9050 retVal = LoadGame(f, gameNumber, title, useList);
9052 /* Make move registered during previous look at this game, if any */
9053 MakeRegisteredMove();
9055 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9056 commentList[currentMove]
9057 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9058 DisplayComment(currentMove - 1, commentList[currentMove]);
9064 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9069 int gameNumber = lastLoadGameNumber + offset;
9070 if (lastLoadGameFP == NULL) {
9071 DisplayError(_("No game has been loaded yet"), 0);
9074 if (gameNumber <= 0) {
9075 DisplayError(_("Can't back up any further"), 0);
9078 if (cmailMsgLoaded) {
9079 return CmailLoadGame(lastLoadGameFP, gameNumber,
9080 lastLoadGameTitle, lastLoadGameUseList);
9082 return LoadGame(lastLoadGameFP, gameNumber,
9083 lastLoadGameTitle, lastLoadGameUseList);
9089 /* Load the nth game from open file f */
9091 LoadGame(f, gameNumber, title, useList)
9099 int gn = gameNumber;
9100 ListGame *lg = NULL;
9103 GameMode oldGameMode;
9104 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9106 if (appData.debugMode)
9107 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9109 if (gameMode == Training )
9110 SetTrainingModeOff();
9112 oldGameMode = gameMode;
9113 if (gameMode != BeginningOfGame)
9119 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
9121 fclose(lastLoadGameFP);
9126 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9130 fseek(f, lg->offset, 0);
9131 GameListHighlight(gameNumber);
9136 DisplayError(_("Game number out of range"), 0);
9143 if (fseek(f, 0, 0) == -1)
9145 if (f == lastLoadGameFP ?
9146 gameNumber == lastLoadGameNumber + 1 :
9153 DisplayError(_("Can't seek on game file"), 0);
9160 lastLoadGameNumber = gameNumber;
9161 strcpy(lastLoadGameTitle, title);
9162 lastLoadGameUseList = useList;
9166 if (lg && lg->gameInfo.white && lg->gameInfo.black)
9168 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9169 lg->gameInfo.black);
9172 else if (*title != NULLCHAR)
9176 sprintf(buf, "%s %d", title, gameNumber);
9181 DisplayTitle(title);
9185 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
9187 gameMode = PlayFromGameFile;
9191 currentMove = forwardMostMove = backwardMostMove = 0;
9192 CopyBoard(boards[0], initialPosition);
9196 * Skip the first gn-1 games in the file.
9197 * Also skip over anything that precedes an identifiable
9198 * start of game marker, to avoid being confused by
9199 * garbage at the start of the file. Currently
9200 * recognized start of game markers are the move number "1",
9201 * the pattern "gnuchess .* game", the pattern
9202 * "^[#;%] [^ ]* game file", and a PGN tag block.
9203 * A game that starts with one of the latter two patterns
9204 * will also have a move number 1, possibly
9205 * following a position diagram.
9206 * 5-4-02: Let's try being more lenient and allowing a game to
9207 * start with an unnumbered move. Does that break anything?
9209 cm = lastLoadGameStart = (ChessMove) 0;
9211 yyboardindex = forwardMostMove;
9212 cm = (ChessMove) yylex();
9215 if (cmailMsgLoaded) {
9216 nCmailGames = CMAIL_MAX_GAMES - gn;
9219 DisplayError(_("Game not found in file"), 0);
9226 lastLoadGameStart = cm;
9230 switch (lastLoadGameStart) {
9237 gn--; /* count this game */
9238 lastLoadGameStart = cm;
9247 switch (lastLoadGameStart) {
9252 gn--; /* count this game */
9253 lastLoadGameStart = cm;
9256 lastLoadGameStart = cm; /* game counted already */
9264 yyboardindex = forwardMostMove;
9265 cm = (ChessMove) yylex();
9266 } while (cm == PGNTag || cm == Comment);
9273 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9274 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9275 != CMAIL_OLD_RESULT) {
9277 cmailResult[ CMAIL_MAX_GAMES
9278 - gn - 1] = CMAIL_OLD_RESULT;
9284 /* Only a NormalMove can be at the start of a game
9285 * without a position diagram. */
9286 if (lastLoadGameStart == (ChessMove) 0) {
9288 lastLoadGameStart = MoveNumberOne;
9297 if (appData.debugMode)
9298 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9300 if (cm == XBoardGame) {
9301 /* Skip any header junk before position diagram and/or move 1 */
9303 yyboardindex = forwardMostMove;
9304 cm = (ChessMove) yylex();
9306 if (cm == (ChessMove) 0 ||
9307 cm == GNUChessGame || cm == XBoardGame) {
9308 /* Empty game; pretend end-of-file and handle later */
9313 if (cm == MoveNumberOne || cm == PositionDiagram ||
9314 cm == PGNTag || cm == Comment)
9317 } else if (cm == GNUChessGame) {
9318 if (gameInfo.event != NULL) {
9319 free(gameInfo.event);
9321 gameInfo.event = StrSave(yy_text);
9324 startedFromSetupPosition = FALSE;
9325 while (cm == PGNTag) {
9326 if (appData.debugMode)
9327 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9328 err = ParsePGNTag(yy_text, &gameInfo);
9329 if (!err) numPGNTags++;
9331 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9332 if(gameInfo.variant != oldVariant) {
9333 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9335 oldVariant = gameInfo.variant;
9336 if (appData.debugMode)
9337 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9341 if (gameInfo.fen != NULL) {
9342 Board initial_position;
9343 startedFromSetupPosition = TRUE;
9344 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9346 DisplayError(_("Bad FEN position in file"), 0);
9349 CopyBoard(boards[0], initial_position);
9350 if (blackPlaysFirst) {
9351 currentMove = forwardMostMove = backwardMostMove = 1;
9352 CopyBoard(boards[1], initial_position);
9353 strcpy(moveList[0], "");
9354 strcpy(parseList[0], "");
9355 timeRemaining[0][1] = whiteTimeRemaining;
9356 timeRemaining[1][1] = blackTimeRemaining;
9357 if (commentList[0] != NULL) {
9358 commentList[1] = commentList[0];
9359 commentList[0] = NULL;
9362 currentMove = forwardMostMove = backwardMostMove = 0;
9364 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9366 initialRulePlies = FENrulePlies;
9367 for( i=0; i< nrCastlingRights; i++ )
9368 initialRights[i] = initial_position[CASTLING][i];
9370 yyboardindex = forwardMostMove;
9372 gameInfo.fen = NULL;
9375 yyboardindex = forwardMostMove;
9376 cm = (ChessMove) yylex();
9378 /* Handle comments interspersed among the tags */
9379 while (cm == Comment) {
9381 if (appData.debugMode)
9382 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9384 AppendComment(currentMove, p, FALSE);
9385 yyboardindex = forwardMostMove;
9386 cm = (ChessMove) yylex();
9390 /* don't rely on existence of Event tag since if game was
9391 * pasted from clipboard the Event tag may not exist
9393 if (numPGNTags > 0){
9395 if (gameInfo.variant == VariantNormal) {
9396 gameInfo.variant = StringToVariant(gameInfo.event);
9399 if( appData.autoDisplayTags ) {
9400 tags = PGNTags(&gameInfo);
9401 TagsPopUp(tags, CmailMsg());
9406 /* Make something up, but don't display it now */
9411 if (cm == PositionDiagram) {
9414 Board initial_position;
9416 if (appData.debugMode)
9417 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9419 if (!startedFromSetupPosition) {
9421 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9422 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9432 initial_position[i][j++] = CharToPiece(*p);
9435 while (*p == ' ' || *p == '\t' ||
9436 *p == '\n' || *p == '\r') p++;
9438 if (strncmp(p, "black", strlen("black"))==0)
9439 blackPlaysFirst = TRUE;
9441 blackPlaysFirst = FALSE;
9442 startedFromSetupPosition = TRUE;
9444 CopyBoard(boards[0], initial_position);
9445 if (blackPlaysFirst) {
9446 currentMove = forwardMostMove = backwardMostMove = 1;
9447 CopyBoard(boards[1], initial_position);
9448 strcpy(moveList[0], "");
9449 strcpy(parseList[0], "");
9450 timeRemaining[0][1] = whiteTimeRemaining;
9451 timeRemaining[1][1] = blackTimeRemaining;
9452 if (commentList[0] != NULL) {
9453 commentList[1] = commentList[0];
9454 commentList[0] = NULL;
9457 currentMove = forwardMostMove = backwardMostMove = 0;
9460 yyboardindex = forwardMostMove;
9461 cm = (ChessMove) yylex();
9464 if (first.pr == NoProc) {
9465 StartChessProgram(&first);
9467 InitChessProgram(&first, FALSE);
9468 SendToProgram("force\n", &first);
9469 if (startedFromSetupPosition) {
9470 SendBoard(&first, forwardMostMove);
9471 if (appData.debugMode) {
9472 fprintf(debugFP, "Load Game\n");
9474 DisplayBothClocks();
9477 /* [HGM] server: flag to write setup moves in broadcast file as one */
9478 loadFlag = appData.suppressLoadMoves;
9480 while (cm == Comment) {
9482 if (appData.debugMode)
9483 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9485 AppendComment(currentMove, p, FALSE);
9486 yyboardindex = forwardMostMove;
9487 cm = (ChessMove) yylex();
9490 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9491 cm == WhiteWins || cm == BlackWins ||
9492 cm == GameIsDrawn || cm == GameUnfinished) {
9493 DisplayMessage("", _("No moves in game"));
9494 if (cmailMsgLoaded) {
9495 if (appData.debugMode)
9496 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9500 DrawPosition(FALSE, boards[currentMove]);
9501 DisplayBothClocks();
9502 gameMode = EditGame;
9509 // [HGM] PV info: routine tests if comment empty
9510 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9511 DisplayComment(currentMove - 1, commentList[currentMove]);
9513 if (!matchMode && appData.timeDelay != 0)
9514 DrawPosition(FALSE, boards[currentMove]);
9516 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9517 programStats.ok_to_send = 1;
9520 /* if the first token after the PGN tags is a move
9521 * and not move number 1, retrieve it from the parser
9523 if (cm != MoveNumberOne)
9524 LoadGameOneMove(cm);
9526 /* load the remaining moves from the file */
9527 while (LoadGameOneMove((ChessMove)0)) {
9528 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9529 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9532 /* rewind to the start of the game */
9533 currentMove = backwardMostMove;
9535 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9537 if (oldGameMode == AnalyzeFile ||
9538 oldGameMode == AnalyzeMode) {
9542 if (matchMode || appData.timeDelay == 0) {
9544 gameMode = EditGame;
9546 } else if (appData.timeDelay > 0) {
9550 if (appData.debugMode)
9551 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9553 loadFlag = 0; /* [HGM] true game starts */
9557 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9559 ReloadPosition(offset)
9562 int positionNumber = lastLoadPositionNumber + offset;
9563 if (lastLoadPositionFP == NULL) {
9564 DisplayError(_("No position has been loaded yet"), 0);
9567 if (positionNumber <= 0) {
9568 DisplayError(_("Can't back up any further"), 0);
9571 return LoadPosition(lastLoadPositionFP, positionNumber,
9572 lastLoadPositionTitle);
9575 /* Load the nth position from the given file */
9577 LoadPositionFromFile(filename, n, title)
9585 if (strcmp(filename, "-") == 0) {
9586 return LoadPosition(stdin, n, "stdin");
9588 f = fopen(filename, "rb");
9590 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9591 DisplayError(buf, errno);
9594 return LoadPosition(f, n, title);
9599 /* Load the nth position from the given open file, and close it */
9601 LoadPosition(f, positionNumber, title)
9606 char *p, line[MSG_SIZ];
9607 Board initial_position;
9608 int i, j, fenMode, pn;
9610 if (gameMode == Training )
9611 SetTrainingModeOff();
9613 if (gameMode != BeginningOfGame) {
9616 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9617 fclose(lastLoadPositionFP);
9619 if (positionNumber == 0) positionNumber = 1;
9620 lastLoadPositionFP = f;
9621 lastLoadPositionNumber = positionNumber;
9622 strcpy(lastLoadPositionTitle, title);
9623 if (first.pr == NoProc) {
9624 StartChessProgram(&first);
9625 InitChessProgram(&first, FALSE);
9627 pn = positionNumber;
9628 if (positionNumber < 0) {
9629 /* Negative position number means to seek to that byte offset */
9630 if (fseek(f, -positionNumber, 0) == -1) {
9631 DisplayError(_("Can't seek on position file"), 0);
9636 if (fseek(f, 0, 0) == -1) {
9637 if (f == lastLoadPositionFP ?
9638 positionNumber == lastLoadPositionNumber + 1 :
9639 positionNumber == 1) {
9642 DisplayError(_("Can't seek on position file"), 0);
9647 /* See if this file is FEN or old-style xboard */
9648 if (fgets(line, MSG_SIZ, f) == NULL) {
9649 DisplayError(_("Position not found in file"), 0);
9652 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9653 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9656 if (fenMode || line[0] == '#') pn--;
9658 /* skip positions before number pn */
9659 if (fgets(line, MSG_SIZ, f) == NULL) {
9661 DisplayError(_("Position not found in file"), 0);
9664 if (fenMode || line[0] == '#') pn--;
9669 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9670 DisplayError(_("Bad FEN position in file"), 0);
9674 (void) fgets(line, MSG_SIZ, f);
9675 (void) fgets(line, MSG_SIZ, f);
9677 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9678 (void) fgets(line, MSG_SIZ, f);
9679 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9682 initial_position[i][j++] = CharToPiece(*p);
9686 blackPlaysFirst = FALSE;
9688 (void) fgets(line, MSG_SIZ, f);
9689 if (strncmp(line, "black", strlen("black"))==0)
9690 blackPlaysFirst = TRUE;
9693 startedFromSetupPosition = TRUE;
9695 SendToProgram("force\n", &first);
9696 CopyBoard(boards[0], initial_position);
9697 if (blackPlaysFirst) {
9698 currentMove = forwardMostMove = backwardMostMove = 1;
9699 strcpy(moveList[0], "");
9700 strcpy(parseList[0], "");
9701 CopyBoard(boards[1], initial_position);
9702 DisplayMessage("", _("Black to play"));
9704 currentMove = forwardMostMove = backwardMostMove = 0;
9705 DisplayMessage("", _("White to play"));
9707 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9708 SendBoard(&first, forwardMostMove);
9709 if (appData.debugMode) {
9711 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9712 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9713 fprintf(debugFP, "Load Position\n");
9716 if (positionNumber > 1) {
9717 sprintf(line, "%s %d", title, positionNumber);
9720 DisplayTitle(title);
9722 gameMode = EditGame;
9725 timeRemaining[0][1] = whiteTimeRemaining;
9726 timeRemaining[1][1] = blackTimeRemaining;
9727 DrawPosition(FALSE, boards[currentMove]);
9734 CopyPlayerNameIntoFileName(dest, src)
9737 while (*src != NULLCHAR && *src != ',') {
9742 *(*dest)++ = *src++;
9747 char *DefaultFileName(ext)
9750 static char def[MSG_SIZ];
9753 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9755 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9757 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9766 /* Save the current game to the given file */
9768 SaveGameToFile(filename, append)
9775 if (strcmp(filename, "-") == 0) {
9776 return SaveGame(stdout, 0, NULL);
9778 f = fopen(filename, append ? "a" : "w");
9780 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9781 DisplayError(buf, errno);
9784 return SaveGame(f, 0, NULL);
9793 static char buf[MSG_SIZ];
9796 p = strchr(str, ' ');
9797 if (p == NULL) return str;
9798 strncpy(buf, str, p - str);
9799 buf[p - str] = NULLCHAR;
9803 #define PGN_MAX_LINE 75
9805 #define PGN_SIDE_WHITE 0
9806 #define PGN_SIDE_BLACK 1
9809 static int FindFirstMoveOutOfBook( int side )
9813 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9814 int index = backwardMostMove;
9815 int has_book_hit = 0;
9817 if( (index % 2) != side ) {
9821 while( index < forwardMostMove ) {
9822 /* Check to see if engine is in book */
9823 int depth = pvInfoList[index].depth;
9824 int score = pvInfoList[index].score;
9830 else if( score == 0 && depth == 63 ) {
9831 in_book = 1; /* Zappa */
9833 else if( score == 2 && depth == 99 ) {
9834 in_book = 1; /* Abrok */
9837 has_book_hit += in_book;
9853 void GetOutOfBookInfo( char * buf )
9857 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9859 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9860 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9864 if( oob[0] >= 0 || oob[1] >= 0 ) {
9865 for( i=0; i<2; i++ ) {
9869 if( i > 0 && oob[0] >= 0 ) {
9873 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9874 sprintf( buf+strlen(buf), "%s%.2f",
9875 pvInfoList[idx].score >= 0 ? "+" : "",
9876 pvInfoList[idx].score / 100.0 );
9882 /* Save game in PGN style and close the file */
9887 int i, offset, linelen, newblock;
9891 int movelen, numlen, blank;
9892 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9894 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9896 tm = time((time_t *) NULL);
9898 PrintPGNTags(f, &gameInfo);
9900 if (backwardMostMove > 0 || startedFromSetupPosition) {
9901 char *fen = PositionToFEN(backwardMostMove, NULL);
9902 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9903 fprintf(f, "\n{--------------\n");
9904 PrintPosition(f, backwardMostMove);
9905 fprintf(f, "--------------}\n");
9909 /* [AS] Out of book annotation */
9910 if( appData.saveOutOfBookInfo ) {
9913 GetOutOfBookInfo( buf );
9915 if( buf[0] != '\0' ) {
9916 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9923 i = backwardMostMove;
9927 while (i < forwardMostMove) {
9928 /* Print comments preceding this move */
9929 if (commentList[i] != NULL) {
9930 if (linelen > 0) fprintf(f, "\n");
9931 fprintf(f, "%s", commentList[i]);
9936 /* Format move number */
9938 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9941 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9943 numtext[0] = NULLCHAR;
9946 numlen = strlen(numtext);
9949 /* Print move number */
9950 blank = linelen > 0 && numlen > 0;
9951 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9960 fprintf(f, "%s", numtext);
9964 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9965 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9968 blank = linelen > 0 && movelen > 0;
9969 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9978 fprintf(f, "%s", move_buffer);
9981 /* [AS] Add PV info if present */
9982 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9983 /* [HGM] add time */
9984 char buf[MSG_SIZ]; int seconds;
9986 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9988 if( seconds <= 0) buf[0] = 0; else
9989 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9990 seconds = (seconds + 4)/10; // round to full seconds
9991 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9992 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9995 sprintf( move_buffer, "{%s%.2f/%d%s}",
9996 pvInfoList[i].score >= 0 ? "+" : "",
9997 pvInfoList[i].score / 100.0,
9998 pvInfoList[i].depth,
10001 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10003 /* Print score/depth */
10004 blank = linelen > 0 && movelen > 0;
10005 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10014 fprintf(f, "%s", move_buffer);
10015 linelen += movelen;
10021 /* Start a new line */
10022 if (linelen > 0) fprintf(f, "\n");
10024 /* Print comments after last move */
10025 if (commentList[i] != NULL) {
10026 fprintf(f, "%s\n", commentList[i]);
10030 if (gameInfo.resultDetails != NULL &&
10031 gameInfo.resultDetails[0] != NULLCHAR) {
10032 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10033 PGNResult(gameInfo.result));
10035 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10039 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10043 /* Save game in old style and close the file */
10045 SaveGameOldStyle(f)
10051 tm = time((time_t *) NULL);
10053 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10056 if (backwardMostMove > 0 || startedFromSetupPosition) {
10057 fprintf(f, "\n[--------------\n");
10058 PrintPosition(f, backwardMostMove);
10059 fprintf(f, "--------------]\n");
10064 i = backwardMostMove;
10065 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10067 while (i < forwardMostMove) {
10068 if (commentList[i] != NULL) {
10069 fprintf(f, "[%s]\n", commentList[i]);
10072 if ((i % 2) == 1) {
10073 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10076 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10078 if (commentList[i] != NULL) {
10082 if (i >= forwardMostMove) {
10086 fprintf(f, "%s\n", parseList[i]);
10091 if (commentList[i] != NULL) {
10092 fprintf(f, "[%s]\n", commentList[i]);
10095 /* This isn't really the old style, but it's close enough */
10096 if (gameInfo.resultDetails != NULL &&
10097 gameInfo.resultDetails[0] != NULLCHAR) {
10098 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10099 gameInfo.resultDetails);
10101 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10108 /* Save the current game to open file f and close the file */
10110 SaveGame(f, dummy, dummy2)
10115 if (gameMode == EditPosition) EditPositionDone(TRUE);
10116 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10117 if (appData.oldSaveStyle)
10118 return SaveGameOldStyle(f);
10120 return SaveGamePGN(f);
10123 /* Save the current position to the given file */
10125 SavePositionToFile(filename)
10131 if (strcmp(filename, "-") == 0) {
10132 return SavePosition(stdout, 0, NULL);
10134 f = fopen(filename, "a");
10136 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10137 DisplayError(buf, errno);
10140 SavePosition(f, 0, NULL);
10146 /* Save the current position to the given open file and close the file */
10148 SavePosition(f, dummy, dummy2)
10155 if (gameMode == EditPosition) EditPositionDone(TRUE);
10156 if (appData.oldSaveStyle) {
10157 tm = time((time_t *) NULL);
10159 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10161 fprintf(f, "[--------------\n");
10162 PrintPosition(f, currentMove);
10163 fprintf(f, "--------------]\n");
10165 fen = PositionToFEN(currentMove, NULL);
10166 fprintf(f, "%s\n", fen);
10174 ReloadCmailMsgEvent(unregister)
10178 static char *inFilename = NULL;
10179 static char *outFilename;
10181 struct stat inbuf, outbuf;
10184 /* Any registered moves are unregistered if unregister is set, */
10185 /* i.e. invoked by the signal handler */
10187 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10188 cmailMoveRegistered[i] = FALSE;
10189 if (cmailCommentList[i] != NULL) {
10190 free(cmailCommentList[i]);
10191 cmailCommentList[i] = NULL;
10194 nCmailMovesRegistered = 0;
10197 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10198 cmailResult[i] = CMAIL_NOT_RESULT;
10202 if (inFilename == NULL) {
10203 /* Because the filenames are static they only get malloced once */
10204 /* and they never get freed */
10205 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10206 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10208 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10209 sprintf(outFilename, "%s.out", appData.cmailGameName);
10212 status = stat(outFilename, &outbuf);
10214 cmailMailedMove = FALSE;
10216 status = stat(inFilename, &inbuf);
10217 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10220 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10221 counts the games, notes how each one terminated, etc.
10223 It would be nice to remove this kludge and instead gather all
10224 the information while building the game list. (And to keep it
10225 in the game list nodes instead of having a bunch of fixed-size
10226 parallel arrays.) Note this will require getting each game's
10227 termination from the PGN tags, as the game list builder does
10228 not process the game moves. --mann
10230 cmailMsgLoaded = TRUE;
10231 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10233 /* Load first game in the file or popup game menu */
10234 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10236 #endif /* !WIN32 */
10244 char string[MSG_SIZ];
10246 if ( cmailMailedMove
10247 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10248 return TRUE; /* Allow free viewing */
10251 /* Unregister move to ensure that we don't leave RegisterMove */
10252 /* with the move registered when the conditions for registering no */
10254 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10255 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10256 nCmailMovesRegistered --;
10258 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10260 free(cmailCommentList[lastLoadGameNumber - 1]);
10261 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10265 if (cmailOldMove == -1) {
10266 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10270 if (currentMove > cmailOldMove + 1) {
10271 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10275 if (currentMove < cmailOldMove) {
10276 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10280 if (forwardMostMove > currentMove) {
10281 /* Silently truncate extra moves */
10285 if ( (currentMove == cmailOldMove + 1)
10286 || ( (currentMove == cmailOldMove)
10287 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10288 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10289 if (gameInfo.result != GameUnfinished) {
10290 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10293 if (commentList[currentMove] != NULL) {
10294 cmailCommentList[lastLoadGameNumber - 1]
10295 = StrSave(commentList[currentMove]);
10297 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10299 if (appData.debugMode)
10300 fprintf(debugFP, "Saving %s for game %d\n",
10301 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10304 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10306 f = fopen(string, "w");
10307 if (appData.oldSaveStyle) {
10308 SaveGameOldStyle(f); /* also closes the file */
10310 sprintf(string, "%s.pos.out", appData.cmailGameName);
10311 f = fopen(string, "w");
10312 SavePosition(f, 0, NULL); /* also closes the file */
10314 fprintf(f, "{--------------\n");
10315 PrintPosition(f, currentMove);
10316 fprintf(f, "--------------}\n\n");
10318 SaveGame(f, 0, NULL); /* also closes the file*/
10321 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10322 nCmailMovesRegistered ++;
10323 } else if (nCmailGames == 1) {
10324 DisplayError(_("You have not made a move yet"), 0);
10335 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10336 FILE *commandOutput;
10337 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10338 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10344 if (! cmailMsgLoaded) {
10345 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10349 if (nCmailGames == nCmailResults) {
10350 DisplayError(_("No unfinished games"), 0);
10354 #if CMAIL_PROHIBIT_REMAIL
10355 if (cmailMailedMove) {
10356 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);
10357 DisplayError(msg, 0);
10362 if (! (cmailMailedMove || RegisterMove())) return;
10364 if ( cmailMailedMove
10365 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10366 sprintf(string, partCommandString,
10367 appData.debugMode ? " -v" : "", appData.cmailGameName);
10368 commandOutput = popen(string, "r");
10370 if (commandOutput == NULL) {
10371 DisplayError(_("Failed to invoke cmail"), 0);
10373 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10374 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10376 if (nBuffers > 1) {
10377 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10378 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10379 nBytes = MSG_SIZ - 1;
10381 (void) memcpy(msg, buffer, nBytes);
10383 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10385 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10386 cmailMailedMove = TRUE; /* Prevent >1 moves */
10389 for (i = 0; i < nCmailGames; i ++) {
10390 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10395 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10397 sprintf(buffer, "%s/%s.%s.archive",
10399 appData.cmailGameName,
10401 LoadGameFromFile(buffer, 1, buffer, FALSE);
10402 cmailMsgLoaded = FALSE;
10406 DisplayInformation(msg);
10407 pclose(commandOutput);
10410 if ((*cmailMsg) != '\0') {
10411 DisplayInformation(cmailMsg);
10416 #endif /* !WIN32 */
10425 int prependComma = 0;
10427 char string[MSG_SIZ]; /* Space for game-list */
10430 if (!cmailMsgLoaded) return "";
10432 if (cmailMailedMove) {
10433 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10435 /* Create a list of games left */
10436 sprintf(string, "[");
10437 for (i = 0; i < nCmailGames; i ++) {
10438 if (! ( cmailMoveRegistered[i]
10439 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10440 if (prependComma) {
10441 sprintf(number, ",%d", i + 1);
10443 sprintf(number, "%d", i + 1);
10447 strcat(string, number);
10450 strcat(string, "]");
10452 if (nCmailMovesRegistered + nCmailResults == 0) {
10453 switch (nCmailGames) {
10456 _("Still need to make move for game\n"));
10461 _("Still need to make moves for both games\n"));
10466 _("Still need to make moves for all %d games\n"),
10471 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10474 _("Still need to make a move for game %s\n"),
10479 if (nCmailResults == nCmailGames) {
10480 sprintf(cmailMsg, _("No unfinished games\n"));
10482 sprintf(cmailMsg, _("Ready to send mail\n"));
10488 _("Still need to make moves for games %s\n"),
10500 if (gameMode == Training)
10501 SetTrainingModeOff();
10504 cmailMsgLoaded = FALSE;
10505 if (appData.icsActive) {
10506 SendToICS(ics_prefix);
10507 SendToICS("refresh\n");
10517 /* Give up on clean exit */
10521 /* Keep trying for clean exit */
10525 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10527 if (telnetISR != NULL) {
10528 RemoveInputSource(telnetISR);
10530 if (icsPR != NoProc) {
10531 DestroyChildProcess(icsPR, TRUE);
10534 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10535 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10537 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10538 /* make sure this other one finishes before killing it! */
10539 if(endingGame) { int count = 0;
10540 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10541 while(endingGame && count++ < 10) DoSleep(1);
10542 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10545 /* Kill off chess programs */
10546 if (first.pr != NoProc) {
10549 DoSleep( appData.delayBeforeQuit );
10550 SendToProgram("quit\n", &first);
10551 DoSleep( appData.delayAfterQuit );
10552 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10554 if (second.pr != NoProc) {
10555 DoSleep( appData.delayBeforeQuit );
10556 SendToProgram("quit\n", &second);
10557 DoSleep( appData.delayAfterQuit );
10558 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10560 if (first.isr != NULL) {
10561 RemoveInputSource(first.isr);
10563 if (second.isr != NULL) {
10564 RemoveInputSource(second.isr);
10567 ShutDownFrontEnd();
10574 if (appData.debugMode)
10575 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10579 if (gameMode == MachinePlaysWhite ||
10580 gameMode == MachinePlaysBlack) {
10583 DisplayBothClocks();
10585 if (gameMode == PlayFromGameFile) {
10586 if (appData.timeDelay >= 0)
10587 AutoPlayGameLoop();
10588 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10589 Reset(FALSE, TRUE);
10590 SendToICS(ics_prefix);
10591 SendToICS("refresh\n");
10592 } else if (currentMove < forwardMostMove) {
10593 ForwardInner(forwardMostMove);
10595 pauseExamInvalid = FALSE;
10597 switch (gameMode) {
10601 pauseExamForwardMostMove = forwardMostMove;
10602 pauseExamInvalid = FALSE;
10605 case IcsPlayingWhite:
10606 case IcsPlayingBlack:
10610 case PlayFromGameFile:
10611 (void) StopLoadGameTimer();
10615 case BeginningOfGame:
10616 if (appData.icsActive) return;
10617 /* else fall through */
10618 case MachinePlaysWhite:
10619 case MachinePlaysBlack:
10620 case TwoMachinesPlay:
10621 if (forwardMostMove == 0)
10622 return; /* don't pause if no one has moved */
10623 if ((gameMode == MachinePlaysWhite &&
10624 !WhiteOnMove(forwardMostMove)) ||
10625 (gameMode == MachinePlaysBlack &&
10626 WhiteOnMove(forwardMostMove))) {
10639 char title[MSG_SIZ];
10641 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10642 strcpy(title, _("Edit comment"));
10644 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10645 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10646 parseList[currentMove - 1]);
10649 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10656 char *tags = PGNTags(&gameInfo);
10657 EditTagsPopUp(tags);
10664 if (appData.noChessProgram || gameMode == AnalyzeMode)
10667 if (gameMode != AnalyzeFile) {
10668 if (!appData.icsEngineAnalyze) {
10670 if (gameMode != EditGame) return;
10672 ResurrectChessProgram();
10673 SendToProgram("analyze\n", &first);
10674 first.analyzing = TRUE;
10675 /*first.maybeThinking = TRUE;*/
10676 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10677 EngineOutputPopUp();
10679 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10684 StartAnalysisClock();
10685 GetTimeMark(&lastNodeCountTime);
10692 if (appData.noChessProgram || gameMode == AnalyzeFile)
10695 if (gameMode != AnalyzeMode) {
10697 if (gameMode != EditGame) return;
10698 ResurrectChessProgram();
10699 SendToProgram("analyze\n", &first);
10700 first.analyzing = TRUE;
10701 /*first.maybeThinking = TRUE;*/
10702 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10703 EngineOutputPopUp();
10705 gameMode = AnalyzeFile;
10710 StartAnalysisClock();
10711 GetTimeMark(&lastNodeCountTime);
10716 MachineWhiteEvent()
10719 char *bookHit = NULL;
10721 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10725 if (gameMode == PlayFromGameFile ||
10726 gameMode == TwoMachinesPlay ||
10727 gameMode == Training ||
10728 gameMode == AnalyzeMode ||
10729 gameMode == EndOfGame)
10732 if (gameMode == EditPosition)
10733 EditPositionDone(TRUE);
10735 if (!WhiteOnMove(currentMove)) {
10736 DisplayError(_("It is not White's turn"), 0);
10740 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10743 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10744 gameMode == AnalyzeFile)
10747 ResurrectChessProgram(); /* in case it isn't running */
10748 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10749 gameMode = MachinePlaysWhite;
10752 gameMode = MachinePlaysWhite;
10756 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10758 if (first.sendName) {
10759 sprintf(buf, "name %s\n", gameInfo.black);
10760 SendToProgram(buf, &first);
10762 if (first.sendTime) {
10763 if (first.useColors) {
10764 SendToProgram("black\n", &first); /*gnu kludge*/
10766 SendTimeRemaining(&first, TRUE);
10768 if (first.useColors) {
10769 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10771 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10772 SetMachineThinkingEnables();
10773 first.maybeThinking = TRUE;
10777 if (appData.autoFlipView && !flipView) {
10778 flipView = !flipView;
10779 DrawPosition(FALSE, NULL);
10780 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10783 if(bookHit) { // [HGM] book: simulate book reply
10784 static char bookMove[MSG_SIZ]; // a bit generous?
10786 programStats.nodes = programStats.depth = programStats.time =
10787 programStats.score = programStats.got_only_move = 0;
10788 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10790 strcpy(bookMove, "move ");
10791 strcat(bookMove, bookHit);
10792 HandleMachineMove(bookMove, &first);
10797 MachineBlackEvent()
10800 char *bookHit = NULL;
10802 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10806 if (gameMode == PlayFromGameFile ||
10807 gameMode == TwoMachinesPlay ||
10808 gameMode == Training ||
10809 gameMode == AnalyzeMode ||
10810 gameMode == EndOfGame)
10813 if (gameMode == EditPosition)
10814 EditPositionDone(TRUE);
10816 if (WhiteOnMove(currentMove)) {
10817 DisplayError(_("It is not Black's turn"), 0);
10821 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10824 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10825 gameMode == AnalyzeFile)
10828 ResurrectChessProgram(); /* in case it isn't running */
10829 gameMode = MachinePlaysBlack;
10833 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10835 if (first.sendName) {
10836 sprintf(buf, "name %s\n", gameInfo.white);
10837 SendToProgram(buf, &first);
10839 if (first.sendTime) {
10840 if (first.useColors) {
10841 SendToProgram("white\n", &first); /*gnu kludge*/
10843 SendTimeRemaining(&first, FALSE);
10845 if (first.useColors) {
10846 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10848 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10849 SetMachineThinkingEnables();
10850 first.maybeThinking = TRUE;
10853 if (appData.autoFlipView && flipView) {
10854 flipView = !flipView;
10855 DrawPosition(FALSE, NULL);
10856 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10858 if(bookHit) { // [HGM] book: simulate book reply
10859 static char bookMove[MSG_SIZ]; // a bit generous?
10861 programStats.nodes = programStats.depth = programStats.time =
10862 programStats.score = programStats.got_only_move = 0;
10863 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10865 strcpy(bookMove, "move ");
10866 strcat(bookMove, bookHit);
10867 HandleMachineMove(bookMove, &first);
10873 DisplayTwoMachinesTitle()
10876 if (appData.matchGames > 0) {
10877 if (first.twoMachinesColor[0] == 'w') {
10878 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10879 gameInfo.white, gameInfo.black,
10880 first.matchWins, second.matchWins,
10881 matchGame - 1 - (first.matchWins + second.matchWins));
10883 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10884 gameInfo.white, gameInfo.black,
10885 second.matchWins, first.matchWins,
10886 matchGame - 1 - (first.matchWins + second.matchWins));
10889 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10895 TwoMachinesEvent P((void))
10899 ChessProgramState *onmove;
10900 char *bookHit = NULL;
10902 if (appData.noChessProgram) return;
10904 switch (gameMode) {
10905 case TwoMachinesPlay:
10907 case MachinePlaysWhite:
10908 case MachinePlaysBlack:
10909 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10910 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10914 case BeginningOfGame:
10915 case PlayFromGameFile:
10918 if (gameMode != EditGame) return;
10921 EditPositionDone(TRUE);
10932 // forwardMostMove = currentMove;
10933 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10934 ResurrectChessProgram(); /* in case first program isn't running */
10936 if (second.pr == NULL) {
10937 StartChessProgram(&second);
10938 if (second.protocolVersion == 1) {
10939 TwoMachinesEventIfReady();
10941 /* kludge: allow timeout for initial "feature" command */
10943 DisplayMessage("", _("Starting second chess program"));
10944 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10948 DisplayMessage("", "");
10949 InitChessProgram(&second, FALSE);
10950 SendToProgram("force\n", &second);
10951 if (startedFromSetupPosition) {
10952 SendBoard(&second, backwardMostMove);
10953 if (appData.debugMode) {
10954 fprintf(debugFP, "Two Machines\n");
10957 for (i = backwardMostMove; i < forwardMostMove; i++) {
10958 SendMoveToProgram(i, &second);
10961 gameMode = TwoMachinesPlay;
10965 DisplayTwoMachinesTitle();
10967 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10973 SendToProgram(first.computerString, &first);
10974 if (first.sendName) {
10975 sprintf(buf, "name %s\n", second.tidy);
10976 SendToProgram(buf, &first);
10978 SendToProgram(second.computerString, &second);
10979 if (second.sendName) {
10980 sprintf(buf, "name %s\n", first.tidy);
10981 SendToProgram(buf, &second);
10985 if (!first.sendTime || !second.sendTime) {
10986 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10987 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10989 if (onmove->sendTime) {
10990 if (onmove->useColors) {
10991 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10993 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10995 if (onmove->useColors) {
10996 SendToProgram(onmove->twoMachinesColor, onmove);
10998 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10999 // SendToProgram("go\n", onmove);
11000 onmove->maybeThinking = TRUE;
11001 SetMachineThinkingEnables();
11005 if(bookHit) { // [HGM] book: simulate book reply
11006 static char bookMove[MSG_SIZ]; // a bit generous?
11008 programStats.nodes = programStats.depth = programStats.time =
11009 programStats.score = programStats.got_only_move = 0;
11010 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11012 strcpy(bookMove, "move ");
11013 strcat(bookMove, bookHit);
11014 savedMessage = bookMove; // args for deferred call
11015 savedState = onmove;
11016 ScheduleDelayedEvent(DeferredBookMove, 1);
11023 if (gameMode == Training) {
11024 SetTrainingModeOff();
11025 gameMode = PlayFromGameFile;
11026 DisplayMessage("", _("Training mode off"));
11028 gameMode = Training;
11029 animateTraining = appData.animate;
11031 /* make sure we are not already at the end of the game */
11032 if (currentMove < forwardMostMove) {
11033 SetTrainingModeOn();
11034 DisplayMessage("", _("Training mode on"));
11036 gameMode = PlayFromGameFile;
11037 DisplayError(_("Already at end of game"), 0);
11046 if (!appData.icsActive) return;
11047 switch (gameMode) {
11048 case IcsPlayingWhite:
11049 case IcsPlayingBlack:
11052 case BeginningOfGame:
11060 EditPositionDone(TRUE);
11073 gameMode = IcsIdle;
11084 switch (gameMode) {
11086 SetTrainingModeOff();
11088 case MachinePlaysWhite:
11089 case MachinePlaysBlack:
11090 case BeginningOfGame:
11091 SendToProgram("force\n", &first);
11092 SetUserThinkingEnables();
11094 case PlayFromGameFile:
11095 (void) StopLoadGameTimer();
11096 if (gameFileFP != NULL) {
11101 EditPositionDone(TRUE);
11106 SendToProgram("force\n", &first);
11108 case TwoMachinesPlay:
11109 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11110 ResurrectChessProgram();
11111 SetUserThinkingEnables();
11114 ResurrectChessProgram();
11116 case IcsPlayingBlack:
11117 case IcsPlayingWhite:
11118 DisplayError(_("Warning: You are still playing a game"), 0);
11121 DisplayError(_("Warning: You are still observing a game"), 0);
11124 DisplayError(_("Warning: You are still examining a game"), 0);
11135 first.offeredDraw = second.offeredDraw = 0;
11137 if (gameMode == PlayFromGameFile) {
11138 whiteTimeRemaining = timeRemaining[0][currentMove];
11139 blackTimeRemaining = timeRemaining[1][currentMove];
11143 if (gameMode == MachinePlaysWhite ||
11144 gameMode == MachinePlaysBlack ||
11145 gameMode == TwoMachinesPlay ||
11146 gameMode == EndOfGame) {
11147 i = forwardMostMove;
11148 while (i > currentMove) {
11149 SendToProgram("undo\n", &first);
11152 whiteTimeRemaining = timeRemaining[0][currentMove];
11153 blackTimeRemaining = timeRemaining[1][currentMove];
11154 DisplayBothClocks();
11155 if (whiteFlag || blackFlag) {
11156 whiteFlag = blackFlag = 0;
11161 gameMode = EditGame;
11168 EditPositionEvent()
11170 if (gameMode == EditPosition) {
11176 if (gameMode != EditGame) return;
11178 gameMode = EditPosition;
11181 if (currentMove > 0)
11182 CopyBoard(boards[0], boards[currentMove]);
11184 blackPlaysFirst = !WhiteOnMove(currentMove);
11186 currentMove = forwardMostMove = backwardMostMove = 0;
11187 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11194 /* [DM] icsEngineAnalyze - possible call from other functions */
11195 if (appData.icsEngineAnalyze) {
11196 appData.icsEngineAnalyze = FALSE;
11198 DisplayMessage("",_("Close ICS engine analyze..."));
11200 if (first.analysisSupport && first.analyzing) {
11201 SendToProgram("exit\n", &first);
11202 first.analyzing = FALSE;
11204 thinkOutput[0] = NULLCHAR;
11208 EditPositionDone(Boolean fakeRights)
11210 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11212 startedFromSetupPosition = TRUE;
11213 InitChessProgram(&first, FALSE);
11214 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11215 boards[0][EP_STATUS] = EP_NONE;
11216 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11217 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11218 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11219 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11220 } else boards[0][CASTLING][2] = NoRights;
11221 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11222 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11223 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11224 } else boards[0][CASTLING][5] = NoRights;
11226 SendToProgram("force\n", &first);
11227 if (blackPlaysFirst) {
11228 strcpy(moveList[0], "");
11229 strcpy(parseList[0], "");
11230 currentMove = forwardMostMove = backwardMostMove = 1;
11231 CopyBoard(boards[1], boards[0]);
11233 currentMove = forwardMostMove = backwardMostMove = 0;
11235 SendBoard(&first, forwardMostMove);
11236 if (appData.debugMode) {
11237 fprintf(debugFP, "EditPosDone\n");
11240 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11241 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11242 gameMode = EditGame;
11244 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11245 ClearHighlights(); /* [AS] */
11248 /* Pause for `ms' milliseconds */
11249 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11259 } while (SubtractTimeMarks(&m2, &m1) < ms);
11262 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11264 SendMultiLineToICS(buf)
11267 char temp[MSG_SIZ+1], *p;
11274 strncpy(temp, buf, len);
11279 if (*p == '\n' || *p == '\r')
11284 strcat(temp, "\n");
11286 SendToPlayer(temp, strlen(temp));
11290 SetWhiteToPlayEvent()
11292 if (gameMode == EditPosition) {
11293 blackPlaysFirst = FALSE;
11294 DisplayBothClocks(); /* works because currentMove is 0 */
11295 } else if (gameMode == IcsExamining) {
11296 SendToICS(ics_prefix);
11297 SendToICS("tomove white\n");
11302 SetBlackToPlayEvent()
11304 if (gameMode == EditPosition) {
11305 blackPlaysFirst = TRUE;
11306 currentMove = 1; /* kludge */
11307 DisplayBothClocks();
11309 } else if (gameMode == IcsExamining) {
11310 SendToICS(ics_prefix);
11311 SendToICS("tomove black\n");
11316 EditPositionMenuEvent(selection, x, y)
11317 ChessSquare selection;
11321 ChessSquare piece = boards[0][y][x];
11323 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11325 switch (selection) {
11327 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11328 SendToICS(ics_prefix);
11329 SendToICS("bsetup clear\n");
11330 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11331 SendToICS(ics_prefix);
11332 SendToICS("clearboard\n");
11334 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11335 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11336 for (y = 0; y < BOARD_HEIGHT; y++) {
11337 if (gameMode == IcsExamining) {
11338 if (boards[currentMove][y][x] != EmptySquare) {
11339 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11344 boards[0][y][x] = p;
11349 if (gameMode == EditPosition) {
11350 DrawPosition(FALSE, boards[0]);
11355 SetWhiteToPlayEvent();
11359 SetBlackToPlayEvent();
11363 if (gameMode == IcsExamining) {
11364 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11367 boards[0][y][x] = EmptySquare;
11368 DrawPosition(FALSE, boards[0]);
11373 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11374 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11375 selection = (ChessSquare) (PROMOTED piece);
11376 } else if(piece == EmptySquare) selection = WhiteSilver;
11377 else selection = (ChessSquare)((int)piece - 1);
11381 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11382 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11383 selection = (ChessSquare) (DEMOTED piece);
11384 } else if(piece == EmptySquare) selection = BlackSilver;
11385 else selection = (ChessSquare)((int)piece + 1);
11390 if(gameInfo.variant == VariantShatranj ||
11391 gameInfo.variant == VariantXiangqi ||
11392 gameInfo.variant == VariantCourier )
11393 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11398 if(gameInfo.variant == VariantXiangqi)
11399 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11400 if(gameInfo.variant == VariantKnightmate)
11401 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11404 if (gameMode == IcsExamining) {
11405 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11406 PieceToChar(selection), AAA + x, ONE + y);
11409 boards[0][y][x] = selection;
11410 DrawPosition(FALSE, boards[0]);
11418 DropMenuEvent(selection, x, y)
11419 ChessSquare selection;
11422 ChessMove moveType;
11424 switch (gameMode) {
11425 case IcsPlayingWhite:
11426 case MachinePlaysBlack:
11427 if (!WhiteOnMove(currentMove)) {
11428 DisplayMoveError(_("It is Black's turn"));
11431 moveType = WhiteDrop;
11433 case IcsPlayingBlack:
11434 case MachinePlaysWhite:
11435 if (WhiteOnMove(currentMove)) {
11436 DisplayMoveError(_("It is White's turn"));
11439 moveType = BlackDrop;
11442 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11448 if (moveType == BlackDrop && selection < BlackPawn) {
11449 selection = (ChessSquare) ((int) selection
11450 + (int) BlackPawn - (int) WhitePawn);
11452 if (boards[currentMove][y][x] != EmptySquare) {
11453 DisplayMoveError(_("That square is occupied"));
11457 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11463 /* Accept a pending offer of any kind from opponent */
11465 if (appData.icsActive) {
11466 SendToICS(ics_prefix);
11467 SendToICS("accept\n");
11468 } else if (cmailMsgLoaded) {
11469 if (currentMove == cmailOldMove &&
11470 commentList[cmailOldMove] != NULL &&
11471 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11472 "Black offers a draw" : "White offers a draw")) {
11474 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11475 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11477 DisplayError(_("There is no pending offer on this move"), 0);
11478 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11481 /* Not used for offers from chess program */
11488 /* Decline a pending offer of any kind from opponent */
11490 if (appData.icsActive) {
11491 SendToICS(ics_prefix);
11492 SendToICS("decline\n");
11493 } else if (cmailMsgLoaded) {
11494 if (currentMove == cmailOldMove &&
11495 commentList[cmailOldMove] != NULL &&
11496 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11497 "Black offers a draw" : "White offers a draw")) {
11499 AppendComment(cmailOldMove, "Draw declined", TRUE);
11500 DisplayComment(cmailOldMove - 1, "Draw declined");
11503 DisplayError(_("There is no pending offer on this move"), 0);
11506 /* Not used for offers from chess program */
11513 /* Issue ICS rematch command */
11514 if (appData.icsActive) {
11515 SendToICS(ics_prefix);
11516 SendToICS("rematch\n");
11523 /* Call your opponent's flag (claim a win on time) */
11524 if (appData.icsActive) {
11525 SendToICS(ics_prefix);
11526 SendToICS("flag\n");
11528 switch (gameMode) {
11531 case MachinePlaysWhite:
11534 GameEnds(GameIsDrawn, "Both players ran out of time",
11537 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11539 DisplayError(_("Your opponent is not out of time"), 0);
11542 case MachinePlaysBlack:
11545 GameEnds(GameIsDrawn, "Both players ran out of time",
11548 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11550 DisplayError(_("Your opponent is not out of time"), 0);
11560 /* Offer draw or accept pending draw offer from opponent */
11562 if (appData.icsActive) {
11563 /* Note: tournament rules require draw offers to be
11564 made after you make your move but before you punch
11565 your clock. Currently ICS doesn't let you do that;
11566 instead, you immediately punch your clock after making
11567 a move, but you can offer a draw at any time. */
11569 SendToICS(ics_prefix);
11570 SendToICS("draw\n");
11571 } else if (cmailMsgLoaded) {
11572 if (currentMove == cmailOldMove &&
11573 commentList[cmailOldMove] != NULL &&
11574 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11575 "Black offers a draw" : "White offers a draw")) {
11576 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11577 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11578 } else if (currentMove == cmailOldMove + 1) {
11579 char *offer = WhiteOnMove(cmailOldMove) ?
11580 "White offers a draw" : "Black offers a draw";
11581 AppendComment(currentMove, offer, TRUE);
11582 DisplayComment(currentMove - 1, offer);
11583 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11585 DisplayError(_("You must make your move before offering a draw"), 0);
11586 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11588 } else if (first.offeredDraw) {
11589 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11591 if (first.sendDrawOffers) {
11592 SendToProgram("draw\n", &first);
11593 userOfferedDraw = TRUE;
11601 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11603 if (appData.icsActive) {
11604 SendToICS(ics_prefix);
11605 SendToICS("adjourn\n");
11607 /* Currently GNU Chess doesn't offer or accept Adjourns */
11615 /* Offer Abort or accept pending Abort offer from opponent */
11617 if (appData.icsActive) {
11618 SendToICS(ics_prefix);
11619 SendToICS("abort\n");
11621 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11628 /* Resign. You can do this even if it's not your turn. */
11630 if (appData.icsActive) {
11631 SendToICS(ics_prefix);
11632 SendToICS("resign\n");
11634 switch (gameMode) {
11635 case MachinePlaysWhite:
11636 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11638 case MachinePlaysBlack:
11639 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11642 if (cmailMsgLoaded) {
11644 if (WhiteOnMove(cmailOldMove)) {
11645 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11647 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11649 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11660 StopObservingEvent()
11662 /* Stop observing current games */
11663 SendToICS(ics_prefix);
11664 SendToICS("unobserve\n");
11668 StopExaminingEvent()
11670 /* Stop observing current game */
11671 SendToICS(ics_prefix);
11672 SendToICS("unexamine\n");
11676 ForwardInner(target)
11681 if (appData.debugMode)
11682 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11683 target, currentMove, forwardMostMove);
11685 if (gameMode == EditPosition)
11688 if (gameMode == PlayFromGameFile && !pausing)
11691 if (gameMode == IcsExamining && pausing)
11692 limit = pauseExamForwardMostMove;
11694 limit = forwardMostMove;
11696 if (target > limit) target = limit;
11698 if (target > 0 && moveList[target - 1][0]) {
11699 int fromX, fromY, toX, toY;
11700 toX = moveList[target - 1][2] - AAA;
11701 toY = moveList[target - 1][3] - ONE;
11702 if (moveList[target - 1][1] == '@') {
11703 if (appData.highlightLastMove) {
11704 SetHighlights(-1, -1, toX, toY);
11707 fromX = moveList[target - 1][0] - AAA;
11708 fromY = moveList[target - 1][1] - ONE;
11709 if (target == currentMove + 1) {
11710 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11712 if (appData.highlightLastMove) {
11713 SetHighlights(fromX, fromY, toX, toY);
11717 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11718 gameMode == Training || gameMode == PlayFromGameFile ||
11719 gameMode == AnalyzeFile) {
11720 while (currentMove < target) {
11721 SendMoveToProgram(currentMove++, &first);
11724 currentMove = target;
11727 if (gameMode == EditGame || gameMode == EndOfGame) {
11728 whiteTimeRemaining = timeRemaining[0][currentMove];
11729 blackTimeRemaining = timeRemaining[1][currentMove];
11731 DisplayBothClocks();
11732 DisplayMove(currentMove - 1);
11733 DrawPosition(FALSE, boards[currentMove]);
11734 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11735 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11736 DisplayComment(currentMove - 1, commentList[currentMove]);
11744 if (gameMode == IcsExamining && !pausing) {
11745 SendToICS(ics_prefix);
11746 SendToICS("forward\n");
11748 ForwardInner(currentMove + 1);
11755 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11756 /* to optimze, we temporarily turn off analysis mode while we feed
11757 * the remaining moves to the engine. Otherwise we get analysis output
11760 if (first.analysisSupport) {
11761 SendToProgram("exit\nforce\n", &first);
11762 first.analyzing = FALSE;
11766 if (gameMode == IcsExamining && !pausing) {
11767 SendToICS(ics_prefix);
11768 SendToICS("forward 999999\n");
11770 ForwardInner(forwardMostMove);
11773 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11774 /* we have fed all the moves, so reactivate analysis mode */
11775 SendToProgram("analyze\n", &first);
11776 first.analyzing = TRUE;
11777 /*first.maybeThinking = TRUE;*/
11778 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11783 BackwardInner(target)
11786 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11788 if (appData.debugMode)
11789 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11790 target, currentMove, forwardMostMove);
11792 if (gameMode == EditPosition) return;
11793 if (currentMove <= backwardMostMove) {
11795 DrawPosition(full_redraw, boards[currentMove]);
11798 if (gameMode == PlayFromGameFile && !pausing)
11801 if (moveList[target][0]) {
11802 int fromX, fromY, toX, toY;
11803 toX = moveList[target][2] - AAA;
11804 toY = moveList[target][3] - ONE;
11805 if (moveList[target][1] == '@') {
11806 if (appData.highlightLastMove) {
11807 SetHighlights(-1, -1, toX, toY);
11810 fromX = moveList[target][0] - AAA;
11811 fromY = moveList[target][1] - ONE;
11812 if (target == currentMove - 1) {
11813 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11815 if (appData.highlightLastMove) {
11816 SetHighlights(fromX, fromY, toX, toY);
11820 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11821 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11822 while (currentMove > target) {
11823 SendToProgram("undo\n", &first);
11827 currentMove = target;
11830 if (gameMode == EditGame || gameMode == EndOfGame) {
11831 whiteTimeRemaining = timeRemaining[0][currentMove];
11832 blackTimeRemaining = timeRemaining[1][currentMove];
11834 DisplayBothClocks();
11835 DisplayMove(currentMove - 1);
11836 DrawPosition(full_redraw, boards[currentMove]);
11837 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11838 // [HGM] PV info: routine tests if comment empty
11839 DisplayComment(currentMove - 1, commentList[currentMove]);
11845 if (gameMode == IcsExamining && !pausing) {
11846 SendToICS(ics_prefix);
11847 SendToICS("backward\n");
11849 BackwardInner(currentMove - 1);
11856 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11857 /* to optimize, we temporarily turn off analysis mode while we undo
11858 * all the moves. Otherwise we get analysis output after each undo.
11860 if (first.analysisSupport) {
11861 SendToProgram("exit\nforce\n", &first);
11862 first.analyzing = FALSE;
11866 if (gameMode == IcsExamining && !pausing) {
11867 SendToICS(ics_prefix);
11868 SendToICS("backward 999999\n");
11870 BackwardInner(backwardMostMove);
11873 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11874 /* we have fed all the moves, so reactivate analysis mode */
11875 SendToProgram("analyze\n", &first);
11876 first.analyzing = TRUE;
11877 /*first.maybeThinking = TRUE;*/
11878 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11885 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11886 if (to >= forwardMostMove) to = forwardMostMove;
11887 if (to <= backwardMostMove) to = backwardMostMove;
11888 if (to < currentMove) {
11898 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11901 if (gameMode != IcsExamining) {
11902 DisplayError(_("You are not examining a game"), 0);
11906 DisplayError(_("You can't revert while pausing"), 0);
11909 SendToICS(ics_prefix);
11910 SendToICS("revert\n");
11916 switch (gameMode) {
11917 case MachinePlaysWhite:
11918 case MachinePlaysBlack:
11919 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11920 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11923 if (forwardMostMove < 2) return;
11924 currentMove = forwardMostMove = forwardMostMove - 2;
11925 whiteTimeRemaining = timeRemaining[0][currentMove];
11926 blackTimeRemaining = timeRemaining[1][currentMove];
11927 DisplayBothClocks();
11928 DisplayMove(currentMove - 1);
11929 ClearHighlights();/*!! could figure this out*/
11930 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11931 SendToProgram("remove\n", &first);
11932 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11935 case BeginningOfGame:
11939 case IcsPlayingWhite:
11940 case IcsPlayingBlack:
11941 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11942 SendToICS(ics_prefix);
11943 SendToICS("takeback 2\n");
11945 SendToICS(ics_prefix);
11946 SendToICS("takeback 1\n");
11955 ChessProgramState *cps;
11957 switch (gameMode) {
11958 case MachinePlaysWhite:
11959 if (!WhiteOnMove(forwardMostMove)) {
11960 DisplayError(_("It is your turn"), 0);
11965 case MachinePlaysBlack:
11966 if (WhiteOnMove(forwardMostMove)) {
11967 DisplayError(_("It is your turn"), 0);
11972 case TwoMachinesPlay:
11973 if (WhiteOnMove(forwardMostMove) ==
11974 (first.twoMachinesColor[0] == 'w')) {
11980 case BeginningOfGame:
11984 SendToProgram("?\n", cps);
11988 TruncateGameEvent()
11991 if (gameMode != EditGame) return;
11998 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11999 if (forwardMostMove > currentMove) {
12000 if (gameInfo.resultDetails != NULL) {
12001 free(gameInfo.resultDetails);
12002 gameInfo.resultDetails = NULL;
12003 gameInfo.result = GameUnfinished;
12005 forwardMostMove = currentMove;
12006 HistorySet(parseList, backwardMostMove, forwardMostMove,
12014 if (appData.noChessProgram) return;
12015 switch (gameMode) {
12016 case MachinePlaysWhite:
12017 if (WhiteOnMove(forwardMostMove)) {
12018 DisplayError(_("Wait until your turn"), 0);
12022 case BeginningOfGame:
12023 case MachinePlaysBlack:
12024 if (!WhiteOnMove(forwardMostMove)) {
12025 DisplayError(_("Wait until your turn"), 0);
12030 DisplayError(_("No hint available"), 0);
12033 SendToProgram("hint\n", &first);
12034 hintRequested = TRUE;
12040 if (appData.noChessProgram) return;
12041 switch (gameMode) {
12042 case MachinePlaysWhite:
12043 if (WhiteOnMove(forwardMostMove)) {
12044 DisplayError(_("Wait until your turn"), 0);
12048 case BeginningOfGame:
12049 case MachinePlaysBlack:
12050 if (!WhiteOnMove(forwardMostMove)) {
12051 DisplayError(_("Wait until your turn"), 0);
12056 EditPositionDone(TRUE);
12058 case TwoMachinesPlay:
12063 SendToProgram("bk\n", &first);
12064 bookOutput[0] = NULLCHAR;
12065 bookRequested = TRUE;
12071 char *tags = PGNTags(&gameInfo);
12072 TagsPopUp(tags, CmailMsg());
12076 /* end button procedures */
12079 PrintPosition(fp, move)
12085 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12086 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12087 char c = PieceToChar(boards[move][i][j]);
12088 fputc(c == 'x' ? '.' : c, fp);
12089 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12092 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12093 fprintf(fp, "white to play\n");
12095 fprintf(fp, "black to play\n");
12102 if (gameInfo.white != NULL) {
12103 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12109 /* Find last component of program's own name, using some heuristics */
12111 TidyProgramName(prog, host, buf)
12112 char *prog, *host, buf[MSG_SIZ];
12115 int local = (strcmp(host, "localhost") == 0);
12116 while (!local && (p = strchr(prog, ';')) != NULL) {
12118 while (*p == ' ') p++;
12121 if (*prog == '"' || *prog == '\'') {
12122 q = strchr(prog + 1, *prog);
12124 q = strchr(prog, ' ');
12126 if (q == NULL) q = prog + strlen(prog);
12128 while (p >= prog && *p != '/' && *p != '\\') p--;
12130 if(p == prog && *p == '"') p++;
12131 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12132 memcpy(buf, p, q - p);
12133 buf[q - p] = NULLCHAR;
12141 TimeControlTagValue()
12144 if (!appData.clockMode) {
12146 } else if (movesPerSession > 0) {
12147 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12148 } else if (timeIncrement == 0) {
12149 sprintf(buf, "%ld", timeControl/1000);
12151 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12153 return StrSave(buf);
12159 /* This routine is used only for certain modes */
12160 VariantClass v = gameInfo.variant;
12161 ChessMove r = GameUnfinished;
12164 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12165 r = gameInfo.result;
12166 p = gameInfo.resultDetails;
12167 gameInfo.resultDetails = NULL;
12169 ClearGameInfo(&gameInfo);
12170 gameInfo.variant = v;
12172 switch (gameMode) {
12173 case MachinePlaysWhite:
12174 gameInfo.event = StrSave( appData.pgnEventHeader );
12175 gameInfo.site = StrSave(HostName());
12176 gameInfo.date = PGNDate();
12177 gameInfo.round = StrSave("-");
12178 gameInfo.white = StrSave(first.tidy);
12179 gameInfo.black = StrSave(UserName());
12180 gameInfo.timeControl = TimeControlTagValue();
12183 case MachinePlaysBlack:
12184 gameInfo.event = StrSave( appData.pgnEventHeader );
12185 gameInfo.site = StrSave(HostName());
12186 gameInfo.date = PGNDate();
12187 gameInfo.round = StrSave("-");
12188 gameInfo.white = StrSave(UserName());
12189 gameInfo.black = StrSave(first.tidy);
12190 gameInfo.timeControl = TimeControlTagValue();
12193 case TwoMachinesPlay:
12194 gameInfo.event = StrSave( appData.pgnEventHeader );
12195 gameInfo.site = StrSave(HostName());
12196 gameInfo.date = PGNDate();
12197 if (matchGame > 0) {
12199 sprintf(buf, "%d", matchGame);
12200 gameInfo.round = StrSave(buf);
12202 gameInfo.round = StrSave("-");
12204 if (first.twoMachinesColor[0] == 'w') {
12205 gameInfo.white = StrSave(first.tidy);
12206 gameInfo.black = StrSave(second.tidy);
12208 gameInfo.white = StrSave(second.tidy);
12209 gameInfo.black = StrSave(first.tidy);
12211 gameInfo.timeControl = TimeControlTagValue();
12215 gameInfo.event = StrSave("Edited game");
12216 gameInfo.site = StrSave(HostName());
12217 gameInfo.date = PGNDate();
12218 gameInfo.round = StrSave("-");
12219 gameInfo.white = StrSave("-");
12220 gameInfo.black = StrSave("-");
12221 gameInfo.result = r;
12222 gameInfo.resultDetails = p;
12226 gameInfo.event = StrSave("Edited position");
12227 gameInfo.site = StrSave(HostName());
12228 gameInfo.date = PGNDate();
12229 gameInfo.round = StrSave("-");
12230 gameInfo.white = StrSave("-");
12231 gameInfo.black = StrSave("-");
12234 case IcsPlayingWhite:
12235 case IcsPlayingBlack:
12240 case PlayFromGameFile:
12241 gameInfo.event = StrSave("Game from non-PGN file");
12242 gameInfo.site = StrSave(HostName());
12243 gameInfo.date = PGNDate();
12244 gameInfo.round = StrSave("-");
12245 gameInfo.white = StrSave("?");
12246 gameInfo.black = StrSave("?");
12255 ReplaceComment(index, text)
12261 while (*text == '\n') text++;
12262 len = strlen(text);
12263 while (len > 0 && text[len - 1] == '\n') len--;
12265 if (commentList[index] != NULL)
12266 free(commentList[index]);
12269 commentList[index] = NULL;
12272 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12273 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12274 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12275 commentList[index] = (char *) malloc(len + 2);
12276 strncpy(commentList[index], text, len);
12277 commentList[index][len] = '\n';
12278 commentList[index][len + 1] = NULLCHAR;
12280 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12282 commentList[index] = (char *) malloc(len + 6);
12283 strcpy(commentList[index], "{\n");
12284 strncpy(commentList[index]+2, text, len);
12285 commentList[index][len+2] = NULLCHAR;
12286 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12287 strcat(commentList[index], "\n}\n");
12301 if (ch == '\r') continue;
12303 } while (ch != '\0');
12307 AppendComment(index, text, addBraces)
12310 Boolean addBraces; // [HGM] braces: tells if we should add {}
12315 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12316 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12319 while (*text == '\n') text++;
12320 len = strlen(text);
12321 while (len > 0 && text[len - 1] == '\n') len--;
12323 if (len == 0) return;
12325 if (commentList[index] != NULL) {
12326 old = commentList[index];
12327 oldlen = strlen(old);
12328 while(commentList[index][oldlen-1] == '\n')
12329 commentList[index][--oldlen] = NULLCHAR;
12330 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12331 strcpy(commentList[index], old);
12333 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12334 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12335 if(addBraces) addBraces = FALSE; else { text++; len--; }
12336 while (*text == '\n') { text++; len--; }
12337 commentList[index][--oldlen] = NULLCHAR;
12339 if(addBraces) strcat(commentList[index], "\n{\n");
12340 else strcat(commentList[index], "\n");
12341 strcat(commentList[index], text);
12342 if(addBraces) strcat(commentList[index], "\n}\n");
12343 else strcat(commentList[index], "\n");
12345 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12347 strcpy(commentList[index], "{\n");
12348 else commentList[index][0] = NULLCHAR;
12349 strcat(commentList[index], text);
12350 strcat(commentList[index], "\n");
12351 if(addBraces) strcat(commentList[index], "}\n");
12355 static char * FindStr( char * text, char * sub_text )
12357 char * result = strstr( text, sub_text );
12359 if( result != NULL ) {
12360 result += strlen( sub_text );
12366 /* [AS] Try to extract PV info from PGN comment */
12367 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12368 char *GetInfoFromComment( int index, char * text )
12372 if( text != NULL && index > 0 ) {
12375 int time = -1, sec = 0, deci;
12376 char * s_eval = FindStr( text, "[%eval " );
12377 char * s_emt = FindStr( text, "[%emt " );
12379 if( s_eval != NULL || s_emt != NULL ) {
12383 if( s_eval != NULL ) {
12384 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12388 if( delim != ']' ) {
12393 if( s_emt != NULL ) {
12398 /* We expect something like: [+|-]nnn.nn/dd */
12401 if(*text != '{') return text; // [HGM] braces: must be normal comment
12403 sep = strchr( text, '/' );
12404 if( sep == NULL || sep < (text+4) ) {
12408 time = -1; sec = -1; deci = -1;
12409 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12410 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12411 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12412 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12416 if( score_lo < 0 || score_lo >= 100 ) {
12420 if(sec >= 0) time = 600*time + 10*sec; else
12421 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12423 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12425 /* [HGM] PV time: now locate end of PV info */
12426 while( *++sep >= '0' && *sep <= '9'); // strip depth
12428 while( *++sep >= '0' && *sep <= '9'); // strip time
12430 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12432 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12433 while(*sep == ' ') sep++;
12444 pvInfoList[index-1].depth = depth;
12445 pvInfoList[index-1].score = score;
12446 pvInfoList[index-1].time = 10*time; // centi-sec
12447 if(*sep == '}') *sep = 0; else *--sep = '{';
12453 SendToProgram(message, cps)
12455 ChessProgramState *cps;
12457 int count, outCount, error;
12460 if (cps->pr == NULL) return;
12463 if (appData.debugMode) {
12466 fprintf(debugFP, "%ld >%-6s: %s",
12467 SubtractTimeMarks(&now, &programStartTime),
12468 cps->which, message);
12471 count = strlen(message);
12472 outCount = OutputToProcess(cps->pr, message, count, &error);
12473 if (outCount < count && !exiting
12474 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12475 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12476 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12477 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12478 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12479 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12481 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12483 gameInfo.resultDetails = StrSave(buf);
12485 DisplayFatalError(buf, error, 1);
12490 ReceiveFromProgram(isr, closure, message, count, error)
12491 InputSourceRef isr;
12499 ChessProgramState *cps = (ChessProgramState *)closure;
12501 if (isr != cps->isr) return; /* Killed intentionally */
12505 _("Error: %s chess program (%s) exited unexpectedly"),
12506 cps->which, cps->program);
12507 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12508 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12509 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12510 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12512 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12514 gameInfo.resultDetails = StrSave(buf);
12516 RemoveInputSource(cps->isr);
12517 DisplayFatalError(buf, 0, 1);
12520 _("Error reading from %s chess program (%s)"),
12521 cps->which, cps->program);
12522 RemoveInputSource(cps->isr);
12524 /* [AS] Program is misbehaving badly... kill it */
12525 if( count == -2 ) {
12526 DestroyChildProcess( cps->pr, 9 );
12530 DisplayFatalError(buf, error, 1);
12535 if ((end_str = strchr(message, '\r')) != NULL)
12536 *end_str = NULLCHAR;
12537 if ((end_str = strchr(message, '\n')) != NULL)
12538 *end_str = NULLCHAR;
12540 if (appData.debugMode) {
12541 TimeMark now; int print = 1;
12542 char *quote = ""; char c; int i;
12544 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12545 char start = message[0];
12546 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12547 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12548 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12549 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12550 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12551 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12552 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12553 sscanf(message, "pong %c", &c)!=1 && start != '#')
12554 { quote = "# "; print = (appData.engineComments == 2); }
12555 message[0] = start; // restore original message
12559 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12560 SubtractTimeMarks(&now, &programStartTime), cps->which,
12566 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12567 if (appData.icsEngineAnalyze) {
12568 if (strstr(message, "whisper") != NULL ||
12569 strstr(message, "kibitz") != NULL ||
12570 strstr(message, "tellics") != NULL) return;
12573 HandleMachineMove(message, cps);
12578 SendTimeControl(cps, mps, tc, inc, sd, st)
12579 ChessProgramState *cps;
12580 int mps, inc, sd, st;
12586 if( timeControl_2 > 0 ) {
12587 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12588 tc = timeControl_2;
12591 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12592 inc /= cps->timeOdds;
12593 st /= cps->timeOdds;
12595 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12598 /* Set exact time per move, normally using st command */
12599 if (cps->stKludge) {
12600 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12602 if (seconds == 0) {
12603 sprintf(buf, "level 1 %d\n", st/60);
12605 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12608 sprintf(buf, "st %d\n", st);
12611 /* Set conventional or incremental time control, using level command */
12612 if (seconds == 0) {
12613 /* Note old gnuchess bug -- minutes:seconds used to not work.
12614 Fixed in later versions, but still avoid :seconds
12615 when seconds is 0. */
12616 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12618 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12619 seconds, inc/1000);
12622 SendToProgram(buf, cps);
12624 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12625 /* Orthogonally, limit search to given depth */
12627 if (cps->sdKludge) {
12628 sprintf(buf, "depth\n%d\n", sd);
12630 sprintf(buf, "sd %d\n", sd);
12632 SendToProgram(buf, cps);
12635 if(cps->nps > 0) { /* [HGM] nps */
12636 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12638 sprintf(buf, "nps %d\n", cps->nps);
12639 SendToProgram(buf, cps);
12644 ChessProgramState *WhitePlayer()
12645 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12647 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12648 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12654 SendTimeRemaining(cps, machineWhite)
12655 ChessProgramState *cps;
12656 int /*boolean*/ machineWhite;
12658 char message[MSG_SIZ];
12661 /* Note: this routine must be called when the clocks are stopped
12662 or when they have *just* been set or switched; otherwise
12663 it will be off by the time since the current tick started.
12665 if (machineWhite) {
12666 time = whiteTimeRemaining / 10;
12667 otime = blackTimeRemaining / 10;
12669 time = blackTimeRemaining / 10;
12670 otime = whiteTimeRemaining / 10;
12672 /* [HGM] translate opponent's time by time-odds factor */
12673 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12674 if (appData.debugMode) {
12675 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12678 if (time <= 0) time = 1;
12679 if (otime <= 0) otime = 1;
12681 sprintf(message, "time %ld\n", time);
12682 SendToProgram(message, cps);
12684 sprintf(message, "otim %ld\n", otime);
12685 SendToProgram(message, cps);
12689 BoolFeature(p, name, loc, cps)
12693 ChessProgramState *cps;
12696 int len = strlen(name);
12698 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12700 sscanf(*p, "%d", &val);
12702 while (**p && **p != ' ') (*p)++;
12703 sprintf(buf, "accepted %s\n", name);
12704 SendToProgram(buf, cps);
12711 IntFeature(p, name, loc, cps)
12715 ChessProgramState *cps;
12718 int len = strlen(name);
12719 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12721 sscanf(*p, "%d", loc);
12722 while (**p && **p != ' ') (*p)++;
12723 sprintf(buf, "accepted %s\n", name);
12724 SendToProgram(buf, cps);
12731 StringFeature(p, name, loc, cps)
12735 ChessProgramState *cps;
12738 int len = strlen(name);
12739 if (strncmp((*p), name, len) == 0
12740 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12742 sscanf(*p, "%[^\"]", loc);
12743 while (**p && **p != '\"') (*p)++;
12744 if (**p == '\"') (*p)++;
12745 sprintf(buf, "accepted %s\n", name);
12746 SendToProgram(buf, cps);
12753 ParseOption(Option *opt, ChessProgramState *cps)
12754 // [HGM] options: process the string that defines an engine option, and determine
12755 // name, type, default value, and allowed value range
12757 char *p, *q, buf[MSG_SIZ];
12758 int n, min = (-1)<<31, max = 1<<31, def;
12760 if(p = strstr(opt->name, " -spin ")) {
12761 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12762 if(max < min) max = min; // enforce consistency
12763 if(def < min) def = min;
12764 if(def > max) def = max;
12769 } else if((p = strstr(opt->name, " -slider "))) {
12770 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12771 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12772 if(max < min) max = min; // enforce consistency
12773 if(def < min) def = min;
12774 if(def > max) def = max;
12778 opt->type = Spin; // Slider;
12779 } else if((p = strstr(opt->name, " -string "))) {
12780 opt->textValue = p+9;
12781 opt->type = TextBox;
12782 } else if((p = strstr(opt->name, " -file "))) {
12783 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12784 opt->textValue = p+7;
12785 opt->type = TextBox; // FileName;
12786 } else if((p = strstr(opt->name, " -path "))) {
12787 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12788 opt->textValue = p+7;
12789 opt->type = TextBox; // PathName;
12790 } else if(p = strstr(opt->name, " -check ")) {
12791 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12792 opt->value = (def != 0);
12793 opt->type = CheckBox;
12794 } else if(p = strstr(opt->name, " -combo ")) {
12795 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12796 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12797 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12798 opt->value = n = 0;
12799 while(q = StrStr(q, " /// ")) {
12800 n++; *q = 0; // count choices, and null-terminate each of them
12802 if(*q == '*') { // remember default, which is marked with * prefix
12806 cps->comboList[cps->comboCnt++] = q;
12808 cps->comboList[cps->comboCnt++] = NULL;
12810 opt->type = ComboBox;
12811 } else if(p = strstr(opt->name, " -button")) {
12812 opt->type = Button;
12813 } else if(p = strstr(opt->name, " -save")) {
12814 opt->type = SaveButton;
12815 } else return FALSE;
12816 *p = 0; // terminate option name
12817 // now look if the command-line options define a setting for this engine option.
12818 if(cps->optionSettings && cps->optionSettings[0])
12819 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12820 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12821 sprintf(buf, "option %s", p);
12822 if(p = strstr(buf, ",")) *p = 0;
12824 SendToProgram(buf, cps);
12830 FeatureDone(cps, val)
12831 ChessProgramState* cps;
12834 DelayedEventCallback cb = GetDelayedEvent();
12835 if ((cb == InitBackEnd3 && cps == &first) ||
12836 (cb == TwoMachinesEventIfReady && cps == &second)) {
12837 CancelDelayedEvent();
12838 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12840 cps->initDone = val;
12843 /* Parse feature command from engine */
12845 ParseFeatures(args, cps)
12847 ChessProgramState *cps;
12855 while (*p == ' ') p++;
12856 if (*p == NULLCHAR) return;
12858 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12859 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12860 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12861 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12862 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12863 if (BoolFeature(&p, "reuse", &val, cps)) {
12864 /* Engine can disable reuse, but can't enable it if user said no */
12865 if (!val) cps->reuse = FALSE;
12868 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12869 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12870 if (gameMode == TwoMachinesPlay) {
12871 DisplayTwoMachinesTitle();
12877 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12878 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12879 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12880 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12881 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12882 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12883 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12884 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12885 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12886 if (IntFeature(&p, "done", &val, cps)) {
12887 FeatureDone(cps, val);
12890 /* Added by Tord: */
12891 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12892 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12893 /* End of additions by Tord */
12895 /* [HGM] added features: */
12896 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12897 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12898 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12899 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12900 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12901 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12902 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12903 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12904 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12905 SendToProgram(buf, cps);
12908 if(cps->nrOptions >= MAX_OPTIONS) {
12910 sprintf(buf, "%s engine has too many options\n", cps->which);
12911 DisplayError(buf, 0);
12915 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12916 /* End of additions by HGM */
12918 /* unknown feature: complain and skip */
12920 while (*q && *q != '=') q++;
12921 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12922 SendToProgram(buf, cps);
12928 while (*p && *p != '\"') p++;
12929 if (*p == '\"') p++;
12931 while (*p && *p != ' ') p++;
12939 PeriodicUpdatesEvent(newState)
12942 if (newState == appData.periodicUpdates)
12945 appData.periodicUpdates=newState;
12947 /* Display type changes, so update it now */
12948 // DisplayAnalysis();
12950 /* Get the ball rolling again... */
12952 AnalysisPeriodicEvent(1);
12953 StartAnalysisClock();
12958 PonderNextMoveEvent(newState)
12961 if (newState == appData.ponderNextMove) return;
12962 if (gameMode == EditPosition) EditPositionDone(TRUE);
12964 SendToProgram("hard\n", &first);
12965 if (gameMode == TwoMachinesPlay) {
12966 SendToProgram("hard\n", &second);
12969 SendToProgram("easy\n", &first);
12970 thinkOutput[0] = NULLCHAR;
12971 if (gameMode == TwoMachinesPlay) {
12972 SendToProgram("easy\n", &second);
12975 appData.ponderNextMove = newState;
12979 NewSettingEvent(option, command, value)
12985 if (gameMode == EditPosition) EditPositionDone(TRUE);
12986 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12987 SendToProgram(buf, &first);
12988 if (gameMode == TwoMachinesPlay) {
12989 SendToProgram(buf, &second);
12994 ShowThinkingEvent()
12995 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12997 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12998 int newState = appData.showThinking
12999 // [HGM] thinking: other features now need thinking output as well
13000 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13002 if (oldState == newState) return;
13003 oldState = newState;
13004 if (gameMode == EditPosition) EditPositionDone(TRUE);
13006 SendToProgram("post\n", &first);
13007 if (gameMode == TwoMachinesPlay) {
13008 SendToProgram("post\n", &second);
13011 SendToProgram("nopost\n", &first);
13012 thinkOutput[0] = NULLCHAR;
13013 if (gameMode == TwoMachinesPlay) {
13014 SendToProgram("nopost\n", &second);
13017 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13021 AskQuestionEvent(title, question, replyPrefix, which)
13022 char *title; char *question; char *replyPrefix; char *which;
13024 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13025 if (pr == NoProc) return;
13026 AskQuestion(title, question, replyPrefix, pr);
13030 DisplayMove(moveNumber)
13033 char message[MSG_SIZ];
13035 char cpThinkOutput[MSG_SIZ];
13037 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13039 if (moveNumber == forwardMostMove - 1 ||
13040 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13042 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13044 if (strchr(cpThinkOutput, '\n')) {
13045 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13048 *cpThinkOutput = NULLCHAR;
13051 /* [AS] Hide thinking from human user */
13052 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13053 *cpThinkOutput = NULLCHAR;
13054 if( thinkOutput[0] != NULLCHAR ) {
13057 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13058 cpThinkOutput[i] = '.';
13060 cpThinkOutput[i] = NULLCHAR;
13061 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13065 if (moveNumber == forwardMostMove - 1 &&
13066 gameInfo.resultDetails != NULL) {
13067 if (gameInfo.resultDetails[0] == NULLCHAR) {
13068 sprintf(res, " %s", PGNResult(gameInfo.result));
13070 sprintf(res, " {%s} %s",
13071 gameInfo.resultDetails, PGNResult(gameInfo.result));
13077 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13078 DisplayMessage(res, cpThinkOutput);
13080 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13081 WhiteOnMove(moveNumber) ? " " : ".. ",
13082 parseList[moveNumber], res);
13083 DisplayMessage(message, cpThinkOutput);
13088 DisplayComment(moveNumber, text)
13092 char title[MSG_SIZ];
13093 char buf[8000]; // comment can be long!
13095 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13096 strcpy(title, "Comment");
13098 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13099 WhiteOnMove(moveNumber) ? " " : ".. ",
13100 parseList[moveNumber]);
13102 // [HGM] PV info: display PV info together with (or as) comment
13103 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13104 if(text == NULL) text = "";
13105 score = pvInfoList[moveNumber].score;
13106 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13107 depth, (pvInfoList[moveNumber].time+50)/100, text);
13110 if (text != NULL && (appData.autoDisplayComment || commentUp))
13111 CommentPopUp(title, text);
13114 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13115 * might be busy thinking or pondering. It can be omitted if your
13116 * gnuchess is configured to stop thinking immediately on any user
13117 * input. However, that gnuchess feature depends on the FIONREAD
13118 * ioctl, which does not work properly on some flavors of Unix.
13122 ChessProgramState *cps;
13125 if (!cps->useSigint) return;
13126 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13127 switch (gameMode) {
13128 case MachinePlaysWhite:
13129 case MachinePlaysBlack:
13130 case TwoMachinesPlay:
13131 case IcsPlayingWhite:
13132 case IcsPlayingBlack:
13135 /* Skip if we know it isn't thinking */
13136 if (!cps->maybeThinking) return;
13137 if (appData.debugMode)
13138 fprintf(debugFP, "Interrupting %s\n", cps->which);
13139 InterruptChildProcess(cps->pr);
13140 cps->maybeThinking = FALSE;
13145 #endif /*ATTENTION*/
13151 if (whiteTimeRemaining <= 0) {
13154 if (appData.icsActive) {
13155 if (appData.autoCallFlag &&
13156 gameMode == IcsPlayingBlack && !blackFlag) {
13157 SendToICS(ics_prefix);
13158 SendToICS("flag\n");
13162 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13164 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13165 if (appData.autoCallFlag) {
13166 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13173 if (blackTimeRemaining <= 0) {
13176 if (appData.icsActive) {
13177 if (appData.autoCallFlag &&
13178 gameMode == IcsPlayingWhite && !whiteFlag) {
13179 SendToICS(ics_prefix);
13180 SendToICS("flag\n");
13184 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13186 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13187 if (appData.autoCallFlag) {
13188 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13201 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13202 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13205 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13207 if ( !WhiteOnMove(forwardMostMove) )
13208 /* White made time control */
13209 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13210 /* [HGM] time odds: correct new time quota for time odds! */
13211 / WhitePlayer()->timeOdds;
13213 /* Black made time control */
13214 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13215 / WhitePlayer()->other->timeOdds;
13219 DisplayBothClocks()
13221 int wom = gameMode == EditPosition ?
13222 !blackPlaysFirst : WhiteOnMove(currentMove);
13223 DisplayWhiteClock(whiteTimeRemaining, wom);
13224 DisplayBlackClock(blackTimeRemaining, !wom);
13228 /* Timekeeping seems to be a portability nightmare. I think everyone
13229 has ftime(), but I'm really not sure, so I'm including some ifdefs
13230 to use other calls if you don't. Clocks will be less accurate if
13231 you have neither ftime nor gettimeofday.
13234 /* VS 2008 requires the #include outside of the function */
13235 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13236 #include <sys/timeb.h>
13239 /* Get the current time as a TimeMark */
13244 #if HAVE_GETTIMEOFDAY
13246 struct timeval timeVal;
13247 struct timezone timeZone;
13249 gettimeofday(&timeVal, &timeZone);
13250 tm->sec = (long) timeVal.tv_sec;
13251 tm->ms = (int) (timeVal.tv_usec / 1000L);
13253 #else /*!HAVE_GETTIMEOFDAY*/
13256 // include <sys/timeb.h> / moved to just above start of function
13257 struct timeb timeB;
13260 tm->sec = (long) timeB.time;
13261 tm->ms = (int) timeB.millitm;
13263 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13264 tm->sec = (long) time(NULL);
13270 /* Return the difference in milliseconds between two
13271 time marks. We assume the difference will fit in a long!
13274 SubtractTimeMarks(tm2, tm1)
13275 TimeMark *tm2, *tm1;
13277 return 1000L*(tm2->sec - tm1->sec) +
13278 (long) (tm2->ms - tm1->ms);
13283 * Code to manage the game clocks.
13285 * In tournament play, black starts the clock and then white makes a move.
13286 * We give the human user a slight advantage if he is playing white---the
13287 * clocks don't run until he makes his first move, so it takes zero time.
13288 * Also, we don't account for network lag, so we could get out of sync
13289 * with GNU Chess's clock -- but then, referees are always right.
13292 static TimeMark tickStartTM;
13293 static long intendedTickLength;
13296 NextTickLength(timeRemaining)
13297 long timeRemaining;
13299 long nominalTickLength, nextTickLength;
13301 if (timeRemaining > 0L && timeRemaining <= 10000L)
13302 nominalTickLength = 100L;
13304 nominalTickLength = 1000L;
13305 nextTickLength = timeRemaining % nominalTickLength;
13306 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13308 return nextTickLength;
13311 /* Adjust clock one minute up or down */
13313 AdjustClock(Boolean which, int dir)
13315 if(which) blackTimeRemaining += 60000*dir;
13316 else whiteTimeRemaining += 60000*dir;
13317 DisplayBothClocks();
13320 /* Stop clocks and reset to a fresh time control */
13324 (void) StopClockTimer();
13325 if (appData.icsActive) {
13326 whiteTimeRemaining = blackTimeRemaining = 0;
13327 } else if (searchTime) {
13328 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13329 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13330 } else { /* [HGM] correct new time quote for time odds */
13331 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13332 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13334 if (whiteFlag || blackFlag) {
13336 whiteFlag = blackFlag = FALSE;
13338 DisplayBothClocks();
13341 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13343 /* Decrement running clock by amount of time that has passed */
13347 long timeRemaining;
13348 long lastTickLength, fudge;
13351 if (!appData.clockMode) return;
13352 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13356 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13358 /* Fudge if we woke up a little too soon */
13359 fudge = intendedTickLength - lastTickLength;
13360 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13362 if (WhiteOnMove(forwardMostMove)) {
13363 if(whiteNPS >= 0) lastTickLength = 0;
13364 timeRemaining = whiteTimeRemaining -= lastTickLength;
13365 DisplayWhiteClock(whiteTimeRemaining - fudge,
13366 WhiteOnMove(currentMove));
13368 if(blackNPS >= 0) lastTickLength = 0;
13369 timeRemaining = blackTimeRemaining -= lastTickLength;
13370 DisplayBlackClock(blackTimeRemaining - fudge,
13371 !WhiteOnMove(currentMove));
13374 if (CheckFlags()) return;
13377 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13378 StartClockTimer(intendedTickLength);
13380 /* if the time remaining has fallen below the alarm threshold, sound the
13381 * alarm. if the alarm has sounded and (due to a takeback or time control
13382 * with increment) the time remaining has increased to a level above the
13383 * threshold, reset the alarm so it can sound again.
13386 if (appData.icsActive && appData.icsAlarm) {
13388 /* make sure we are dealing with the user's clock */
13389 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13390 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13393 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13394 alarmSounded = FALSE;
13395 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13397 alarmSounded = TRUE;
13403 /* A player has just moved, so stop the previously running
13404 clock and (if in clock mode) start the other one.
13405 We redisplay both clocks in case we're in ICS mode, because
13406 ICS gives us an update to both clocks after every move.
13407 Note that this routine is called *after* forwardMostMove
13408 is updated, so the last fractional tick must be subtracted
13409 from the color that is *not* on move now.
13414 long lastTickLength;
13416 int flagged = FALSE;
13420 if (StopClockTimer() && appData.clockMode) {
13421 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13422 if (WhiteOnMove(forwardMostMove)) {
13423 if(blackNPS >= 0) lastTickLength = 0;
13424 blackTimeRemaining -= lastTickLength;
13425 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13426 // if(pvInfoList[forwardMostMove-1].time == -1)
13427 pvInfoList[forwardMostMove-1].time = // use GUI time
13428 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13430 if(whiteNPS >= 0) lastTickLength = 0;
13431 whiteTimeRemaining -= lastTickLength;
13432 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13433 // if(pvInfoList[forwardMostMove-1].time == -1)
13434 pvInfoList[forwardMostMove-1].time =
13435 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13437 flagged = CheckFlags();
13439 CheckTimeControl();
13441 if (flagged || !appData.clockMode) return;
13443 switch (gameMode) {
13444 case MachinePlaysBlack:
13445 case MachinePlaysWhite:
13446 case BeginningOfGame:
13447 if (pausing) return;
13451 case PlayFromGameFile:
13459 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13460 if(WhiteOnMove(forwardMostMove))
13461 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13462 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13466 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13467 whiteTimeRemaining : blackTimeRemaining);
13468 StartClockTimer(intendedTickLength);
13472 /* Stop both clocks */
13476 long lastTickLength;
13479 if (!StopClockTimer()) return;
13480 if (!appData.clockMode) return;
13484 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13485 if (WhiteOnMove(forwardMostMove)) {
13486 if(whiteNPS >= 0) lastTickLength = 0;
13487 whiteTimeRemaining -= lastTickLength;
13488 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13490 if(blackNPS >= 0) lastTickLength = 0;
13491 blackTimeRemaining -= lastTickLength;
13492 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13497 /* Start clock of player on move. Time may have been reset, so
13498 if clock is already running, stop and restart it. */
13502 (void) StopClockTimer(); /* in case it was running already */
13503 DisplayBothClocks();
13504 if (CheckFlags()) return;
13506 if (!appData.clockMode) return;
13507 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13509 GetTimeMark(&tickStartTM);
13510 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13511 whiteTimeRemaining : blackTimeRemaining);
13513 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13514 whiteNPS = blackNPS = -1;
13515 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13516 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13517 whiteNPS = first.nps;
13518 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13519 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13520 blackNPS = first.nps;
13521 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13522 whiteNPS = second.nps;
13523 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13524 blackNPS = second.nps;
13525 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13527 StartClockTimer(intendedTickLength);
13534 long second, minute, hour, day;
13536 static char buf[32];
13538 if (ms > 0 && ms <= 9900) {
13539 /* convert milliseconds to tenths, rounding up */
13540 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13542 sprintf(buf, " %03.1f ", tenths/10.0);
13546 /* convert milliseconds to seconds, rounding up */
13547 /* use floating point to avoid strangeness of integer division
13548 with negative dividends on many machines */
13549 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13556 day = second / (60 * 60 * 24);
13557 second = second % (60 * 60 * 24);
13558 hour = second / (60 * 60);
13559 second = second % (60 * 60);
13560 minute = second / 60;
13561 second = second % 60;
13564 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13565 sign, day, hour, minute, second);
13567 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13569 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13576 * This is necessary because some C libraries aren't ANSI C compliant yet.
13579 StrStr(string, match)
13580 char *string, *match;
13584 length = strlen(match);
13586 for (i = strlen(string) - length; i >= 0; i--, string++)
13587 if (!strncmp(match, string, length))
13594 StrCaseStr(string, match)
13595 char *string, *match;
13599 length = strlen(match);
13601 for (i = strlen(string) - length; i >= 0; i--, string++) {
13602 for (j = 0; j < length; j++) {
13603 if (ToLower(match[j]) != ToLower(string[j]))
13606 if (j == length) return string;
13620 c1 = ToLower(*s1++);
13621 c2 = ToLower(*s2++);
13622 if (c1 > c2) return 1;
13623 if (c1 < c2) return -1;
13624 if (c1 == NULLCHAR) return 0;
13633 return isupper(c) ? tolower(c) : c;
13641 return islower(c) ? toupper(c) : c;
13643 #endif /* !_amigados */
13651 if ((ret = (char *) malloc(strlen(s) + 1))) {
13658 StrSavePtr(s, savePtr)
13659 char *s, **savePtr;
13664 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13665 strcpy(*savePtr, s);
13677 clock = time((time_t *)NULL);
13678 tm = localtime(&clock);
13679 sprintf(buf, "%04d.%02d.%02d",
13680 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13681 return StrSave(buf);
13686 PositionToFEN(move, overrideCastling)
13688 char *overrideCastling;
13690 int i, j, fromX, fromY, toX, toY;
13697 whiteToPlay = (gameMode == EditPosition) ?
13698 !blackPlaysFirst : (move % 2 == 0);
13701 /* Piece placement data */
13702 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13704 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13705 if (boards[move][i][j] == EmptySquare) {
13707 } else { ChessSquare piece = boards[move][i][j];
13708 if (emptycount > 0) {
13709 if(emptycount<10) /* [HGM] can be >= 10 */
13710 *p++ = '0' + emptycount;
13711 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13714 if(PieceToChar(piece) == '+') {
13715 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13717 piece = (ChessSquare)(DEMOTED piece);
13719 *p++ = PieceToChar(piece);
13721 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13722 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13727 if (emptycount > 0) {
13728 if(emptycount<10) /* [HGM] can be >= 10 */
13729 *p++ = '0' + emptycount;
13730 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13737 /* [HGM] print Crazyhouse or Shogi holdings */
13738 if( gameInfo.holdingsWidth ) {
13739 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13741 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13742 piece = boards[move][i][BOARD_WIDTH-1];
13743 if( piece != EmptySquare )
13744 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13745 *p++ = PieceToChar(piece);
13747 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13748 piece = boards[move][BOARD_HEIGHT-i-1][0];
13749 if( piece != EmptySquare )
13750 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13751 *p++ = PieceToChar(piece);
13754 if( q == p ) *p++ = '-';
13760 *p++ = whiteToPlay ? 'w' : 'b';
13763 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13764 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13766 if(nrCastlingRights) {
13768 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13769 /* [HGM] write directly from rights */
13770 if(boards[move][CASTLING][2] != NoRights &&
13771 boards[move][CASTLING][0] != NoRights )
13772 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13773 if(boards[move][CASTLING][2] != NoRights &&
13774 boards[move][CASTLING][1] != NoRights )
13775 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13776 if(boards[move][CASTLING][5] != NoRights &&
13777 boards[move][CASTLING][3] != NoRights )
13778 *p++ = boards[move][CASTLING][3] + AAA;
13779 if(boards[move][CASTLING][5] != NoRights &&
13780 boards[move][CASTLING][4] != NoRights )
13781 *p++ = boards[move][CASTLING][4] + AAA;
13784 /* [HGM] write true castling rights */
13785 if( nrCastlingRights == 6 ) {
13786 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13787 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13788 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13789 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13790 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13791 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13792 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13793 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13796 if (q == p) *p++ = '-'; /* No castling rights */
13800 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13801 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13802 /* En passant target square */
13803 if (move > backwardMostMove) {
13804 fromX = moveList[move - 1][0] - AAA;
13805 fromY = moveList[move - 1][1] - ONE;
13806 toX = moveList[move - 1][2] - AAA;
13807 toY = moveList[move - 1][3] - ONE;
13808 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13809 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13810 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13812 /* 2-square pawn move just happened */
13814 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13818 } else if(move == backwardMostMove) {
13819 // [HGM] perhaps we should always do it like this, and forget the above?
13820 if((signed char)boards[move][EP_STATUS] >= 0) {
13821 *p++ = boards[move][EP_STATUS] + AAA;
13822 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13833 /* [HGM] find reversible plies */
13834 { int i = 0, j=move;
13836 if (appData.debugMode) { int k;
13837 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13838 for(k=backwardMostMove; k<=forwardMostMove; k++)
13839 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13843 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13844 if( j == backwardMostMove ) i += initialRulePlies;
13845 sprintf(p, "%d ", i);
13846 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13848 /* Fullmove number */
13849 sprintf(p, "%d", (move / 2) + 1);
13851 return StrSave(buf);
13855 ParseFEN(board, blackPlaysFirst, fen)
13857 int *blackPlaysFirst;
13867 /* [HGM] by default clear Crazyhouse holdings, if present */
13868 if(gameInfo.holdingsWidth) {
13869 for(i=0; i<BOARD_HEIGHT; i++) {
13870 board[i][0] = EmptySquare; /* black holdings */
13871 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13872 board[i][1] = (ChessSquare) 0; /* black counts */
13873 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13877 /* Piece placement data */
13878 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13881 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13882 if (*p == '/') p++;
13883 emptycount = gameInfo.boardWidth - j;
13884 while (emptycount--)
13885 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13887 #if(BOARD_FILES >= 10)
13888 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13889 p++; emptycount=10;
13890 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13891 while (emptycount--)
13892 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13894 } else if (isdigit(*p)) {
13895 emptycount = *p++ - '0';
13896 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13897 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13898 while (emptycount--)
13899 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13900 } else if (*p == '+' || isalpha(*p)) {
13901 if (j >= gameInfo.boardWidth) return FALSE;
13903 piece = CharToPiece(*++p);
13904 if(piece == EmptySquare) return FALSE; /* unknown piece */
13905 piece = (ChessSquare) (PROMOTED piece ); p++;
13906 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13907 } else piece = CharToPiece(*p++);
13909 if(piece==EmptySquare) return FALSE; /* unknown piece */
13910 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13911 piece = (ChessSquare) (PROMOTED piece);
13912 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13915 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13921 while (*p == '/' || *p == ' ') p++;
13923 /* [HGM] look for Crazyhouse holdings here */
13924 while(*p==' ') p++;
13925 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13927 if(*p == '-' ) *p++; /* empty holdings */ else {
13928 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13929 /* if we would allow FEN reading to set board size, we would */
13930 /* have to add holdings and shift the board read so far here */
13931 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13933 if((int) piece >= (int) BlackPawn ) {
13934 i = (int)piece - (int)BlackPawn;
13935 i = PieceToNumber((ChessSquare)i);
13936 if( i >= gameInfo.holdingsSize ) return FALSE;
13937 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13938 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13940 i = (int)piece - (int)WhitePawn;
13941 i = PieceToNumber((ChessSquare)i);
13942 if( i >= gameInfo.holdingsSize ) return FALSE;
13943 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13944 board[i][BOARD_WIDTH-2]++; /* black holdings */
13948 if(*p == ']') *p++;
13951 while(*p == ' ') p++;
13956 *blackPlaysFirst = FALSE;
13959 *blackPlaysFirst = TRUE;
13965 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13966 /* return the extra info in global variiables */
13968 /* set defaults in case FEN is incomplete */
13969 board[EP_STATUS] = EP_UNKNOWN;
13970 for(i=0; i<nrCastlingRights; i++ ) {
13971 board[CASTLING][i] =
13972 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13973 } /* assume possible unless obviously impossible */
13974 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13975 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13976 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13977 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13978 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13979 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13982 while(*p==' ') p++;
13983 if(nrCastlingRights) {
13984 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13985 /* castling indicator present, so default becomes no castlings */
13986 for(i=0; i<nrCastlingRights; i++ ) {
13987 board[CASTLING][i] = NoRights;
13990 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13991 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13992 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13993 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13994 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13996 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13997 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13998 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14002 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14003 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14004 board[CASTLING][2] = whiteKingFile;
14007 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14008 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14009 board[CASTLING][2] = whiteKingFile;
14012 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14013 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14014 board[CASTLING][5] = blackKingFile;
14017 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14018 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14019 board[CASTLING][5] = blackKingFile;
14022 default: /* FRC castlings */
14023 if(c >= 'a') { /* black rights */
14024 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14025 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14026 if(i == BOARD_RGHT) break;
14027 board[CASTLING][5] = i;
14029 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14030 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14032 board[CASTLING][3] = c;
14034 board[CASTLING][4] = c;
14035 } else { /* white rights */
14036 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14037 if(board[0][i] == WhiteKing) break;
14038 if(i == BOARD_RGHT) break;
14039 board[CASTLING][2] = i;
14040 c -= AAA - 'a' + 'A';
14041 if(board[0][c] >= WhiteKing) break;
14043 board[CASTLING][0] = c;
14045 board[CASTLING][1] = c;
14049 if (appData.debugMode) {
14050 fprintf(debugFP, "FEN castling rights:");
14051 for(i=0; i<nrCastlingRights; i++)
14052 fprintf(debugFP, " %d", board[CASTLING][i]);
14053 fprintf(debugFP, "\n");
14056 while(*p==' ') p++;
14059 /* read e.p. field in games that know e.p. capture */
14060 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14061 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14063 p++; board[EP_STATUS] = EP_NONE;
14065 char c = *p++ - AAA;
14067 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14068 if(*p >= '0' && *p <='9') *p++;
14069 board[EP_STATUS] = c;
14074 if(sscanf(p, "%d", &i) == 1) {
14075 FENrulePlies = i; /* 50-move ply counter */
14076 /* (The move number is still ignored) */
14083 EditPositionPasteFEN(char *fen)
14086 Board initial_position;
14088 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14089 DisplayError(_("Bad FEN position in clipboard"), 0);
14092 int savedBlackPlaysFirst = blackPlaysFirst;
14093 EditPositionEvent();
14094 blackPlaysFirst = savedBlackPlaysFirst;
14095 CopyBoard(boards[0], initial_position);
14096 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14097 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14098 DisplayBothClocks();
14099 DrawPosition(FALSE, boards[currentMove]);
14104 static char cseq[12] = "\\ ";
14106 Boolean set_cont_sequence(char *new_seq)
14111 // handle bad attempts to set the sequence
14113 return 0; // acceptable error - no debug
14115 len = strlen(new_seq);
14116 ret = (len > 0) && (len < sizeof(cseq));
14118 strcpy(cseq, new_seq);
14119 else if (appData.debugMode)
14120 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14125 reformat a source message so words don't cross the width boundary. internal
14126 newlines are not removed. returns the wrapped size (no null character unless
14127 included in source message). If dest is NULL, only calculate the size required
14128 for the dest buffer. lp argument indicats line position upon entry, and it's
14129 passed back upon exit.
14131 int wrap(char *dest, char *src, int count, int width, int *lp)
14133 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14135 cseq_len = strlen(cseq);
14136 old_line = line = *lp;
14137 ansi = len = clen = 0;
14139 for (i=0; i < count; i++)
14141 if (src[i] == '\033')
14144 // if we hit the width, back up
14145 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14147 // store i & len in case the word is too long
14148 old_i = i, old_len = len;
14150 // find the end of the last word
14151 while (i && src[i] != ' ' && src[i] != '\n')
14157 // word too long? restore i & len before splitting it
14158 if ((old_i-i+clen) >= width)
14165 if (i && src[i-1] == ' ')
14168 if (src[i] != ' ' && src[i] != '\n')
14175 // now append the newline and continuation sequence
14180 strncpy(dest+len, cseq, cseq_len);
14188 dest[len] = src[i];
14192 if (src[i] == '\n')
14197 if (dest && appData.debugMode)
14199 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14200 count, width, line, len, *lp);
14201 show_bytes(debugFP, src, count);
14202 fprintf(debugFP, "\ndest: ");
14203 show_bytes(debugFP, dest, len);
14204 fprintf(debugFP, "\n");
14206 *lp = dest ? line : old_line;
14211 // [HGM] vari: routines for shelving variations
14214 PushTail(int firstMove, int lastMove)
14216 int i, j, nrMoves = lastMove - firstMove;
14218 if(appData.icsActive) { // only in local mode
14219 forwardMostMove = currentMove; // mimic old ICS behavior
14222 if(storedGames >= MAX_VARIATIONS-1) return;
14224 // push current tail of game on stack
14225 savedResult[storedGames] = gameInfo.result;
14226 savedDetails[storedGames] = gameInfo.resultDetails;
14227 gameInfo.resultDetails = NULL;
14228 savedFirst[storedGames] = firstMove;
14229 savedLast [storedGames] = lastMove;
14230 savedFramePtr[storedGames] = framePtr;
14231 framePtr -= nrMoves; // reserve space for the boards
14232 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14233 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14234 for(j=0; j<MOVE_LEN; j++)
14235 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14236 for(j=0; j<2*MOVE_LEN; j++)
14237 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14238 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14239 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14240 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14241 pvInfoList[firstMove+i-1].depth = 0;
14242 commentList[framePtr+i] = commentList[firstMove+i];
14243 commentList[firstMove+i] = NULL;
14247 forwardMostMove = currentMove; // truncte game so we can start variation
14248 if(storedGames == 1) GreyRevert(FALSE);
14252 PopTail(Boolean annotate)
14255 char buf[8000], moveBuf[20];
14257 if(appData.icsActive) return FALSE; // only in local mode
14258 if(!storedGames) return FALSE; // sanity
14261 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14262 nrMoves = savedLast[storedGames] - currentMove;
14265 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14266 else strcpy(buf, "(");
14267 for(i=currentMove; i<forwardMostMove; i++) {
14269 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14270 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14271 strcat(buf, moveBuf);
14272 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14276 for(i=1; i<nrMoves; i++) { // copy last variation back
14277 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14278 for(j=0; j<MOVE_LEN; j++)
14279 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14280 for(j=0; j<2*MOVE_LEN; j++)
14281 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14282 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14283 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14284 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14285 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14286 commentList[currentMove+i] = commentList[framePtr+i];
14287 commentList[framePtr+i] = NULL;
14289 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14290 framePtr = savedFramePtr[storedGames];
14291 gameInfo.result = savedResult[storedGames];
14292 if(gameInfo.resultDetails != NULL) {
14293 free(gameInfo.resultDetails);
14295 gameInfo.resultDetails = savedDetails[storedGames];
14296 forwardMostMove = currentMove + nrMoves;
14297 if(storedGames == 0) GreyRevert(TRUE);
14303 { // remove all shelved variations
14305 for(i=0; i<storedGames; i++) {
14306 if(savedDetails[i])
14307 free(savedDetails[i]);
14308 savedDetails[i] = NULL;
14310 for(i=framePtr; i<MAX_MOVES; i++) {
14311 if(commentList[i]) free(commentList[i]);
14312 commentList[i] = NULL;
14314 framePtr = MAX_MOVES-1;