2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char initialRights[BOARD_FILES];
446 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int initialRulePlies, FENrulePlies;
448 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
451 int mute; // mute all sounds
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((Boolean annotate));
465 void CleanupTail P((void));
467 ChessSquare FIDEArray[2][BOARD_FILES] = {
468 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471 BlackKing, BlackBishop, BlackKnight, BlackRook }
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackKing, BlackKnight, BlackRook }
481 ChessSquare KnightmateArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484 { BlackRook, BlackMan, BlackBishop, BlackQueen,
485 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492 BlackKing, BlackBishop, BlackKnight, BlackRook }
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
522 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
527 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
529 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
534 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
536 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
542 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
544 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
547 #define GothicArray CapablancaArray
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
553 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
555 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
558 #define FalconArray CapablancaArray
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
580 Board initialPosition;
583 /* Convert str to a rating. Checks for special cases of "----",
585 "++++", etc. Also strips ()'s */
587 string_to_rating(str)
590 while(*str && !isdigit(*str)) ++str;
592 return 0; /* One of the special "no rating" cases */
600 /* Init programStats */
601 programStats.movelist[0] = 0;
602 programStats.depth = 0;
603 programStats.nr_moves = 0;
604 programStats.moves_left = 0;
605 programStats.nodes = 0;
606 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
607 programStats.score = 0;
608 programStats.got_only_move = 0;
609 programStats.got_fail = 0;
610 programStats.line_is_book = 0;
616 int matched, min, sec;
618 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
620 GetTimeMark(&programStartTime);
621 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
624 programStats.ok_to_send = 1;
625 programStats.seen_stat = 0;
628 * Initialize game list
634 * Internet chess server status
636 if (appData.icsActive) {
637 appData.matchMode = FALSE;
638 appData.matchGames = 0;
640 appData.noChessProgram = !appData.zippyPlay;
642 appData.zippyPlay = FALSE;
643 appData.zippyTalk = FALSE;
644 appData.noChessProgram = TRUE;
646 if (*appData.icsHelper != NULLCHAR) {
647 appData.useTelnet = TRUE;
648 appData.telnetProgram = appData.icsHelper;
651 appData.zippyTalk = appData.zippyPlay = FALSE;
654 /* [AS] Initialize pv info list [HGM] and game state */
658 for( i=0; i<=framePtr; i++ ) {
659 pvInfoList[i].depth = -1;
660 boards[i][EP_STATUS] = EP_NONE;
661 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
666 * Parse timeControl resource
668 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669 appData.movesPerSession)) {
671 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672 DisplayFatalError(buf, 0, 2);
676 * Parse searchTime resource
678 if (*appData.searchTime != NULLCHAR) {
679 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
681 searchTime = min * 60;
682 } else if (matched == 2) {
683 searchTime = min * 60 + sec;
686 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687 DisplayFatalError(buf, 0, 2);
691 /* [AS] Adjudication threshold */
692 adjudicateLossThreshold = appData.adjudicateLossThreshold;
694 first.which = "first";
695 second.which = "second";
696 first.maybeThinking = second.maybeThinking = FALSE;
697 first.pr = second.pr = NoProc;
698 first.isr = second.isr = NULL;
699 first.sendTime = second.sendTime = 2;
700 first.sendDrawOffers = 1;
701 if (appData.firstPlaysBlack) {
702 first.twoMachinesColor = "black\n";
703 second.twoMachinesColor = "white\n";
705 first.twoMachinesColor = "white\n";
706 second.twoMachinesColor = "black\n";
708 first.program = appData.firstChessProgram;
709 second.program = appData.secondChessProgram;
710 first.host = appData.firstHost;
711 second.host = appData.secondHost;
712 first.dir = appData.firstDirectory;
713 second.dir = appData.secondDirectory;
714 first.other = &second;
715 second.other = &first;
716 first.initString = appData.initString;
717 second.initString = appData.secondInitString;
718 first.computerString = appData.firstComputerString;
719 second.computerString = appData.secondComputerString;
720 first.useSigint = second.useSigint = TRUE;
721 first.useSigterm = second.useSigterm = TRUE;
722 first.reuse = appData.reuseFirst;
723 second.reuse = appData.reuseSecond;
724 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
725 second.nps = appData.secondNPS;
726 first.useSetboard = second.useSetboard = FALSE;
727 first.useSAN = second.useSAN = FALSE;
728 first.usePing = second.usePing = FALSE;
729 first.lastPing = second.lastPing = 0;
730 first.lastPong = second.lastPong = 0;
731 first.usePlayother = second.usePlayother = FALSE;
732 first.useColors = second.useColors = TRUE;
733 first.useUsermove = second.useUsermove = FALSE;
734 first.sendICS = second.sendICS = FALSE;
735 first.sendName = second.sendName = appData.icsActive;
736 first.sdKludge = second.sdKludge = FALSE;
737 first.stKludge = second.stKludge = FALSE;
738 TidyProgramName(first.program, first.host, first.tidy);
739 TidyProgramName(second.program, second.host, second.tidy);
740 first.matchWins = second.matchWins = 0;
741 strcpy(first.variants, appData.variant);
742 strcpy(second.variants, appData.variant);
743 first.analysisSupport = second.analysisSupport = 2; /* detect */
744 first.analyzing = second.analyzing = FALSE;
745 first.initDone = second.initDone = FALSE;
747 /* New features added by Tord: */
748 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750 /* End of new features added by Tord. */
751 first.fenOverride = appData.fenOverride1;
752 second.fenOverride = appData.fenOverride2;
754 /* [HGM] time odds: set factor for each machine */
755 first.timeOdds = appData.firstTimeOdds;
756 second.timeOdds = appData.secondTimeOdds;
758 if(appData.timeOddsMode) {
759 norm = first.timeOdds;
760 if(norm > second.timeOdds) norm = second.timeOdds;
762 first.timeOdds /= norm;
763 second.timeOdds /= norm;
766 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767 first.accumulateTC = appData.firstAccumulateTC;
768 second.accumulateTC = appData.secondAccumulateTC;
769 first.maxNrOfSessions = second.maxNrOfSessions = 1;
772 first.debug = second.debug = FALSE;
773 first.supportsNPS = second.supportsNPS = UNKNOWN;
776 first.optionSettings = appData.firstOptions;
777 second.optionSettings = appData.secondOptions;
779 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781 first.isUCI = appData.firstIsUCI; /* [AS] */
782 second.isUCI = appData.secondIsUCI; /* [AS] */
783 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
786 if (appData.firstProtocolVersion > PROTOVER ||
787 appData.firstProtocolVersion < 1) {
789 sprintf(buf, _("protocol version %d not supported"),
790 appData.firstProtocolVersion);
791 DisplayFatalError(buf, 0, 2);
793 first.protocolVersion = appData.firstProtocolVersion;
796 if (appData.secondProtocolVersion > PROTOVER ||
797 appData.secondProtocolVersion < 1) {
799 sprintf(buf, _("protocol version %d not supported"),
800 appData.secondProtocolVersion);
801 DisplayFatalError(buf, 0, 2);
803 second.protocolVersion = appData.secondProtocolVersion;
806 if (appData.icsActive) {
807 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
808 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810 appData.clockMode = FALSE;
811 first.sendTime = second.sendTime = 0;
815 /* Override some settings from environment variables, for backward
816 compatibility. Unfortunately it's not feasible to have the env
817 vars just set defaults, at least in xboard. Ugh.
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
824 if (appData.noChessProgram) {
825 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826 sprintf(programVersion, "%s", PACKAGE_STRING);
828 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
833 if (!appData.icsActive) {
835 /* Check for variants that are supported only in ICS mode,
836 or not at all. Some that are accepted here nevertheless
837 have bugs; see comments below.
839 VariantClass variant = StringToVariant(appData.variant);
841 case VariantBughouse: /* need four players and two boards */
842 case VariantKriegspiel: /* need to hide pieces and move details */
843 /* case VariantFischeRandom: (Fabien: moved below) */
844 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845 DisplayFatalError(buf, 0, 2);
849 case VariantLoadable:
859 sprintf(buf, _("Unknown variant name %s"), appData.variant);
860 DisplayFatalError(buf, 0, 2);
863 case VariantXiangqi: /* [HGM] repetition rules not implemented */
864 case VariantFairy: /* [HGM] TestLegality definitely off! */
865 case VariantGothic: /* [HGM] should work */
866 case VariantCapablanca: /* [HGM] should work */
867 case VariantCourier: /* [HGM] initial forced moves not implemented */
868 case VariantShogi: /* [HGM] drops not tested for legality */
869 case VariantKnightmate: /* [HGM] should work */
870 case VariantCylinder: /* [HGM] untested */
871 case VariantFalcon: /* [HGM] untested */
872 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873 offboard interposition not understood */
874 case VariantNormal: /* definitely works! */
875 case VariantWildCastle: /* pieces not automatically shuffled */
876 case VariantNoCastle: /* pieces not automatically shuffled */
877 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878 case VariantLosers: /* should work except for win condition,
879 and doesn't know captures are mandatory */
880 case VariantSuicide: /* should work except for win condition,
881 and doesn't know captures are mandatory */
882 case VariantGiveaway: /* should work except for win condition,
883 and doesn't know captures are mandatory */
884 case VariantTwoKings: /* should work */
885 case VariantAtomic: /* should work except for win condition */
886 case Variant3Check: /* should work except for win condition */
887 case VariantShatranj: /* should work except for all win conditions */
888 case VariantBerolina: /* might work if TestLegality is off */
889 case VariantCapaRandom: /* should work */
890 case VariantJanus: /* should work */
891 case VariantSuper: /* experimental */
892 case VariantGreat: /* experimental, requires legality testing to be off */
897 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
898 InitEngineUCI( installDir, &second );
901 int NextIntegerFromString( char ** str, long * value )
906 while( *s == ' ' || *s == '\t' ) {
912 if( *s >= '0' && *s <= '9' ) {
913 while( *s >= '0' && *s <= '9' ) {
914 *value = *value * 10 + (*s - '0');
926 int NextTimeControlFromString( char ** str, long * value )
929 int result = NextIntegerFromString( str, &temp );
932 *value = temp * 60; /* Minutes */
935 result = NextIntegerFromString( str, &temp );
936 *value += temp; /* Seconds */
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 { /* [HGM] routine added to read '+moves/time' for secondary time control */
945 int result = -1; long temp, temp2;
947 if(**str != '+') return -1; // old params remain in force!
949 if( NextTimeControlFromString( str, &temp ) ) return -1;
952 /* time only: incremental or sudden-death time control */
953 if(**str == '+') { /* increment follows; read it */
955 if(result = NextIntegerFromString( str, &temp2)) return -1;
958 *moves = 0; *tc = temp * 1000;
960 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
962 (*str)++; /* classical time control */
963 result = NextTimeControlFromString( str, &temp2);
972 int GetTimeQuota(int movenr)
973 { /* [HGM] get time to add from the multi-session time-control string */
974 int moves=1; /* kludge to force reading of first session */
975 long time, increment;
976 char *s = fullTimeControlString;
978 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
980 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982 if(movenr == -1) return time; /* last move before new session */
983 if(!moves) return increment; /* current session is incremental */
984 if(movenr >= 0) movenr -= moves; /* we already finished this session */
985 } while(movenr >= -1); /* try again for next session */
987 return 0; // no new time quota on this move
991 ParseTimeControl(tc, ti, mps)
1000 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1003 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004 else sprintf(buf, "+%s+%d", tc, ti);
1007 sprintf(buf, "+%d/%s", mps, tc);
1008 else sprintf(buf, "+%s", tc);
1010 fullTimeControlString = StrSave(buf);
1012 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1017 /* Parse second time control */
1020 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1028 timeControl_2 = tc2 * 1000;
1038 timeControl = tc1 * 1000;
1041 timeIncrement = ti * 1000; /* convert to ms */
1042 movesPerSession = 0;
1045 movesPerSession = mps;
1053 if (appData.debugMode) {
1054 fprintf(debugFP, "%s\n", programVersion);
1057 set_cont_sequence(appData.wrapContSeq);
1058 if (appData.matchGames > 0) {
1059 appData.matchMode = TRUE;
1060 } else if (appData.matchMode) {
1061 appData.matchGames = 1;
1063 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064 appData.matchGames = appData.sameColorGames;
1065 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1070 if (appData.noChessProgram || first.protocolVersion == 1) {
1073 /* kludge: allow timeout for initial "feature" commands */
1075 DisplayMessage("", _("Starting chess program"));
1076 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1081 InitBackEnd3 P((void))
1083 GameMode initialMode;
1087 InitChessProgram(&first, startedFromSetupPosition);
1090 if (appData.icsActive) {
1092 /* [DM] Make a console window if needed [HGM] merged ifs */
1097 if (*appData.icsCommPort != NULLCHAR) {
1098 sprintf(buf, _("Could not open comm port %s"),
1099 appData.icsCommPort);
1101 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1102 appData.icsHost, appData.icsPort);
1104 DisplayFatalError(buf, err, 1);
1109 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1111 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112 } else if (appData.noChessProgram) {
1118 if (*appData.cmailGameName != NULLCHAR) {
1120 OpenLoopback(&cmailPR);
1122 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1126 DisplayMessage("", "");
1127 if (StrCaseCmp(appData.initialMode, "") == 0) {
1128 initialMode = BeginningOfGame;
1129 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130 initialMode = TwoMachinesPlay;
1131 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132 initialMode = AnalyzeFile;
1133 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134 initialMode = AnalyzeMode;
1135 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136 initialMode = MachinePlaysWhite;
1137 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138 initialMode = MachinePlaysBlack;
1139 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140 initialMode = EditGame;
1141 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142 initialMode = EditPosition;
1143 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144 initialMode = Training;
1146 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147 DisplayFatalError(buf, 0, 2);
1151 if (appData.matchMode) {
1152 /* Set up machine vs. machine match */
1153 if (appData.noChessProgram) {
1154 DisplayFatalError(_("Can't have a match with no chess programs"),
1160 if (*appData.loadGameFile != NULLCHAR) {
1161 int index = appData.loadGameIndex; // [HGM] autoinc
1162 if(index<0) lastIndex = index = 1;
1163 if (!LoadGameFromFile(appData.loadGameFile,
1165 appData.loadGameFile, FALSE)) {
1166 DisplayFatalError(_("Bad game file"), 0, 1);
1169 } else if (*appData.loadPositionFile != NULLCHAR) {
1170 int index = appData.loadPositionIndex; // [HGM] autoinc
1171 if(index<0) lastIndex = index = 1;
1172 if (!LoadPositionFromFile(appData.loadPositionFile,
1174 appData.loadPositionFile)) {
1175 DisplayFatalError(_("Bad position file"), 0, 1);
1180 } else if (*appData.cmailGameName != NULLCHAR) {
1181 /* Set up cmail mode */
1182 ReloadCmailMsgEvent(TRUE);
1184 /* Set up other modes */
1185 if (initialMode == AnalyzeFile) {
1186 if (*appData.loadGameFile == NULLCHAR) {
1187 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1191 if (*appData.loadGameFile != NULLCHAR) {
1192 (void) LoadGameFromFile(appData.loadGameFile,
1193 appData.loadGameIndex,
1194 appData.loadGameFile, TRUE);
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
1196 (void) LoadPositionFromFile(appData.loadPositionFile,
1197 appData.loadPositionIndex,
1198 appData.loadPositionFile);
1199 /* [HGM] try to make self-starting even after FEN load */
1200 /* to allow automatic setup of fairy variants with wtm */
1201 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202 gameMode = BeginningOfGame;
1203 setboardSpoiledMachineBlack = 1;
1205 /* [HGM] loadPos: make that every new game uses the setup */
1206 /* from file as long as we do not switch variant */
1207 if(!blackPlaysFirst) {
1208 startedFromPositionFile = TRUE;
1209 CopyBoard(filePosition, boards[0]);
1212 if (initialMode == AnalyzeMode) {
1213 if (appData.noChessProgram) {
1214 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217 if (appData.icsActive) {
1218 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1222 } else if (initialMode == AnalyzeFile) {
1223 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224 ShowThinkingEvent();
1226 AnalysisPeriodicEvent(1);
1227 } else if (initialMode == MachinePlaysWhite) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1238 MachineWhiteEvent();
1239 } else if (initialMode == MachinePlaysBlack) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1250 MachineBlackEvent();
1251 } else if (initialMode == TwoMachinesPlay) {
1252 if (appData.noChessProgram) {
1253 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1257 if (appData.icsActive) {
1258 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1263 } else if (initialMode == EditGame) {
1265 } else if (initialMode == EditPosition) {
1266 EditPositionEvent();
1267 } else if (initialMode == Training) {
1268 if (*appData.loadGameFile == NULLCHAR) {
1269 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1278 * Establish will establish a contact to a remote host.port.
1279 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280 * used to talk to the host.
1281 * Returns 0 if okay, error code if not.
1288 if (*appData.icsCommPort != NULLCHAR) {
1289 /* Talk to the host through a serial comm port */
1290 return OpenCommPort(appData.icsCommPort, &icsPR);
1292 } else if (*appData.gateway != NULLCHAR) {
1293 if (*appData.remoteShell == NULLCHAR) {
1294 /* Use the rcmd protocol to run telnet program on a gateway host */
1295 snprintf(buf, sizeof(buf), "%s %s %s",
1296 appData.telnetProgram, appData.icsHost, appData.icsPort);
1297 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300 /* Use the rsh program to run telnet program on a gateway host */
1301 if (*appData.remoteUser == NULLCHAR) {
1302 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303 appData.gateway, appData.telnetProgram,
1304 appData.icsHost, appData.icsPort);
1306 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307 appData.remoteShell, appData.gateway,
1308 appData.remoteUser, appData.telnetProgram,
1309 appData.icsHost, appData.icsPort);
1311 return StartChildProcess(buf, "", &icsPR);
1314 } else if (appData.useTelnet) {
1315 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318 /* TCP socket interface differs somewhat between
1319 Unix and NT; handle details in the front end.
1321 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1326 show_bytes(fp, buf, count)
1332 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333 fprintf(fp, "\\%03o", *buf & 0xff);
1342 /* Returns an errno value */
1344 OutputMaybeTelnet(pr, message, count, outError)
1350 char buf[8192], *p, *q, *buflim;
1351 int left, newcount, outcount;
1353 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354 *appData.gateway != NULLCHAR) {
1355 if (appData.debugMode) {
1356 fprintf(debugFP, ">ICS: ");
1357 show_bytes(debugFP, message, count);
1358 fprintf(debugFP, "\n");
1360 return OutputToProcess(pr, message, count, outError);
1363 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1370 if (appData.debugMode) {
1371 fprintf(debugFP, ">ICS: ");
1372 show_bytes(debugFP, buf, newcount);
1373 fprintf(debugFP, "\n");
1375 outcount = OutputToProcess(pr, buf, newcount, outError);
1376 if (outcount < newcount) return -1; /* to be sure */
1383 } else if (((unsigned char) *p) == TN_IAC) {
1384 *q++ = (char) TN_IAC;
1391 if (appData.debugMode) {
1392 fprintf(debugFP, ">ICS: ");
1393 show_bytes(debugFP, buf, newcount);
1394 fprintf(debugFP, "\n");
1396 outcount = OutputToProcess(pr, buf, newcount, outError);
1397 if (outcount < newcount) return -1; /* to be sure */
1402 read_from_player(isr, closure, message, count, error)
1409 int outError, outCount;
1410 static int gotEof = 0;
1412 /* Pass data read from player on to ICS */
1415 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416 if (outCount < count) {
1417 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1419 } else if (count < 0) {
1420 RemoveInputSource(isr);
1421 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422 } else if (gotEof++ > 0) {
1423 RemoveInputSource(isr);
1424 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1430 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431 SendToICS("date\n");
1432 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1438 char buffer[MSG_SIZ];
1441 va_start(args, format);
1442 vsnprintf(buffer, sizeof(buffer), format, args);
1443 buffer[sizeof(buffer)-1] = '\0';
1452 int count, outCount, outError;
1454 if (icsPR == NULL) return;
1457 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458 if (outCount < count) {
1459 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463 /* This is used for sending logon scripts to the ICS. Sending
1464 without a delay causes problems when using timestamp on ICC
1465 (at least on my machine). */
1467 SendToICSDelayed(s,msdelay)
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 if (appData.debugMode) {
1477 fprintf(debugFP, ">ICS: ");
1478 show_bytes(debugFP, s, count);
1479 fprintf(debugFP, "\n");
1481 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1483 if (outCount < count) {
1484 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1489 /* Remove all highlighting escape sequences in s
1490 Also deletes any suffix starting with '('
1493 StripHighlightAndTitle(s)
1496 static char retbuf[MSG_SIZ];
1499 while (*s != NULLCHAR) {
1500 while (*s == '\033') {
1501 while (*s != NULLCHAR && !isalpha(*s)) s++;
1502 if (*s != NULLCHAR) s++;
1504 while (*s != NULLCHAR && *s != '\033') {
1505 if (*s == '(' || *s == '[') {
1516 /* Remove all highlighting escape sequences in s */
1521 static char retbuf[MSG_SIZ];
1524 while (*s != NULLCHAR) {
1525 while (*s == '\033') {
1526 while (*s != NULLCHAR && !isalpha(*s)) s++;
1527 if (*s != NULLCHAR) s++;
1529 while (*s != NULLCHAR && *s != '\033') {
1537 char *variantNames[] = VARIANT_NAMES;
1542 return variantNames[v];
1546 /* Identify a variant from the strings the chess servers use or the
1547 PGN Variant tag names we use. */
1554 VariantClass v = VariantNormal;
1555 int i, found = FALSE;
1560 /* [HGM] skip over optional board-size prefixes */
1561 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563 while( *e++ != '_');
1566 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1570 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571 if (StrCaseStr(e, variantNames[i])) {
1572 v = (VariantClass) i;
1579 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580 || StrCaseStr(e, "wild/fr")
1581 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582 v = VariantFischeRandom;
1583 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584 (i = 1, p = StrCaseStr(e, "w"))) {
1586 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1593 case 0: /* FICS only, actually */
1595 /* Castling legal even if K starts on d-file */
1596 v = VariantWildCastle;
1601 /* Castling illegal even if K & R happen to start in
1602 normal positions. */
1603 v = VariantNoCastle;
1616 /* Castling legal iff K & R start in normal positions */
1622 /* Special wilds for position setup; unclear what to do here */
1623 v = VariantLoadable;
1626 /* Bizarre ICC game */
1627 v = VariantTwoKings;
1630 v = VariantKriegspiel;
1636 v = VariantFischeRandom;
1639 v = VariantCrazyhouse;
1642 v = VariantBughouse;
1648 /* Not quite the same as FICS suicide! */
1649 v = VariantGiveaway;
1655 v = VariantShatranj;
1658 /* Temporary names for future ICC types. The name *will* change in
1659 the next xboard/WinBoard release after ICC defines it. */
1697 v = VariantCapablanca;
1700 v = VariantKnightmate;
1706 v = VariantCylinder;
1712 v = VariantCapaRandom;
1715 v = VariantBerolina;
1727 /* Found "wild" or "w" in the string but no number;
1728 must assume it's normal chess. */
1732 sprintf(buf, _("Unknown wild type %d"), wnum);
1733 DisplayError(buf, 0);
1739 if (appData.debugMode) {
1740 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741 e, wnum, VariantName(v));
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750 advance *index beyond it, and set leftover_start to the new value of
1751 *index; else return FALSE. If pattern contains the character '*', it
1752 matches any sequence of characters not containing '\r', '\n', or the
1753 character following the '*' (if any), and the matched sequence(s) are
1754 copied into star_match.
1757 looking_at(buf, index, pattern)
1762 char *bufp = &buf[*index], *patternp = pattern;
1764 char *matchp = star_match[0];
1767 if (*patternp == NULLCHAR) {
1768 *index = leftover_start = bufp - buf;
1772 if (*bufp == NULLCHAR) return FALSE;
1773 if (*patternp == '*') {
1774 if (*bufp == *(patternp + 1)) {
1776 matchp = star_match[++star_count];
1780 } else if (*bufp == '\n' || *bufp == '\r') {
1782 if (*patternp == NULLCHAR)
1787 *matchp++ = *bufp++;
1791 if (*patternp != *bufp) return FALSE;
1798 SendToPlayer(data, length)
1802 int error, outCount;
1803 outCount = OutputToProcess(NoProc, data, length, &error);
1804 if (outCount < length) {
1805 DisplayFatalError(_("Error writing to display"), error, 1);
1810 PackHolding(packed, holding)
1822 switch (runlength) {
1833 sprintf(q, "%d", runlength);
1845 /* Telnet protocol requests from the front end */
1847 TelnetRequest(ddww, option)
1848 unsigned char ddww, option;
1850 unsigned char msg[3];
1851 int outCount, outError;
1853 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1855 if (appData.debugMode) {
1856 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1872 sprintf(buf1, "%d", ddww);
1881 sprintf(buf2, "%d", option);
1884 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1889 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1891 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898 if (!appData.icsActive) return;
1899 TelnetRequest(TN_DO, TN_ECHO);
1905 if (!appData.icsActive) return;
1906 TelnetRequest(TN_DONT, TN_ECHO);
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1912 /* put the holdings sent to us by the server on the board holdings area */
1913 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917 if(gameInfo.holdingsWidth < 2) return;
1918 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919 return; // prevent overwriting by pre-board holdings
1921 if( (int)lowestPiece >= BlackPawn ) {
1924 holdingsStartRow = BOARD_HEIGHT-1;
1927 holdingsColumn = BOARD_WIDTH-1;
1928 countsColumn = BOARD_WIDTH-2;
1929 holdingsStartRow = 0;
1933 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934 board[i][holdingsColumn] = EmptySquare;
1935 board[i][countsColumn] = (ChessSquare) 0;
1937 while( (p=*holdings++) != NULLCHAR ) {
1938 piece = CharToPiece( ToUpper(p) );
1939 if(piece == EmptySquare) continue;
1940 /*j = (int) piece - (int) WhitePawn;*/
1941 j = PieceToNumber(piece);
1942 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943 if(j < 0) continue; /* should not happen */
1944 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946 board[holdingsStartRow+j*direction][countsColumn]++;
1952 VariantSwitch(Board board, VariantClass newVariant)
1954 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957 startedFromPositionFile = FALSE;
1958 if(gameInfo.variant == newVariant) return;
1960 /* [HGM] This routine is called each time an assignment is made to
1961 * gameInfo.variant during a game, to make sure the board sizes
1962 * are set to match the new variant. If that means adding or deleting
1963 * holdings, we shift the playing board accordingly
1964 * This kludge is needed because in ICS observe mode, we get boards
1965 * of an ongoing game without knowing the variant, and learn about the
1966 * latter only later. This can be because of the move list we requested,
1967 * in which case the game history is refilled from the beginning anyway,
1968 * but also when receiving holdings of a crazyhouse game. In the latter
1969 * case we want to add those holdings to the already received position.
1973 if (appData.debugMode) {
1974 fprintf(debugFP, "Switch board from %s to %s\n",
1975 VariantName(gameInfo.variant), VariantName(newVariant));
1976 setbuf(debugFP, NULL);
1978 shuffleOpenings = 0; /* [HGM] shuffle */
1979 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983 newWidth = 9; newHeight = 9;
1984 gameInfo.holdingsSize = 7;
1985 case VariantBughouse:
1986 case VariantCrazyhouse:
1987 newHoldingsWidth = 2; break;
1991 newHoldingsWidth = 2;
1992 gameInfo.holdingsSize = 8;
1995 case VariantCapablanca:
1996 case VariantCapaRandom:
1999 newHoldingsWidth = gameInfo.holdingsSize = 0;
2002 if(newWidth != gameInfo.boardWidth ||
2003 newHeight != gameInfo.boardHeight ||
2004 newHoldingsWidth != gameInfo.holdingsWidth ) {
2006 /* shift position to new playing area, if needed */
2007 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008 for(i=0; i<BOARD_HEIGHT; i++)
2009 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2012 for(i=0; i<newHeight; i++) {
2013 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2016 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017 for(i=0; i<BOARD_HEIGHT; i++)
2018 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022 gameInfo.boardWidth = newWidth;
2023 gameInfo.boardHeight = newHeight;
2024 gameInfo.holdingsWidth = newHoldingsWidth;
2025 gameInfo.variant = newVariant;
2026 InitDrawingSizes(-2, 0);
2027 } else gameInfo.variant = newVariant;
2028 CopyBoard(oldBoard, board); // remember correctly formatted board
2029 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2030 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2033 static int loggedOn = FALSE;
2035 /*-- Game start info cache: --*/
2037 char gs_kind[MSG_SIZ];
2038 static char player1Name[128] = "";
2039 static char player2Name[128] = "";
2040 static char cont_seq[] = "\n\\ ";
2041 static int player1Rating = -1;
2042 static int player2Rating = -1;
2043 /*----------------------------*/
2045 ColorClass curColor = ColorNormal;
2046 int suppressKibitz = 0;
2049 read_from_ics(isr, closure, data, count, error)
2056 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2057 #define STARTED_NONE 0
2058 #define STARTED_MOVES 1
2059 #define STARTED_BOARD 2
2060 #define STARTED_OBSERVE 3
2061 #define STARTED_HOLDINGS 4
2062 #define STARTED_CHATTER 5
2063 #define STARTED_COMMENT 6
2064 #define STARTED_MOVES_NOHIDE 7
2066 static int started = STARTED_NONE;
2067 static char parse[20000];
2068 static int parse_pos = 0;
2069 static char buf[BUF_SIZE + 1];
2070 static int firstTime = TRUE, intfSet = FALSE;
2071 static ColorClass prevColor = ColorNormal;
2072 static int savingComment = FALSE;
2073 static int cmatch = 0; // continuation sequence match
2080 int backup; /* [DM] For zippy color lines */
2082 char talker[MSG_SIZ]; // [HGM] chat
2085 if (appData.debugMode) {
2087 fprintf(debugFP, "<ICS: ");
2088 show_bytes(debugFP, data, count);
2089 fprintf(debugFP, "\n");
2093 if (appData.debugMode) { int f = forwardMostMove;
2094 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2095 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2096 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2099 /* If last read ended with a partial line that we couldn't parse,
2100 prepend it to the new read and try again. */
2101 if (leftover_len > 0) {
2102 for (i=0; i<leftover_len; i++)
2103 buf[i] = buf[leftover_start + i];
2106 /* copy new characters into the buffer */
2107 bp = buf + leftover_len;
2108 buf_len=leftover_len;
2109 for (i=0; i<count; i++)
2112 if (data[i] == '\r')
2115 // join lines split by ICS?
2116 if (!appData.noJoin)
2119 Joining just consists of finding matches against the
2120 continuation sequence, and discarding that sequence
2121 if found instead of copying it. So, until a match
2122 fails, there's nothing to do since it might be the
2123 complete sequence, and thus, something we don't want
2126 if (data[i] == cont_seq[cmatch])
2129 if (cmatch == strlen(cont_seq))
2131 cmatch = 0; // complete match. just reset the counter
2134 it's possible for the ICS to not include the space
2135 at the end of the last word, making our [correct]
2136 join operation fuse two separate words. the server
2137 does this when the space occurs at the width setting.
2139 if (!buf_len || buf[buf_len-1] != ' ')
2150 match failed, so we have to copy what matched before
2151 falling through and copying this character. In reality,
2152 this will only ever be just the newline character, but
2153 it doesn't hurt to be precise.
2155 strncpy(bp, cont_seq, cmatch);
2167 buf[buf_len] = NULLCHAR;
2168 next_out = leftover_len;
2172 while (i < buf_len) {
2173 /* Deal with part of the TELNET option negotiation
2174 protocol. We refuse to do anything beyond the
2175 defaults, except that we allow the WILL ECHO option,
2176 which ICS uses to turn off password echoing when we are
2177 directly connected to it. We reject this option
2178 if localLineEditing mode is on (always on in xboard)
2179 and we are talking to port 23, which might be a real
2180 telnet server that will try to keep WILL ECHO on permanently.
2182 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2183 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2184 unsigned char option;
2186 switch ((unsigned char) buf[++i]) {
2188 if (appData.debugMode)
2189 fprintf(debugFP, "\n<WILL ");
2190 switch (option = (unsigned char) buf[++i]) {
2192 if (appData.debugMode)
2193 fprintf(debugFP, "ECHO ");
2194 /* Reply only if this is a change, according
2195 to the protocol rules. */
2196 if (remoteEchoOption) break;
2197 if (appData.localLineEditing &&
2198 atoi(appData.icsPort) == TN_PORT) {
2199 TelnetRequest(TN_DONT, TN_ECHO);
2202 TelnetRequest(TN_DO, TN_ECHO);
2203 remoteEchoOption = TRUE;
2207 if (appData.debugMode)
2208 fprintf(debugFP, "%d ", option);
2209 /* Whatever this is, we don't want it. */
2210 TelnetRequest(TN_DONT, option);
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<WONT ");
2217 switch (option = (unsigned char) buf[++i]) {
2219 if (appData.debugMode)
2220 fprintf(debugFP, "ECHO ");
2221 /* Reply only if this is a change, according
2222 to the protocol rules. */
2223 if (!remoteEchoOption) break;
2225 TelnetRequest(TN_DONT, TN_ECHO);
2226 remoteEchoOption = FALSE;
2229 if (appData.debugMode)
2230 fprintf(debugFP, "%d ", (unsigned char) option);
2231 /* Whatever this is, it must already be turned
2232 off, because we never agree to turn on
2233 anything non-default, so according to the
2234 protocol rules, we don't reply. */
2239 if (appData.debugMode)
2240 fprintf(debugFP, "\n<DO ");
2241 switch (option = (unsigned char) buf[++i]) {
2243 /* Whatever this is, we refuse to do it. */
2244 if (appData.debugMode)
2245 fprintf(debugFP, "%d ", option);
2246 TelnetRequest(TN_WONT, option);
2251 if (appData.debugMode)
2252 fprintf(debugFP, "\n<DONT ");
2253 switch (option = (unsigned char) buf[++i]) {
2255 if (appData.debugMode)
2256 fprintf(debugFP, "%d ", option);
2257 /* Whatever this is, we are already not doing
2258 it, because we never agree to do anything
2259 non-default, so according to the protocol
2260 rules, we don't reply. */
2265 if (appData.debugMode)
2266 fprintf(debugFP, "\n<IAC ");
2267 /* Doubled IAC; pass it through */
2271 if (appData.debugMode)
2272 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2273 /* Drop all other telnet commands on the floor */
2276 if (oldi > next_out)
2277 SendToPlayer(&buf[next_out], oldi - next_out);
2283 /* OK, this at least will *usually* work */
2284 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2288 if (loggedOn && !intfSet) {
2289 if (ics_type == ICS_ICC) {
2291 "/set-quietly interface %s\n/set-quietly style 12\n",
2293 } else if (ics_type == ICS_CHESSNET) {
2294 sprintf(str, "/style 12\n");
2296 strcpy(str, "alias $ @\n$set interface ");
2297 strcat(str, programVersion);
2298 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2300 strcat(str, "$iset nohighlight 1\n");
2302 strcat(str, "$iset lock 1\n$style 12\n");
2305 NotifyFrontendLogin();
2309 if (started == STARTED_COMMENT) {
2310 /* Accumulate characters in comment */
2311 parse[parse_pos++] = buf[i];
2312 if (buf[i] == '\n') {
2313 parse[parse_pos] = NULLCHAR;
2314 if(chattingPartner>=0) {
2316 sprintf(mess, "%s%s", talker, parse);
2317 OutputChatMessage(chattingPartner, mess);
2318 chattingPartner = -1;
2320 if(!suppressKibitz) // [HGM] kibitz
2321 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2322 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2323 int nrDigit = 0, nrAlph = 0, i;
2324 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2325 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2326 parse[parse_pos] = NULLCHAR;
2327 // try to be smart: if it does not look like search info, it should go to
2328 // ICS interaction window after all, not to engine-output window.
2329 for(i=0; i<parse_pos; i++) { // count letters and digits
2330 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2331 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2332 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2334 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2335 int depth=0; float score;
2336 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2337 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2338 pvInfoList[forwardMostMove-1].depth = depth;
2339 pvInfoList[forwardMostMove-1].score = 100*score;
2341 OutputKibitz(suppressKibitz, parse);
2344 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2345 SendToPlayer(tmp, strlen(tmp));
2348 started = STARTED_NONE;
2350 /* Don't match patterns against characters in chatter */
2355 if (started == STARTED_CHATTER) {
2356 if (buf[i] != '\n') {
2357 /* Don't match patterns against characters in chatter */
2361 started = STARTED_NONE;
2364 /* Kludge to deal with rcmd protocol */
2365 if (firstTime && looking_at(buf, &i, "\001*")) {
2366 DisplayFatalError(&buf[1], 0, 1);
2372 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2375 if (appData.debugMode)
2376 fprintf(debugFP, "ics_type %d\n", ics_type);
2379 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2380 ics_type = ICS_FICS;
2382 if (appData.debugMode)
2383 fprintf(debugFP, "ics_type %d\n", ics_type);
2386 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2387 ics_type = ICS_CHESSNET;
2389 if (appData.debugMode)
2390 fprintf(debugFP, "ics_type %d\n", ics_type);
2395 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2396 looking_at(buf, &i, "Logging you in as \"*\"") ||
2397 looking_at(buf, &i, "will be \"*\""))) {
2398 strcpy(ics_handle, star_match[0]);
2402 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2404 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2405 DisplayIcsInteractionTitle(buf);
2406 have_set_title = TRUE;
2409 /* skip finger notes */
2410 if (started == STARTED_NONE &&
2411 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2412 (buf[i] == '1' && buf[i+1] == '0')) &&
2413 buf[i+2] == ':' && buf[i+3] == ' ') {
2414 started = STARTED_CHATTER;
2419 /* skip formula vars */
2420 if (started == STARTED_NONE &&
2421 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2422 started = STARTED_CHATTER;
2428 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2429 if (appData.autoKibitz && started == STARTED_NONE &&
2430 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2431 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2432 if(looking_at(buf, &i, "* kibitzes: ") &&
2433 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2434 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2435 suppressKibitz = TRUE;
2436 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2437 && (gameMode == IcsPlayingWhite)) ||
2438 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2439 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2440 started = STARTED_CHATTER; // own kibitz we simply discard
2442 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2443 parse_pos = 0; parse[0] = NULLCHAR;
2444 savingComment = TRUE;
2445 suppressKibitz = gameMode != IcsObserving ? 2 :
2446 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2450 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2451 started = STARTED_CHATTER;
2452 suppressKibitz = TRUE;
2454 } // [HGM] kibitz: end of patch
2456 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2458 // [HGM] chat: intercept tells by users for which we have an open chat window
2460 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2461 looking_at(buf, &i, "* whispers:") ||
2462 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2463 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2465 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2466 chattingPartner = -1;
2468 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2469 for(p=0; p<MAX_CHAT; p++) {
2470 if(channel == atoi(chatPartner[p])) {
2471 talker[0] = '['; strcat(talker, "]");
2472 chattingPartner = p; break;
2475 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2476 for(p=0; p<MAX_CHAT; p++) {
2477 if(!strcmp("WHISPER", chatPartner[p])) {
2478 talker[0] = '['; strcat(talker, "]");
2479 chattingPartner = p; break;
2482 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2483 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2485 chattingPartner = p; break;
2487 if(chattingPartner<0) i = oldi; else {
2488 started = STARTED_COMMENT;
2489 parse_pos = 0; parse[0] = NULLCHAR;
2490 savingComment = TRUE;
2491 suppressKibitz = TRUE;
2493 } // [HGM] chat: end of patch
2495 if (appData.zippyTalk || appData.zippyPlay) {
2496 /* [DM] Backup address for color zippy lines */
2500 if (loggedOn == TRUE)
2501 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2502 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2504 if (ZippyControl(buf, &i) ||
2505 ZippyConverse(buf, &i) ||
2506 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2508 if (!appData.colorize) continue;
2512 } // [DM] 'else { ' deleted
2514 /* Regular tells and says */
2515 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2516 looking_at(buf, &i, "* (your partner) tells you: ") ||
2517 looking_at(buf, &i, "* says: ") ||
2518 /* Don't color "message" or "messages" output */
2519 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2520 looking_at(buf, &i, "*. * at *:*: ") ||
2521 looking_at(buf, &i, "--* (*:*): ") ||
2522 /* Message notifications (same color as tells) */
2523 looking_at(buf, &i, "* has left a message ") ||
2524 looking_at(buf, &i, "* just sent you a message:\n") ||
2525 /* Whispers and kibitzes */
2526 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2527 looking_at(buf, &i, "* kibitzes: ") ||
2529 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2531 if (tkind == 1 && strchr(star_match[0], ':')) {
2532 /* Avoid "tells you:" spoofs in channels */
2535 if (star_match[0][0] == NULLCHAR ||
2536 strchr(star_match[0], ' ') ||
2537 (tkind == 3 && strchr(star_match[1], ' '))) {
2538 /* Reject bogus matches */
2541 if (appData.colorize) {
2542 if (oldi > next_out) {
2543 SendToPlayer(&buf[next_out], oldi - next_out);
2548 Colorize(ColorTell, FALSE);
2549 curColor = ColorTell;
2552 Colorize(ColorKibitz, FALSE);
2553 curColor = ColorKibitz;
2556 p = strrchr(star_match[1], '(');
2563 Colorize(ColorChannel1, FALSE);
2564 curColor = ColorChannel1;
2566 Colorize(ColorChannel, FALSE);
2567 curColor = ColorChannel;
2571 curColor = ColorNormal;
2575 if (started == STARTED_NONE && appData.autoComment &&
2576 (gameMode == IcsObserving ||
2577 gameMode == IcsPlayingWhite ||
2578 gameMode == IcsPlayingBlack)) {
2579 parse_pos = i - oldi;
2580 memcpy(parse, &buf[oldi], parse_pos);
2581 parse[parse_pos] = NULLCHAR;
2582 started = STARTED_COMMENT;
2583 savingComment = TRUE;
2585 started = STARTED_CHATTER;
2586 savingComment = FALSE;
2593 if (looking_at(buf, &i, "* s-shouts: ") ||
2594 looking_at(buf, &i, "* c-shouts: ")) {
2595 if (appData.colorize) {
2596 if (oldi > next_out) {
2597 SendToPlayer(&buf[next_out], oldi - next_out);
2600 Colorize(ColorSShout, FALSE);
2601 curColor = ColorSShout;
2604 started = STARTED_CHATTER;
2608 if (looking_at(buf, &i, "--->")) {
2613 if (looking_at(buf, &i, "* shouts: ") ||
2614 looking_at(buf, &i, "--> ")) {
2615 if (appData.colorize) {
2616 if (oldi > next_out) {
2617 SendToPlayer(&buf[next_out], oldi - next_out);
2620 Colorize(ColorShout, FALSE);
2621 curColor = ColorShout;
2624 started = STARTED_CHATTER;
2628 if (looking_at( buf, &i, "Challenge:")) {
2629 if (appData.colorize) {
2630 if (oldi > next_out) {
2631 SendToPlayer(&buf[next_out], oldi - next_out);
2634 Colorize(ColorChallenge, FALSE);
2635 curColor = ColorChallenge;
2641 if (looking_at(buf, &i, "* offers you") ||
2642 looking_at(buf, &i, "* offers to be") ||
2643 looking_at(buf, &i, "* would like to") ||
2644 looking_at(buf, &i, "* requests to") ||
2645 looking_at(buf, &i, "Your opponent offers") ||
2646 looking_at(buf, &i, "Your opponent requests")) {
2648 if (appData.colorize) {
2649 if (oldi > next_out) {
2650 SendToPlayer(&buf[next_out], oldi - next_out);
2653 Colorize(ColorRequest, FALSE);
2654 curColor = ColorRequest;
2659 if (looking_at(buf, &i, "* (*) seeking")) {
2660 if (appData.colorize) {
2661 if (oldi > next_out) {
2662 SendToPlayer(&buf[next_out], oldi - next_out);
2665 Colorize(ColorSeek, FALSE);
2666 curColor = ColorSeek;
2671 if (looking_at(buf, &i, "\\ ")) {
2672 if (prevColor != ColorNormal) {
2673 if (oldi > next_out) {
2674 SendToPlayer(&buf[next_out], oldi - next_out);
2677 Colorize(prevColor, TRUE);
2678 curColor = prevColor;
2680 if (savingComment) {
2681 parse_pos = i - oldi;
2682 memcpy(parse, &buf[oldi], parse_pos);
2683 parse[parse_pos] = NULLCHAR;
2684 started = STARTED_COMMENT;
2686 started = STARTED_CHATTER;
2691 if (looking_at(buf, &i, "Black Strength :") ||
2692 looking_at(buf, &i, "<<< style 10 board >>>") ||
2693 looking_at(buf, &i, "<10>") ||
2694 looking_at(buf, &i, "#@#")) {
2695 /* Wrong board style */
2697 SendToICS(ics_prefix);
2698 SendToICS("set style 12\n");
2699 SendToICS(ics_prefix);
2700 SendToICS("refresh\n");
2704 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2706 have_sent_ICS_logon = 1;
2710 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2711 (looking_at(buf, &i, "\n<12> ") ||
2712 looking_at(buf, &i, "<12> "))) {
2714 if (oldi > next_out) {
2715 SendToPlayer(&buf[next_out], oldi - next_out);
2718 started = STARTED_BOARD;
2723 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2724 looking_at(buf, &i, "<b1> ")) {
2725 if (oldi > next_out) {
2726 SendToPlayer(&buf[next_out], oldi - next_out);
2729 started = STARTED_HOLDINGS;
2734 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2736 /* Header for a move list -- first line */
2738 switch (ics_getting_history) {
2742 case BeginningOfGame:
2743 /* User typed "moves" or "oldmoves" while we
2744 were idle. Pretend we asked for these
2745 moves and soak them up so user can step
2746 through them and/or save them.
2749 gameMode = IcsObserving;
2752 ics_getting_history = H_GOT_UNREQ_HEADER;
2754 case EditGame: /*?*/
2755 case EditPosition: /*?*/
2756 /* Should above feature work in these modes too? */
2757 /* For now it doesn't */
2758 ics_getting_history = H_GOT_UNWANTED_HEADER;
2761 ics_getting_history = H_GOT_UNWANTED_HEADER;
2766 /* Is this the right one? */
2767 if (gameInfo.white && gameInfo.black &&
2768 strcmp(gameInfo.white, star_match[0]) == 0 &&
2769 strcmp(gameInfo.black, star_match[2]) == 0) {
2771 ics_getting_history = H_GOT_REQ_HEADER;
2774 case H_GOT_REQ_HEADER:
2775 case H_GOT_UNREQ_HEADER:
2776 case H_GOT_UNWANTED_HEADER:
2777 case H_GETTING_MOVES:
2778 /* Should not happen */
2779 DisplayError(_("Error gathering move list: two headers"), 0);
2780 ics_getting_history = H_FALSE;
2784 /* Save player ratings into gameInfo if needed */
2785 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2786 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2787 (gameInfo.whiteRating == -1 ||
2788 gameInfo.blackRating == -1)) {
2790 gameInfo.whiteRating = string_to_rating(star_match[1]);
2791 gameInfo.blackRating = string_to_rating(star_match[3]);
2792 if (appData.debugMode)
2793 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2794 gameInfo.whiteRating, gameInfo.blackRating);
2799 if (looking_at(buf, &i,
2800 "* * match, initial time: * minute*, increment: * second")) {
2801 /* Header for a move list -- second line */
2802 /* Initial board will follow if this is a wild game */
2803 if (gameInfo.event != NULL) free(gameInfo.event);
2804 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2805 gameInfo.event = StrSave(str);
2806 /* [HGM] we switched variant. Translate boards if needed. */
2807 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2811 if (looking_at(buf, &i, "Move ")) {
2812 /* Beginning of a move list */
2813 switch (ics_getting_history) {
2815 /* Normally should not happen */
2816 /* Maybe user hit reset while we were parsing */
2819 /* Happens if we are ignoring a move list that is not
2820 * the one we just requested. Common if the user
2821 * tries to observe two games without turning off
2824 case H_GETTING_MOVES:
2825 /* Should not happen */
2826 DisplayError(_("Error gathering move list: nested"), 0);
2827 ics_getting_history = H_FALSE;
2829 case H_GOT_REQ_HEADER:
2830 ics_getting_history = H_GETTING_MOVES;
2831 started = STARTED_MOVES;
2833 if (oldi > next_out) {
2834 SendToPlayer(&buf[next_out], oldi - next_out);
2837 case H_GOT_UNREQ_HEADER:
2838 ics_getting_history = H_GETTING_MOVES;
2839 started = STARTED_MOVES_NOHIDE;
2842 case H_GOT_UNWANTED_HEADER:
2843 ics_getting_history = H_FALSE;
2849 if (looking_at(buf, &i, "% ") ||
2850 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2851 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2852 savingComment = FALSE;
2855 case STARTED_MOVES_NOHIDE:
2856 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2857 parse[parse_pos + i - oldi] = NULLCHAR;
2858 ParseGameHistory(parse);
2860 if (appData.zippyPlay && first.initDone) {
2861 FeedMovesToProgram(&first, forwardMostMove);
2862 if (gameMode == IcsPlayingWhite) {
2863 if (WhiteOnMove(forwardMostMove)) {
2864 if (first.sendTime) {
2865 if (first.useColors) {
2866 SendToProgram("black\n", &first);
2868 SendTimeRemaining(&first, TRUE);
2870 if (first.useColors) {
2871 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2873 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2874 first.maybeThinking = TRUE;
2876 if (first.usePlayother) {
2877 if (first.sendTime) {
2878 SendTimeRemaining(&first, TRUE);
2880 SendToProgram("playother\n", &first);
2886 } else if (gameMode == IcsPlayingBlack) {
2887 if (!WhiteOnMove(forwardMostMove)) {
2888 if (first.sendTime) {
2889 if (first.useColors) {
2890 SendToProgram("white\n", &first);
2892 SendTimeRemaining(&first, FALSE);
2894 if (first.useColors) {
2895 SendToProgram("black\n", &first);
2897 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2898 first.maybeThinking = TRUE;
2900 if (first.usePlayother) {
2901 if (first.sendTime) {
2902 SendTimeRemaining(&first, FALSE);
2904 SendToProgram("playother\n", &first);
2913 if (gameMode == IcsObserving && ics_gamenum == -1) {
2914 /* Moves came from oldmoves or moves command
2915 while we weren't doing anything else.
2917 currentMove = forwardMostMove;
2918 ClearHighlights();/*!!could figure this out*/
2919 flipView = appData.flipView;
2920 DrawPosition(TRUE, boards[currentMove]);
2921 DisplayBothClocks();
2922 sprintf(str, "%s vs. %s",
2923 gameInfo.white, gameInfo.black);
2927 /* Moves were history of an active game */
2928 if (gameInfo.resultDetails != NULL) {
2929 free(gameInfo.resultDetails);
2930 gameInfo.resultDetails = NULL;
2933 HistorySet(parseList, backwardMostMove,
2934 forwardMostMove, currentMove-1);
2935 DisplayMove(currentMove - 1);
2936 if (started == STARTED_MOVES) next_out = i;
2937 started = STARTED_NONE;
2938 ics_getting_history = H_FALSE;
2941 case STARTED_OBSERVE:
2942 started = STARTED_NONE;
2943 SendToICS(ics_prefix);
2944 SendToICS("refresh\n");
2950 if(bookHit) { // [HGM] book: simulate book reply
2951 static char bookMove[MSG_SIZ]; // a bit generous?
2953 programStats.nodes = programStats.depth = programStats.time =
2954 programStats.score = programStats.got_only_move = 0;
2955 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2957 strcpy(bookMove, "move ");
2958 strcat(bookMove, bookHit);
2959 HandleMachineMove(bookMove, &first);
2964 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2965 started == STARTED_HOLDINGS ||
2966 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2967 /* Accumulate characters in move list or board */
2968 parse[parse_pos++] = buf[i];
2971 /* Start of game messages. Mostly we detect start of game
2972 when the first board image arrives. On some versions
2973 of the ICS, though, we need to do a "refresh" after starting
2974 to observe in order to get the current board right away. */
2975 if (looking_at(buf, &i, "Adding game * to observation list")) {
2976 started = STARTED_OBSERVE;
2980 /* Handle auto-observe */
2981 if (appData.autoObserve &&
2982 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2983 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2985 /* Choose the player that was highlighted, if any. */
2986 if (star_match[0][0] == '\033' ||
2987 star_match[1][0] != '\033') {
2988 player = star_match[0];
2990 player = star_match[2];
2992 sprintf(str, "%sobserve %s\n",
2993 ics_prefix, StripHighlightAndTitle(player));
2996 /* Save ratings from notify string */
2997 strcpy(player1Name, star_match[0]);
2998 player1Rating = string_to_rating(star_match[1]);
2999 strcpy(player2Name, star_match[2]);
3000 player2Rating = string_to_rating(star_match[3]);
3002 if (appData.debugMode)
3004 "Ratings from 'Game notification:' %s %d, %s %d\n",
3005 player1Name, player1Rating,
3006 player2Name, player2Rating);
3011 /* Deal with automatic examine mode after a game,
3012 and with IcsObserving -> IcsExamining transition */
3013 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3014 looking_at(buf, &i, "has made you an examiner of game *")) {
3016 int gamenum = atoi(star_match[0]);
3017 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3018 gamenum == ics_gamenum) {
3019 /* We were already playing or observing this game;
3020 no need to refetch history */
3021 gameMode = IcsExamining;
3023 pauseExamForwardMostMove = forwardMostMove;
3024 } else if (currentMove < forwardMostMove) {
3025 ForwardInner(forwardMostMove);
3028 /* I don't think this case really can happen */
3029 SendToICS(ics_prefix);
3030 SendToICS("refresh\n");
3035 /* Error messages */
3036 // if (ics_user_moved) {
3037 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3038 if (looking_at(buf, &i, "Illegal move") ||
3039 looking_at(buf, &i, "Not a legal move") ||
3040 looking_at(buf, &i, "Your king is in check") ||
3041 looking_at(buf, &i, "It isn't your turn") ||
3042 looking_at(buf, &i, "It is not your move")) {
3044 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3045 currentMove = --forwardMostMove;
3046 DisplayMove(currentMove - 1); /* before DMError */
3047 DrawPosition(FALSE, boards[currentMove]);
3049 DisplayBothClocks();
3051 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3057 if (looking_at(buf, &i, "still have time") ||
3058 looking_at(buf, &i, "not out of time") ||
3059 looking_at(buf, &i, "either player is out of time") ||
3060 looking_at(buf, &i, "has timeseal; checking")) {
3061 /* We must have called his flag a little too soon */
3062 whiteFlag = blackFlag = FALSE;
3066 if (looking_at(buf, &i, "added * seconds to") ||
3067 looking_at(buf, &i, "seconds were added to")) {
3068 /* Update the clocks */
3069 SendToICS(ics_prefix);
3070 SendToICS("refresh\n");
3074 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3075 ics_clock_paused = TRUE;
3080 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3081 ics_clock_paused = FALSE;
3086 /* Grab player ratings from the Creating: message.
3087 Note we have to check for the special case when
3088 the ICS inserts things like [white] or [black]. */
3089 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3090 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3092 0 player 1 name (not necessarily white)
3094 2 empty, white, or black (IGNORED)
3095 3 player 2 name (not necessarily black)
3098 The names/ratings are sorted out when the game
3099 actually starts (below).
3101 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3102 player1Rating = string_to_rating(star_match[1]);
3103 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3104 player2Rating = string_to_rating(star_match[4]);
3106 if (appData.debugMode)
3108 "Ratings from 'Creating:' %s %d, %s %d\n",
3109 player1Name, player1Rating,
3110 player2Name, player2Rating);
3115 /* Improved generic start/end-of-game messages */
3116 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3117 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3118 /* If tkind == 0: */
3119 /* star_match[0] is the game number */
3120 /* [1] is the white player's name */
3121 /* [2] is the black player's name */
3122 /* For end-of-game: */
3123 /* [3] is the reason for the game end */
3124 /* [4] is a PGN end game-token, preceded by " " */
3125 /* For start-of-game: */
3126 /* [3] begins with "Creating" or "Continuing" */
3127 /* [4] is " *" or empty (don't care). */
3128 int gamenum = atoi(star_match[0]);
3129 char *whitename, *blackname, *why, *endtoken;
3130 ChessMove endtype = (ChessMove) 0;
3133 whitename = star_match[1];
3134 blackname = star_match[2];
3135 why = star_match[3];
3136 endtoken = star_match[4];
3138 whitename = star_match[1];
3139 blackname = star_match[3];
3140 why = star_match[5];
3141 endtoken = star_match[6];
3144 /* Game start messages */
3145 if (strncmp(why, "Creating ", 9) == 0 ||
3146 strncmp(why, "Continuing ", 11) == 0) {
3147 gs_gamenum = gamenum;
3148 strcpy(gs_kind, strchr(why, ' ') + 1);
3150 if (appData.zippyPlay) {
3151 ZippyGameStart(whitename, blackname);
3157 /* Game end messages */
3158 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3159 ics_gamenum != gamenum) {
3162 while (endtoken[0] == ' ') endtoken++;
3163 switch (endtoken[0]) {
3166 endtype = GameUnfinished;
3169 endtype = BlackWins;
3172 if (endtoken[1] == '/')
3173 endtype = GameIsDrawn;
3175 endtype = WhiteWins;
3178 GameEnds(endtype, why, GE_ICS);
3180 if (appData.zippyPlay && first.initDone) {
3181 ZippyGameEnd(endtype, why);
3182 if (first.pr == NULL) {
3183 /* Start the next process early so that we'll
3184 be ready for the next challenge */
3185 StartChessProgram(&first);
3187 /* Send "new" early, in case this command takes
3188 a long time to finish, so that we'll be ready
3189 for the next challenge. */
3190 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3197 if (looking_at(buf, &i, "Removing game * from observation") ||
3198 looking_at(buf, &i, "no longer observing game *") ||
3199 looking_at(buf, &i, "Game * (*) has no examiners")) {
3200 if (gameMode == IcsObserving &&
3201 atoi(star_match[0]) == ics_gamenum)
3203 /* icsEngineAnalyze */
3204 if (appData.icsEngineAnalyze) {
3211 ics_user_moved = FALSE;
3216 if (looking_at(buf, &i, "no longer examining game *")) {
3217 if (gameMode == IcsExamining &&
3218 atoi(star_match[0]) == ics_gamenum)
3222 ics_user_moved = FALSE;
3227 /* Advance leftover_start past any newlines we find,
3228 so only partial lines can get reparsed */
3229 if (looking_at(buf, &i, "\n")) {
3230 prevColor = curColor;
3231 if (curColor != ColorNormal) {
3232 if (oldi > next_out) {
3233 SendToPlayer(&buf[next_out], oldi - next_out);
3236 Colorize(ColorNormal, FALSE);
3237 curColor = ColorNormal;
3239 if (started == STARTED_BOARD) {
3240 started = STARTED_NONE;
3241 parse[parse_pos] = NULLCHAR;
3242 ParseBoard12(parse);
3245 /* Send premove here */
3246 if (appData.premove) {
3248 if (currentMove == 0 &&
3249 gameMode == IcsPlayingWhite &&
3250 appData.premoveWhite) {
3251 sprintf(str, "%s\n", appData.premoveWhiteText);
3252 if (appData.debugMode)
3253 fprintf(debugFP, "Sending premove:\n");
3255 } else if (currentMove == 1 &&
3256 gameMode == IcsPlayingBlack &&
3257 appData.premoveBlack) {
3258 sprintf(str, "%s\n", appData.premoveBlackText);
3259 if (appData.debugMode)
3260 fprintf(debugFP, "Sending premove:\n");
3262 } else if (gotPremove) {
3264 ClearPremoveHighlights();
3265 if (appData.debugMode)
3266 fprintf(debugFP, "Sending premove:\n");
3267 UserMoveEvent(premoveFromX, premoveFromY,
3268 premoveToX, premoveToY,
3273 /* Usually suppress following prompt */
3274 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3275 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3276 if (looking_at(buf, &i, "*% ")) {
3277 savingComment = FALSE;
3281 } else if (started == STARTED_HOLDINGS) {
3283 char new_piece[MSG_SIZ];
3284 started = STARTED_NONE;
3285 parse[parse_pos] = NULLCHAR;
3286 if (appData.debugMode)
3287 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3288 parse, currentMove);
3289 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3290 gamenum == ics_gamenum) {
3291 if (gameInfo.variant == VariantNormal) {
3292 /* [HGM] We seem to switch variant during a game!
3293 * Presumably no holdings were displayed, so we have
3294 * to move the position two files to the right to
3295 * create room for them!
3297 VariantClass newVariant;
3298 switch(gameInfo.boardWidth) { // base guess on board width
3299 case 9: newVariant = VariantShogi; break;
3300 case 10: newVariant = VariantGreat; break;
3301 default: newVariant = VariantCrazyhouse; break;
3303 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3304 /* Get a move list just to see the header, which
3305 will tell us whether this is really bug or zh */
3306 if (ics_getting_history == H_FALSE) {
3307 ics_getting_history = H_REQUESTED;
3308 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3312 new_piece[0] = NULLCHAR;
3313 sscanf(parse, "game %d white [%s black [%s <- %s",
3314 &gamenum, white_holding, black_holding,
3316 white_holding[strlen(white_holding)-1] = NULLCHAR;
3317 black_holding[strlen(black_holding)-1] = NULLCHAR;
3318 /* [HGM] copy holdings to board holdings area */
3319 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3320 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3321 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3323 if (appData.zippyPlay && first.initDone) {
3324 ZippyHoldings(white_holding, black_holding,
3328 if (tinyLayout || smallLayout) {
3329 char wh[16], bh[16];
3330 PackHolding(wh, white_holding);
3331 PackHolding(bh, black_holding);
3332 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3333 gameInfo.white, gameInfo.black);
3335 sprintf(str, "%s [%s] vs. %s [%s]",
3336 gameInfo.white, white_holding,
3337 gameInfo.black, black_holding);
3340 DrawPosition(FALSE, boards[currentMove]);
3343 /* Suppress following prompt */
3344 if (looking_at(buf, &i, "*% ")) {
3345 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3346 savingComment = FALSE;
3353 i++; /* skip unparsed character and loop back */
3356 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3357 started != STARTED_HOLDINGS && i > next_out) {
3358 SendToPlayer(&buf[next_out], i - next_out);
3361 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3363 leftover_len = buf_len - leftover_start;
3364 /* if buffer ends with something we couldn't parse,
3365 reparse it after appending the next read */
3367 } else if (count == 0) {
3368 RemoveInputSource(isr);
3369 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3371 DisplayFatalError(_("Error reading from ICS"), error, 1);
3376 /* Board style 12 looks like this:
3378 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3380 * The "<12> " is stripped before it gets to this routine. The two
3381 * trailing 0's (flip state and clock ticking) are later addition, and
3382 * some chess servers may not have them, or may have only the first.
3383 * Additional trailing fields may be added in the future.
3386 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3388 #define RELATION_OBSERVING_PLAYED 0
3389 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3390 #define RELATION_PLAYING_MYMOVE 1
3391 #define RELATION_PLAYING_NOTMYMOVE -1
3392 #define RELATION_EXAMINING 2
3393 #define RELATION_ISOLATED_BOARD -3
3394 #define RELATION_STARTING_POSITION -4 /* FICS only */
3397 ParseBoard12(string)
3400 GameMode newGameMode;
3401 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3402 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3403 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3404 char to_play, board_chars[200];
3405 char move_str[500], str[500], elapsed_time[500];
3406 char black[32], white[32];
3408 int prevMove = currentMove;
3411 int fromX, fromY, toX, toY;
3413 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3414 char *bookHit = NULL; // [HGM] book
3415 Boolean weird = FALSE, reqFlag = FALSE;
3417 fromX = fromY = toX = toY = -1;
3421 if (appData.debugMode)
3422 fprintf(debugFP, _("Parsing board: %s\n"), string);
3424 move_str[0] = NULLCHAR;
3425 elapsed_time[0] = NULLCHAR;
3426 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3428 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3429 if(string[i] == ' ') { ranks++; files = 0; }
3431 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3434 for(j = 0; j <i; j++) board_chars[j] = string[j];
3435 board_chars[i] = '\0';
3438 n = sscanf(string, PATTERN, &to_play, &double_push,
3439 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3440 &gamenum, white, black, &relation, &basetime, &increment,
3441 &white_stren, &black_stren, &white_time, &black_time,
3442 &moveNum, str, elapsed_time, move_str, &ics_flip,
3446 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3447 DisplayError(str, 0);
3451 /* Convert the move number to internal form */
3452 moveNum = (moveNum - 1) * 2;
3453 if (to_play == 'B') moveNum++;
3454 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3455 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3461 case RELATION_OBSERVING_PLAYED:
3462 case RELATION_OBSERVING_STATIC:
3463 if (gamenum == -1) {
3464 /* Old ICC buglet */
3465 relation = RELATION_OBSERVING_STATIC;
3467 newGameMode = IcsObserving;
3469 case RELATION_PLAYING_MYMOVE:
3470 case RELATION_PLAYING_NOTMYMOVE:
3472 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3473 IcsPlayingWhite : IcsPlayingBlack;
3475 case RELATION_EXAMINING:
3476 newGameMode = IcsExamining;
3478 case RELATION_ISOLATED_BOARD:
3480 /* Just display this board. If user was doing something else,
3481 we will forget about it until the next board comes. */
3482 newGameMode = IcsIdle;
3484 case RELATION_STARTING_POSITION:
3485 newGameMode = gameMode;
3489 /* Modify behavior for initial board display on move listing
3492 switch (ics_getting_history) {
3496 case H_GOT_REQ_HEADER:
3497 case H_GOT_UNREQ_HEADER:
3498 /* This is the initial position of the current game */
3499 gamenum = ics_gamenum;
3500 moveNum = 0; /* old ICS bug workaround */
3501 if (to_play == 'B') {
3502 startedFromSetupPosition = TRUE;
3503 blackPlaysFirst = TRUE;
3505 if (forwardMostMove == 0) forwardMostMove = 1;
3506 if (backwardMostMove == 0) backwardMostMove = 1;
3507 if (currentMove == 0) currentMove = 1;
3509 newGameMode = gameMode;
3510 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3512 case H_GOT_UNWANTED_HEADER:
3513 /* This is an initial board that we don't want */
3515 case H_GETTING_MOVES:
3516 /* Should not happen */
3517 DisplayError(_("Error gathering move list: extra board"), 0);
3518 ics_getting_history = H_FALSE;
3522 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3523 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3524 /* [HGM] We seem to have switched variant unexpectedly
3525 * Try to guess new variant from board size
3527 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3528 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3529 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3530 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3531 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3532 if(!weird) newVariant = VariantNormal;
3533 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3534 /* Get a move list just to see the header, which
3535 will tell us whether this is really bug or zh */
3536 if (ics_getting_history == H_FALSE) {
3537 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3538 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3543 /* Take action if this is the first board of a new game, or of a
3544 different game than is currently being displayed. */
3545 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3546 relation == RELATION_ISOLATED_BOARD) {
3548 /* Forget the old game and get the history (if any) of the new one */
3549 if (gameMode != BeginningOfGame) {
3553 if (appData.autoRaiseBoard) BoardToTop();
3555 if (gamenum == -1) {
3556 newGameMode = IcsIdle;
3557 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3558 appData.getMoveList && !reqFlag) {
3559 /* Need to get game history */
3560 ics_getting_history = H_REQUESTED;
3561 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3565 /* Initially flip the board to have black on the bottom if playing
3566 black or if the ICS flip flag is set, but let the user change
3567 it with the Flip View button. */
3568 flipView = appData.autoFlipView ?
3569 (newGameMode == IcsPlayingBlack) || ics_flip :
3572 /* Done with values from previous mode; copy in new ones */
3573 gameMode = newGameMode;
3575 ics_gamenum = gamenum;
3576 if (gamenum == gs_gamenum) {
3577 int klen = strlen(gs_kind);
3578 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3579 sprintf(str, "ICS %s", gs_kind);
3580 gameInfo.event = StrSave(str);
3582 gameInfo.event = StrSave("ICS game");
3584 gameInfo.site = StrSave(appData.icsHost);
3585 gameInfo.date = PGNDate();
3586 gameInfo.round = StrSave("-");
3587 gameInfo.white = StrSave(white);
3588 gameInfo.black = StrSave(black);
3589 timeControl = basetime * 60 * 1000;
3591 timeIncrement = increment * 1000;
3592 movesPerSession = 0;
3593 gameInfo.timeControl = TimeControlTagValue();
3594 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3595 if (appData.debugMode) {
3596 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3597 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3598 setbuf(debugFP, NULL);
3601 gameInfo.outOfBook = NULL;
3603 /* Do we have the ratings? */
3604 if (strcmp(player1Name, white) == 0 &&
3605 strcmp(player2Name, black) == 0) {
3606 if (appData.debugMode)
3607 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3608 player1Rating, player2Rating);
3609 gameInfo.whiteRating = player1Rating;
3610 gameInfo.blackRating = player2Rating;
3611 } else if (strcmp(player2Name, white) == 0 &&
3612 strcmp(player1Name, black) == 0) {
3613 if (appData.debugMode)
3614 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3615 player2Rating, player1Rating);
3616 gameInfo.whiteRating = player2Rating;
3617 gameInfo.blackRating = player1Rating;
3619 player1Name[0] = player2Name[0] = NULLCHAR;
3621 /* Silence shouts if requested */
3622 if (appData.quietPlay &&
3623 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3624 SendToICS(ics_prefix);
3625 SendToICS("set shout 0\n");
3629 /* Deal with midgame name changes */
3631 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3632 if (gameInfo.white) free(gameInfo.white);
3633 gameInfo.white = StrSave(white);
3635 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3636 if (gameInfo.black) free(gameInfo.black);
3637 gameInfo.black = StrSave(black);
3641 /* Throw away game result if anything actually changes in examine mode */
3642 if (gameMode == IcsExamining && !newGame) {
3643 gameInfo.result = GameUnfinished;
3644 if (gameInfo.resultDetails != NULL) {
3645 free(gameInfo.resultDetails);
3646 gameInfo.resultDetails = NULL;
3650 /* In pausing && IcsExamining mode, we ignore boards coming
3651 in if they are in a different variation than we are. */
3652 if (pauseExamInvalid) return;
3653 if (pausing && gameMode == IcsExamining) {
3654 if (moveNum <= pauseExamForwardMostMove) {
3655 pauseExamInvalid = TRUE;
3656 forwardMostMove = pauseExamForwardMostMove;
3661 if (appData.debugMode) {
3662 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3664 /* Parse the board */
3665 for (k = 0; k < ranks; k++) {
3666 for (j = 0; j < files; j++)
3667 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3668 if(gameInfo.holdingsWidth > 1) {
3669 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3670 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3673 CopyBoard(boards[moveNum], board);
3674 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3676 startedFromSetupPosition =
3677 !CompareBoards(board, initialPosition);
3678 if(startedFromSetupPosition)
3679 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3682 /* [HGM] Set castling rights. Take the outermost Rooks,
3683 to make it also work for FRC opening positions. Note that board12
3684 is really defective for later FRC positions, as it has no way to
3685 indicate which Rook can castle if they are on the same side of King.
3686 For the initial position we grant rights to the outermost Rooks,
3687 and remember thos rights, and we then copy them on positions
3688 later in an FRC game. This means WB might not recognize castlings with
3689 Rooks that have moved back to their original position as illegal,
3690 but in ICS mode that is not its job anyway.
3692 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3693 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3695 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3696 if(board[0][i] == WhiteRook) j = i;
3697 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3698 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3699 if(board[0][i] == WhiteRook) j = i;
3700 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3702 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3705 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3708 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3709 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3710 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3711 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712 if(board[BOARD_HEIGHT-1][k] == bKing)
3713 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3715 r = boards[moveNum][CASTLING][0] = initialRights[0];
3716 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3717 r = boards[moveNum][CASTLING][1] = initialRights[1];
3718 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3719 r = boards[moveNum][CASTLING][3] = initialRights[3];
3720 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3721 r = boards[moveNum][CASTLING][4] = initialRights[4];
3722 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3723 /* wildcastle kludge: always assume King has rights */
3724 r = boards[moveNum][CASTLING][2] = initialRights[2];
3725 r = boards[moveNum][CASTLING][5] = initialRights[5];
3727 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3728 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3731 if (ics_getting_history == H_GOT_REQ_HEADER ||
3732 ics_getting_history == H_GOT_UNREQ_HEADER) {
3733 /* This was an initial position from a move list, not
3734 the current position */
3738 /* Update currentMove and known move number limits */
3739 newMove = newGame || moveNum > forwardMostMove;
3742 forwardMostMove = backwardMostMove = currentMove = moveNum;
3743 if (gameMode == IcsExamining && moveNum == 0) {
3744 /* Workaround for ICS limitation: we are not told the wild
3745 type when starting to examine a game. But if we ask for
3746 the move list, the move list header will tell us */
3747 ics_getting_history = H_REQUESTED;
3748 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3751 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3752 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3754 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3755 /* [HGM] applied this also to an engine that is silently watching */
3756 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3757 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3758 gameInfo.variant == currentlyInitializedVariant) {
3759 takeback = forwardMostMove - moveNum;
3760 for (i = 0; i < takeback; i++) {
3761 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3762 SendToProgram("undo\n", &first);
3767 forwardMostMove = moveNum;
3768 if (!pausing || currentMove > forwardMostMove)
3769 currentMove = forwardMostMove;
3771 /* New part of history that is not contiguous with old part */
3772 if (pausing && gameMode == IcsExamining) {
3773 pauseExamInvalid = TRUE;
3774 forwardMostMove = pauseExamForwardMostMove;
3777 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3779 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3780 // [HGM] when we will receive the move list we now request, it will be
3781 // fed to the engine from the first move on. So if the engine is not
3782 // in the initial position now, bring it there.
3783 InitChessProgram(&first, 0);
3786 ics_getting_history = H_REQUESTED;
3787 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3790 forwardMostMove = backwardMostMove = currentMove = moveNum;
3793 /* Update the clocks */
3794 if (strchr(elapsed_time, '.')) {
3796 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3797 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3799 /* Time is in seconds */
3800 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3801 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3806 if (appData.zippyPlay && newGame &&
3807 gameMode != IcsObserving && gameMode != IcsIdle &&
3808 gameMode != IcsExamining)
3809 ZippyFirstBoard(moveNum, basetime, increment);
3812 /* Put the move on the move list, first converting
3813 to canonical algebraic form. */
3815 if (appData.debugMode) {
3816 if (appData.debugMode) { int f = forwardMostMove;
3817 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3818 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3819 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3821 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3822 fprintf(debugFP, "moveNum = %d\n", moveNum);
3823 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3824 setbuf(debugFP, NULL);
3826 if (moveNum <= backwardMostMove) {
3827 /* We don't know what the board looked like before
3829 strcpy(parseList[moveNum - 1], move_str);
3830 strcat(parseList[moveNum - 1], " ");
3831 strcat(parseList[moveNum - 1], elapsed_time);
3832 moveList[moveNum - 1][0] = NULLCHAR;
3833 } else if (strcmp(move_str, "none") == 0) {
3834 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3835 /* Again, we don't know what the board looked like;
3836 this is really the start of the game. */
3837 parseList[moveNum - 1][0] = NULLCHAR;
3838 moveList[moveNum - 1][0] = NULLCHAR;
3839 backwardMostMove = moveNum;
3840 startedFromSetupPosition = TRUE;
3841 fromX = fromY = toX = toY = -1;
3843 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3844 // So we parse the long-algebraic move string in stead of the SAN move
3845 int valid; char buf[MSG_SIZ], *prom;
3847 // str looks something like "Q/a1-a2"; kill the slash
3849 sprintf(buf, "%c%s", str[0], str+2);
3850 else strcpy(buf, str); // might be castling
3851 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3852 strcat(buf, prom); // long move lacks promo specification!
3853 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3854 if(appData.debugMode)
3855 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3856 strcpy(move_str, buf);
3858 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3859 &fromX, &fromY, &toX, &toY, &promoChar)
3860 || ParseOneMove(buf, moveNum - 1, &moveType,
3861 &fromX, &fromY, &toX, &toY, &promoChar);
3862 // end of long SAN patch
3864 (void) CoordsToAlgebraic(boards[moveNum - 1],
3865 PosFlags(moveNum - 1),
3866 fromY, fromX, toY, toX, promoChar,
3867 parseList[moveNum-1]);
3868 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3874 if(gameInfo.variant != VariantShogi)
3875 strcat(parseList[moveNum - 1], "+");
3878 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3879 strcat(parseList[moveNum - 1], "#");
3882 strcat(parseList[moveNum - 1], " ");
3883 strcat(parseList[moveNum - 1], elapsed_time);
3884 /* currentMoveString is set as a side-effect of ParseOneMove */
3885 strcpy(moveList[moveNum - 1], currentMoveString);
3886 strcat(moveList[moveNum - 1], "\n");
3888 /* Move from ICS was illegal!? Punt. */
3889 if (appData.debugMode) {
3890 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3891 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3893 strcpy(parseList[moveNum - 1], move_str);
3894 strcat(parseList[moveNum - 1], " ");
3895 strcat(parseList[moveNum - 1], elapsed_time);
3896 moveList[moveNum - 1][0] = NULLCHAR;
3897 fromX = fromY = toX = toY = -1;
3900 if (appData.debugMode) {
3901 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3902 setbuf(debugFP, NULL);
3906 /* Send move to chess program (BEFORE animating it). */
3907 if (appData.zippyPlay && !newGame && newMove &&
3908 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3910 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3911 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3912 if (moveList[moveNum - 1][0] == NULLCHAR) {
3913 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3915 DisplayError(str, 0);
3917 if (first.sendTime) {
3918 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3920 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3921 if (firstMove && !bookHit) {
3923 if (first.useColors) {
3924 SendToProgram(gameMode == IcsPlayingWhite ?
3926 "black\ngo\n", &first);
3928 SendToProgram("go\n", &first);
3930 first.maybeThinking = TRUE;
3933 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3934 if (moveList[moveNum - 1][0] == NULLCHAR) {
3935 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3936 DisplayError(str, 0);
3938 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3939 SendMoveToProgram(moveNum - 1, &first);
3946 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3947 /* If move comes from a remote source, animate it. If it
3948 isn't remote, it will have already been animated. */
3949 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3950 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3952 if (!pausing && appData.highlightLastMove) {
3953 SetHighlights(fromX, fromY, toX, toY);
3957 /* Start the clocks */
3958 whiteFlag = blackFlag = FALSE;
3959 appData.clockMode = !(basetime == 0 && increment == 0);
3961 ics_clock_paused = TRUE;
3963 } else if (ticking == 1) {
3964 ics_clock_paused = FALSE;
3966 if (gameMode == IcsIdle ||
3967 relation == RELATION_OBSERVING_STATIC ||
3968 relation == RELATION_EXAMINING ||
3970 DisplayBothClocks();
3974 /* Display opponents and material strengths */
3975 if (gameInfo.variant != VariantBughouse &&
3976 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3977 if (tinyLayout || smallLayout) {
3978 if(gameInfo.variant == VariantNormal)
3979 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3980 gameInfo.white, white_stren, gameInfo.black, black_stren,
3981 basetime, increment);
3983 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3984 gameInfo.white, white_stren, gameInfo.black, black_stren,
3985 basetime, increment, (int) gameInfo.variant);
3987 if(gameInfo.variant == VariantNormal)
3988 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3989 gameInfo.white, white_stren, gameInfo.black, black_stren,
3990 basetime, increment);
3992 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3993 gameInfo.white, white_stren, gameInfo.black, black_stren,
3994 basetime, increment, VariantName(gameInfo.variant));
3997 if (appData.debugMode) {
3998 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4003 /* Display the board */
4004 if (!pausing && !appData.noGUI) {
4006 if (appData.premove)
4008 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4009 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4010 ClearPremoveHighlights();
4012 DrawPosition(FALSE, boards[currentMove]);
4013 DisplayMove(moveNum - 1);
4014 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4015 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4016 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4017 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4021 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4023 if(bookHit) { // [HGM] book: simulate book reply
4024 static char bookMove[MSG_SIZ]; // a bit generous?
4026 programStats.nodes = programStats.depth = programStats.time =
4027 programStats.score = programStats.got_only_move = 0;
4028 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4030 strcpy(bookMove, "move ");
4031 strcat(bookMove, bookHit);
4032 HandleMachineMove(bookMove, &first);
4041 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4042 ics_getting_history = H_REQUESTED;
4043 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4049 AnalysisPeriodicEvent(force)
4052 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4053 && !force) || !appData.periodicUpdates)
4056 /* Send . command to Crafty to collect stats */
4057 SendToProgram(".\n", &first);
4059 /* Don't send another until we get a response (this makes
4060 us stop sending to old Crafty's which don't understand
4061 the "." command (sending illegal cmds resets node count & time,
4062 which looks bad)) */
4063 programStats.ok_to_send = 0;
4066 void ics_update_width(new_width)
4069 ics_printf("set width %d\n", new_width);
4073 SendMoveToProgram(moveNum, cps)
4075 ChessProgramState *cps;
4079 if (cps->useUsermove) {
4080 SendToProgram("usermove ", cps);
4084 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4085 int len = space - parseList[moveNum];
4086 memcpy(buf, parseList[moveNum], len);
4088 buf[len] = NULLCHAR;
4090 sprintf(buf, "%s\n", parseList[moveNum]);
4092 SendToProgram(buf, cps);
4094 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4095 AlphaRank(moveList[moveNum], 4);
4096 SendToProgram(moveList[moveNum], cps);
4097 AlphaRank(moveList[moveNum], 4); // and back
4099 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4100 * the engine. It would be nice to have a better way to identify castle
4102 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4103 && cps->useOOCastle) {
4104 int fromX = moveList[moveNum][0] - AAA;
4105 int fromY = moveList[moveNum][1] - ONE;
4106 int toX = moveList[moveNum][2] - AAA;
4107 int toY = moveList[moveNum][3] - ONE;
4108 if((boards[moveNum][fromY][fromX] == WhiteKing
4109 && boards[moveNum][toY][toX] == WhiteRook)
4110 || (boards[moveNum][fromY][fromX] == BlackKing
4111 && boards[moveNum][toY][toX] == BlackRook)) {
4112 if(toX > fromX) SendToProgram("O-O\n", cps);
4113 else SendToProgram("O-O-O\n", cps);
4115 else SendToProgram(moveList[moveNum], cps);
4117 else SendToProgram(moveList[moveNum], cps);
4118 /* End of additions by Tord */
4121 /* [HGM] setting up the opening has brought engine in force mode! */
4122 /* Send 'go' if we are in a mode where machine should play. */
4123 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4124 (gameMode == TwoMachinesPlay ||
4126 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4128 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4129 SendToProgram("go\n", cps);
4130 if (appData.debugMode) {
4131 fprintf(debugFP, "(extra)\n");
4134 setboardSpoiledMachineBlack = 0;
4138 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4140 int fromX, fromY, toX, toY;
4142 char user_move[MSG_SIZ];
4146 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4147 (int)moveType, fromX, fromY, toX, toY);
4148 DisplayError(user_move + strlen("say "), 0);
4150 case WhiteKingSideCastle:
4151 case BlackKingSideCastle:
4152 case WhiteQueenSideCastleWild:
4153 case BlackQueenSideCastleWild:
4155 case WhiteHSideCastleFR:
4156 case BlackHSideCastleFR:
4158 sprintf(user_move, "o-o\n");
4160 case WhiteQueenSideCastle:
4161 case BlackQueenSideCastle:
4162 case WhiteKingSideCastleWild:
4163 case BlackKingSideCastleWild:
4165 case WhiteASideCastleFR:
4166 case BlackASideCastleFR:
4168 sprintf(user_move, "o-o-o\n");
4170 case WhitePromotionQueen:
4171 case BlackPromotionQueen:
4172 case WhitePromotionRook:
4173 case BlackPromotionRook:
4174 case WhitePromotionBishop:
4175 case BlackPromotionBishop:
4176 case WhitePromotionKnight:
4177 case BlackPromotionKnight:
4178 case WhitePromotionKing:
4179 case BlackPromotionKing:
4180 case WhitePromotionChancellor:
4181 case BlackPromotionChancellor:
4182 case WhitePromotionArchbishop:
4183 case BlackPromotionArchbishop:
4184 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4185 sprintf(user_move, "%c%c%c%c=%c\n",
4186 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4187 PieceToChar(WhiteFerz));
4188 else if(gameInfo.variant == VariantGreat)
4189 sprintf(user_move, "%c%c%c%c=%c\n",
4190 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191 PieceToChar(WhiteMan));
4193 sprintf(user_move, "%c%c%c%c=%c\n",
4194 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195 PieceToChar(PromoPiece(moveType)));
4199 sprintf(user_move, "%c@%c%c\n",
4200 ToUpper(PieceToChar((ChessSquare) fromX)),
4201 AAA + toX, ONE + toY);
4204 case WhiteCapturesEnPassant:
4205 case BlackCapturesEnPassant:
4206 case IllegalMove: /* could be a variant we don't quite understand */
4207 sprintf(user_move, "%c%c%c%c\n",
4208 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4211 SendToICS(user_move);
4212 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4213 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4217 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4222 if (rf == DROP_RANK) {
4223 sprintf(move, "%c@%c%c\n",
4224 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4226 if (promoChar == 'x' || promoChar == NULLCHAR) {
4227 sprintf(move, "%c%c%c%c\n",
4228 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4230 sprintf(move, "%c%c%c%c%c\n",
4231 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4237 ProcessICSInitScript(f)
4242 while (fgets(buf, MSG_SIZ, f)) {
4243 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4250 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4252 AlphaRank(char *move, int n)
4254 // char *p = move, c; int x, y;
4256 if (appData.debugMode) {
4257 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4261 move[2]>='0' && move[2]<='9' &&
4262 move[3]>='a' && move[3]<='x' ) {
4264 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4265 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4267 if(move[0]>='0' && move[0]<='9' &&
4268 move[1]>='a' && move[1]<='x' &&
4269 move[2]>='0' && move[2]<='9' &&
4270 move[3]>='a' && move[3]<='x' ) {
4271 /* input move, Shogi -> normal */
4272 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4273 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4274 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4275 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4278 move[3]>='0' && move[3]<='9' &&
4279 move[2]>='a' && move[2]<='x' ) {
4281 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4282 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4285 move[0]>='a' && move[0]<='x' &&
4286 move[3]>='0' && move[3]<='9' &&
4287 move[2]>='a' && move[2]<='x' ) {
4288 /* output move, normal -> Shogi */
4289 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4290 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4291 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4292 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4293 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4295 if (appData.debugMode) {
4296 fprintf(debugFP, " out = '%s'\n", move);
4300 /* Parser for moves from gnuchess, ICS, or user typein box */
4302 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4305 ChessMove *moveType;
4306 int *fromX, *fromY, *toX, *toY;
4309 if (appData.debugMode) {
4310 fprintf(debugFP, "move to parse: %s\n", move);
4312 *moveType = yylexstr(moveNum, move);
4314 switch (*moveType) {
4315 case WhitePromotionChancellor:
4316 case BlackPromotionChancellor:
4317 case WhitePromotionArchbishop:
4318 case BlackPromotionArchbishop:
4319 case WhitePromotionQueen:
4320 case BlackPromotionQueen:
4321 case WhitePromotionRook:
4322 case BlackPromotionRook:
4323 case WhitePromotionBishop:
4324 case BlackPromotionBishop:
4325 case WhitePromotionKnight:
4326 case BlackPromotionKnight:
4327 case WhitePromotionKing:
4328 case BlackPromotionKing:
4330 case WhiteCapturesEnPassant:
4331 case BlackCapturesEnPassant:
4332 case WhiteKingSideCastle:
4333 case WhiteQueenSideCastle:
4334 case BlackKingSideCastle:
4335 case BlackQueenSideCastle:
4336 case WhiteKingSideCastleWild:
4337 case WhiteQueenSideCastleWild:
4338 case BlackKingSideCastleWild:
4339 case BlackQueenSideCastleWild:
4340 /* Code added by Tord: */
4341 case WhiteHSideCastleFR:
4342 case WhiteASideCastleFR:
4343 case BlackHSideCastleFR:
4344 case BlackASideCastleFR:
4345 /* End of code added by Tord */
4346 case IllegalMove: /* bug or odd chess variant */
4347 *fromX = currentMoveString[0] - AAA;
4348 *fromY = currentMoveString[1] - ONE;
4349 *toX = currentMoveString[2] - AAA;
4350 *toY = currentMoveString[3] - ONE;
4351 *promoChar = currentMoveString[4];
4352 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4353 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4354 if (appData.debugMode) {
4355 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4357 *fromX = *fromY = *toX = *toY = 0;
4360 if (appData.testLegality) {
4361 return (*moveType != IllegalMove);
4363 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4364 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4369 *fromX = *moveType == WhiteDrop ?
4370 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4371 (int) CharToPiece(ToLower(currentMoveString[0]));
4373 *toX = currentMoveString[2] - AAA;
4374 *toY = currentMoveString[3] - ONE;
4375 *promoChar = NULLCHAR;
4379 case ImpossibleMove:
4380 case (ChessMove) 0: /* end of file */
4389 if (appData.debugMode) {
4390 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4393 *fromX = *fromY = *toX = *toY = 0;
4394 *promoChar = NULLCHAR;
4399 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4400 // All positions will have equal probability, but the current method will not provide a unique
4401 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4407 int piecesLeft[(int)BlackPawn];
4408 int seed, nrOfShuffles;
4410 void GetPositionNumber()
4411 { // sets global variable seed
4414 seed = appData.defaultFrcPosition;
4415 if(seed < 0) { // randomize based on time for negative FRC position numbers
4416 for(i=0; i<50; i++) seed += random();
4417 seed = random() ^ random() >> 8 ^ random() << 8;
4418 if(seed<0) seed = -seed;
4422 int put(Board board, int pieceType, int rank, int n, int shade)
4423 // put the piece on the (n-1)-th empty squares of the given shade
4427 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4428 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4429 board[rank][i] = (ChessSquare) pieceType;
4430 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4432 piecesLeft[pieceType]--;
4440 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4441 // calculate where the next piece goes, (any empty square), and put it there
4445 i = seed % squaresLeft[shade];
4446 nrOfShuffles *= squaresLeft[shade];
4447 seed /= squaresLeft[shade];
4448 put(board, pieceType, rank, i, shade);
4451 void AddTwoPieces(Board board, int pieceType, int rank)
4452 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4454 int i, n=squaresLeft[ANY], j=n-1, k;
4456 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4457 i = seed % k; // pick one
4460 while(i >= j) i -= j--;
4461 j = n - 1 - j; i += j;
4462 put(board, pieceType, rank, j, ANY);
4463 put(board, pieceType, rank, i, ANY);
4466 void SetUpShuffle(Board board, int number)
4470 GetPositionNumber(); nrOfShuffles = 1;
4472 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4473 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4474 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4476 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4478 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4479 p = (int) board[0][i];
4480 if(p < (int) BlackPawn) piecesLeft[p] ++;
4481 board[0][i] = EmptySquare;
4484 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4485 // shuffles restricted to allow normal castling put KRR first
4486 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4487 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4488 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4489 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4490 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4491 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4492 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4493 put(board, WhiteRook, 0, 0, ANY);
4494 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4497 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4498 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4499 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4500 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4501 while(piecesLeft[p] >= 2) {
4502 AddOnePiece(board, p, 0, LITE);
4503 AddOnePiece(board, p, 0, DARK);
4505 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4508 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4509 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4510 // but we leave King and Rooks for last, to possibly obey FRC restriction
4511 if(p == (int)WhiteRook) continue;
4512 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4513 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4516 // now everything is placed, except perhaps King (Unicorn) and Rooks
4518 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4519 // Last King gets castling rights
4520 while(piecesLeft[(int)WhiteUnicorn]) {
4521 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4522 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4525 while(piecesLeft[(int)WhiteKing]) {
4526 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4527 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4532 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4533 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4536 // Only Rooks can be left; simply place them all
4537 while(piecesLeft[(int)WhiteRook]) {
4538 i = put(board, WhiteRook, 0, 0, ANY);
4539 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4542 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4544 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4547 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4548 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4551 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4554 int SetCharTable( char *table, const char * map )
4555 /* [HGM] moved here from winboard.c because of its general usefulness */
4556 /* Basically a safe strcpy that uses the last character as King */
4558 int result = FALSE; int NrPieces;
4560 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4561 && NrPieces >= 12 && !(NrPieces&1)) {
4562 int i; /* [HGM] Accept even length from 12 to 34 */
4564 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4565 for( i=0; i<NrPieces/2-1; i++ ) {
4567 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4569 table[(int) WhiteKing] = map[NrPieces/2-1];
4570 table[(int) BlackKing] = map[NrPieces-1];
4578 void Prelude(Board board)
4579 { // [HGM] superchess: random selection of exo-pieces
4580 int i, j, k; ChessSquare p;
4581 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4583 GetPositionNumber(); // use FRC position number
4585 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4586 SetCharTable(pieceToChar, appData.pieceToCharTable);
4587 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4588 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4591 j = seed%4; seed /= 4;
4592 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4593 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4594 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4595 j = seed%3 + (seed%3 >= j); seed /= 3;
4596 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4597 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4598 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4599 j = seed%3; seed /= 3;
4600 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4601 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4602 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4603 j = seed%2 + (seed%2 >= j); seed /= 2;
4604 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4605 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4606 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4607 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4608 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4609 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4610 put(board, exoPieces[0], 0, 0, ANY);
4611 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4615 InitPosition(redraw)
4618 ChessSquare (* pieces)[BOARD_FILES];
4619 int i, j, pawnRow, overrule,
4620 oldx = gameInfo.boardWidth,
4621 oldy = gameInfo.boardHeight,
4622 oldh = gameInfo.holdingsWidth,
4623 oldv = gameInfo.variant;
4625 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4627 /* [AS] Initialize pv info list [HGM] and game status */
4629 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4630 pvInfoList[i].depth = 0;
4631 boards[i][EP_STATUS] = EP_NONE;
4632 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4635 initialRulePlies = 0; /* 50-move counter start */
4637 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4638 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4642 /* [HGM] logic here is completely changed. In stead of full positions */
4643 /* the initialized data only consist of the two backranks. The switch */
4644 /* selects which one we will use, which is than copied to the Board */
4645 /* initialPosition, which for the rest is initialized by Pawns and */
4646 /* empty squares. This initial position is then copied to boards[0], */
4647 /* possibly after shuffling, so that it remains available. */
4649 gameInfo.holdingsWidth = 0; /* default board sizes */
4650 gameInfo.boardWidth = 8;
4651 gameInfo.boardHeight = 8;
4652 gameInfo.holdingsSize = 0;
4653 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4654 for(i=0; i<BOARD_FILES-2; i++)
4655 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4656 initialPosition[EP_STATUS] = EP_NONE;
4657 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4659 switch (gameInfo.variant) {
4660 case VariantFischeRandom:
4661 shuffleOpenings = TRUE;
4665 case VariantShatranj:
4666 pieces = ShatranjArray;
4667 nrCastlingRights = 0;
4668 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4670 case VariantTwoKings:
4671 pieces = twoKingsArray;
4673 case VariantCapaRandom:
4674 shuffleOpenings = TRUE;
4675 case VariantCapablanca:
4676 pieces = CapablancaArray;
4677 gameInfo.boardWidth = 10;
4678 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4681 pieces = GothicArray;
4682 gameInfo.boardWidth = 10;
4683 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4686 pieces = JanusArray;
4687 gameInfo.boardWidth = 10;
4688 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4689 nrCastlingRights = 6;
4690 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4691 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4692 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4693 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4694 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4695 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4698 pieces = FalconArray;
4699 gameInfo.boardWidth = 10;
4700 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4702 case VariantXiangqi:
4703 pieces = XiangqiArray;
4704 gameInfo.boardWidth = 9;
4705 gameInfo.boardHeight = 10;
4706 nrCastlingRights = 0;
4707 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4710 pieces = ShogiArray;
4711 gameInfo.boardWidth = 9;
4712 gameInfo.boardHeight = 9;
4713 gameInfo.holdingsSize = 7;
4714 nrCastlingRights = 0;
4715 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4717 case VariantCourier:
4718 pieces = CourierArray;
4719 gameInfo.boardWidth = 12;
4720 nrCastlingRights = 0;
4721 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4723 case VariantKnightmate:
4724 pieces = KnightmateArray;
4725 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4728 pieces = fairyArray;
4729 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4732 pieces = GreatArray;
4733 gameInfo.boardWidth = 10;
4734 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4735 gameInfo.holdingsSize = 8;
4739 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4740 gameInfo.holdingsSize = 8;
4741 startedFromSetupPosition = TRUE;
4743 case VariantCrazyhouse:
4744 case VariantBughouse:
4746 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4747 gameInfo.holdingsSize = 5;
4749 case VariantWildCastle:
4751 /* !!?shuffle with kings guaranteed to be on d or e file */
4752 shuffleOpenings = 1;
4754 case VariantNoCastle:
4756 nrCastlingRights = 0;
4757 /* !!?unconstrained back-rank shuffle */
4758 shuffleOpenings = 1;
4763 if(appData.NrFiles >= 0) {
4764 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4765 gameInfo.boardWidth = appData.NrFiles;
4767 if(appData.NrRanks >= 0) {
4768 gameInfo.boardHeight = appData.NrRanks;
4770 if(appData.holdingsSize >= 0) {
4771 i = appData.holdingsSize;
4772 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4773 gameInfo.holdingsSize = i;
4775 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4776 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4777 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4779 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4780 if(pawnRow < 1) pawnRow = 1;
4782 /* User pieceToChar list overrules defaults */
4783 if(appData.pieceToCharTable != NULL)
4784 SetCharTable(pieceToChar, appData.pieceToCharTable);
4786 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4788 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4789 s = (ChessSquare) 0; /* account holding counts in guard band */
4790 for( i=0; i<BOARD_HEIGHT; i++ )
4791 initialPosition[i][j] = s;
4793 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4794 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4795 initialPosition[pawnRow][j] = WhitePawn;
4796 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4797 if(gameInfo.variant == VariantXiangqi) {
4799 initialPosition[pawnRow][j] =
4800 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4801 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4802 initialPosition[2][j] = WhiteCannon;
4803 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4807 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4809 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4812 initialPosition[1][j] = WhiteBishop;
4813 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4815 initialPosition[1][j] = WhiteRook;
4816 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4819 if( nrCastlingRights == -1) {
4820 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4821 /* This sets default castling rights from none to normal corners */
4822 /* Variants with other castling rights must set them themselves above */
4823 nrCastlingRights = 6;
4825 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4826 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4827 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4828 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4829 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4830 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4833 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4834 if(gameInfo.variant == VariantGreat) { // promotion commoners
4835 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4836 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4837 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4838 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4840 if (appData.debugMode) {
4841 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4843 if(shuffleOpenings) {
4844 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4845 startedFromSetupPosition = TRUE;
4847 if(startedFromPositionFile) {
4848 /* [HGM] loadPos: use PositionFile for every new game */
4849 CopyBoard(initialPosition, filePosition);
4850 for(i=0; i<nrCastlingRights; i++)
4851 initialRights[i] = filePosition[CASTLING][i];
4852 startedFromSetupPosition = TRUE;
4855 CopyBoard(boards[0], initialPosition);
4857 if(oldx != gameInfo.boardWidth ||
4858 oldy != gameInfo.boardHeight ||
4859 oldh != gameInfo.holdingsWidth
4861 || oldv == VariantGothic || // For licensing popups
4862 gameInfo.variant == VariantGothic
4865 || oldv == VariantFalcon ||
4866 gameInfo.variant == VariantFalcon
4869 InitDrawingSizes(-2 ,0);
4872 DrawPosition(TRUE, boards[currentMove]);
4876 SendBoard(cps, moveNum)
4877 ChessProgramState *cps;
4880 char message[MSG_SIZ];
4882 if (cps->useSetboard) {
4883 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4884 sprintf(message, "setboard %s\n", fen);
4885 SendToProgram(message, cps);
4891 /* Kludge to set black to move, avoiding the troublesome and now
4892 * deprecated "black" command.
4894 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4896 SendToProgram("edit\n", cps);
4897 SendToProgram("#\n", cps);
4898 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4899 bp = &boards[moveNum][i][BOARD_LEFT];
4900 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4901 if ((int) *bp < (int) BlackPawn) {
4902 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4904 if(message[0] == '+' || message[0] == '~') {
4905 sprintf(message, "%c%c%c+\n",
4906 PieceToChar((ChessSquare)(DEMOTED *bp)),
4909 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4910 message[1] = BOARD_RGHT - 1 - j + '1';
4911 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4913 SendToProgram(message, cps);
4918 SendToProgram("c\n", cps);
4919 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4920 bp = &boards[moveNum][i][BOARD_LEFT];
4921 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4922 if (((int) *bp != (int) EmptySquare)
4923 && ((int) *bp >= (int) BlackPawn)) {
4924 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4926 if(message[0] == '+' || message[0] == '~') {
4927 sprintf(message, "%c%c%c+\n",
4928 PieceToChar((ChessSquare)(DEMOTED *bp)),
4931 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4932 message[1] = BOARD_RGHT - 1 - j + '1';
4933 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4935 SendToProgram(message, cps);
4940 SendToProgram(".\n", cps);
4942 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4946 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4948 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4949 /* [HGM] add Shogi promotions */
4950 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4955 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4956 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4958 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4959 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4962 piece = boards[currentMove][fromY][fromX];
4963 if(gameInfo.variant == VariantShogi) {
4964 promotionZoneSize = 3;
4965 highestPromotingPiece = (int)WhiteFerz;
4968 // next weed out all moves that do not touch the promotion zone at all
4969 if((int)piece >= BlackPawn) {
4970 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4972 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4974 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4975 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4978 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4980 // weed out mandatory Shogi promotions
4981 if(gameInfo.variant == VariantShogi) {
4982 if(piece >= BlackPawn) {
4983 if(toY == 0 && piece == BlackPawn ||
4984 toY == 0 && piece == BlackQueen ||
4985 toY <= 1 && piece == BlackKnight) {
4990 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4991 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4992 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4999 // weed out obviously illegal Pawn moves
5000 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5001 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5002 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5003 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5004 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5005 // note we are not allowed to test for valid (non-)capture, due to premove
5008 // we either have a choice what to promote to, or (in Shogi) whether to promote
5009 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5010 *promoChoice = PieceToChar(BlackFerz); // no choice
5013 if(appData.alwaysPromoteToQueen) { // predetermined
5014 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5015 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5016 else *promoChoice = PieceToChar(BlackQueen);
5020 // suppress promotion popup on illegal moves that are not premoves
5021 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5022 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5023 if(appData.testLegality && !premove) {
5024 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5025 fromY, fromX, toY, toX, NULLCHAR);
5026 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5027 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5035 InPalace(row, column)
5037 { /* [HGM] for Xiangqi */
5038 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5039 column < (BOARD_WIDTH + 4)/2 &&
5040 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5045 PieceForSquare (x, y)
5049 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5052 return boards[currentMove][y][x];
5056 OKToStartUserMove(x, y)
5059 ChessSquare from_piece;
5062 if (matchMode) return FALSE;
5063 if (gameMode == EditPosition) return TRUE;
5065 if (x >= 0 && y >= 0)
5066 from_piece = boards[currentMove][y][x];
5068 from_piece = EmptySquare;
5070 if (from_piece == EmptySquare) return FALSE;
5072 white_piece = (int)from_piece >= (int)WhitePawn &&
5073 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5076 case PlayFromGameFile:
5078 case TwoMachinesPlay:
5086 case MachinePlaysWhite:
5087 case IcsPlayingBlack:
5088 if (appData.zippyPlay) return FALSE;
5090 DisplayMoveError(_("You are playing Black"));
5095 case MachinePlaysBlack:
5096 case IcsPlayingWhite:
5097 if (appData.zippyPlay) return FALSE;
5099 DisplayMoveError(_("You are playing White"));
5105 if (!white_piece && WhiteOnMove(currentMove)) {
5106 DisplayMoveError(_("It is White's turn"));
5109 if (white_piece && !WhiteOnMove(currentMove)) {
5110 DisplayMoveError(_("It is Black's turn"));
5113 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5114 /* Editing correspondence game history */
5115 /* Could disallow this or prompt for confirmation */
5120 case BeginningOfGame:
5121 if (appData.icsActive) return FALSE;
5122 if (!appData.noChessProgram) {
5124 DisplayMoveError(_("You are playing White"));
5131 if (!white_piece && WhiteOnMove(currentMove)) {
5132 DisplayMoveError(_("It is White's turn"));
5135 if (white_piece && !WhiteOnMove(currentMove)) {
5136 DisplayMoveError(_("It is Black's turn"));
5145 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5146 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5147 && gameMode != AnalyzeFile && gameMode != Training) {
5148 DisplayMoveError(_("Displayed position is not current"));
5154 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5155 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5156 int lastLoadGameUseList = FALSE;
5157 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5158 ChessMove lastLoadGameStart = (ChessMove) 0;
5161 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5162 int fromX, fromY, toX, toY;
5167 ChessSquare pdown, pup;
5169 /* Check if the user is playing in turn. This is complicated because we
5170 let the user "pick up" a piece before it is his turn. So the piece he
5171 tried to pick up may have been captured by the time he puts it down!
5172 Therefore we use the color the user is supposed to be playing in this
5173 test, not the color of the piece that is currently on the starting
5174 square---except in EditGame mode, where the user is playing both
5175 sides; fortunately there the capture race can't happen. (It can
5176 now happen in IcsExamining mode, but that's just too bad. The user
5177 will get a somewhat confusing message in that case.)
5181 case PlayFromGameFile:
5183 case TwoMachinesPlay:
5187 /* We switched into a game mode where moves are not accepted,
5188 perhaps while the mouse button was down. */
5189 return ImpossibleMove;
5191 case MachinePlaysWhite:
5192 /* User is moving for Black */
5193 if (WhiteOnMove(currentMove)) {
5194 DisplayMoveError(_("It is White's turn"));
5195 return ImpossibleMove;
5199 case MachinePlaysBlack:
5200 /* User is moving for White */
5201 if (!WhiteOnMove(currentMove)) {
5202 DisplayMoveError(_("It is Black's turn"));
5203 return ImpossibleMove;
5209 case BeginningOfGame:
5212 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5213 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5214 /* User is moving for Black */
5215 if (WhiteOnMove(currentMove)) {
5216 DisplayMoveError(_("It is White's turn"));
5217 return ImpossibleMove;
5220 /* User is moving for White */
5221 if (!WhiteOnMove(currentMove)) {
5222 DisplayMoveError(_("It is Black's turn"));
5223 return ImpossibleMove;
5228 case IcsPlayingBlack:
5229 /* User is moving for Black */
5230 if (WhiteOnMove(currentMove)) {
5231 if (!appData.premove) {
5232 DisplayMoveError(_("It is White's turn"));
5233 } else if (toX >= 0 && toY >= 0) {
5236 premoveFromX = fromX;
5237 premoveFromY = fromY;
5238 premovePromoChar = promoChar;
5240 if (appData.debugMode)
5241 fprintf(debugFP, "Got premove: fromX %d,"
5242 "fromY %d, toX %d, toY %d\n",
5243 fromX, fromY, toX, toY);
5245 return ImpossibleMove;
5249 case IcsPlayingWhite:
5250 /* User is moving for White */
5251 if (!WhiteOnMove(currentMove)) {
5252 if (!appData.premove) {
5253 DisplayMoveError(_("It is Black's turn"));
5254 } else if (toX >= 0 && toY >= 0) {
5257 premoveFromX = fromX;
5258 premoveFromY = fromY;
5259 premovePromoChar = promoChar;
5261 if (appData.debugMode)
5262 fprintf(debugFP, "Got premove: fromX %d,"
5263 "fromY %d, toX %d, toY %d\n",
5264 fromX, fromY, toX, toY);
5266 return ImpossibleMove;
5274 /* EditPosition, empty square, or different color piece;
5275 click-click move is possible */
5276 if (toX == -2 || toY == -2) {
5277 boards[0][fromY][fromX] = EmptySquare;
5278 return AmbiguousMove;
5279 } else if (toX >= 0 && toY >= 0) {
5280 boards[0][toY][toX] = boards[0][fromY][fromX];
5281 boards[0][fromY][fromX] = EmptySquare;
5282 return AmbiguousMove;
5284 return ImpossibleMove;
5287 if(toX < 0 || toY < 0) return ImpossibleMove;
5288 pdown = boards[currentMove][fromY][fromX];
5289 pup = boards[currentMove][toY][toX];
5291 /* [HGM] If move started in holdings, it means a drop */
5292 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5293 if( pup != EmptySquare ) return ImpossibleMove;
5294 if(appData.testLegality) {
5295 /* it would be more logical if LegalityTest() also figured out
5296 * which drops are legal. For now we forbid pawns on back rank.
5297 * Shogi is on its own here...
5299 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5300 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5301 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5303 return WhiteDrop; /* Not needed to specify white or black yet */
5306 userOfferedDraw = FALSE;
5308 /* [HGM] always test for legality, to get promotion info */
5309 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5310 fromY, fromX, toY, toX, promoChar);
5311 /* [HGM] but possibly ignore an IllegalMove result */
5312 if (appData.testLegality) {
5313 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5314 DisplayMoveError(_("Illegal move"));
5315 return ImpossibleMove;
5320 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5321 function is made into one that returns an OK move type if FinishMove
5322 should be called. This to give the calling driver routine the
5323 opportunity to finish the userMove input with a promotion popup,
5324 without bothering the user with this for invalid or illegal moves */
5326 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5329 /* Common tail of UserMoveEvent and DropMenuEvent */
5331 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5333 int fromX, fromY, toX, toY;
5334 /*char*/int promoChar;
5338 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5339 // [HGM] superchess: suppress promotions to non-available piece
5340 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5341 if(WhiteOnMove(currentMove)) {
5342 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5344 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5348 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5349 move type in caller when we know the move is a legal promotion */
5350 if(moveType == NormalMove && promoChar)
5351 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5353 /* [HGM] convert drag-and-drop piece drops to standard form */
5354 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5355 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5356 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5357 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5358 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5359 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5360 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5361 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5365 /* [HGM] <popupFix> The following if has been moved here from
5366 UserMoveEvent(). Because it seemed to belong here (why not allow
5367 piece drops in training games?), and because it can only be
5368 performed after it is known to what we promote. */
5369 if (gameMode == Training) {
5370 /* compare the move played on the board to the next move in the
5371 * game. If they match, display the move and the opponent's response.
5372 * If they don't match, display an error message.
5376 CopyBoard(testBoard, boards[currentMove]);
5377 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5379 if (CompareBoards(testBoard, boards[currentMove+1])) {
5380 ForwardInner(currentMove+1);
5382 /* Autoplay the opponent's response.
5383 * if appData.animate was TRUE when Training mode was entered,
5384 * the response will be animated.
5386 saveAnimate = appData.animate;
5387 appData.animate = animateTraining;
5388 ForwardInner(currentMove+1);
5389 appData.animate = saveAnimate;
5391 /* check for the end of the game */
5392 if (currentMove >= forwardMostMove) {
5393 gameMode = PlayFromGameFile;
5395 SetTrainingModeOff();
5396 DisplayInformation(_("End of game"));
5399 DisplayError(_("Incorrect move"), 0);
5404 /* Ok, now we know that the move is good, so we can kill
5405 the previous line in Analysis Mode */
5406 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5407 && currentMove < forwardMostMove) {
5408 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5411 /* If we need the chess program but it's dead, restart it */
5412 ResurrectChessProgram();
5414 /* A user move restarts a paused game*/
5418 thinkOutput[0] = NULLCHAR;
5420 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5422 if (gameMode == BeginningOfGame) {
5423 if (appData.noChessProgram) {
5424 gameMode = EditGame;
5428 gameMode = MachinePlaysBlack;
5431 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5433 if (first.sendName) {
5434 sprintf(buf, "name %s\n", gameInfo.white);
5435 SendToProgram(buf, &first);
5442 /* Relay move to ICS or chess engine */
5443 if (appData.icsActive) {
5444 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5445 gameMode == IcsExamining) {
5446 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5450 if (first.sendTime && (gameMode == BeginningOfGame ||
5451 gameMode == MachinePlaysWhite ||
5452 gameMode == MachinePlaysBlack)) {
5453 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5455 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5456 // [HGM] book: if program might be playing, let it use book
5457 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5458 first.maybeThinking = TRUE;
5459 } else SendMoveToProgram(forwardMostMove-1, &first);
5460 if (currentMove == cmailOldMove + 1) {
5461 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5465 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5469 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5475 if (WhiteOnMove(currentMove)) {
5476 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5478 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5482 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5487 case MachinePlaysBlack:
5488 case MachinePlaysWhite:
5489 /* disable certain menu options while machine is thinking */
5490 SetMachineThinkingEnables();
5497 if(bookHit) { // [HGM] book: simulate book reply
5498 static char bookMove[MSG_SIZ]; // a bit generous?
5500 programStats.nodes = programStats.depth = programStats.time =
5501 programStats.score = programStats.got_only_move = 0;
5502 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5504 strcpy(bookMove, "move ");
5505 strcat(bookMove, bookHit);
5506 HandleMachineMove(bookMove, &first);
5512 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5513 int fromX, fromY, toX, toY;
5516 /* [HGM] This routine was added to allow calling of its two logical
5517 parts from other modules in the old way. Before, UserMoveEvent()
5518 automatically called FinishMove() if the move was OK, and returned
5519 otherwise. I separated the two, in order to make it possible to
5520 slip a promotion popup in between. But that it always needs two
5521 calls, to the first part, (now called UserMoveTest() ), and to
5522 FinishMove if the first part succeeded. Calls that do not need
5523 to do anything in between, can call this routine the old way.
5525 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5526 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5527 if(moveType == AmbiguousMove)
5528 DrawPosition(FALSE, boards[currentMove]);
5529 else if(moveType != ImpossibleMove && moveType != Comment)
5530 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5533 void LeftClick(ClickType clickType, int xPix, int yPix)
5536 Boolean saveAnimate;
5537 static int second = 0, promotionChoice = 0;
5538 char promoChoice = NULLCHAR;
5540 if (clickType == Press) ErrorPopDown();
5542 x = EventToSquare(xPix, BOARD_WIDTH);
5543 y = EventToSquare(yPix, BOARD_HEIGHT);
5544 if (!flipView && y >= 0) {
5545 y = BOARD_HEIGHT - 1 - y;
5547 if (flipView && x >= 0) {
5548 x = BOARD_WIDTH - 1 - x;
5551 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5552 if(clickType == Release) return; // ignore upclick of click-click destination
5553 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5554 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5555 if(gameInfo.holdingsWidth &&
5556 (WhiteOnMove(currentMove)
5557 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5558 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5559 // click in right holdings, for determining promotion piece
5560 ChessSquare p = boards[currentMove][y][x];
5561 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5562 if(p != EmptySquare) {
5563 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5568 DrawPosition(FALSE, boards[currentMove]);
5572 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5573 if(clickType == Press
5574 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5575 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5576 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5580 if (clickType == Press) {
5582 if (OKToStartUserMove(x, y)) {
5586 DragPieceBegin(xPix, yPix);
5587 if (appData.highlightDragging) {
5588 SetHighlights(x, y, -1, -1);
5596 if (clickType == Press && gameMode != EditPosition) {
5601 // ignore off-board to clicks
5602 if(y < 0 || x < 0) return;
5604 /* Check if clicking again on the same color piece */
5605 fromP = boards[currentMove][fromY][fromX];
5606 toP = boards[currentMove][y][x];
5607 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5608 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5609 WhitePawn <= toP && toP <= WhiteKing &&
5610 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5611 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5612 (BlackPawn <= fromP && fromP <= BlackKing &&
5613 BlackPawn <= toP && toP <= BlackKing &&
5614 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5615 !(fromP == BlackKing && toP == BlackRook && frc))) {
5616 /* Clicked again on same color piece -- changed his mind */
5617 second = (x == fromX && y == fromY);
5618 if (appData.highlightDragging) {
5619 SetHighlights(x, y, -1, -1);
5623 if (OKToStartUserMove(x, y)) {
5626 DragPieceBegin(xPix, yPix);
5630 // ignore clicks on holdings
5631 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5634 if (clickType == Release && x == fromX && y == fromY) {
5635 DragPieceEnd(xPix, yPix);
5636 if (appData.animateDragging) {
5637 /* Undo animation damage if any */
5638 DrawPosition(FALSE, NULL);
5641 /* Second up/down in same square; just abort move */
5646 ClearPremoveHighlights();
5648 /* First upclick in same square; start click-click mode */
5649 SetHighlights(x, y, -1, -1);
5654 /* we now have a different from- and (possibly off-board) to-square */
5655 /* Completed move */
5658 saveAnimate = appData.animate;
5659 if (clickType == Press) {
5660 /* Finish clickclick move */
5661 if (appData.animate || appData.highlightLastMove) {
5662 SetHighlights(fromX, fromY, toX, toY);
5667 /* Finish drag move */
5668 if (appData.highlightLastMove) {
5669 SetHighlights(fromX, fromY, toX, toY);
5673 DragPieceEnd(xPix, yPix);
5674 /* Don't animate move and drag both */
5675 appData.animate = FALSE;
5678 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5679 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5682 DrawPosition(TRUE, NULL);
5686 // off-board moves should not be highlighted
5687 if(x < 0 || x < 0) ClearHighlights();
5689 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5690 SetHighlights(fromX, fromY, toX, toY);
5691 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5692 // [HGM] super: promotion to captured piece selected from holdings
5693 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5694 promotionChoice = TRUE;
5695 // kludge follows to temporarily execute move on display, without promoting yet
5696 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5697 boards[currentMove][toY][toX] = p;
5698 DrawPosition(FALSE, boards[currentMove]);
5699 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5700 boards[currentMove][toY][toX] = q;
5701 DisplayMessage("Click in holdings to choose piece", "");
5706 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5707 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5708 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5711 appData.animate = saveAnimate;
5712 if (appData.animate || appData.animateDragging) {
5713 /* Undo animation damage if needed */
5714 DrawPosition(FALSE, NULL);
5718 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5720 // char * hint = lastHint;
5721 FrontEndProgramStats stats;
5723 stats.which = cps == &first ? 0 : 1;
5724 stats.depth = cpstats->depth;
5725 stats.nodes = cpstats->nodes;
5726 stats.score = cpstats->score;
5727 stats.time = cpstats->time;
5728 stats.pv = cpstats->movelist;
5729 stats.hint = lastHint;
5730 stats.an_move_index = 0;
5731 stats.an_move_count = 0;
5733 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5734 stats.hint = cpstats->move_name;
5735 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5736 stats.an_move_count = cpstats->nr_moves;
5739 SetProgramStats( &stats );
5742 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5743 { // [HGM] book: this routine intercepts moves to simulate book replies
5744 char *bookHit = NULL;
5746 //first determine if the incoming move brings opponent into his book
5747 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5748 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5749 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5750 if(bookHit != NULL && !cps->bookSuspend) {
5751 // make sure opponent is not going to reply after receiving move to book position
5752 SendToProgram("force\n", cps);
5753 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5755 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5756 // now arrange restart after book miss
5758 // after a book hit we never send 'go', and the code after the call to this routine
5759 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5761 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5762 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5763 SendToProgram(buf, cps);
5764 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5765 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5766 SendToProgram("go\n", cps);
5767 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5768 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5769 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5770 SendToProgram("go\n", cps);
5771 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5773 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5777 ChessProgramState *savedState;
5778 void DeferredBookMove(void)
5780 if(savedState->lastPing != savedState->lastPong)
5781 ScheduleDelayedEvent(DeferredBookMove, 10);
5783 HandleMachineMove(savedMessage, savedState);
5787 HandleMachineMove(message, cps)
5789 ChessProgramState *cps;
5791 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5792 char realname[MSG_SIZ];
5793 int fromX, fromY, toX, toY;
5802 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5804 * Kludge to ignore BEL characters
5806 while (*message == '\007') message++;
5809 * [HGM] engine debug message: ignore lines starting with '#' character
5811 if(cps->debug && *message == '#') return;
5814 * Look for book output
5816 if (cps == &first && bookRequested) {
5817 if (message[0] == '\t' || message[0] == ' ') {
5818 /* Part of the book output is here; append it */
5819 strcat(bookOutput, message);
5820 strcat(bookOutput, " \n");
5822 } else if (bookOutput[0] != NULLCHAR) {
5823 /* All of book output has arrived; display it */
5824 char *p = bookOutput;
5825 while (*p != NULLCHAR) {
5826 if (*p == '\t') *p = ' ';
5829 DisplayInformation(bookOutput);
5830 bookRequested = FALSE;
5831 /* Fall through to parse the current output */
5836 * Look for machine move.
5838 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5839 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5841 /* This method is only useful on engines that support ping */
5842 if (cps->lastPing != cps->lastPong) {
5843 if (gameMode == BeginningOfGame) {
5844 /* Extra move from before last new; ignore */
5845 if (appData.debugMode) {
5846 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5849 if (appData.debugMode) {
5850 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5851 cps->which, gameMode);
5854 SendToProgram("undo\n", cps);
5860 case BeginningOfGame:
5861 /* Extra move from before last reset; ignore */
5862 if (appData.debugMode) {
5863 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5870 /* Extra move after we tried to stop. The mode test is
5871 not a reliable way of detecting this problem, but it's
5872 the best we can do on engines that don't support ping.
5874 if (appData.debugMode) {
5875 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5876 cps->which, gameMode);
5878 SendToProgram("undo\n", cps);
5881 case MachinePlaysWhite:
5882 case IcsPlayingWhite:
5883 machineWhite = TRUE;
5886 case MachinePlaysBlack:
5887 case IcsPlayingBlack:
5888 machineWhite = FALSE;
5891 case TwoMachinesPlay:
5892 machineWhite = (cps->twoMachinesColor[0] == 'w');
5895 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5896 if (appData.debugMode) {
5898 "Ignoring move out of turn by %s, gameMode %d"
5899 ", forwardMost %d\n",
5900 cps->which, gameMode, forwardMostMove);
5905 if (appData.debugMode) { int f = forwardMostMove;
5906 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5907 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5908 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5910 if(cps->alphaRank) AlphaRank(machineMove, 4);
5911 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5912 &fromX, &fromY, &toX, &toY, &promoChar)) {
5913 /* Machine move could not be parsed; ignore it. */
5914 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5915 machineMove, cps->which);
5916 DisplayError(buf1, 0);
5917 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5918 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5919 if (gameMode == TwoMachinesPlay) {
5920 GameEnds(machineWhite ? BlackWins : WhiteWins,
5926 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5927 /* So we have to redo legality test with true e.p. status here, */
5928 /* to make sure an illegal e.p. capture does not slip through, */
5929 /* to cause a forfeit on a justified illegal-move complaint */
5930 /* of the opponent. */
5931 if( gameMode==TwoMachinesPlay && appData.testLegality
5932 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5935 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5936 fromY, fromX, toY, toX, promoChar);
5937 if (appData.debugMode) {
5939 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5940 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5941 fprintf(debugFP, "castling rights\n");
5943 if(moveType == IllegalMove) {
5944 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5945 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5946 GameEnds(machineWhite ? BlackWins : WhiteWins,
5949 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5950 /* [HGM] Kludge to handle engines that send FRC-style castling
5951 when they shouldn't (like TSCP-Gothic) */
5953 case WhiteASideCastleFR:
5954 case BlackASideCastleFR:
5956 currentMoveString[2]++;
5958 case WhiteHSideCastleFR:
5959 case BlackHSideCastleFR:
5961 currentMoveString[2]--;
5963 default: ; // nothing to do, but suppresses warning of pedantic compilers
5966 hintRequested = FALSE;
5967 lastHint[0] = NULLCHAR;
5968 bookRequested = FALSE;
5969 /* Program may be pondering now */
5970 cps->maybeThinking = TRUE;
5971 if (cps->sendTime == 2) cps->sendTime = 1;
5972 if (cps->offeredDraw) cps->offeredDraw--;
5975 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5977 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5979 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5980 char buf[3*MSG_SIZ];
5982 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5983 programStats.score / 100.,
5985 programStats.time / 100.,
5986 (unsigned int)programStats.nodes,
5987 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5988 programStats.movelist);
5990 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5994 /* currentMoveString is set as a side-effect of ParseOneMove */
5995 strcpy(machineMove, currentMoveString);
5996 strcat(machineMove, "\n");
5997 strcpy(moveList[forwardMostMove], machineMove);
5999 /* [AS] Save move info and clear stats for next move */
6000 pvInfoList[ forwardMostMove ].score = programStats.score;
6001 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6002 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6003 ClearProgramStats();
6004 thinkOutput[0] = NULLCHAR;
6005 hiddenThinkOutputState = 0;
6007 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6009 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6010 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6013 while( count < adjudicateLossPlies ) {
6014 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6017 score = -score; /* Flip score for winning side */
6020 if( score > adjudicateLossThreshold ) {
6027 if( count >= adjudicateLossPlies ) {
6028 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6030 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6031 "Xboard adjudication",
6038 if( gameMode == TwoMachinesPlay ) {
6039 // [HGM] some adjudications useful with buggy engines
6040 int k, count = 0; static int bare = 1;
6041 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6044 if( appData.testLegality )
6045 { /* [HGM] Some more adjudications for obstinate engines */
6046 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6047 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6048 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6049 static int moveCount = 6;
6051 char *reason = NULL;
6053 /* Count what is on board. */
6054 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6055 { ChessSquare p = boards[forwardMostMove][i][j];
6059 { /* count B,N,R and other of each side */
6062 NrK++; break; // [HGM] atomic: count Kings
6066 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6067 bishopsColor |= 1 << ((i^j)&1);
6072 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6073 bishopsColor |= 1 << ((i^j)&1);
6088 PawnAdvance += m; NrPawns++;
6090 NrPieces += (p != EmptySquare);
6091 NrW += ((int)p < (int)BlackPawn);
6092 if(gameInfo.variant == VariantXiangqi &&
6093 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6094 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6095 NrW -= ((int)p < (int)BlackPawn);
6099 /* Some material-based adjudications that have to be made before stalemate test */
6100 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6101 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6102 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6103 if(appData.checkMates) {
6104 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6105 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6106 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6107 "Xboard adjudication: King destroyed", GE_XBOARD );
6112 /* Bare King in Shatranj (loses) or Losers (wins) */
6113 if( NrW == 1 || NrPieces - NrW == 1) {
6114 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6115 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6116 if(appData.checkMates) {
6117 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6118 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6119 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6120 "Xboard adjudication: Bare king", GE_XBOARD );
6124 if( gameInfo.variant == VariantShatranj && --bare < 0)
6126 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6127 if(appData.checkMates) {
6128 /* but only adjudicate if adjudication enabled */
6129 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6130 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6131 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6132 "Xboard adjudication: Bare king", GE_XBOARD );
6139 // don't wait for engine to announce game end if we can judge ourselves
6140 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6142 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6143 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6144 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6145 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6148 reason = "Xboard adjudication: 3rd check";
6149 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6159 reason = "Xboard adjudication: Stalemate";
6160 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6161 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6162 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6163 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6164 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6165 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6166 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6167 EP_CHECKMATE : EP_WINS);
6168 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6169 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6173 reason = "Xboard adjudication: Checkmate";
6174 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6178 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6180 result = GameIsDrawn; break;
6182 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6184 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6186 result = (ChessMove) 0;
6188 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6189 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6190 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6191 GameEnds( result, reason, GE_XBOARD );
6195 /* Next absolutely insufficient mating material. */
6196 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6197 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6198 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6199 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6200 { /* KBK, KNK, KK of KBKB with like Bishops */
6202 /* always flag draws, for judging claims */
6203 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6205 if(appData.materialDraws) {
6206 /* but only adjudicate them if adjudication enabled */
6207 SendToProgram("force\n", cps->other); // suppress reply
6208 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6209 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6210 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6215 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6217 ( NrWR == 1 && NrBR == 1 /* KRKR */
6218 || NrWQ==1 && NrBQ==1 /* KQKQ */
6219 || NrWN==2 || NrBN==2 /* KNNK */
6220 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6222 if(--moveCount < 0 && appData.trivialDraws)
6223 { /* if the first 3 moves do not show a tactical win, declare draw */
6224 SendToProgram("force\n", cps->other); // suppress reply
6225 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6226 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6227 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6230 } else moveCount = 6;
6234 if (appData.debugMode) { int i;
6235 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6236 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6237 appData.drawRepeats);
6238 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6239 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6243 /* Check for rep-draws */
6245 for(k = forwardMostMove-2;
6246 k>=backwardMostMove && k>=forwardMostMove-100 &&
6247 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6248 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6251 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6252 /* compare castling rights */
6253 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6254 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6255 rights++; /* King lost rights, while rook still had them */
6256 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6257 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6258 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6259 rights++; /* but at least one rook lost them */
6261 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6262 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6264 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6265 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6266 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6269 if( rights == 0 && ++count > appData.drawRepeats-2
6270 && appData.drawRepeats > 1) {
6271 /* adjudicate after user-specified nr of repeats */
6272 SendToProgram("force\n", cps->other); // suppress reply
6273 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6274 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6275 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6276 // [HGM] xiangqi: check for forbidden perpetuals
6277 int m, ourPerpetual = 1, hisPerpetual = 1;
6278 for(m=forwardMostMove; m>k; m-=2) {
6279 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6280 ourPerpetual = 0; // the current mover did not always check
6281 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6282 hisPerpetual = 0; // the opponent did not always check
6284 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6285 ourPerpetual, hisPerpetual);
6286 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6287 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6288 "Xboard adjudication: perpetual checking", GE_XBOARD );
6291 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6292 break; // (or we would have caught him before). Abort repetition-checking loop.
6293 // Now check for perpetual chases
6294 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6295 hisPerpetual = PerpetualChase(k, forwardMostMove);
6296 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6297 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6298 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6299 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6302 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6303 break; // Abort repetition-checking loop.
6305 // if neither of us is checking or chasing all the time, or both are, it is draw
6307 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6310 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6311 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6315 /* Now we test for 50-move draws. Determine ply count */
6316 count = forwardMostMove;
6317 /* look for last irreversble move */
6318 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6320 /* if we hit starting position, add initial plies */
6321 if( count == backwardMostMove )
6322 count -= initialRulePlies;
6323 count = forwardMostMove - count;
6325 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6326 /* this is used to judge if draw claims are legal */
6327 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6328 SendToProgram("force\n", cps->other); // suppress reply
6329 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6330 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6331 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6335 /* if draw offer is pending, treat it as a draw claim
6336 * when draw condition present, to allow engines a way to
6337 * claim draws before making their move to avoid a race
6338 * condition occurring after their move
6340 if( cps->other->offeredDraw || cps->offeredDraw ) {
6342 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6343 p = "Draw claim: 50-move rule";
6344 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6345 p = "Draw claim: 3-fold repetition";
6346 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6347 p = "Draw claim: insufficient mating material";
6349 SendToProgram("force\n", cps->other); // suppress reply
6350 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6351 GameEnds( GameIsDrawn, p, GE_XBOARD );
6352 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6358 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
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*/
6363 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6370 if (gameMode == TwoMachinesPlay) {
6371 /* [HGM] relaying draw offers moved to after reception of move */
6372 /* and interpreting offer as claim if it brings draw condition */
6373 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6374 SendToProgram("draw\n", cps->other);
6376 if (cps->other->sendTime) {
6377 SendTimeRemaining(cps->other,
6378 cps->other->twoMachinesColor[0] == 'w');
6380 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6381 if (firstMove && !bookHit) {
6383 if (cps->other->useColors) {
6384 SendToProgram(cps->other->twoMachinesColor, cps->other);
6386 SendToProgram("go\n", cps->other);
6388 cps->other->maybeThinking = TRUE;
6391 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6393 if (!pausing && appData.ringBellAfterMoves) {
6398 * Reenable menu items that were disabled while
6399 * machine was thinking
6401 if (gameMode != TwoMachinesPlay)
6402 SetUserThinkingEnables();
6404 // [HGM] book: after book hit opponent has received move and is now in force mode
6405 // force the book reply into it, and then fake that it outputted this move by jumping
6406 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6408 static char bookMove[MSG_SIZ]; // a bit generous?
6410 strcpy(bookMove, "move ");
6411 strcat(bookMove, bookHit);
6414 programStats.nodes = programStats.depth = programStats.time =
6415 programStats.score = programStats.got_only_move = 0;
6416 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6418 if(cps->lastPing != cps->lastPong) {
6419 savedMessage = message; // args for deferred call
6421 ScheduleDelayedEvent(DeferredBookMove, 10);
6430 /* Set special modes for chess engines. Later something general
6431 * could be added here; for now there is just one kludge feature,
6432 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6433 * when "xboard" is given as an interactive command.
6435 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6436 cps->useSigint = FALSE;
6437 cps->useSigterm = FALSE;
6439 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6440 ParseFeatures(message+8, cps);
6441 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6444 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6445 * want this, I was asked to put it in, and obliged.
6447 if (!strncmp(message, "setboard ", 9)) {
6448 Board initial_position;
6450 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6452 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6453 DisplayError(_("Bad FEN received from engine"), 0);
6457 CopyBoard(boards[0], initial_position);
6458 initialRulePlies = FENrulePlies;
6459 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6460 else gameMode = MachinePlaysBlack;
6461 DrawPosition(FALSE, boards[currentMove]);
6467 * Look for communication commands
6469 if (!strncmp(message, "telluser ", 9)) {
6470 DisplayNote(message + 9);
6473 if (!strncmp(message, "tellusererror ", 14)) {
6475 DisplayError(message + 14, 0);
6478 if (!strncmp(message, "tellopponent ", 13)) {
6479 if (appData.icsActive) {
6481 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6485 DisplayNote(message + 13);
6489 if (!strncmp(message, "tellothers ", 11)) {
6490 if (appData.icsActive) {
6492 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6498 if (!strncmp(message, "tellall ", 8)) {
6499 if (appData.icsActive) {
6501 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6505 DisplayNote(message + 8);
6509 if (strncmp(message, "warning", 7) == 0) {
6510 /* Undocumented feature, use tellusererror in new code */
6511 DisplayError(message, 0);
6514 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6515 strcpy(realname, cps->tidy);
6516 strcat(realname, " query");
6517 AskQuestion(realname, buf2, buf1, cps->pr);
6520 /* Commands from the engine directly to ICS. We don't allow these to be
6521 * sent until we are logged on. Crafty kibitzes have been known to
6522 * interfere with the login process.
6525 if (!strncmp(message, "tellics ", 8)) {
6526 SendToICS(message + 8);
6530 if (!strncmp(message, "tellicsnoalias ", 15)) {
6531 SendToICS(ics_prefix);
6532 SendToICS(message + 15);
6536 /* The following are for backward compatibility only */
6537 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6538 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6539 SendToICS(ics_prefix);
6545 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6549 * If the move is illegal, cancel it and redraw the board.
6550 * Also deal with other error cases. Matching is rather loose
6551 * here to accommodate engines written before the spec.
6553 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6554 strncmp(message, "Error", 5) == 0) {
6555 if (StrStr(message, "name") ||
6556 StrStr(message, "rating") || StrStr(message, "?") ||
6557 StrStr(message, "result") || StrStr(message, "board") ||
6558 StrStr(message, "bk") || StrStr(message, "computer") ||
6559 StrStr(message, "variant") || StrStr(message, "hint") ||
6560 StrStr(message, "random") || StrStr(message, "depth") ||
6561 StrStr(message, "accepted")) {
6564 if (StrStr(message, "protover")) {
6565 /* Program is responding to input, so it's apparently done
6566 initializing, and this error message indicates it is
6567 protocol version 1. So we don't need to wait any longer
6568 for it to initialize and send feature commands. */
6569 FeatureDone(cps, 1);
6570 cps->protocolVersion = 1;
6573 cps->maybeThinking = FALSE;
6575 if (StrStr(message, "draw")) {
6576 /* Program doesn't have "draw" command */
6577 cps->sendDrawOffers = 0;
6580 if (cps->sendTime != 1 &&
6581 (StrStr(message, "time") || StrStr(message, "otim"))) {
6582 /* Program apparently doesn't have "time" or "otim" command */
6586 if (StrStr(message, "analyze")) {
6587 cps->analysisSupport = FALSE;
6588 cps->analyzing = FALSE;
6590 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6591 DisplayError(buf2, 0);
6594 if (StrStr(message, "(no matching move)st")) {
6595 /* Special kludge for GNU Chess 4 only */
6596 cps->stKludge = TRUE;
6597 SendTimeControl(cps, movesPerSession, timeControl,
6598 timeIncrement, appData.searchDepth,
6602 if (StrStr(message, "(no matching move)sd")) {
6603 /* Special kludge for GNU Chess 4 only */
6604 cps->sdKludge = TRUE;
6605 SendTimeControl(cps, movesPerSession, timeControl,
6606 timeIncrement, appData.searchDepth,
6610 if (!StrStr(message, "llegal")) {
6613 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6614 gameMode == IcsIdle) return;
6615 if (forwardMostMove <= backwardMostMove) return;
6616 if (pausing) PauseEvent();
6617 if(appData.forceIllegal) {
6618 // [HGM] illegal: machine refused move; force position after move into it
6619 SendToProgram("force\n", cps);
6620 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6621 // we have a real problem now, as SendBoard will use the a2a3 kludge
6622 // when black is to move, while there might be nothing on a2 or black
6623 // might already have the move. So send the board as if white has the move.
6624 // But first we must change the stm of the engine, as it refused the last move
6625 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6626 if(WhiteOnMove(forwardMostMove)) {
6627 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6628 SendBoard(cps, forwardMostMove); // kludgeless board
6630 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6631 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6632 SendBoard(cps, forwardMostMove+1); // kludgeless board
6634 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6635 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6636 gameMode == TwoMachinesPlay)
6637 SendToProgram("go\n", cps);
6640 if (gameMode == PlayFromGameFile) {
6641 /* Stop reading this game file */
6642 gameMode = EditGame;
6645 currentMove = --forwardMostMove;
6646 DisplayMove(currentMove-1); /* before DisplayMoveError */
6648 DisplayBothClocks();
6649 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6650 parseList[currentMove], cps->which);
6651 DisplayMoveError(buf1);
6652 DrawPosition(FALSE, boards[currentMove]);
6654 /* [HGM] illegal-move claim should forfeit game when Xboard */
6655 /* only passes fully legal moves */
6656 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6657 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6658 "False illegal-move claim", GE_XBOARD );
6662 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6663 /* Program has a broken "time" command that
6664 outputs a string not ending in newline.
6670 * If chess program startup fails, exit with an error message.
6671 * Attempts to recover here are futile.
6673 if ((StrStr(message, "unknown host") != NULL)
6674 || (StrStr(message, "No remote directory") != NULL)
6675 || (StrStr(message, "not found") != NULL)
6676 || (StrStr(message, "No such file") != NULL)
6677 || (StrStr(message, "can't alloc") != NULL)
6678 || (StrStr(message, "Permission denied") != NULL)) {
6680 cps->maybeThinking = FALSE;
6681 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6682 cps->which, cps->program, cps->host, message);
6683 RemoveInputSource(cps->isr);
6684 DisplayFatalError(buf1, 0, 1);
6689 * Look for hint output
6691 if (sscanf(message, "Hint: %s", buf1) == 1) {
6692 if (cps == &first && hintRequested) {
6693 hintRequested = FALSE;
6694 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6695 &fromX, &fromY, &toX, &toY, &promoChar)) {
6696 (void) CoordsToAlgebraic(boards[forwardMostMove],
6697 PosFlags(forwardMostMove),
6698 fromY, fromX, toY, toX, promoChar, buf1);
6699 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6700 DisplayInformation(buf2);
6702 /* Hint move could not be parsed!? */
6703 snprintf(buf2, sizeof(buf2),
6704 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6706 DisplayError(buf2, 0);
6709 strcpy(lastHint, buf1);
6715 * Ignore other messages if game is not in progress
6717 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6718 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6721 * look for win, lose, draw, or draw offer
6723 if (strncmp(message, "1-0", 3) == 0) {
6724 char *p, *q, *r = "";
6725 p = strchr(message, '{');
6733 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6735 } else if (strncmp(message, "0-1", 3) == 0) {
6736 char *p, *q, *r = "";
6737 p = strchr(message, '{');
6745 /* Kludge for Arasan 4.1 bug */
6746 if (strcmp(r, "Black resigns") == 0) {
6747 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6750 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6752 } else if (strncmp(message, "1/2", 3) == 0) {
6753 char *p, *q, *r = "";
6754 p = strchr(message, '{');
6763 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6766 } else if (strncmp(message, "White resign", 12) == 0) {
6767 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6769 } else if (strncmp(message, "Black resign", 12) == 0) {
6770 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6772 } else if (strncmp(message, "White matches", 13) == 0 ||
6773 strncmp(message, "Black matches", 13) == 0 ) {
6774 /* [HGM] ignore GNUShogi noises */
6776 } else if (strncmp(message, "White", 5) == 0 &&
6777 message[5] != '(' &&
6778 StrStr(message, "Black") == NULL) {
6779 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6781 } else if (strncmp(message, "Black", 5) == 0 &&
6782 message[5] != '(') {
6783 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6785 } else if (strcmp(message, "resign") == 0 ||
6786 strcmp(message, "computer resigns") == 0) {
6788 case MachinePlaysBlack:
6789 case IcsPlayingBlack:
6790 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6792 case MachinePlaysWhite:
6793 case IcsPlayingWhite:
6794 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6796 case TwoMachinesPlay:
6797 if (cps->twoMachinesColor[0] == 'w')
6798 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6800 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6807 } else if (strncmp(message, "opponent mates", 14) == 0) {
6809 case MachinePlaysBlack:
6810 case IcsPlayingBlack:
6811 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6813 case MachinePlaysWhite:
6814 case IcsPlayingWhite:
6815 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6817 case TwoMachinesPlay:
6818 if (cps->twoMachinesColor[0] == 'w')
6819 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6821 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6828 } else if (strncmp(message, "computer mates", 14) == 0) {
6830 case MachinePlaysBlack:
6831 case IcsPlayingBlack:
6832 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6834 case MachinePlaysWhite:
6835 case IcsPlayingWhite:
6836 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6838 case TwoMachinesPlay:
6839 if (cps->twoMachinesColor[0] == 'w')
6840 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6842 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6849 } else if (strncmp(message, "checkmate", 9) == 0) {
6850 if (WhiteOnMove(forwardMostMove)) {
6851 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6853 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6856 } else if (strstr(message, "Draw") != NULL ||
6857 strstr(message, "game is a draw") != NULL) {
6858 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6860 } else if (strstr(message, "offer") != NULL &&
6861 strstr(message, "draw") != NULL) {
6863 if (appData.zippyPlay && first.initDone) {
6864 /* Relay offer to ICS */
6865 SendToICS(ics_prefix);
6866 SendToICS("draw\n");
6869 cps->offeredDraw = 2; /* valid until this engine moves twice */
6870 if (gameMode == TwoMachinesPlay) {
6871 if (cps->other->offeredDraw) {
6872 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6873 /* [HGM] in two-machine mode we delay relaying draw offer */
6874 /* until after we also have move, to see if it is really claim */
6876 } else if (gameMode == MachinePlaysWhite ||
6877 gameMode == MachinePlaysBlack) {
6878 if (userOfferedDraw) {
6879 DisplayInformation(_("Machine accepts your draw offer"));
6880 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6882 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6889 * Look for thinking output
6891 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6892 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6894 int plylev, mvleft, mvtot, curscore, time;
6895 char mvname[MOVE_LEN];
6899 int prefixHint = FALSE;
6900 mvname[0] = NULLCHAR;
6903 case MachinePlaysBlack:
6904 case IcsPlayingBlack:
6905 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6907 case MachinePlaysWhite:
6908 case IcsPlayingWhite:
6909 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6914 case IcsObserving: /* [DM] icsEngineAnalyze */
6915 if (!appData.icsEngineAnalyze) ignore = TRUE;
6917 case TwoMachinesPlay:
6918 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6929 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6930 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6932 if (plyext != ' ' && plyext != '\t') {
6936 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6937 if( cps->scoreIsAbsolute &&
6938 ( gameMode == MachinePlaysBlack ||
6939 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6940 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
6941 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6942 !WhiteOnMove(currentMove)
6945 curscore = -curscore;
6949 programStats.depth = plylev;
6950 programStats.nodes = nodes;
6951 programStats.time = time;
6952 programStats.score = curscore;
6953 programStats.got_only_move = 0;
6955 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6958 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6959 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6960 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6961 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6962 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6963 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6964 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6965 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6968 /* Buffer overflow protection */
6969 if (buf1[0] != NULLCHAR) {
6970 if (strlen(buf1) >= sizeof(programStats.movelist)
6971 && appData.debugMode) {
6973 "PV is too long; using the first %u bytes.\n",
6974 (unsigned) sizeof(programStats.movelist) - 1);
6977 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6979 sprintf(programStats.movelist, " no PV\n");
6982 if (programStats.seen_stat) {
6983 programStats.ok_to_send = 1;
6986 if (strchr(programStats.movelist, '(') != NULL) {
6987 programStats.line_is_book = 1;
6988 programStats.nr_moves = 0;
6989 programStats.moves_left = 0;
6991 programStats.line_is_book = 0;
6994 SendProgramStatsToFrontend( cps, &programStats );
6997 [AS] Protect the thinkOutput buffer from overflow... this
6998 is only useful if buf1 hasn't overflowed first!
7000 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7002 (gameMode == TwoMachinesPlay ?
7003 ToUpper(cps->twoMachinesColor[0]) : ' '),
7004 ((double) curscore) / 100.0,
7005 prefixHint ? lastHint : "",
7006 prefixHint ? " " : "" );
7008 if( buf1[0] != NULLCHAR ) {
7009 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7011 if( strlen(buf1) > max_len ) {
7012 if( appData.debugMode) {
7013 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7015 buf1[max_len+1] = '\0';
7018 strcat( thinkOutput, buf1 );
7021 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7022 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7023 DisplayMove(currentMove - 1);
7027 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7028 /* crafty (9.25+) says "(only move) <move>"
7029 * if there is only 1 legal move
7031 sscanf(p, "(only move) %s", buf1);
7032 sprintf(thinkOutput, "%s (only move)", buf1);
7033 sprintf(programStats.movelist, "%s (only move)", buf1);
7034 programStats.depth = 1;
7035 programStats.nr_moves = 1;
7036 programStats.moves_left = 1;
7037 programStats.nodes = 1;
7038 programStats.time = 1;
7039 programStats.got_only_move = 1;
7041 /* Not really, but we also use this member to
7042 mean "line isn't going to change" (Crafty
7043 isn't searching, so stats won't change) */
7044 programStats.line_is_book = 1;
7046 SendProgramStatsToFrontend( cps, &programStats );
7048 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7049 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7050 DisplayMove(currentMove - 1);
7053 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7054 &time, &nodes, &plylev, &mvleft,
7055 &mvtot, mvname) >= 5) {
7056 /* The stat01: line is from Crafty (9.29+) in response
7057 to the "." command */
7058 programStats.seen_stat = 1;
7059 cps->maybeThinking = TRUE;
7061 if (programStats.got_only_move || !appData.periodicUpdates)
7064 programStats.depth = plylev;
7065 programStats.time = time;
7066 programStats.nodes = nodes;
7067 programStats.moves_left = mvleft;
7068 programStats.nr_moves = mvtot;
7069 strcpy(programStats.move_name, mvname);
7070 programStats.ok_to_send = 1;
7071 programStats.movelist[0] = '\0';
7073 SendProgramStatsToFrontend( cps, &programStats );
7077 } else if (strncmp(message,"++",2) == 0) {
7078 /* Crafty 9.29+ outputs this */
7079 programStats.got_fail = 2;
7082 } else if (strncmp(message,"--",2) == 0) {
7083 /* Crafty 9.29+ outputs this */
7084 programStats.got_fail = 1;
7087 } else if (thinkOutput[0] != NULLCHAR &&
7088 strncmp(message, " ", 4) == 0) {
7089 unsigned message_len;
7092 while (*p && *p == ' ') p++;
7094 message_len = strlen( p );
7096 /* [AS] Avoid buffer overflow */
7097 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7098 strcat(thinkOutput, " ");
7099 strcat(thinkOutput, p);
7102 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7103 strcat(programStats.movelist, " ");
7104 strcat(programStats.movelist, p);
7107 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7108 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7109 DisplayMove(currentMove - 1);
7117 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7118 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7120 ChessProgramStats cpstats;
7122 if (plyext != ' ' && plyext != '\t') {
7126 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7127 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7128 curscore = -curscore;
7131 cpstats.depth = plylev;
7132 cpstats.nodes = nodes;
7133 cpstats.time = time;
7134 cpstats.score = curscore;
7135 cpstats.got_only_move = 0;
7136 cpstats.movelist[0] = '\0';
7138 if (buf1[0] != NULLCHAR) {
7139 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7142 cpstats.ok_to_send = 0;
7143 cpstats.line_is_book = 0;
7144 cpstats.nr_moves = 0;
7145 cpstats.moves_left = 0;
7147 SendProgramStatsToFrontend( cps, &cpstats );
7154 /* Parse a game score from the character string "game", and
7155 record it as the history of the current game. The game
7156 score is NOT assumed to start from the standard position.
7157 The display is not updated in any way.
7160 ParseGameHistory(game)
7164 int fromX, fromY, toX, toY, boardIndex;
7169 if (appData.debugMode)
7170 fprintf(debugFP, "Parsing game history: %s\n", game);
7172 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7173 gameInfo.site = StrSave(appData.icsHost);
7174 gameInfo.date = PGNDate();
7175 gameInfo.round = StrSave("-");
7177 /* Parse out names of players */
7178 while (*game == ' ') game++;
7180 while (*game != ' ') *p++ = *game++;
7182 gameInfo.white = StrSave(buf);
7183 while (*game == ' ') game++;
7185 while (*game != ' ' && *game != '\n') *p++ = *game++;
7187 gameInfo.black = StrSave(buf);
7190 boardIndex = blackPlaysFirst ? 1 : 0;
7193 yyboardindex = boardIndex;
7194 moveType = (ChessMove) yylex();
7196 case IllegalMove: /* maybe suicide chess, etc. */
7197 if (appData.debugMode) {
7198 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7199 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7200 setbuf(debugFP, NULL);
7202 case WhitePromotionChancellor:
7203 case BlackPromotionChancellor:
7204 case WhitePromotionArchbishop:
7205 case BlackPromotionArchbishop:
7206 case WhitePromotionQueen:
7207 case BlackPromotionQueen:
7208 case WhitePromotionRook:
7209 case BlackPromotionRook:
7210 case WhitePromotionBishop:
7211 case BlackPromotionBishop:
7212 case WhitePromotionKnight:
7213 case BlackPromotionKnight:
7214 case WhitePromotionKing:
7215 case BlackPromotionKing:
7217 case WhiteCapturesEnPassant:
7218 case BlackCapturesEnPassant:
7219 case WhiteKingSideCastle:
7220 case WhiteQueenSideCastle:
7221 case BlackKingSideCastle:
7222 case BlackQueenSideCastle:
7223 case WhiteKingSideCastleWild:
7224 case WhiteQueenSideCastleWild:
7225 case BlackKingSideCastleWild:
7226 case BlackQueenSideCastleWild:
7228 case WhiteHSideCastleFR:
7229 case WhiteASideCastleFR:
7230 case BlackHSideCastleFR:
7231 case BlackASideCastleFR:
7233 fromX = currentMoveString[0] - AAA;
7234 fromY = currentMoveString[1] - ONE;
7235 toX = currentMoveString[2] - AAA;
7236 toY = currentMoveString[3] - ONE;
7237 promoChar = currentMoveString[4];
7241 fromX = moveType == WhiteDrop ?
7242 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7243 (int) CharToPiece(ToLower(currentMoveString[0]));
7245 toX = currentMoveString[2] - AAA;
7246 toY = currentMoveString[3] - ONE;
7247 promoChar = NULLCHAR;
7251 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7252 if (appData.debugMode) {
7253 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7254 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7255 setbuf(debugFP, NULL);
7257 DisplayError(buf, 0);
7259 case ImpossibleMove:
7261 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7262 if (appData.debugMode) {
7263 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7264 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7265 setbuf(debugFP, NULL);
7267 DisplayError(buf, 0);
7269 case (ChessMove) 0: /* end of file */
7270 if (boardIndex < backwardMostMove) {
7271 /* Oops, gap. How did that happen? */
7272 DisplayError(_("Gap in move list"), 0);
7275 backwardMostMove = blackPlaysFirst ? 1 : 0;
7276 if (boardIndex > forwardMostMove) {
7277 forwardMostMove = boardIndex;
7281 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7282 strcat(parseList[boardIndex-1], " ");
7283 strcat(parseList[boardIndex-1], yy_text);
7295 case GameUnfinished:
7296 if (gameMode == IcsExamining) {
7297 if (boardIndex < backwardMostMove) {
7298 /* Oops, gap. How did that happen? */
7301 backwardMostMove = blackPlaysFirst ? 1 : 0;
7304 gameInfo.result = moveType;
7305 p = strchr(yy_text, '{');
7306 if (p == NULL) p = strchr(yy_text, '(');
7309 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7311 q = strchr(p, *p == '{' ? '}' : ')');
7312 if (q != NULL) *q = NULLCHAR;
7315 gameInfo.resultDetails = StrSave(p);
7318 if (boardIndex >= forwardMostMove &&
7319 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7320 backwardMostMove = blackPlaysFirst ? 1 : 0;
7323 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7324 fromY, fromX, toY, toX, promoChar,
7325 parseList[boardIndex]);
7326 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7327 /* currentMoveString is set as a side-effect of yylex */
7328 strcpy(moveList[boardIndex], currentMoveString);
7329 strcat(moveList[boardIndex], "\n");
7331 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7332 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7338 if(gameInfo.variant != VariantShogi)
7339 strcat(parseList[boardIndex - 1], "+");
7343 strcat(parseList[boardIndex - 1], "#");
7350 /* Apply a move to the given board */
7352 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7353 int fromX, fromY, toX, toY;
7357 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7359 /* [HGM] compute & store e.p. status and castling rights for new position */
7360 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7363 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7364 oldEP = (signed char)board[EP_STATUS];
7365 board[EP_STATUS] = EP_NONE;
7367 if( board[toY][toX] != EmptySquare )
7368 board[EP_STATUS] = EP_CAPTURE;
7370 if( board[fromY][fromX] == WhitePawn ) {
7371 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7372 board[EP_STATUS] = EP_PAWN_MOVE;
7374 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7375 gameInfo.variant != VariantBerolina || toX < fromX)
7376 board[EP_STATUS] = toX | berolina;
7377 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7378 gameInfo.variant != VariantBerolina || toX > fromX)
7379 board[EP_STATUS] = toX;
7382 if( board[fromY][fromX] == BlackPawn ) {
7383 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7384 board[EP_STATUS] = EP_PAWN_MOVE;
7385 if( toY-fromY== -2) {
7386 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7387 gameInfo.variant != VariantBerolina || toX < fromX)
7388 board[EP_STATUS] = toX | berolina;
7389 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7390 gameInfo.variant != VariantBerolina || toX > fromX)
7391 board[EP_STATUS] = toX;
7395 for(i=0; i<nrCastlingRights; i++) {
7396 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7397 board[CASTLING][i] == toX && castlingRank[i] == toY
7398 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7403 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7404 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7405 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7407 if (fromX == toX && fromY == toY) return;
7409 if (fromY == DROP_RANK) {
7411 piece = board[toY][toX] = (ChessSquare) fromX;
7413 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7414 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7415 if(gameInfo.variant == VariantKnightmate)
7416 king += (int) WhiteUnicorn - (int) WhiteKing;
7418 /* Code added by Tord: */
7419 /* FRC castling assumed when king captures friendly rook. */
7420 if (board[fromY][fromX] == WhiteKing &&
7421 board[toY][toX] == WhiteRook) {
7422 board[fromY][fromX] = EmptySquare;
7423 board[toY][toX] = EmptySquare;
7425 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7427 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7429 } else if (board[fromY][fromX] == BlackKing &&
7430 board[toY][toX] == BlackRook) {
7431 board[fromY][fromX] = EmptySquare;
7432 board[toY][toX] = EmptySquare;
7434 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7436 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7438 /* End of code added by Tord */
7440 } else if (board[fromY][fromX] == king
7441 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7442 && toY == fromY && toX > fromX+1) {
7443 board[fromY][fromX] = EmptySquare;
7444 board[toY][toX] = king;
7445 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7446 board[fromY][BOARD_RGHT-1] = EmptySquare;
7447 } else if (board[fromY][fromX] == king
7448 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7449 && toY == fromY && toX < fromX-1) {
7450 board[fromY][fromX] = EmptySquare;
7451 board[toY][toX] = king;
7452 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7453 board[fromY][BOARD_LEFT] = EmptySquare;
7454 } else if (board[fromY][fromX] == WhitePawn
7455 && toY == BOARD_HEIGHT-1
7456 && gameInfo.variant != VariantXiangqi
7458 /* white pawn promotion */
7459 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7460 if (board[toY][toX] == EmptySquare) {
7461 board[toY][toX] = WhiteQueen;
7463 if(gameInfo.variant==VariantBughouse ||
7464 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7465 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7466 board[fromY][fromX] = EmptySquare;
7467 } else if ((fromY == BOARD_HEIGHT-4)
7469 && gameInfo.variant != VariantXiangqi
7470 && gameInfo.variant != VariantBerolina
7471 && (board[fromY][fromX] == WhitePawn)
7472 && (board[toY][toX] == EmptySquare)) {
7473 board[fromY][fromX] = EmptySquare;
7474 board[toY][toX] = WhitePawn;
7475 captured = board[toY - 1][toX];
7476 board[toY - 1][toX] = EmptySquare;
7477 } else if ((fromY == BOARD_HEIGHT-4)
7479 && gameInfo.variant == VariantBerolina
7480 && (board[fromY][fromX] == WhitePawn)
7481 && (board[toY][toX] == EmptySquare)) {
7482 board[fromY][fromX] = EmptySquare;
7483 board[toY][toX] = WhitePawn;
7484 if(oldEP & EP_BEROLIN_A) {
7485 captured = board[fromY][fromX-1];
7486 board[fromY][fromX-1] = EmptySquare;
7487 }else{ captured = board[fromY][fromX+1];
7488 board[fromY][fromX+1] = EmptySquare;
7490 } else if (board[fromY][fromX] == king
7491 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7492 && toY == fromY && toX > fromX+1) {
7493 board[fromY][fromX] = EmptySquare;
7494 board[toY][toX] = king;
7495 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7496 board[fromY][BOARD_RGHT-1] = EmptySquare;
7497 } else if (board[fromY][fromX] == king
7498 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7499 && toY == fromY && toX < fromX-1) {
7500 board[fromY][fromX] = EmptySquare;
7501 board[toY][toX] = king;
7502 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7503 board[fromY][BOARD_LEFT] = EmptySquare;
7504 } else if (fromY == 7 && fromX == 3
7505 && board[fromY][fromX] == BlackKing
7506 && toY == 7 && toX == 5) {
7507 board[fromY][fromX] = EmptySquare;
7508 board[toY][toX] = BlackKing;
7509 board[fromY][7] = EmptySquare;
7510 board[toY][4] = BlackRook;
7511 } else if (fromY == 7 && fromX == 3
7512 && board[fromY][fromX] == BlackKing
7513 && toY == 7 && toX == 1) {
7514 board[fromY][fromX] = EmptySquare;
7515 board[toY][toX] = BlackKing;
7516 board[fromY][0] = EmptySquare;
7517 board[toY][2] = BlackRook;
7518 } else if (board[fromY][fromX] == BlackPawn
7520 && gameInfo.variant != VariantXiangqi
7522 /* black pawn promotion */
7523 board[0][toX] = CharToPiece(ToLower(promoChar));
7524 if (board[0][toX] == EmptySquare) {
7525 board[0][toX] = BlackQueen;
7527 if(gameInfo.variant==VariantBughouse ||
7528 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7529 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7530 board[fromY][fromX] = EmptySquare;
7531 } else if ((fromY == 3)
7533 && gameInfo.variant != VariantXiangqi
7534 && gameInfo.variant != VariantBerolina
7535 && (board[fromY][fromX] == BlackPawn)
7536 && (board[toY][toX] == EmptySquare)) {
7537 board[fromY][fromX] = EmptySquare;
7538 board[toY][toX] = BlackPawn;
7539 captured = board[toY + 1][toX];
7540 board[toY + 1][toX] = EmptySquare;
7541 } else if ((fromY == 3)
7543 && gameInfo.variant == VariantBerolina
7544 && (board[fromY][fromX] == BlackPawn)
7545 && (board[toY][toX] == EmptySquare)) {
7546 board[fromY][fromX] = EmptySquare;
7547 board[toY][toX] = BlackPawn;
7548 if(oldEP & EP_BEROLIN_A) {
7549 captured = board[fromY][fromX-1];
7550 board[fromY][fromX-1] = EmptySquare;
7551 }else{ captured = board[fromY][fromX+1];
7552 board[fromY][fromX+1] = EmptySquare;
7555 board[toY][toX] = board[fromY][fromX];
7556 board[fromY][fromX] = EmptySquare;
7559 /* [HGM] now we promote for Shogi, if needed */
7560 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7561 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7564 if (gameInfo.holdingsWidth != 0) {
7566 /* !!A lot more code needs to be written to support holdings */
7567 /* [HGM] OK, so I have written it. Holdings are stored in the */
7568 /* penultimate board files, so they are automaticlly stored */
7569 /* in the game history. */
7570 if (fromY == DROP_RANK) {
7571 /* Delete from holdings, by decreasing count */
7572 /* and erasing image if necessary */
7574 if(p < (int) BlackPawn) { /* white drop */
7575 p -= (int)WhitePawn;
7576 p = PieceToNumber((ChessSquare)p);
7577 if(p >= gameInfo.holdingsSize) p = 0;
7578 if(--board[p][BOARD_WIDTH-2] <= 0)
7579 board[p][BOARD_WIDTH-1] = EmptySquare;
7580 if((int)board[p][BOARD_WIDTH-2] < 0)
7581 board[p][BOARD_WIDTH-2] = 0;
7582 } else { /* black drop */
7583 p -= (int)BlackPawn;
7584 p = PieceToNumber((ChessSquare)p);
7585 if(p >= gameInfo.holdingsSize) p = 0;
7586 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7587 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7588 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7589 board[BOARD_HEIGHT-1-p][1] = 0;
7592 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7593 && gameInfo.variant != VariantBughouse ) {
7594 /* [HGM] holdings: Add to holdings, if holdings exist */
7595 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7596 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7597 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7600 if (p >= (int) BlackPawn) {
7601 p -= (int)BlackPawn;
7602 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7603 /* in Shogi restore piece to its original first */
7604 captured = (ChessSquare) (DEMOTED captured);
7607 p = PieceToNumber((ChessSquare)p);
7608 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7609 board[p][BOARD_WIDTH-2]++;
7610 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7612 p -= (int)WhitePawn;
7613 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7614 captured = (ChessSquare) (DEMOTED captured);
7617 p = PieceToNumber((ChessSquare)p);
7618 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7619 board[BOARD_HEIGHT-1-p][1]++;
7620 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7623 } else if (gameInfo.variant == VariantAtomic) {
7624 if (captured != EmptySquare) {
7626 for (y = toY-1; y <= toY+1; y++) {
7627 for (x = toX-1; x <= toX+1; x++) {
7628 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7629 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7630 board[y][x] = EmptySquare;
7634 board[toY][toX] = EmptySquare;
7637 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7638 /* [HGM] Shogi promotions */
7639 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7642 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7643 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7644 // [HGM] superchess: take promotion piece out of holdings
7645 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7646 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7647 if(!--board[k][BOARD_WIDTH-2])
7648 board[k][BOARD_WIDTH-1] = EmptySquare;
7650 if(!--board[BOARD_HEIGHT-1-k][1])
7651 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7657 /* Updates forwardMostMove */
7659 MakeMove(fromX, fromY, toX, toY, promoChar)
7660 int fromX, fromY, toX, toY;
7663 // forwardMostMove++; // [HGM] bare: moved downstream
7665 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7666 int timeLeft; static int lastLoadFlag=0; int king, piece;
7667 piece = boards[forwardMostMove][fromY][fromX];
7668 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7669 if(gameInfo.variant == VariantKnightmate)
7670 king += (int) WhiteUnicorn - (int) WhiteKing;
7671 if(forwardMostMove == 0) {
7673 fprintf(serverMoves, "%s;", second.tidy);
7674 fprintf(serverMoves, "%s;", first.tidy);
7675 if(!blackPlaysFirst)
7676 fprintf(serverMoves, "%s;", second.tidy);
7677 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7678 lastLoadFlag = loadFlag;
7680 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7681 // print castling suffix
7682 if( toY == fromY && piece == king ) {
7684 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7686 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7689 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7690 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7691 boards[forwardMostMove][toY][toX] == EmptySquare
7693 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7695 if(promoChar != NULLCHAR)
7696 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7698 fprintf(serverMoves, "/%d/%d",
7699 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7700 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7701 else timeLeft = blackTimeRemaining/1000;
7702 fprintf(serverMoves, "/%d", timeLeft);
7704 fflush(serverMoves);
7707 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7708 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7712 if (commentList[forwardMostMove+1] != NULL) {
7713 free(commentList[forwardMostMove+1]);
7714 commentList[forwardMostMove+1] = NULL;
7716 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7717 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7718 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7719 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7720 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7721 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7722 gameInfo.result = GameUnfinished;
7723 if (gameInfo.resultDetails != NULL) {
7724 free(gameInfo.resultDetails);
7725 gameInfo.resultDetails = NULL;
7727 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7728 moveList[forwardMostMove - 1]);
7729 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7730 PosFlags(forwardMostMove - 1),
7731 fromY, fromX, toY, toX, promoChar,
7732 parseList[forwardMostMove - 1]);
7733 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7739 if(gameInfo.variant != VariantShogi)
7740 strcat(parseList[forwardMostMove - 1], "+");
7744 strcat(parseList[forwardMostMove - 1], "#");
7747 if (appData.debugMode) {
7748 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7753 /* Updates currentMove if not pausing */
7755 ShowMove(fromX, fromY, toX, toY)
7757 int instant = (gameMode == PlayFromGameFile) ?
7758 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7759 if(appData.noGUI) return;
7760 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7762 if (forwardMostMove == currentMove + 1) {
7763 AnimateMove(boards[forwardMostMove - 1],
7764 fromX, fromY, toX, toY);
7766 if (appData.highlightLastMove) {
7767 SetHighlights(fromX, fromY, toX, toY);
7770 currentMove = forwardMostMove;
7773 if (instant) return;
7775 DisplayMove(currentMove - 1);
7776 DrawPosition(FALSE, boards[currentMove]);
7777 DisplayBothClocks();
7778 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7781 void SendEgtPath(ChessProgramState *cps)
7782 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7783 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7785 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7788 char c, *q = name+1, *r, *s;
7790 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7791 while(*p && *p != ',') *q++ = *p++;
7793 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7794 strcmp(name, ",nalimov:") == 0 ) {
7795 // take nalimov path from the menu-changeable option first, if it is defined
7796 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7797 SendToProgram(buf,cps); // send egtbpath command for nalimov
7799 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7800 (s = StrStr(appData.egtFormats, name)) != NULL) {
7801 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7802 s = r = StrStr(s, ":") + 1; // beginning of path info
7803 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7804 c = *r; *r = 0; // temporarily null-terminate path info
7805 *--q = 0; // strip of trailig ':' from name
7806 sprintf(buf, "egtpath %s %s\n", name+1, s);
7808 SendToProgram(buf,cps); // send egtbpath command for this format
7810 if(*p == ',') p++; // read away comma to position for next format name
7815 InitChessProgram(cps, setup)
7816 ChessProgramState *cps;
7817 int setup; /* [HGM] needed to setup FRC opening position */
7819 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7820 if (appData.noChessProgram) return;
7821 hintRequested = FALSE;
7822 bookRequested = FALSE;
7824 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7825 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7826 if(cps->memSize) { /* [HGM] memory */
7827 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7828 SendToProgram(buf, cps);
7830 SendEgtPath(cps); /* [HGM] EGT */
7831 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7832 sprintf(buf, "cores %d\n", appData.smpCores);
7833 SendToProgram(buf, cps);
7836 SendToProgram(cps->initString, cps);
7837 if (gameInfo.variant != VariantNormal &&
7838 gameInfo.variant != VariantLoadable
7839 /* [HGM] also send variant if board size non-standard */
7840 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7842 char *v = VariantName(gameInfo.variant);
7843 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7844 /* [HGM] in protocol 1 we have to assume all variants valid */
7845 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7846 DisplayFatalError(buf, 0, 1);
7850 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7851 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7852 if( gameInfo.variant == VariantXiangqi )
7853 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7854 if( gameInfo.variant == VariantShogi )
7855 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7856 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7857 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7858 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7859 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7860 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7861 if( gameInfo.variant == VariantCourier )
7862 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7863 if( gameInfo.variant == VariantSuper )
7864 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7865 if( gameInfo.variant == VariantGreat )
7866 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7869 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7870 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7871 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7872 if(StrStr(cps->variants, b) == NULL) {
7873 // specific sized variant not known, check if general sizing allowed
7874 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7875 if(StrStr(cps->variants, "boardsize") == NULL) {
7876 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7877 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7878 DisplayFatalError(buf, 0, 1);
7881 /* [HGM] here we really should compare with the maximum supported board size */
7884 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7885 sprintf(buf, "variant %s\n", b);
7886 SendToProgram(buf, cps);
7888 currentlyInitializedVariant = gameInfo.variant;
7890 /* [HGM] send opening position in FRC to first engine */
7892 SendToProgram("force\n", cps);
7894 /* engine is now in force mode! Set flag to wake it up after first move. */
7895 setboardSpoiledMachineBlack = 1;
7899 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7900 SendToProgram(buf, cps);
7902 cps->maybeThinking = FALSE;
7903 cps->offeredDraw = 0;
7904 if (!appData.icsActive) {
7905 SendTimeControl(cps, movesPerSession, timeControl,
7906 timeIncrement, appData.searchDepth,
7909 if (appData.showThinking
7910 // [HGM] thinking: four options require thinking output to be sent
7911 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7913 SendToProgram("post\n", cps);
7915 SendToProgram("hard\n", cps);
7916 if (!appData.ponderNextMove) {
7917 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7918 it without being sure what state we are in first. "hard"
7919 is not a toggle, so that one is OK.
7921 SendToProgram("easy\n", cps);
7924 sprintf(buf, "ping %d\n", ++cps->lastPing);
7925 SendToProgram(buf, cps);
7927 cps->initDone = TRUE;
7932 StartChessProgram(cps)
7933 ChessProgramState *cps;
7938 if (appData.noChessProgram) return;
7939 cps->initDone = FALSE;
7941 if (strcmp(cps->host, "localhost") == 0) {
7942 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7943 } else if (*appData.remoteShell == NULLCHAR) {
7944 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7946 if (*appData.remoteUser == NULLCHAR) {
7947 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7950 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7951 cps->host, appData.remoteUser, cps->program);
7953 err = StartChildProcess(buf, "", &cps->pr);
7957 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7958 DisplayFatalError(buf, err, 1);
7964 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7965 if (cps->protocolVersion > 1) {
7966 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7967 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7968 cps->comboCnt = 0; // and values of combo boxes
7969 SendToProgram(buf, cps);
7971 SendToProgram("xboard\n", cps);
7977 TwoMachinesEventIfReady P((void))
7979 if (first.lastPing != first.lastPong) {
7980 DisplayMessage("", _("Waiting for first chess program"));
7981 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7984 if (second.lastPing != second.lastPong) {
7985 DisplayMessage("", _("Waiting for second chess program"));
7986 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7994 NextMatchGame P((void))
7996 int index; /* [HGM] autoinc: step load index during match */
7998 if (*appData.loadGameFile != NULLCHAR) {
7999 index = appData.loadGameIndex;
8000 if(index < 0) { // [HGM] autoinc
8001 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8002 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8004 LoadGameFromFile(appData.loadGameFile,
8006 appData.loadGameFile, FALSE);
8007 } else if (*appData.loadPositionFile != NULLCHAR) {
8008 index = appData.loadPositionIndex;
8009 if(index < 0) { // [HGM] autoinc
8010 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8011 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8013 LoadPositionFromFile(appData.loadPositionFile,
8015 appData.loadPositionFile);
8017 TwoMachinesEventIfReady();
8020 void UserAdjudicationEvent( int result )
8022 ChessMove gameResult = GameIsDrawn;
8025 gameResult = WhiteWins;
8027 else if( result < 0 ) {
8028 gameResult = BlackWins;
8031 if( gameMode == TwoMachinesPlay ) {
8032 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8037 // [HGM] save: calculate checksum of game to make games easily identifiable
8038 int StringCheckSum(char *s)
8041 if(s==NULL) return 0;
8042 while(*s) i = i*259 + *s++;
8049 for(i=backwardMostMove; i<forwardMostMove; i++) {
8050 sum += pvInfoList[i].depth;
8051 sum += StringCheckSum(parseList[i]);
8052 sum += StringCheckSum(commentList[i]);
8055 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8056 return sum + StringCheckSum(commentList[i]);
8057 } // end of save patch
8060 GameEnds(result, resultDetails, whosays)
8062 char *resultDetails;
8065 GameMode nextGameMode;
8069 if(endingGame) return; /* [HGM] crash: forbid recursion */
8072 if (appData.debugMode) {
8073 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8074 result, resultDetails ? resultDetails : "(null)", whosays);
8077 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8078 /* If we are playing on ICS, the server decides when the
8079 game is over, but the engine can offer to draw, claim
8083 if (appData.zippyPlay && first.initDone) {
8084 if (result == GameIsDrawn) {
8085 /* In case draw still needs to be claimed */
8086 SendToICS(ics_prefix);
8087 SendToICS("draw\n");
8088 } else if (StrCaseStr(resultDetails, "resign")) {
8089 SendToICS(ics_prefix);
8090 SendToICS("resign\n");
8094 endingGame = 0; /* [HGM] crash */
8098 /* If we're loading the game from a file, stop */
8099 if (whosays == GE_FILE) {
8100 (void) StopLoadGameTimer();
8104 /* Cancel draw offers */
8105 first.offeredDraw = second.offeredDraw = 0;
8107 /* If this is an ICS game, only ICS can really say it's done;
8108 if not, anyone can. */
8109 isIcsGame = (gameMode == IcsPlayingWhite ||
8110 gameMode == IcsPlayingBlack ||
8111 gameMode == IcsObserving ||
8112 gameMode == IcsExamining);
8114 if (!isIcsGame || whosays == GE_ICS) {
8115 /* OK -- not an ICS game, or ICS said it was done */
8117 if (!isIcsGame && !appData.noChessProgram)
8118 SetUserThinkingEnables();
8120 /* [HGM] if a machine claims the game end we verify this claim */
8121 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8122 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8124 ChessMove trueResult = (ChessMove) -1;
8126 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8127 first.twoMachinesColor[0] :
8128 second.twoMachinesColor[0] ;
8130 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8131 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8132 /* [HGM] verify: engine mate claims accepted if they were flagged */
8133 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8135 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8136 /* [HGM] verify: engine mate claims accepted if they were flagged */
8137 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8139 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8140 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8143 // now verify win claims, but not in drop games, as we don't understand those yet
8144 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8145 || gameInfo.variant == VariantGreat) &&
8146 (result == WhiteWins && claimer == 'w' ||
8147 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8148 if (appData.debugMode) {
8149 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8150 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8152 if(result != trueResult) {
8153 sprintf(buf, "False win claim: '%s'", resultDetails);
8154 result = claimer == 'w' ? BlackWins : WhiteWins;
8155 resultDetails = buf;
8158 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8159 && (forwardMostMove <= backwardMostMove ||
8160 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8161 (claimer=='b')==(forwardMostMove&1))
8163 /* [HGM] verify: draws that were not flagged are false claims */
8164 sprintf(buf, "False draw claim: '%s'", resultDetails);
8165 result = claimer == 'w' ? BlackWins : WhiteWins;
8166 resultDetails = buf;
8168 /* (Claiming a loss is accepted no questions asked!) */
8170 /* [HGM] bare: don't allow bare King to win */
8171 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8172 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8173 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8174 && result != GameIsDrawn)
8175 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8176 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8177 int p = (signed char)boards[forwardMostMove][i][j] - color;
8178 if(p >= 0 && p <= (int)WhiteKing) k++;
8180 if (appData.debugMode) {
8181 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8182 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8185 result = GameIsDrawn;
8186 sprintf(buf, "%s but bare king", resultDetails);
8187 resultDetails = buf;
8193 if(serverMoves != NULL && !loadFlag) { char c = '=';
8194 if(result==WhiteWins) c = '+';
8195 if(result==BlackWins) c = '-';
8196 if(resultDetails != NULL)
8197 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8199 if (resultDetails != NULL) {
8200 gameInfo.result = result;
8201 gameInfo.resultDetails = StrSave(resultDetails);
8203 /* display last move only if game was not loaded from file */
8204 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8205 DisplayMove(currentMove - 1);
8207 if (forwardMostMove != 0) {
8208 if (gameMode != PlayFromGameFile && gameMode != EditGame
8209 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8211 if (*appData.saveGameFile != NULLCHAR) {
8212 SaveGameToFile(appData.saveGameFile, TRUE);
8213 } else if (appData.autoSaveGames) {
8216 if (*appData.savePositionFile != NULLCHAR) {
8217 SavePositionToFile(appData.savePositionFile);
8222 /* Tell program how game ended in case it is learning */
8223 /* [HGM] Moved this to after saving the PGN, just in case */
8224 /* engine died and we got here through time loss. In that */
8225 /* case we will get a fatal error writing the pipe, which */
8226 /* would otherwise lose us the PGN. */
8227 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8228 /* output during GameEnds should never be fatal anymore */
8229 if (gameMode == MachinePlaysWhite ||
8230 gameMode == MachinePlaysBlack ||
8231 gameMode == TwoMachinesPlay ||
8232 gameMode == IcsPlayingWhite ||
8233 gameMode == IcsPlayingBlack ||
8234 gameMode == BeginningOfGame) {
8236 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8238 if (first.pr != NoProc) {
8239 SendToProgram(buf, &first);
8241 if (second.pr != NoProc &&
8242 gameMode == TwoMachinesPlay) {
8243 SendToProgram(buf, &second);
8248 if (appData.icsActive) {
8249 if (appData.quietPlay &&
8250 (gameMode == IcsPlayingWhite ||
8251 gameMode == IcsPlayingBlack)) {
8252 SendToICS(ics_prefix);
8253 SendToICS("set shout 1\n");
8255 nextGameMode = IcsIdle;
8256 ics_user_moved = FALSE;
8257 /* clean up premove. It's ugly when the game has ended and the
8258 * premove highlights are still on the board.
8262 ClearPremoveHighlights();
8263 DrawPosition(FALSE, boards[currentMove]);
8265 if (whosays == GE_ICS) {
8268 if (gameMode == IcsPlayingWhite)
8270 else if(gameMode == IcsPlayingBlack)
8274 if (gameMode == IcsPlayingBlack)
8276 else if(gameMode == IcsPlayingWhite)
8283 PlayIcsUnfinishedSound();
8286 } else if (gameMode == EditGame ||
8287 gameMode == PlayFromGameFile ||
8288 gameMode == AnalyzeMode ||
8289 gameMode == AnalyzeFile) {
8290 nextGameMode = gameMode;
8292 nextGameMode = EndOfGame;
8297 nextGameMode = gameMode;
8300 if (appData.noChessProgram) {
8301 gameMode = nextGameMode;
8303 endingGame = 0; /* [HGM] crash */
8308 /* Put first chess program into idle state */
8309 if (first.pr != NoProc &&
8310 (gameMode == MachinePlaysWhite ||
8311 gameMode == MachinePlaysBlack ||
8312 gameMode == TwoMachinesPlay ||
8313 gameMode == IcsPlayingWhite ||
8314 gameMode == IcsPlayingBlack ||
8315 gameMode == BeginningOfGame)) {
8316 SendToProgram("force\n", &first);
8317 if (first.usePing) {
8319 sprintf(buf, "ping %d\n", ++first.lastPing);
8320 SendToProgram(buf, &first);
8323 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8324 /* Kill off first chess program */
8325 if (first.isr != NULL)
8326 RemoveInputSource(first.isr);
8329 if (first.pr != NoProc) {
8331 DoSleep( appData.delayBeforeQuit );
8332 SendToProgram("quit\n", &first);
8333 DoSleep( appData.delayAfterQuit );
8334 DestroyChildProcess(first.pr, first.useSigterm);
8339 /* Put second chess program into idle state */
8340 if (second.pr != NoProc &&
8341 gameMode == TwoMachinesPlay) {
8342 SendToProgram("force\n", &second);
8343 if (second.usePing) {
8345 sprintf(buf, "ping %d\n", ++second.lastPing);
8346 SendToProgram(buf, &second);
8349 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8350 /* Kill off second chess program */
8351 if (second.isr != NULL)
8352 RemoveInputSource(second.isr);
8355 if (second.pr != NoProc) {
8356 DoSleep( appData.delayBeforeQuit );
8357 SendToProgram("quit\n", &second);
8358 DoSleep( appData.delayAfterQuit );
8359 DestroyChildProcess(second.pr, second.useSigterm);
8364 if (matchMode && gameMode == TwoMachinesPlay) {
8367 if (first.twoMachinesColor[0] == 'w') {
8374 if (first.twoMachinesColor[0] == 'b') {
8383 if (matchGame < appData.matchGames) {
8385 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8386 tmp = first.twoMachinesColor;
8387 first.twoMachinesColor = second.twoMachinesColor;
8388 second.twoMachinesColor = tmp;
8390 gameMode = nextGameMode;
8392 if(appData.matchPause>10000 || appData.matchPause<10)
8393 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8394 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8395 endingGame = 0; /* [HGM] crash */
8399 gameMode = nextGameMode;
8400 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8401 first.tidy, second.tidy,
8402 first.matchWins, second.matchWins,
8403 appData.matchGames - (first.matchWins + second.matchWins));
8404 DisplayFatalError(buf, 0, 0);
8407 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8408 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8410 gameMode = nextGameMode;
8412 endingGame = 0; /* [HGM] crash */
8415 /* Assumes program was just initialized (initString sent).
8416 Leaves program in force mode. */
8418 FeedMovesToProgram(cps, upto)
8419 ChessProgramState *cps;
8424 if (appData.debugMode)
8425 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8426 startedFromSetupPosition ? "position and " : "",
8427 backwardMostMove, upto, cps->which);
8428 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8429 // [HGM] variantswitch: make engine aware of new variant
8430 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8431 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8432 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8433 SendToProgram(buf, cps);
8434 currentlyInitializedVariant = gameInfo.variant;
8436 SendToProgram("force\n", cps);
8437 if (startedFromSetupPosition) {
8438 SendBoard(cps, backwardMostMove);
8439 if (appData.debugMode) {
8440 fprintf(debugFP, "feedMoves\n");
8443 for (i = backwardMostMove; i < upto; i++) {
8444 SendMoveToProgram(i, cps);
8450 ResurrectChessProgram()
8452 /* The chess program may have exited.
8453 If so, restart it and feed it all the moves made so far. */
8455 if (appData.noChessProgram || first.pr != NoProc) return;
8457 StartChessProgram(&first);
8458 InitChessProgram(&first, FALSE);
8459 FeedMovesToProgram(&first, currentMove);
8461 if (!first.sendTime) {
8462 /* can't tell gnuchess what its clock should read,
8463 so we bow to its notion. */
8465 timeRemaining[0][currentMove] = whiteTimeRemaining;
8466 timeRemaining[1][currentMove] = blackTimeRemaining;
8469 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8470 appData.icsEngineAnalyze) && first.analysisSupport) {
8471 SendToProgram("analyze\n", &first);
8472 first.analyzing = TRUE;
8485 if (appData.debugMode) {
8486 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8487 redraw, init, gameMode);
8489 CleanupTail(); // [HGM] vari: delete any stored variations
8490 pausing = pauseExamInvalid = FALSE;
8491 startedFromSetupPosition = blackPlaysFirst = FALSE;
8493 whiteFlag = blackFlag = FALSE;
8494 userOfferedDraw = FALSE;
8495 hintRequested = bookRequested = FALSE;
8496 first.maybeThinking = FALSE;
8497 second.maybeThinking = FALSE;
8498 first.bookSuspend = FALSE; // [HGM] book
8499 second.bookSuspend = FALSE;
8500 thinkOutput[0] = NULLCHAR;
8501 lastHint[0] = NULLCHAR;
8502 ClearGameInfo(&gameInfo);
8503 gameInfo.variant = StringToVariant(appData.variant);
8504 ics_user_moved = ics_clock_paused = FALSE;
8505 ics_getting_history = H_FALSE;
8507 white_holding[0] = black_holding[0] = NULLCHAR;
8508 ClearProgramStats();
8509 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8513 flipView = appData.flipView;
8514 ClearPremoveHighlights();
8516 alarmSounded = FALSE;
8518 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8519 if(appData.serverMovesName != NULL) {
8520 /* [HGM] prepare to make moves file for broadcasting */
8521 clock_t t = clock();
8522 if(serverMoves != NULL) fclose(serverMoves);
8523 serverMoves = fopen(appData.serverMovesName, "r");
8524 if(serverMoves != NULL) {
8525 fclose(serverMoves);
8526 /* delay 15 sec before overwriting, so all clients can see end */
8527 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8529 serverMoves = fopen(appData.serverMovesName, "w");
8533 gameMode = BeginningOfGame;
8535 if(appData.icsActive) gameInfo.variant = VariantNormal;
8536 currentMove = forwardMostMove = backwardMostMove = 0;
8537 InitPosition(redraw);
8538 for (i = 0; i < MAX_MOVES; i++) {
8539 if (commentList[i] != NULL) {
8540 free(commentList[i]);
8541 commentList[i] = NULL;
8545 timeRemaining[0][0] = whiteTimeRemaining;
8546 timeRemaining[1][0] = blackTimeRemaining;
8547 if (first.pr == NULL) {
8548 StartChessProgram(&first);
8551 InitChessProgram(&first, startedFromSetupPosition);
8554 DisplayMessage("", "");
8555 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8556 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8563 if (!AutoPlayOneMove())
8565 if (matchMode || appData.timeDelay == 0)
8567 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8569 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8578 int fromX, fromY, toX, toY;
8580 if (appData.debugMode) {
8581 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8584 if (gameMode != PlayFromGameFile)
8587 if (currentMove >= forwardMostMove) {
8588 gameMode = EditGame;
8591 /* [AS] Clear current move marker at the end of a game */
8592 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8597 toX = moveList[currentMove][2] - AAA;
8598 toY = moveList[currentMove][3] - ONE;
8600 if (moveList[currentMove][1] == '@') {
8601 if (appData.highlightLastMove) {
8602 SetHighlights(-1, -1, toX, toY);
8605 fromX = moveList[currentMove][0] - AAA;
8606 fromY = moveList[currentMove][1] - ONE;
8608 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8610 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8612 if (appData.highlightLastMove) {
8613 SetHighlights(fromX, fromY, toX, toY);
8616 DisplayMove(currentMove);
8617 SendMoveToProgram(currentMove++, &first);
8618 DisplayBothClocks();
8619 DrawPosition(FALSE, boards[currentMove]);
8620 // [HGM] PV info: always display, routine tests if empty
8621 DisplayComment(currentMove - 1, commentList[currentMove]);
8627 LoadGameOneMove(readAhead)
8628 ChessMove readAhead;
8630 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8631 char promoChar = NULLCHAR;
8636 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8637 gameMode != AnalyzeMode && gameMode != Training) {
8642 yyboardindex = forwardMostMove;
8643 if (readAhead != (ChessMove)0) {
8644 moveType = readAhead;
8646 if (gameFileFP == NULL)
8648 moveType = (ChessMove) yylex();
8654 if (appData.debugMode)
8655 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8658 /* append the comment but don't display it */
8659 AppendComment(currentMove, p, FALSE);
8662 case WhiteCapturesEnPassant:
8663 case BlackCapturesEnPassant:
8664 case WhitePromotionChancellor:
8665 case BlackPromotionChancellor:
8666 case WhitePromotionArchbishop:
8667 case BlackPromotionArchbishop:
8668 case WhitePromotionCentaur:
8669 case BlackPromotionCentaur:
8670 case WhitePromotionQueen:
8671 case BlackPromotionQueen:
8672 case WhitePromotionRook:
8673 case BlackPromotionRook:
8674 case WhitePromotionBishop:
8675 case BlackPromotionBishop:
8676 case WhitePromotionKnight:
8677 case BlackPromotionKnight:
8678 case WhitePromotionKing:
8679 case BlackPromotionKing:
8681 case WhiteKingSideCastle:
8682 case WhiteQueenSideCastle:
8683 case BlackKingSideCastle:
8684 case BlackQueenSideCastle:
8685 case WhiteKingSideCastleWild:
8686 case WhiteQueenSideCastleWild:
8687 case BlackKingSideCastleWild:
8688 case BlackQueenSideCastleWild:
8690 case WhiteHSideCastleFR:
8691 case WhiteASideCastleFR:
8692 case BlackHSideCastleFR:
8693 case BlackASideCastleFR:
8695 if (appData.debugMode)
8696 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8697 fromX = currentMoveString[0] - AAA;
8698 fromY = currentMoveString[1] - ONE;
8699 toX = currentMoveString[2] - AAA;
8700 toY = currentMoveString[3] - ONE;
8701 promoChar = currentMoveString[4];
8706 if (appData.debugMode)
8707 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8708 fromX = moveType == WhiteDrop ?
8709 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8710 (int) CharToPiece(ToLower(currentMoveString[0]));
8712 toX = currentMoveString[2] - AAA;
8713 toY = currentMoveString[3] - ONE;
8719 case GameUnfinished:
8720 if (appData.debugMode)
8721 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8722 p = strchr(yy_text, '{');
8723 if (p == NULL) p = strchr(yy_text, '(');
8726 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8728 q = strchr(p, *p == '{' ? '}' : ')');
8729 if (q != NULL) *q = NULLCHAR;
8732 GameEnds(moveType, p, GE_FILE);
8734 if (cmailMsgLoaded) {
8736 flipView = WhiteOnMove(currentMove);
8737 if (moveType == GameUnfinished) flipView = !flipView;
8738 if (appData.debugMode)
8739 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8743 case (ChessMove) 0: /* end of file */
8744 if (appData.debugMode)
8745 fprintf(debugFP, "Parser hit end of file\n");
8746 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8752 if (WhiteOnMove(currentMove)) {
8753 GameEnds(BlackWins, "Black mates", GE_FILE);
8755 GameEnds(WhiteWins, "White mates", GE_FILE);
8759 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8766 if (lastLoadGameStart == GNUChessGame) {
8767 /* GNUChessGames have numbers, but they aren't move numbers */
8768 if (appData.debugMode)
8769 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8770 yy_text, (int) moveType);
8771 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8773 /* else fall thru */
8778 /* Reached start of next game in file */
8779 if (appData.debugMode)
8780 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8781 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8787 if (WhiteOnMove(currentMove)) {
8788 GameEnds(BlackWins, "Black mates", GE_FILE);
8790 GameEnds(WhiteWins, "White mates", GE_FILE);
8794 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8800 case PositionDiagram: /* should not happen; ignore */
8801 case ElapsedTime: /* ignore */
8802 case NAG: /* ignore */
8803 if (appData.debugMode)
8804 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8805 yy_text, (int) moveType);
8806 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8809 if (appData.testLegality) {
8810 if (appData.debugMode)
8811 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8812 sprintf(move, _("Illegal move: %d.%s%s"),
8813 (forwardMostMove / 2) + 1,
8814 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8815 DisplayError(move, 0);
8818 if (appData.debugMode)
8819 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8820 yy_text, currentMoveString);
8821 fromX = currentMoveString[0] - AAA;
8822 fromY = currentMoveString[1] - ONE;
8823 toX = currentMoveString[2] - AAA;
8824 toY = currentMoveString[3] - ONE;
8825 promoChar = currentMoveString[4];
8830 if (appData.debugMode)
8831 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8832 sprintf(move, _("Ambiguous move: %d.%s%s"),
8833 (forwardMostMove / 2) + 1,
8834 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8835 DisplayError(move, 0);
8840 case ImpossibleMove:
8841 if (appData.debugMode)
8842 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8843 sprintf(move, _("Illegal move: %d.%s%s"),
8844 (forwardMostMove / 2) + 1,
8845 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8846 DisplayError(move, 0);
8852 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8853 DrawPosition(FALSE, boards[currentMove]);
8854 DisplayBothClocks();
8855 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8856 DisplayComment(currentMove - 1, commentList[currentMove]);
8858 (void) StopLoadGameTimer();
8860 cmailOldMove = forwardMostMove;
8863 /* currentMoveString is set as a side-effect of yylex */
8864 strcat(currentMoveString, "\n");
8865 strcpy(moveList[forwardMostMove], currentMoveString);
8867 thinkOutput[0] = NULLCHAR;
8868 MakeMove(fromX, fromY, toX, toY, promoChar);
8869 currentMove = forwardMostMove;
8874 /* Load the nth game from the given file */
8876 LoadGameFromFile(filename, n, title, useList)
8880 /*Boolean*/ int useList;
8885 if (strcmp(filename, "-") == 0) {
8889 f = fopen(filename, "rb");
8891 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8892 DisplayError(buf, errno);
8896 if (fseek(f, 0, 0) == -1) {
8897 /* f is not seekable; probably a pipe */
8900 if (useList && n == 0) {
8901 int error = GameListBuild(f);
8903 DisplayError(_("Cannot build game list"), error);
8904 } else if (!ListEmpty(&gameList) &&
8905 ((ListGame *) gameList.tailPred)->number > 1) {
8906 GameListPopUp(f, title);
8913 return LoadGame(f, n, title, FALSE);
8918 MakeRegisteredMove()
8920 int fromX, fromY, toX, toY;
8922 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8923 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8926 if (appData.debugMode)
8927 fprintf(debugFP, "Restoring %s for game %d\n",
8928 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8930 thinkOutput[0] = NULLCHAR;
8931 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8932 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8933 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8934 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8935 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8936 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8937 MakeMove(fromX, fromY, toX, toY, promoChar);
8938 ShowMove(fromX, fromY, toX, toY);
8940 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8947 if (WhiteOnMove(currentMove)) {
8948 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8950 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8955 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8962 if (WhiteOnMove(currentMove)) {
8963 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8965 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8970 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8981 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8983 CmailLoadGame(f, gameNumber, title, useList)
8991 if (gameNumber > nCmailGames) {
8992 DisplayError(_("No more games in this message"), 0);
8995 if (f == lastLoadGameFP) {
8996 int offset = gameNumber - lastLoadGameNumber;
8998 cmailMsg[0] = NULLCHAR;
8999 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9000 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9001 nCmailMovesRegistered--;
9003 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9004 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9005 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9008 if (! RegisterMove()) return FALSE;
9012 retVal = LoadGame(f, gameNumber, title, useList);
9014 /* Make move registered during previous look at this game, if any */
9015 MakeRegisteredMove();
9017 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9018 commentList[currentMove]
9019 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9020 DisplayComment(currentMove - 1, commentList[currentMove]);
9026 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9031 int gameNumber = lastLoadGameNumber + offset;
9032 if (lastLoadGameFP == NULL) {
9033 DisplayError(_("No game has been loaded yet"), 0);
9036 if (gameNumber <= 0) {
9037 DisplayError(_("Can't back up any further"), 0);
9040 if (cmailMsgLoaded) {
9041 return CmailLoadGame(lastLoadGameFP, gameNumber,
9042 lastLoadGameTitle, lastLoadGameUseList);
9044 return LoadGame(lastLoadGameFP, gameNumber,
9045 lastLoadGameTitle, lastLoadGameUseList);
9051 /* Load the nth game from open file f */
9053 LoadGame(f, gameNumber, title, useList)
9061 int gn = gameNumber;
9062 ListGame *lg = NULL;
9065 GameMode oldGameMode;
9066 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9068 if (appData.debugMode)
9069 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9071 if (gameMode == Training )
9072 SetTrainingModeOff();
9074 oldGameMode = gameMode;
9075 if (gameMode != BeginningOfGame) {
9080 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9081 fclose(lastLoadGameFP);
9085 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9088 fseek(f, lg->offset, 0);
9089 GameListHighlight(gameNumber);
9093 DisplayError(_("Game number out of range"), 0);
9098 if (fseek(f, 0, 0) == -1) {
9099 if (f == lastLoadGameFP ?
9100 gameNumber == lastLoadGameNumber + 1 :
9104 DisplayError(_("Can't seek on game file"), 0);
9110 lastLoadGameNumber = gameNumber;
9111 strcpy(lastLoadGameTitle, title);
9112 lastLoadGameUseList = useList;
9116 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9117 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9118 lg->gameInfo.black);
9120 } else if (*title != NULLCHAR) {
9121 if (gameNumber > 1) {
9122 sprintf(buf, "%s %d", title, gameNumber);
9125 DisplayTitle(title);
9129 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9130 gameMode = PlayFromGameFile;
9134 currentMove = forwardMostMove = backwardMostMove = 0;
9135 CopyBoard(boards[0], initialPosition);
9139 * Skip the first gn-1 games in the file.
9140 * Also skip over anything that precedes an identifiable
9141 * start of game marker, to avoid being confused by
9142 * garbage at the start of the file. Currently
9143 * recognized start of game markers are the move number "1",
9144 * the pattern "gnuchess .* game", the pattern
9145 * "^[#;%] [^ ]* game file", and a PGN tag block.
9146 * A game that starts with one of the latter two patterns
9147 * will also have a move number 1, possibly
9148 * following a position diagram.
9149 * 5-4-02: Let's try being more lenient and allowing a game to
9150 * start with an unnumbered move. Does that break anything?
9152 cm = lastLoadGameStart = (ChessMove) 0;
9154 yyboardindex = forwardMostMove;
9155 cm = (ChessMove) yylex();
9158 if (cmailMsgLoaded) {
9159 nCmailGames = CMAIL_MAX_GAMES - gn;
9162 DisplayError(_("Game not found in file"), 0);
9169 lastLoadGameStart = cm;
9173 switch (lastLoadGameStart) {
9180 gn--; /* count this game */
9181 lastLoadGameStart = cm;
9190 switch (lastLoadGameStart) {
9195 gn--; /* count this game */
9196 lastLoadGameStart = cm;
9199 lastLoadGameStart = cm; /* game counted already */
9207 yyboardindex = forwardMostMove;
9208 cm = (ChessMove) yylex();
9209 } while (cm == PGNTag || cm == Comment);
9216 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9217 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9218 != CMAIL_OLD_RESULT) {
9220 cmailResult[ CMAIL_MAX_GAMES
9221 - gn - 1] = CMAIL_OLD_RESULT;
9227 /* Only a NormalMove can be at the start of a game
9228 * without a position diagram. */
9229 if (lastLoadGameStart == (ChessMove) 0) {
9231 lastLoadGameStart = MoveNumberOne;
9240 if (appData.debugMode)
9241 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9243 if (cm == XBoardGame) {
9244 /* Skip any header junk before position diagram and/or move 1 */
9246 yyboardindex = forwardMostMove;
9247 cm = (ChessMove) yylex();
9249 if (cm == (ChessMove) 0 ||
9250 cm == GNUChessGame || cm == XBoardGame) {
9251 /* Empty game; pretend end-of-file and handle later */
9256 if (cm == MoveNumberOne || cm == PositionDiagram ||
9257 cm == PGNTag || cm == Comment)
9260 } else if (cm == GNUChessGame) {
9261 if (gameInfo.event != NULL) {
9262 free(gameInfo.event);
9264 gameInfo.event = StrSave(yy_text);
9267 startedFromSetupPosition = FALSE;
9268 while (cm == PGNTag) {
9269 if (appData.debugMode)
9270 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9271 err = ParsePGNTag(yy_text, &gameInfo);
9272 if (!err) numPGNTags++;
9274 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9275 if(gameInfo.variant != oldVariant) {
9276 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9278 oldVariant = gameInfo.variant;
9279 if (appData.debugMode)
9280 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9284 if (gameInfo.fen != NULL) {
9285 Board initial_position;
9286 startedFromSetupPosition = TRUE;
9287 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9289 DisplayError(_("Bad FEN position in file"), 0);
9292 CopyBoard(boards[0], initial_position);
9293 if (blackPlaysFirst) {
9294 currentMove = forwardMostMove = backwardMostMove = 1;
9295 CopyBoard(boards[1], initial_position);
9296 strcpy(moveList[0], "");
9297 strcpy(parseList[0], "");
9298 timeRemaining[0][1] = whiteTimeRemaining;
9299 timeRemaining[1][1] = blackTimeRemaining;
9300 if (commentList[0] != NULL) {
9301 commentList[1] = commentList[0];
9302 commentList[0] = NULL;
9305 currentMove = forwardMostMove = backwardMostMove = 0;
9307 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9309 initialRulePlies = FENrulePlies;
9310 for( i=0; i< nrCastlingRights; i++ )
9311 initialRights[i] = initial_position[CASTLING][i];
9313 yyboardindex = forwardMostMove;
9315 gameInfo.fen = NULL;
9318 yyboardindex = forwardMostMove;
9319 cm = (ChessMove) yylex();
9321 /* Handle comments interspersed among the tags */
9322 while (cm == Comment) {
9324 if (appData.debugMode)
9325 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9327 AppendComment(currentMove, p, FALSE);
9328 yyboardindex = forwardMostMove;
9329 cm = (ChessMove) yylex();
9333 /* don't rely on existence of Event tag since if game was
9334 * pasted from clipboard the Event tag may not exist
9336 if (numPGNTags > 0){
9338 if (gameInfo.variant == VariantNormal) {
9339 gameInfo.variant = StringToVariant(gameInfo.event);
9342 if( appData.autoDisplayTags ) {
9343 tags = PGNTags(&gameInfo);
9344 TagsPopUp(tags, CmailMsg());
9349 /* Make something up, but don't display it now */
9354 if (cm == PositionDiagram) {
9357 Board initial_position;
9359 if (appData.debugMode)
9360 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9362 if (!startedFromSetupPosition) {
9364 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9365 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9375 initial_position[i][j++] = CharToPiece(*p);
9378 while (*p == ' ' || *p == '\t' ||
9379 *p == '\n' || *p == '\r') p++;
9381 if (strncmp(p, "black", strlen("black"))==0)
9382 blackPlaysFirst = TRUE;
9384 blackPlaysFirst = FALSE;
9385 startedFromSetupPosition = TRUE;
9387 CopyBoard(boards[0], initial_position);
9388 if (blackPlaysFirst) {
9389 currentMove = forwardMostMove = backwardMostMove = 1;
9390 CopyBoard(boards[1], initial_position);
9391 strcpy(moveList[0], "");
9392 strcpy(parseList[0], "");
9393 timeRemaining[0][1] = whiteTimeRemaining;
9394 timeRemaining[1][1] = blackTimeRemaining;
9395 if (commentList[0] != NULL) {
9396 commentList[1] = commentList[0];
9397 commentList[0] = NULL;
9400 currentMove = forwardMostMove = backwardMostMove = 0;
9403 yyboardindex = forwardMostMove;
9404 cm = (ChessMove) yylex();
9407 if (first.pr == NoProc) {
9408 StartChessProgram(&first);
9410 InitChessProgram(&first, FALSE);
9411 SendToProgram("force\n", &first);
9412 if (startedFromSetupPosition) {
9413 SendBoard(&first, forwardMostMove);
9414 if (appData.debugMode) {
9415 fprintf(debugFP, "Load Game\n");
9417 DisplayBothClocks();
9420 /* [HGM] server: flag to write setup moves in broadcast file as one */
9421 loadFlag = appData.suppressLoadMoves;
9423 while (cm == Comment) {
9425 if (appData.debugMode)
9426 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9428 AppendComment(currentMove, p, FALSE);
9429 yyboardindex = forwardMostMove;
9430 cm = (ChessMove) yylex();
9433 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9434 cm == WhiteWins || cm == BlackWins ||
9435 cm == GameIsDrawn || cm == GameUnfinished) {
9436 DisplayMessage("", _("No moves in game"));
9437 if (cmailMsgLoaded) {
9438 if (appData.debugMode)
9439 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9443 DrawPosition(FALSE, boards[currentMove]);
9444 DisplayBothClocks();
9445 gameMode = EditGame;
9452 // [HGM] PV info: routine tests if comment empty
9453 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9454 DisplayComment(currentMove - 1, commentList[currentMove]);
9456 if (!matchMode && appData.timeDelay != 0)
9457 DrawPosition(FALSE, boards[currentMove]);
9459 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9460 programStats.ok_to_send = 1;
9463 /* if the first token after the PGN tags is a move
9464 * and not move number 1, retrieve it from the parser
9466 if (cm != MoveNumberOne)
9467 LoadGameOneMove(cm);
9469 /* load the remaining moves from the file */
9470 while (LoadGameOneMove((ChessMove)0)) {
9471 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9472 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9475 /* rewind to the start of the game */
9476 currentMove = backwardMostMove;
9478 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9480 if (oldGameMode == AnalyzeFile ||
9481 oldGameMode == AnalyzeMode) {
9485 if (matchMode || appData.timeDelay == 0) {
9487 gameMode = EditGame;
9489 } else if (appData.timeDelay > 0) {
9493 if (appData.debugMode)
9494 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9496 loadFlag = 0; /* [HGM] true game starts */
9500 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9502 ReloadPosition(offset)
9505 int positionNumber = lastLoadPositionNumber + offset;
9506 if (lastLoadPositionFP == NULL) {
9507 DisplayError(_("No position has been loaded yet"), 0);
9510 if (positionNumber <= 0) {
9511 DisplayError(_("Can't back up any further"), 0);
9514 return LoadPosition(lastLoadPositionFP, positionNumber,
9515 lastLoadPositionTitle);
9518 /* Load the nth position from the given file */
9520 LoadPositionFromFile(filename, n, title)
9528 if (strcmp(filename, "-") == 0) {
9529 return LoadPosition(stdin, n, "stdin");
9531 f = fopen(filename, "rb");
9533 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9534 DisplayError(buf, errno);
9537 return LoadPosition(f, n, title);
9542 /* Load the nth position from the given open file, and close it */
9544 LoadPosition(f, positionNumber, title)
9549 char *p, line[MSG_SIZ];
9550 Board initial_position;
9551 int i, j, fenMode, pn;
9553 if (gameMode == Training )
9554 SetTrainingModeOff();
9556 if (gameMode != BeginningOfGame) {
9559 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9560 fclose(lastLoadPositionFP);
9562 if (positionNumber == 0) positionNumber = 1;
9563 lastLoadPositionFP = f;
9564 lastLoadPositionNumber = positionNumber;
9565 strcpy(lastLoadPositionTitle, title);
9566 if (first.pr == NoProc) {
9567 StartChessProgram(&first);
9568 InitChessProgram(&first, FALSE);
9570 pn = positionNumber;
9571 if (positionNumber < 0) {
9572 /* Negative position number means to seek to that byte offset */
9573 if (fseek(f, -positionNumber, 0) == -1) {
9574 DisplayError(_("Can't seek on position file"), 0);
9579 if (fseek(f, 0, 0) == -1) {
9580 if (f == lastLoadPositionFP ?
9581 positionNumber == lastLoadPositionNumber + 1 :
9582 positionNumber == 1) {
9585 DisplayError(_("Can't seek on position file"), 0);
9590 /* See if this file is FEN or old-style xboard */
9591 if (fgets(line, MSG_SIZ, f) == NULL) {
9592 DisplayError(_("Position not found in file"), 0);
9595 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9596 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9599 if (fenMode || line[0] == '#') pn--;
9601 /* skip positions before number pn */
9602 if (fgets(line, MSG_SIZ, f) == NULL) {
9604 DisplayError(_("Position not found in file"), 0);
9607 if (fenMode || line[0] == '#') pn--;
9612 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9613 DisplayError(_("Bad FEN position in file"), 0);
9617 (void) fgets(line, MSG_SIZ, f);
9618 (void) fgets(line, MSG_SIZ, f);
9620 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9621 (void) fgets(line, MSG_SIZ, f);
9622 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9625 initial_position[i][j++] = CharToPiece(*p);
9629 blackPlaysFirst = FALSE;
9631 (void) fgets(line, MSG_SIZ, f);
9632 if (strncmp(line, "black", strlen("black"))==0)
9633 blackPlaysFirst = TRUE;
9636 startedFromSetupPosition = TRUE;
9638 SendToProgram("force\n", &first);
9639 CopyBoard(boards[0], initial_position);
9640 if (blackPlaysFirst) {
9641 currentMove = forwardMostMove = backwardMostMove = 1;
9642 strcpy(moveList[0], "");
9643 strcpy(parseList[0], "");
9644 CopyBoard(boards[1], initial_position);
9645 DisplayMessage("", _("Black to play"));
9647 currentMove = forwardMostMove = backwardMostMove = 0;
9648 DisplayMessage("", _("White to play"));
9650 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9651 SendBoard(&first, forwardMostMove);
9652 if (appData.debugMode) {
9654 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9655 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9656 fprintf(debugFP, "Load Position\n");
9659 if (positionNumber > 1) {
9660 sprintf(line, "%s %d", title, positionNumber);
9663 DisplayTitle(title);
9665 gameMode = EditGame;
9668 timeRemaining[0][1] = whiteTimeRemaining;
9669 timeRemaining[1][1] = blackTimeRemaining;
9670 DrawPosition(FALSE, boards[currentMove]);
9677 CopyPlayerNameIntoFileName(dest, src)
9680 while (*src != NULLCHAR && *src != ',') {
9685 *(*dest)++ = *src++;
9690 char *DefaultFileName(ext)
9693 static char def[MSG_SIZ];
9696 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9698 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9700 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9709 /* Save the current game to the given file */
9711 SaveGameToFile(filename, append)
9718 if (strcmp(filename, "-") == 0) {
9719 return SaveGame(stdout, 0, NULL);
9721 f = fopen(filename, append ? "a" : "w");
9723 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9724 DisplayError(buf, errno);
9727 return SaveGame(f, 0, NULL);
9736 static char buf[MSG_SIZ];
9739 p = strchr(str, ' ');
9740 if (p == NULL) return str;
9741 strncpy(buf, str, p - str);
9742 buf[p - str] = NULLCHAR;
9746 #define PGN_MAX_LINE 75
9748 #define PGN_SIDE_WHITE 0
9749 #define PGN_SIDE_BLACK 1
9752 static int FindFirstMoveOutOfBook( int side )
9756 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9757 int index = backwardMostMove;
9758 int has_book_hit = 0;
9760 if( (index % 2) != side ) {
9764 while( index < forwardMostMove ) {
9765 /* Check to see if engine is in book */
9766 int depth = pvInfoList[index].depth;
9767 int score = pvInfoList[index].score;
9773 else if( score == 0 && depth == 63 ) {
9774 in_book = 1; /* Zappa */
9776 else if( score == 2 && depth == 99 ) {
9777 in_book = 1; /* Abrok */
9780 has_book_hit += in_book;
9796 void GetOutOfBookInfo( char * buf )
9800 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9802 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9803 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9807 if( oob[0] >= 0 || oob[1] >= 0 ) {
9808 for( i=0; i<2; i++ ) {
9812 if( i > 0 && oob[0] >= 0 ) {
9816 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9817 sprintf( buf+strlen(buf), "%s%.2f",
9818 pvInfoList[idx].score >= 0 ? "+" : "",
9819 pvInfoList[idx].score / 100.0 );
9825 /* Save game in PGN style and close the file */
9830 int i, offset, linelen, newblock;
9834 int movelen, numlen, blank;
9835 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9837 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9839 tm = time((time_t *) NULL);
9841 PrintPGNTags(f, &gameInfo);
9843 if (backwardMostMove > 0 || startedFromSetupPosition) {
9844 char *fen = PositionToFEN(backwardMostMove, NULL);
9845 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9846 fprintf(f, "\n{--------------\n");
9847 PrintPosition(f, backwardMostMove);
9848 fprintf(f, "--------------}\n");
9852 /* [AS] Out of book annotation */
9853 if( appData.saveOutOfBookInfo ) {
9856 GetOutOfBookInfo( buf );
9858 if( buf[0] != '\0' ) {
9859 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9866 i = backwardMostMove;
9870 while (i < forwardMostMove) {
9871 /* Print comments preceding this move */
9872 if (commentList[i] != NULL) {
9873 if (linelen > 0) fprintf(f, "\n");
9874 fprintf(f, "%s", commentList[i]);
9879 /* Format move number */
9881 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9884 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9886 numtext[0] = NULLCHAR;
9889 numlen = strlen(numtext);
9892 /* Print move number */
9893 blank = linelen > 0 && numlen > 0;
9894 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9903 fprintf(f, "%s", numtext);
9907 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9908 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9911 blank = linelen > 0 && movelen > 0;
9912 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9921 fprintf(f, "%s", move_buffer);
9924 /* [AS] Add PV info if present */
9925 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9926 /* [HGM] add time */
9927 char buf[MSG_SIZ]; int seconds;
9929 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9931 if( seconds <= 0) buf[0] = 0; else
9932 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9933 seconds = (seconds + 4)/10; // round to full seconds
9934 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9935 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9938 sprintf( move_buffer, "{%s%.2f/%d%s}",
9939 pvInfoList[i].score >= 0 ? "+" : "",
9940 pvInfoList[i].score / 100.0,
9941 pvInfoList[i].depth,
9944 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9946 /* Print score/depth */
9947 blank = linelen > 0 && movelen > 0;
9948 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9957 fprintf(f, "%s", move_buffer);
9964 /* Start a new line */
9965 if (linelen > 0) fprintf(f, "\n");
9967 /* Print comments after last move */
9968 if (commentList[i] != NULL) {
9969 fprintf(f, "%s\n", commentList[i]);
9973 if (gameInfo.resultDetails != NULL &&
9974 gameInfo.resultDetails[0] != NULLCHAR) {
9975 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9976 PGNResult(gameInfo.result));
9978 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9982 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9986 /* Save game in old style and close the file */
9994 tm = time((time_t *) NULL);
9996 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9999 if (backwardMostMove > 0 || startedFromSetupPosition) {
10000 fprintf(f, "\n[--------------\n");
10001 PrintPosition(f, backwardMostMove);
10002 fprintf(f, "--------------]\n");
10007 i = backwardMostMove;
10008 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10010 while (i < forwardMostMove) {
10011 if (commentList[i] != NULL) {
10012 fprintf(f, "[%s]\n", commentList[i]);
10015 if ((i % 2) == 1) {
10016 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10019 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10021 if (commentList[i] != NULL) {
10025 if (i >= forwardMostMove) {
10029 fprintf(f, "%s\n", parseList[i]);
10034 if (commentList[i] != NULL) {
10035 fprintf(f, "[%s]\n", commentList[i]);
10038 /* This isn't really the old style, but it's close enough */
10039 if (gameInfo.resultDetails != NULL &&
10040 gameInfo.resultDetails[0] != NULLCHAR) {
10041 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10042 gameInfo.resultDetails);
10044 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10051 /* Save the current game to open file f and close the file */
10053 SaveGame(f, dummy, dummy2)
10058 if (gameMode == EditPosition) EditPositionDone(TRUE);
10059 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10060 if (appData.oldSaveStyle)
10061 return SaveGameOldStyle(f);
10063 return SaveGamePGN(f);
10066 /* Save the current position to the given file */
10068 SavePositionToFile(filename)
10074 if (strcmp(filename, "-") == 0) {
10075 return SavePosition(stdout, 0, NULL);
10077 f = fopen(filename, "a");
10079 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10080 DisplayError(buf, errno);
10083 SavePosition(f, 0, NULL);
10089 /* Save the current position to the given open file and close the file */
10091 SavePosition(f, dummy, dummy2)
10099 if (gameMode == EditPosition) EditPositionDone(TRUE);
10100 if (appData.oldSaveStyle) {
10101 tm = time((time_t *) NULL);
10103 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10105 fprintf(f, "[--------------\n");
10106 PrintPosition(f, currentMove);
10107 fprintf(f, "--------------]\n");
10109 fen = PositionToFEN(currentMove, NULL);
10110 fprintf(f, "%s\n", fen);
10118 ReloadCmailMsgEvent(unregister)
10122 static char *inFilename = NULL;
10123 static char *outFilename;
10125 struct stat inbuf, outbuf;
10128 /* Any registered moves are unregistered if unregister is set, */
10129 /* i.e. invoked by the signal handler */
10131 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10132 cmailMoveRegistered[i] = FALSE;
10133 if (cmailCommentList[i] != NULL) {
10134 free(cmailCommentList[i]);
10135 cmailCommentList[i] = NULL;
10138 nCmailMovesRegistered = 0;
10141 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10142 cmailResult[i] = CMAIL_NOT_RESULT;
10146 if (inFilename == NULL) {
10147 /* Because the filenames are static they only get malloced once */
10148 /* and they never get freed */
10149 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10150 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10152 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10153 sprintf(outFilename, "%s.out", appData.cmailGameName);
10156 status = stat(outFilename, &outbuf);
10158 cmailMailedMove = FALSE;
10160 status = stat(inFilename, &inbuf);
10161 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10164 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10165 counts the games, notes how each one terminated, etc.
10167 It would be nice to remove this kludge and instead gather all
10168 the information while building the game list. (And to keep it
10169 in the game list nodes instead of having a bunch of fixed-size
10170 parallel arrays.) Note this will require getting each game's
10171 termination from the PGN tags, as the game list builder does
10172 not process the game moves. --mann
10174 cmailMsgLoaded = TRUE;
10175 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10177 /* Load first game in the file or popup game menu */
10178 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10180 #endif /* !WIN32 */
10188 char string[MSG_SIZ];
10190 if ( cmailMailedMove
10191 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10192 return TRUE; /* Allow free viewing */
10195 /* Unregister move to ensure that we don't leave RegisterMove */
10196 /* with the move registered when the conditions for registering no */
10198 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10199 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10200 nCmailMovesRegistered --;
10202 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10204 free(cmailCommentList[lastLoadGameNumber - 1]);
10205 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10209 if (cmailOldMove == -1) {
10210 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10214 if (currentMove > cmailOldMove + 1) {
10215 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10219 if (currentMove < cmailOldMove) {
10220 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10224 if (forwardMostMove > currentMove) {
10225 /* Silently truncate extra moves */
10229 if ( (currentMove == cmailOldMove + 1)
10230 || ( (currentMove == cmailOldMove)
10231 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10232 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10233 if (gameInfo.result != GameUnfinished) {
10234 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10237 if (commentList[currentMove] != NULL) {
10238 cmailCommentList[lastLoadGameNumber - 1]
10239 = StrSave(commentList[currentMove]);
10241 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10243 if (appData.debugMode)
10244 fprintf(debugFP, "Saving %s for game %d\n",
10245 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10248 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10250 f = fopen(string, "w");
10251 if (appData.oldSaveStyle) {
10252 SaveGameOldStyle(f); /* also closes the file */
10254 sprintf(string, "%s.pos.out", appData.cmailGameName);
10255 f = fopen(string, "w");
10256 SavePosition(f, 0, NULL); /* also closes the file */
10258 fprintf(f, "{--------------\n");
10259 PrintPosition(f, currentMove);
10260 fprintf(f, "--------------}\n\n");
10262 SaveGame(f, 0, NULL); /* also closes the file*/
10265 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10266 nCmailMovesRegistered ++;
10267 } else if (nCmailGames == 1) {
10268 DisplayError(_("You have not made a move yet"), 0);
10279 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10280 FILE *commandOutput;
10281 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10282 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10288 if (! cmailMsgLoaded) {
10289 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10293 if (nCmailGames == nCmailResults) {
10294 DisplayError(_("No unfinished games"), 0);
10298 #if CMAIL_PROHIBIT_REMAIL
10299 if (cmailMailedMove) {
10300 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);
10301 DisplayError(msg, 0);
10306 if (! (cmailMailedMove || RegisterMove())) return;
10308 if ( cmailMailedMove
10309 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10310 sprintf(string, partCommandString,
10311 appData.debugMode ? " -v" : "", appData.cmailGameName);
10312 commandOutput = popen(string, "r");
10314 if (commandOutput == NULL) {
10315 DisplayError(_("Failed to invoke cmail"), 0);
10317 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10318 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10320 if (nBuffers > 1) {
10321 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10322 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10323 nBytes = MSG_SIZ - 1;
10325 (void) memcpy(msg, buffer, nBytes);
10327 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10329 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10330 cmailMailedMove = TRUE; /* Prevent >1 moves */
10333 for (i = 0; i < nCmailGames; i ++) {
10334 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10339 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10341 sprintf(buffer, "%s/%s.%s.archive",
10343 appData.cmailGameName,
10345 LoadGameFromFile(buffer, 1, buffer, FALSE);
10346 cmailMsgLoaded = FALSE;
10350 DisplayInformation(msg);
10351 pclose(commandOutput);
10354 if ((*cmailMsg) != '\0') {
10355 DisplayInformation(cmailMsg);
10360 #endif /* !WIN32 */
10369 int prependComma = 0;
10371 char string[MSG_SIZ]; /* Space for game-list */
10374 if (!cmailMsgLoaded) return "";
10376 if (cmailMailedMove) {
10377 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10379 /* Create a list of games left */
10380 sprintf(string, "[");
10381 for (i = 0; i < nCmailGames; i ++) {
10382 if (! ( cmailMoveRegistered[i]
10383 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10384 if (prependComma) {
10385 sprintf(number, ",%d", i + 1);
10387 sprintf(number, "%d", i + 1);
10391 strcat(string, number);
10394 strcat(string, "]");
10396 if (nCmailMovesRegistered + nCmailResults == 0) {
10397 switch (nCmailGames) {
10400 _("Still need to make move for game\n"));
10405 _("Still need to make moves for both games\n"));
10410 _("Still need to make moves for all %d games\n"),
10415 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10418 _("Still need to make a move for game %s\n"),
10423 if (nCmailResults == nCmailGames) {
10424 sprintf(cmailMsg, _("No unfinished games\n"));
10426 sprintf(cmailMsg, _("Ready to send mail\n"));
10432 _("Still need to make moves for games %s\n"),
10444 if (gameMode == Training)
10445 SetTrainingModeOff();
10448 cmailMsgLoaded = FALSE;
10449 if (appData.icsActive) {
10450 SendToICS(ics_prefix);
10451 SendToICS("refresh\n");
10461 /* Give up on clean exit */
10465 /* Keep trying for clean exit */
10469 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10471 if (telnetISR != NULL) {
10472 RemoveInputSource(telnetISR);
10474 if (icsPR != NoProc) {
10475 DestroyChildProcess(icsPR, TRUE);
10478 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10479 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10481 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10482 /* make sure this other one finishes before killing it! */
10483 if(endingGame) { int count = 0;
10484 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10485 while(endingGame && count++ < 10) DoSleep(1);
10486 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10489 /* Kill off chess programs */
10490 if (first.pr != NoProc) {
10493 DoSleep( appData.delayBeforeQuit );
10494 SendToProgram("quit\n", &first);
10495 DoSleep( appData.delayAfterQuit );
10496 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10498 if (second.pr != NoProc) {
10499 DoSleep( appData.delayBeforeQuit );
10500 SendToProgram("quit\n", &second);
10501 DoSleep( appData.delayAfterQuit );
10502 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10504 if (first.isr != NULL) {
10505 RemoveInputSource(first.isr);
10507 if (second.isr != NULL) {
10508 RemoveInputSource(second.isr);
10511 ShutDownFrontEnd();
10518 if (appData.debugMode)
10519 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10523 if (gameMode == MachinePlaysWhite ||
10524 gameMode == MachinePlaysBlack) {
10527 DisplayBothClocks();
10529 if (gameMode == PlayFromGameFile) {
10530 if (appData.timeDelay >= 0)
10531 AutoPlayGameLoop();
10532 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10533 Reset(FALSE, TRUE);
10534 SendToICS(ics_prefix);
10535 SendToICS("refresh\n");
10536 } else if (currentMove < forwardMostMove) {
10537 ForwardInner(forwardMostMove);
10539 pauseExamInvalid = FALSE;
10541 switch (gameMode) {
10545 pauseExamForwardMostMove = forwardMostMove;
10546 pauseExamInvalid = FALSE;
10549 case IcsPlayingWhite:
10550 case IcsPlayingBlack:
10554 case PlayFromGameFile:
10555 (void) StopLoadGameTimer();
10559 case BeginningOfGame:
10560 if (appData.icsActive) return;
10561 /* else fall through */
10562 case MachinePlaysWhite:
10563 case MachinePlaysBlack:
10564 case TwoMachinesPlay:
10565 if (forwardMostMove == 0)
10566 return; /* don't pause if no one has moved */
10567 if ((gameMode == MachinePlaysWhite &&
10568 !WhiteOnMove(forwardMostMove)) ||
10569 (gameMode == MachinePlaysBlack &&
10570 WhiteOnMove(forwardMostMove))) {
10583 char title[MSG_SIZ];
10585 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10586 strcpy(title, _("Edit comment"));
10588 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10589 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10590 parseList[currentMove - 1]);
10593 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10600 char *tags = PGNTags(&gameInfo);
10601 EditTagsPopUp(tags);
10608 if (appData.noChessProgram || gameMode == AnalyzeMode)
10611 if (gameMode != AnalyzeFile) {
10612 if (!appData.icsEngineAnalyze) {
10614 if (gameMode != EditGame) return;
10616 ResurrectChessProgram();
10617 SendToProgram("analyze\n", &first);
10618 first.analyzing = TRUE;
10619 /*first.maybeThinking = TRUE;*/
10620 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10621 EngineOutputPopUp();
10623 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10628 StartAnalysisClock();
10629 GetTimeMark(&lastNodeCountTime);
10636 if (appData.noChessProgram || gameMode == AnalyzeFile)
10639 if (gameMode != AnalyzeMode) {
10641 if (gameMode != EditGame) return;
10642 ResurrectChessProgram();
10643 SendToProgram("analyze\n", &first);
10644 first.analyzing = TRUE;
10645 /*first.maybeThinking = TRUE;*/
10646 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10647 EngineOutputPopUp();
10649 gameMode = AnalyzeFile;
10654 StartAnalysisClock();
10655 GetTimeMark(&lastNodeCountTime);
10660 MachineWhiteEvent()
10663 char *bookHit = NULL;
10665 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10669 if (gameMode == PlayFromGameFile ||
10670 gameMode == TwoMachinesPlay ||
10671 gameMode == Training ||
10672 gameMode == AnalyzeMode ||
10673 gameMode == EndOfGame)
10676 if (gameMode == EditPosition)
10677 EditPositionDone(TRUE);
10679 if (!WhiteOnMove(currentMove)) {
10680 DisplayError(_("It is not White's turn"), 0);
10684 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10687 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10688 gameMode == AnalyzeFile)
10691 ResurrectChessProgram(); /* in case it isn't running */
10692 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10693 gameMode = MachinePlaysWhite;
10696 gameMode = MachinePlaysWhite;
10700 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10702 if (first.sendName) {
10703 sprintf(buf, "name %s\n", gameInfo.black);
10704 SendToProgram(buf, &first);
10706 if (first.sendTime) {
10707 if (first.useColors) {
10708 SendToProgram("black\n", &first); /*gnu kludge*/
10710 SendTimeRemaining(&first, TRUE);
10712 if (first.useColors) {
10713 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10715 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10716 SetMachineThinkingEnables();
10717 first.maybeThinking = TRUE;
10721 if (appData.autoFlipView && !flipView) {
10722 flipView = !flipView;
10723 DrawPosition(FALSE, NULL);
10724 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10727 if(bookHit) { // [HGM] book: simulate book reply
10728 static char bookMove[MSG_SIZ]; // a bit generous?
10730 programStats.nodes = programStats.depth = programStats.time =
10731 programStats.score = programStats.got_only_move = 0;
10732 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10734 strcpy(bookMove, "move ");
10735 strcat(bookMove, bookHit);
10736 HandleMachineMove(bookMove, &first);
10741 MachineBlackEvent()
10744 char *bookHit = NULL;
10746 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10750 if (gameMode == PlayFromGameFile ||
10751 gameMode == TwoMachinesPlay ||
10752 gameMode == Training ||
10753 gameMode == AnalyzeMode ||
10754 gameMode == EndOfGame)
10757 if (gameMode == EditPosition)
10758 EditPositionDone(TRUE);
10760 if (WhiteOnMove(currentMove)) {
10761 DisplayError(_("It is not Black's turn"), 0);
10765 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10768 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10769 gameMode == AnalyzeFile)
10772 ResurrectChessProgram(); /* in case it isn't running */
10773 gameMode = MachinePlaysBlack;
10777 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10779 if (first.sendName) {
10780 sprintf(buf, "name %s\n", gameInfo.white);
10781 SendToProgram(buf, &first);
10783 if (first.sendTime) {
10784 if (first.useColors) {
10785 SendToProgram("white\n", &first); /*gnu kludge*/
10787 SendTimeRemaining(&first, FALSE);
10789 if (first.useColors) {
10790 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10792 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10793 SetMachineThinkingEnables();
10794 first.maybeThinking = TRUE;
10797 if (appData.autoFlipView && flipView) {
10798 flipView = !flipView;
10799 DrawPosition(FALSE, NULL);
10800 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10802 if(bookHit) { // [HGM] book: simulate book reply
10803 static char bookMove[MSG_SIZ]; // a bit generous?
10805 programStats.nodes = programStats.depth = programStats.time =
10806 programStats.score = programStats.got_only_move = 0;
10807 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10809 strcpy(bookMove, "move ");
10810 strcat(bookMove, bookHit);
10811 HandleMachineMove(bookMove, &first);
10817 DisplayTwoMachinesTitle()
10820 if (appData.matchGames > 0) {
10821 if (first.twoMachinesColor[0] == 'w') {
10822 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10823 gameInfo.white, gameInfo.black,
10824 first.matchWins, second.matchWins,
10825 matchGame - 1 - (first.matchWins + second.matchWins));
10827 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10828 gameInfo.white, gameInfo.black,
10829 second.matchWins, first.matchWins,
10830 matchGame - 1 - (first.matchWins + second.matchWins));
10833 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10839 TwoMachinesEvent P((void))
10843 ChessProgramState *onmove;
10844 char *bookHit = NULL;
10846 if (appData.noChessProgram) return;
10848 switch (gameMode) {
10849 case TwoMachinesPlay:
10851 case MachinePlaysWhite:
10852 case MachinePlaysBlack:
10853 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10854 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10858 case BeginningOfGame:
10859 case PlayFromGameFile:
10862 if (gameMode != EditGame) return;
10865 EditPositionDone(TRUE);
10876 // forwardMostMove = currentMove;
10877 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10878 ResurrectChessProgram(); /* in case first program isn't running */
10880 if (second.pr == NULL) {
10881 StartChessProgram(&second);
10882 if (second.protocolVersion == 1) {
10883 TwoMachinesEventIfReady();
10885 /* kludge: allow timeout for initial "feature" command */
10887 DisplayMessage("", _("Starting second chess program"));
10888 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10892 DisplayMessage("", "");
10893 InitChessProgram(&second, FALSE);
10894 SendToProgram("force\n", &second);
10895 if (startedFromSetupPosition) {
10896 SendBoard(&second, backwardMostMove);
10897 if (appData.debugMode) {
10898 fprintf(debugFP, "Two Machines\n");
10901 for (i = backwardMostMove; i < forwardMostMove; i++) {
10902 SendMoveToProgram(i, &second);
10905 gameMode = TwoMachinesPlay;
10909 DisplayTwoMachinesTitle();
10911 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10917 SendToProgram(first.computerString, &first);
10918 if (first.sendName) {
10919 sprintf(buf, "name %s\n", second.tidy);
10920 SendToProgram(buf, &first);
10922 SendToProgram(second.computerString, &second);
10923 if (second.sendName) {
10924 sprintf(buf, "name %s\n", first.tidy);
10925 SendToProgram(buf, &second);
10929 if (!first.sendTime || !second.sendTime) {
10930 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10931 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10933 if (onmove->sendTime) {
10934 if (onmove->useColors) {
10935 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10937 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10939 if (onmove->useColors) {
10940 SendToProgram(onmove->twoMachinesColor, onmove);
10942 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10943 // SendToProgram("go\n", onmove);
10944 onmove->maybeThinking = TRUE;
10945 SetMachineThinkingEnables();
10949 if(bookHit) { // [HGM] book: simulate book reply
10950 static char bookMove[MSG_SIZ]; // a bit generous?
10952 programStats.nodes = programStats.depth = programStats.time =
10953 programStats.score = programStats.got_only_move = 0;
10954 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10956 strcpy(bookMove, "move ");
10957 strcat(bookMove, bookHit);
10958 savedMessage = bookMove; // args for deferred call
10959 savedState = onmove;
10960 ScheduleDelayedEvent(DeferredBookMove, 1);
10967 if (gameMode == Training) {
10968 SetTrainingModeOff();
10969 gameMode = PlayFromGameFile;
10970 DisplayMessage("", _("Training mode off"));
10972 gameMode = Training;
10973 animateTraining = appData.animate;
10975 /* make sure we are not already at the end of the game */
10976 if (currentMove < forwardMostMove) {
10977 SetTrainingModeOn();
10978 DisplayMessage("", _("Training mode on"));
10980 gameMode = PlayFromGameFile;
10981 DisplayError(_("Already at end of game"), 0);
10990 if (!appData.icsActive) return;
10991 switch (gameMode) {
10992 case IcsPlayingWhite:
10993 case IcsPlayingBlack:
10996 case BeginningOfGame:
11004 EditPositionDone(TRUE);
11017 gameMode = IcsIdle;
11028 switch (gameMode) {
11030 SetTrainingModeOff();
11032 case MachinePlaysWhite:
11033 case MachinePlaysBlack:
11034 case BeginningOfGame:
11035 SendToProgram("force\n", &first);
11036 SetUserThinkingEnables();
11038 case PlayFromGameFile:
11039 (void) StopLoadGameTimer();
11040 if (gameFileFP != NULL) {
11045 EditPositionDone(TRUE);
11050 SendToProgram("force\n", &first);
11052 case TwoMachinesPlay:
11053 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11054 ResurrectChessProgram();
11055 SetUserThinkingEnables();
11058 ResurrectChessProgram();
11060 case IcsPlayingBlack:
11061 case IcsPlayingWhite:
11062 DisplayError(_("Warning: You are still playing a game"), 0);
11065 DisplayError(_("Warning: You are still observing a game"), 0);
11068 DisplayError(_("Warning: You are still examining a game"), 0);
11079 first.offeredDraw = second.offeredDraw = 0;
11081 if (gameMode == PlayFromGameFile) {
11082 whiteTimeRemaining = timeRemaining[0][currentMove];
11083 blackTimeRemaining = timeRemaining[1][currentMove];
11087 if (gameMode == MachinePlaysWhite ||
11088 gameMode == MachinePlaysBlack ||
11089 gameMode == TwoMachinesPlay ||
11090 gameMode == EndOfGame) {
11091 i = forwardMostMove;
11092 while (i > currentMove) {
11093 SendToProgram("undo\n", &first);
11096 whiteTimeRemaining = timeRemaining[0][currentMove];
11097 blackTimeRemaining = timeRemaining[1][currentMove];
11098 DisplayBothClocks();
11099 if (whiteFlag || blackFlag) {
11100 whiteFlag = blackFlag = 0;
11105 gameMode = EditGame;
11112 EditPositionEvent()
11114 if (gameMode == EditPosition) {
11120 if (gameMode != EditGame) return;
11122 gameMode = EditPosition;
11125 if (currentMove > 0)
11126 CopyBoard(boards[0], boards[currentMove]);
11128 blackPlaysFirst = !WhiteOnMove(currentMove);
11130 currentMove = forwardMostMove = backwardMostMove = 0;
11131 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11138 /* [DM] icsEngineAnalyze - possible call from other functions */
11139 if (appData.icsEngineAnalyze) {
11140 appData.icsEngineAnalyze = FALSE;
11142 DisplayMessage("",_("Close ICS engine analyze..."));
11144 if (first.analysisSupport && first.analyzing) {
11145 SendToProgram("exit\n", &first);
11146 first.analyzing = FALSE;
11148 thinkOutput[0] = NULLCHAR;
11152 EditPositionDone(Boolean fakeRights)
11154 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11156 startedFromSetupPosition = TRUE;
11157 InitChessProgram(&first, FALSE);
11158 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11159 boards[0][EP_STATUS] = EP_NONE;
11160 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11161 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11162 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11163 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11164 } else boards[0][CASTLING][2] = NoRights;
11165 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11166 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11167 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11168 } else boards[0][CASTLING][5] = NoRights;
11170 SendToProgram("force\n", &first);
11171 if (blackPlaysFirst) {
11172 strcpy(moveList[0], "");
11173 strcpy(parseList[0], "");
11174 currentMove = forwardMostMove = backwardMostMove = 1;
11175 CopyBoard(boards[1], boards[0]);
11177 currentMove = forwardMostMove = backwardMostMove = 0;
11179 SendBoard(&first, forwardMostMove);
11180 if (appData.debugMode) {
11181 fprintf(debugFP, "EditPosDone\n");
11184 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11185 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11186 gameMode = EditGame;
11188 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11189 ClearHighlights(); /* [AS] */
11192 /* Pause for `ms' milliseconds */
11193 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11203 } while (SubtractTimeMarks(&m2, &m1) < ms);
11206 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11208 SendMultiLineToICS(buf)
11211 char temp[MSG_SIZ+1], *p;
11218 strncpy(temp, buf, len);
11223 if (*p == '\n' || *p == '\r')
11228 strcat(temp, "\n");
11230 SendToPlayer(temp, strlen(temp));
11234 SetWhiteToPlayEvent()
11236 if (gameMode == EditPosition) {
11237 blackPlaysFirst = FALSE;
11238 DisplayBothClocks(); /* works because currentMove is 0 */
11239 } else if (gameMode == IcsExamining) {
11240 SendToICS(ics_prefix);
11241 SendToICS("tomove white\n");
11246 SetBlackToPlayEvent()
11248 if (gameMode == EditPosition) {
11249 blackPlaysFirst = TRUE;
11250 currentMove = 1; /* kludge */
11251 DisplayBothClocks();
11253 } else if (gameMode == IcsExamining) {
11254 SendToICS(ics_prefix);
11255 SendToICS("tomove black\n");
11260 EditPositionMenuEvent(selection, x, y)
11261 ChessSquare selection;
11265 ChessSquare piece = boards[0][y][x];
11267 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11269 switch (selection) {
11271 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11272 SendToICS(ics_prefix);
11273 SendToICS("bsetup clear\n");
11274 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11275 SendToICS(ics_prefix);
11276 SendToICS("clearboard\n");
11278 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11279 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11280 for (y = 0; y < BOARD_HEIGHT; y++) {
11281 if (gameMode == IcsExamining) {
11282 if (boards[currentMove][y][x] != EmptySquare) {
11283 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11288 boards[0][y][x] = p;
11293 if (gameMode == EditPosition) {
11294 DrawPosition(FALSE, boards[0]);
11299 SetWhiteToPlayEvent();
11303 SetBlackToPlayEvent();
11307 if (gameMode == IcsExamining) {
11308 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11311 boards[0][y][x] = EmptySquare;
11312 DrawPosition(FALSE, boards[0]);
11317 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11318 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11319 selection = (ChessSquare) (PROMOTED piece);
11320 } else if(piece == EmptySquare) selection = WhiteSilver;
11321 else selection = (ChessSquare)((int)piece - 1);
11325 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11326 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11327 selection = (ChessSquare) (DEMOTED piece);
11328 } else if(piece == EmptySquare) selection = BlackSilver;
11329 else selection = (ChessSquare)((int)piece + 1);
11334 if(gameInfo.variant == VariantShatranj ||
11335 gameInfo.variant == VariantXiangqi ||
11336 gameInfo.variant == VariantCourier )
11337 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11342 if(gameInfo.variant == VariantXiangqi)
11343 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11344 if(gameInfo.variant == VariantKnightmate)
11345 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11348 if (gameMode == IcsExamining) {
11349 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11350 PieceToChar(selection), AAA + x, ONE + y);
11353 boards[0][y][x] = selection;
11354 DrawPosition(FALSE, boards[0]);
11362 DropMenuEvent(selection, x, y)
11363 ChessSquare selection;
11366 ChessMove moveType;
11368 switch (gameMode) {
11369 case IcsPlayingWhite:
11370 case MachinePlaysBlack:
11371 if (!WhiteOnMove(currentMove)) {
11372 DisplayMoveError(_("It is Black's turn"));
11375 moveType = WhiteDrop;
11377 case IcsPlayingBlack:
11378 case MachinePlaysWhite:
11379 if (WhiteOnMove(currentMove)) {
11380 DisplayMoveError(_("It is White's turn"));
11383 moveType = BlackDrop;
11386 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11392 if (moveType == BlackDrop && selection < BlackPawn) {
11393 selection = (ChessSquare) ((int) selection
11394 + (int) BlackPawn - (int) WhitePawn);
11396 if (boards[currentMove][y][x] != EmptySquare) {
11397 DisplayMoveError(_("That square is occupied"));
11401 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11407 /* Accept a pending offer of any kind from opponent */
11409 if (appData.icsActive) {
11410 SendToICS(ics_prefix);
11411 SendToICS("accept\n");
11412 } else if (cmailMsgLoaded) {
11413 if (currentMove == cmailOldMove &&
11414 commentList[cmailOldMove] != NULL &&
11415 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11416 "Black offers a draw" : "White offers a draw")) {
11418 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11419 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11421 DisplayError(_("There is no pending offer on this move"), 0);
11422 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11425 /* Not used for offers from chess program */
11432 /* Decline a pending offer of any kind from opponent */
11434 if (appData.icsActive) {
11435 SendToICS(ics_prefix);
11436 SendToICS("decline\n");
11437 } else if (cmailMsgLoaded) {
11438 if (currentMove == cmailOldMove &&
11439 commentList[cmailOldMove] != NULL &&
11440 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11441 "Black offers a draw" : "White offers a draw")) {
11443 AppendComment(cmailOldMove, "Draw declined", TRUE);
11444 DisplayComment(cmailOldMove - 1, "Draw declined");
11447 DisplayError(_("There is no pending offer on this move"), 0);
11450 /* Not used for offers from chess program */
11457 /* Issue ICS rematch command */
11458 if (appData.icsActive) {
11459 SendToICS(ics_prefix);
11460 SendToICS("rematch\n");
11467 /* Call your opponent's flag (claim a win on time) */
11468 if (appData.icsActive) {
11469 SendToICS(ics_prefix);
11470 SendToICS("flag\n");
11472 switch (gameMode) {
11475 case MachinePlaysWhite:
11478 GameEnds(GameIsDrawn, "Both players ran out of time",
11481 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11483 DisplayError(_("Your opponent is not out of time"), 0);
11486 case MachinePlaysBlack:
11489 GameEnds(GameIsDrawn, "Both players ran out of time",
11492 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11494 DisplayError(_("Your opponent is not out of time"), 0);
11504 /* Offer draw or accept pending draw offer from opponent */
11506 if (appData.icsActive) {
11507 /* Note: tournament rules require draw offers to be
11508 made after you make your move but before you punch
11509 your clock. Currently ICS doesn't let you do that;
11510 instead, you immediately punch your clock after making
11511 a move, but you can offer a draw at any time. */
11513 SendToICS(ics_prefix);
11514 SendToICS("draw\n");
11515 } else if (cmailMsgLoaded) {
11516 if (currentMove == cmailOldMove &&
11517 commentList[cmailOldMove] != NULL &&
11518 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11519 "Black offers a draw" : "White offers a draw")) {
11520 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11521 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11522 } else if (currentMove == cmailOldMove + 1) {
11523 char *offer = WhiteOnMove(cmailOldMove) ?
11524 "White offers a draw" : "Black offers a draw";
11525 AppendComment(currentMove, offer, TRUE);
11526 DisplayComment(currentMove - 1, offer);
11527 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11529 DisplayError(_("You must make your move before offering a draw"), 0);
11530 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11532 } else if (first.offeredDraw) {
11533 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11535 if (first.sendDrawOffers) {
11536 SendToProgram("draw\n", &first);
11537 userOfferedDraw = TRUE;
11545 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11547 if (appData.icsActive) {
11548 SendToICS(ics_prefix);
11549 SendToICS("adjourn\n");
11551 /* Currently GNU Chess doesn't offer or accept Adjourns */
11559 /* Offer Abort or accept pending Abort offer from opponent */
11561 if (appData.icsActive) {
11562 SendToICS(ics_prefix);
11563 SendToICS("abort\n");
11565 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11572 /* Resign. You can do this even if it's not your turn. */
11574 if (appData.icsActive) {
11575 SendToICS(ics_prefix);
11576 SendToICS("resign\n");
11578 switch (gameMode) {
11579 case MachinePlaysWhite:
11580 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11582 case MachinePlaysBlack:
11583 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11586 if (cmailMsgLoaded) {
11588 if (WhiteOnMove(cmailOldMove)) {
11589 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11591 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11593 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11604 StopObservingEvent()
11606 /* Stop observing current games */
11607 SendToICS(ics_prefix);
11608 SendToICS("unobserve\n");
11612 StopExaminingEvent()
11614 /* Stop observing current game */
11615 SendToICS(ics_prefix);
11616 SendToICS("unexamine\n");
11620 ForwardInner(target)
11625 if (appData.debugMode)
11626 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11627 target, currentMove, forwardMostMove);
11629 if (gameMode == EditPosition)
11632 if (gameMode == PlayFromGameFile && !pausing)
11635 if (gameMode == IcsExamining && pausing)
11636 limit = pauseExamForwardMostMove;
11638 limit = forwardMostMove;
11640 if (target > limit) target = limit;
11642 if (target > 0 && moveList[target - 1][0]) {
11643 int fromX, fromY, toX, toY;
11644 toX = moveList[target - 1][2] - AAA;
11645 toY = moveList[target - 1][3] - ONE;
11646 if (moveList[target - 1][1] == '@') {
11647 if (appData.highlightLastMove) {
11648 SetHighlights(-1, -1, toX, toY);
11651 fromX = moveList[target - 1][0] - AAA;
11652 fromY = moveList[target - 1][1] - ONE;
11653 if (target == currentMove + 1) {
11654 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11656 if (appData.highlightLastMove) {
11657 SetHighlights(fromX, fromY, toX, toY);
11661 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11662 gameMode == Training || gameMode == PlayFromGameFile ||
11663 gameMode == AnalyzeFile) {
11664 while (currentMove < target) {
11665 SendMoveToProgram(currentMove++, &first);
11668 currentMove = target;
11671 if (gameMode == EditGame || gameMode == EndOfGame) {
11672 whiteTimeRemaining = timeRemaining[0][currentMove];
11673 blackTimeRemaining = timeRemaining[1][currentMove];
11675 DisplayBothClocks();
11676 DisplayMove(currentMove - 1);
11677 DrawPosition(FALSE, boards[currentMove]);
11678 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11679 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11680 DisplayComment(currentMove - 1, commentList[currentMove]);
11688 if (gameMode == IcsExamining && !pausing) {
11689 SendToICS(ics_prefix);
11690 SendToICS("forward\n");
11692 ForwardInner(currentMove + 1);
11699 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11700 /* to optimze, we temporarily turn off analysis mode while we feed
11701 * the remaining moves to the engine. Otherwise we get analysis output
11704 if (first.analysisSupport) {
11705 SendToProgram("exit\nforce\n", &first);
11706 first.analyzing = FALSE;
11710 if (gameMode == IcsExamining && !pausing) {
11711 SendToICS(ics_prefix);
11712 SendToICS("forward 999999\n");
11714 ForwardInner(forwardMostMove);
11717 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11718 /* we have fed all the moves, so reactivate analysis mode */
11719 SendToProgram("analyze\n", &first);
11720 first.analyzing = TRUE;
11721 /*first.maybeThinking = TRUE;*/
11722 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11727 BackwardInner(target)
11730 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11732 if (appData.debugMode)
11733 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11734 target, currentMove, forwardMostMove);
11736 if (gameMode == EditPosition) return;
11737 if (currentMove <= backwardMostMove) {
11739 DrawPosition(full_redraw, boards[currentMove]);
11742 if (gameMode == PlayFromGameFile && !pausing)
11745 if (moveList[target][0]) {
11746 int fromX, fromY, toX, toY;
11747 toX = moveList[target][2] - AAA;
11748 toY = moveList[target][3] - ONE;
11749 if (moveList[target][1] == '@') {
11750 if (appData.highlightLastMove) {
11751 SetHighlights(-1, -1, toX, toY);
11754 fromX = moveList[target][0] - AAA;
11755 fromY = moveList[target][1] - ONE;
11756 if (target == currentMove - 1) {
11757 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11759 if (appData.highlightLastMove) {
11760 SetHighlights(fromX, fromY, toX, toY);
11764 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11765 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11766 while (currentMove > target) {
11767 SendToProgram("undo\n", &first);
11771 currentMove = target;
11774 if (gameMode == EditGame || gameMode == EndOfGame) {
11775 whiteTimeRemaining = timeRemaining[0][currentMove];
11776 blackTimeRemaining = timeRemaining[1][currentMove];
11778 DisplayBothClocks();
11779 DisplayMove(currentMove - 1);
11780 DrawPosition(full_redraw, boards[currentMove]);
11781 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11782 // [HGM] PV info: routine tests if comment empty
11783 DisplayComment(currentMove - 1, commentList[currentMove]);
11789 if (gameMode == IcsExamining && !pausing) {
11790 SendToICS(ics_prefix);
11791 SendToICS("backward\n");
11793 BackwardInner(currentMove - 1);
11800 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11801 /* to optimize, we temporarily turn off analysis mode while we undo
11802 * all the moves. Otherwise we get analysis output after each undo.
11804 if (first.analysisSupport) {
11805 SendToProgram("exit\nforce\n", &first);
11806 first.analyzing = FALSE;
11810 if (gameMode == IcsExamining && !pausing) {
11811 SendToICS(ics_prefix);
11812 SendToICS("backward 999999\n");
11814 BackwardInner(backwardMostMove);
11817 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11818 /* we have fed all the moves, so reactivate analysis mode */
11819 SendToProgram("analyze\n", &first);
11820 first.analyzing = TRUE;
11821 /*first.maybeThinking = TRUE;*/
11822 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11829 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11830 if (to >= forwardMostMove) to = forwardMostMove;
11831 if (to <= backwardMostMove) to = backwardMostMove;
11832 if (to < currentMove) {
11842 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11845 if (gameMode != IcsExamining) {
11846 DisplayError(_("You are not examining a game"), 0);
11850 DisplayError(_("You can't revert while pausing"), 0);
11853 SendToICS(ics_prefix);
11854 SendToICS("revert\n");
11860 switch (gameMode) {
11861 case MachinePlaysWhite:
11862 case MachinePlaysBlack:
11863 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11864 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11867 if (forwardMostMove < 2) return;
11868 currentMove = forwardMostMove = forwardMostMove - 2;
11869 whiteTimeRemaining = timeRemaining[0][currentMove];
11870 blackTimeRemaining = timeRemaining[1][currentMove];
11871 DisplayBothClocks();
11872 DisplayMove(currentMove - 1);
11873 ClearHighlights();/*!! could figure this out*/
11874 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11875 SendToProgram("remove\n", &first);
11876 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11879 case BeginningOfGame:
11883 case IcsPlayingWhite:
11884 case IcsPlayingBlack:
11885 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11886 SendToICS(ics_prefix);
11887 SendToICS("takeback 2\n");
11889 SendToICS(ics_prefix);
11890 SendToICS("takeback 1\n");
11899 ChessProgramState *cps;
11901 switch (gameMode) {
11902 case MachinePlaysWhite:
11903 if (!WhiteOnMove(forwardMostMove)) {
11904 DisplayError(_("It is your turn"), 0);
11909 case MachinePlaysBlack:
11910 if (WhiteOnMove(forwardMostMove)) {
11911 DisplayError(_("It is your turn"), 0);
11916 case TwoMachinesPlay:
11917 if (WhiteOnMove(forwardMostMove) ==
11918 (first.twoMachinesColor[0] == 'w')) {
11924 case BeginningOfGame:
11928 SendToProgram("?\n", cps);
11932 TruncateGameEvent()
11935 if (gameMode != EditGame) return;
11942 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11943 if (forwardMostMove > currentMove) {
11944 if (gameInfo.resultDetails != NULL) {
11945 free(gameInfo.resultDetails);
11946 gameInfo.resultDetails = NULL;
11947 gameInfo.result = GameUnfinished;
11949 forwardMostMove = currentMove;
11950 HistorySet(parseList, backwardMostMove, forwardMostMove,
11958 if (appData.noChessProgram) return;
11959 switch (gameMode) {
11960 case MachinePlaysWhite:
11961 if (WhiteOnMove(forwardMostMove)) {
11962 DisplayError(_("Wait until your turn"), 0);
11966 case BeginningOfGame:
11967 case MachinePlaysBlack:
11968 if (!WhiteOnMove(forwardMostMove)) {
11969 DisplayError(_("Wait until your turn"), 0);
11974 DisplayError(_("No hint available"), 0);
11977 SendToProgram("hint\n", &first);
11978 hintRequested = TRUE;
11984 if (appData.noChessProgram) return;
11985 switch (gameMode) {
11986 case MachinePlaysWhite:
11987 if (WhiteOnMove(forwardMostMove)) {
11988 DisplayError(_("Wait until your turn"), 0);
11992 case BeginningOfGame:
11993 case MachinePlaysBlack:
11994 if (!WhiteOnMove(forwardMostMove)) {
11995 DisplayError(_("Wait until your turn"), 0);
12000 EditPositionDone(TRUE);
12002 case TwoMachinesPlay:
12007 SendToProgram("bk\n", &first);
12008 bookOutput[0] = NULLCHAR;
12009 bookRequested = TRUE;
12015 char *tags = PGNTags(&gameInfo);
12016 TagsPopUp(tags, CmailMsg());
12020 /* end button procedures */
12023 PrintPosition(fp, move)
12029 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12030 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12031 char c = PieceToChar(boards[move][i][j]);
12032 fputc(c == 'x' ? '.' : c, fp);
12033 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12036 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12037 fprintf(fp, "white to play\n");
12039 fprintf(fp, "black to play\n");
12046 if (gameInfo.white != NULL) {
12047 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12053 /* Find last component of program's own name, using some heuristics */
12055 TidyProgramName(prog, host, buf)
12056 char *prog, *host, buf[MSG_SIZ];
12059 int local = (strcmp(host, "localhost") == 0);
12060 while (!local && (p = strchr(prog, ';')) != NULL) {
12062 while (*p == ' ') p++;
12065 if (*prog == '"' || *prog == '\'') {
12066 q = strchr(prog + 1, *prog);
12068 q = strchr(prog, ' ');
12070 if (q == NULL) q = prog + strlen(prog);
12072 while (p >= prog && *p != '/' && *p != '\\') p--;
12074 if(p == prog && *p == '"') p++;
12075 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12076 memcpy(buf, p, q - p);
12077 buf[q - p] = NULLCHAR;
12085 TimeControlTagValue()
12088 if (!appData.clockMode) {
12090 } else if (movesPerSession > 0) {
12091 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12092 } else if (timeIncrement == 0) {
12093 sprintf(buf, "%ld", timeControl/1000);
12095 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12097 return StrSave(buf);
12103 /* This routine is used only for certain modes */
12104 VariantClass v = gameInfo.variant;
12105 ChessMove r = GameUnfinished;
12108 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12109 r = gameInfo.result;
12110 p = gameInfo.resultDetails;
12111 gameInfo.resultDetails = NULL;
12113 ClearGameInfo(&gameInfo);
12114 gameInfo.variant = v;
12116 switch (gameMode) {
12117 case MachinePlaysWhite:
12118 gameInfo.event = StrSave( appData.pgnEventHeader );
12119 gameInfo.site = StrSave(HostName());
12120 gameInfo.date = PGNDate();
12121 gameInfo.round = StrSave("-");
12122 gameInfo.white = StrSave(first.tidy);
12123 gameInfo.black = StrSave(UserName());
12124 gameInfo.timeControl = TimeControlTagValue();
12127 case MachinePlaysBlack:
12128 gameInfo.event = StrSave( appData.pgnEventHeader );
12129 gameInfo.site = StrSave(HostName());
12130 gameInfo.date = PGNDate();
12131 gameInfo.round = StrSave("-");
12132 gameInfo.white = StrSave(UserName());
12133 gameInfo.black = StrSave(first.tidy);
12134 gameInfo.timeControl = TimeControlTagValue();
12137 case TwoMachinesPlay:
12138 gameInfo.event = StrSave( appData.pgnEventHeader );
12139 gameInfo.site = StrSave(HostName());
12140 gameInfo.date = PGNDate();
12141 if (matchGame > 0) {
12143 sprintf(buf, "%d", matchGame);
12144 gameInfo.round = StrSave(buf);
12146 gameInfo.round = StrSave("-");
12148 if (first.twoMachinesColor[0] == 'w') {
12149 gameInfo.white = StrSave(first.tidy);
12150 gameInfo.black = StrSave(second.tidy);
12152 gameInfo.white = StrSave(second.tidy);
12153 gameInfo.black = StrSave(first.tidy);
12155 gameInfo.timeControl = TimeControlTagValue();
12159 gameInfo.event = StrSave("Edited game");
12160 gameInfo.site = StrSave(HostName());
12161 gameInfo.date = PGNDate();
12162 gameInfo.round = StrSave("-");
12163 gameInfo.white = StrSave("-");
12164 gameInfo.black = StrSave("-");
12165 gameInfo.result = r;
12166 gameInfo.resultDetails = p;
12170 gameInfo.event = StrSave("Edited position");
12171 gameInfo.site = StrSave(HostName());
12172 gameInfo.date = PGNDate();
12173 gameInfo.round = StrSave("-");
12174 gameInfo.white = StrSave("-");
12175 gameInfo.black = StrSave("-");
12178 case IcsPlayingWhite:
12179 case IcsPlayingBlack:
12184 case PlayFromGameFile:
12185 gameInfo.event = StrSave("Game from non-PGN file");
12186 gameInfo.site = StrSave(HostName());
12187 gameInfo.date = PGNDate();
12188 gameInfo.round = StrSave("-");
12189 gameInfo.white = StrSave("?");
12190 gameInfo.black = StrSave("?");
12199 ReplaceComment(index, text)
12205 while (*text == '\n') text++;
12206 len = strlen(text);
12207 while (len > 0 && text[len - 1] == '\n') len--;
12209 if (commentList[index] != NULL)
12210 free(commentList[index]);
12213 commentList[index] = NULL;
12216 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12217 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12218 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12219 commentList[index] = (char *) malloc(len + 2);
12220 strncpy(commentList[index], text, len);
12221 commentList[index][len] = '\n';
12222 commentList[index][len + 1] = NULLCHAR;
12224 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12226 commentList[index] = (char *) malloc(len + 6);
12227 strcpy(commentList[index], "{\n");
12228 strncpy(commentList[index]+2, text, len);
12229 commentList[index][len+2] = NULLCHAR;
12230 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12231 strcat(commentList[index], "\n}\n");
12245 if (ch == '\r') continue;
12247 } while (ch != '\0');
12251 AppendComment(index, text, addBraces)
12254 Boolean addBraces; // [HGM] braces: tells if we should add {}
12259 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12260 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12263 while (*text == '\n') text++;
12264 len = strlen(text);
12265 while (len > 0 && text[len - 1] == '\n') len--;
12267 if (len == 0) return;
12269 if (commentList[index] != NULL) {
12270 old = commentList[index];
12271 oldlen = strlen(old);
12272 while(commentList[index][oldlen-1] == '\n')
12273 commentList[index][--oldlen] = NULLCHAR;
12274 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12275 strcpy(commentList[index], old);
12277 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12278 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12279 if(addBraces) addBraces = FALSE; else { text++; len--; }
12280 while (*text == '\n') { text++; len--; }
12281 commentList[index][--oldlen] = NULLCHAR;
12283 if(addBraces) strcat(commentList[index], "\n{\n");
12284 else strcat(commentList[index], "\n");
12285 strcat(commentList[index], text);
12286 if(addBraces) strcat(commentList[index], "\n}\n");
12287 else strcat(commentList[index], "\n");
12289 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12291 strcpy(commentList[index], "{\n");
12292 else commentList[index][0] = NULLCHAR;
12293 strcat(commentList[index], text);
12294 strcat(commentList[index], "\n");
12295 if(addBraces) strcat(commentList[index], "}\n");
12299 static char * FindStr( char * text, char * sub_text )
12301 char * result = strstr( text, sub_text );
12303 if( result != NULL ) {
12304 result += strlen( sub_text );
12310 /* [AS] Try to extract PV info from PGN comment */
12311 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12312 char *GetInfoFromComment( int index, char * text )
12316 if( text != NULL && index > 0 ) {
12319 int time = -1, sec = 0, deci;
12320 char * s_eval = FindStr( text, "[%eval " );
12321 char * s_emt = FindStr( text, "[%emt " );
12323 if( s_eval != NULL || s_emt != NULL ) {
12327 if( s_eval != NULL ) {
12328 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12332 if( delim != ']' ) {
12337 if( s_emt != NULL ) {
12342 /* We expect something like: [+|-]nnn.nn/dd */
12345 if(*text != '{') return text; // [HGM] braces: must be normal comment
12347 sep = strchr( text, '/' );
12348 if( sep == NULL || sep < (text+4) ) {
12352 time = -1; sec = -1; deci = -1;
12353 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12354 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12355 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12356 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12360 if( score_lo < 0 || score_lo >= 100 ) {
12364 if(sec >= 0) time = 600*time + 10*sec; else
12365 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12367 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12369 /* [HGM] PV time: now locate end of PV info */
12370 while( *++sep >= '0' && *sep <= '9'); // strip depth
12372 while( *++sep >= '0' && *sep <= '9'); // strip time
12374 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12376 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12377 while(*sep == ' ') sep++;
12388 pvInfoList[index-1].depth = depth;
12389 pvInfoList[index-1].score = score;
12390 pvInfoList[index-1].time = 10*time; // centi-sec
12391 if(*sep == '}') *sep = 0; else *--sep = '{';
12397 SendToProgram(message, cps)
12399 ChessProgramState *cps;
12401 int count, outCount, error;
12404 if (cps->pr == NULL) return;
12407 if (appData.debugMode) {
12410 fprintf(debugFP, "%ld >%-6s: %s",
12411 SubtractTimeMarks(&now, &programStartTime),
12412 cps->which, message);
12415 count = strlen(message);
12416 outCount = OutputToProcess(cps->pr, message, count, &error);
12417 if (outCount < count && !exiting
12418 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12419 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12420 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12421 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12422 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12423 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12425 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12427 gameInfo.resultDetails = StrSave(buf);
12429 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12434 ReceiveFromProgram(isr, closure, message, count, error)
12435 InputSourceRef isr;
12443 ChessProgramState *cps = (ChessProgramState *)closure;
12445 if (isr != cps->isr) return; /* Killed intentionally */
12449 _("Error: %s chess program (%s) exited unexpectedly"),
12450 cps->which, cps->program);
12451 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12452 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12453 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12454 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12456 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12458 gameInfo.resultDetails = StrSave(buf);
12460 RemoveInputSource(cps->isr);
12461 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12464 _("Error reading from %s chess program (%s)"),
12465 cps->which, cps->program);
12466 RemoveInputSource(cps->isr);
12468 /* [AS] Program is misbehaving badly... kill it */
12469 if( count == -2 ) {
12470 DestroyChildProcess( cps->pr, 9 );
12474 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12479 if ((end_str = strchr(message, '\r')) != NULL)
12480 *end_str = NULLCHAR;
12481 if ((end_str = strchr(message, '\n')) != NULL)
12482 *end_str = NULLCHAR;
12484 if (appData.debugMode) {
12485 TimeMark now; int print = 1;
12486 char *quote = ""; char c; int i;
12488 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12489 char start = message[0];
12490 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12491 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12492 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12493 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12494 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12495 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12496 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12497 sscanf(message, "pong %c", &c)!=1 && start != '#')
12498 { quote = "# "; print = (appData.engineComments == 2); }
12499 message[0] = start; // restore original message
12503 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12504 SubtractTimeMarks(&now, &programStartTime), cps->which,
12510 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12511 if (appData.icsEngineAnalyze) {
12512 if (strstr(message, "whisper") != NULL ||
12513 strstr(message, "kibitz") != NULL ||
12514 strstr(message, "tellics") != NULL) return;
12517 HandleMachineMove(message, cps);
12522 SendTimeControl(cps, mps, tc, inc, sd, st)
12523 ChessProgramState *cps;
12524 int mps, inc, sd, st;
12530 if( timeControl_2 > 0 ) {
12531 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12532 tc = timeControl_2;
12535 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12536 inc /= cps->timeOdds;
12537 st /= cps->timeOdds;
12539 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12542 /* Set exact time per move, normally using st command */
12543 if (cps->stKludge) {
12544 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12546 if (seconds == 0) {
12547 sprintf(buf, "level 1 %d\n", st/60);
12549 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12552 sprintf(buf, "st %d\n", st);
12555 /* Set conventional or incremental time control, using level command */
12556 if (seconds == 0) {
12557 /* Note old gnuchess bug -- minutes:seconds used to not work.
12558 Fixed in later versions, but still avoid :seconds
12559 when seconds is 0. */
12560 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12562 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12563 seconds, inc/1000);
12566 SendToProgram(buf, cps);
12568 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12569 /* Orthogonally, limit search to given depth */
12571 if (cps->sdKludge) {
12572 sprintf(buf, "depth\n%d\n", sd);
12574 sprintf(buf, "sd %d\n", sd);
12576 SendToProgram(buf, cps);
12579 if(cps->nps > 0) { /* [HGM] nps */
12580 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12582 sprintf(buf, "nps %d\n", cps->nps);
12583 SendToProgram(buf, cps);
12588 ChessProgramState *WhitePlayer()
12589 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12591 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12592 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12598 SendTimeRemaining(cps, machineWhite)
12599 ChessProgramState *cps;
12600 int /*boolean*/ machineWhite;
12602 char message[MSG_SIZ];
12605 /* Note: this routine must be called when the clocks are stopped
12606 or when they have *just* been set or switched; otherwise
12607 it will be off by the time since the current tick started.
12609 if (machineWhite) {
12610 time = whiteTimeRemaining / 10;
12611 otime = blackTimeRemaining / 10;
12613 time = blackTimeRemaining / 10;
12614 otime = whiteTimeRemaining / 10;
12616 /* [HGM] translate opponent's time by time-odds factor */
12617 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12618 if (appData.debugMode) {
12619 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12622 if (time <= 0) time = 1;
12623 if (otime <= 0) otime = 1;
12625 sprintf(message, "time %ld\n", time);
12626 SendToProgram(message, cps);
12628 sprintf(message, "otim %ld\n", otime);
12629 SendToProgram(message, cps);
12633 BoolFeature(p, name, loc, cps)
12637 ChessProgramState *cps;
12640 int len = strlen(name);
12642 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12644 sscanf(*p, "%d", &val);
12646 while (**p && **p != ' ') (*p)++;
12647 sprintf(buf, "accepted %s\n", name);
12648 SendToProgram(buf, cps);
12655 IntFeature(p, name, loc, cps)
12659 ChessProgramState *cps;
12662 int len = strlen(name);
12663 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12665 sscanf(*p, "%d", loc);
12666 while (**p && **p != ' ') (*p)++;
12667 sprintf(buf, "accepted %s\n", name);
12668 SendToProgram(buf, cps);
12675 StringFeature(p, name, loc, cps)
12679 ChessProgramState *cps;
12682 int len = strlen(name);
12683 if (strncmp((*p), name, len) == 0
12684 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12686 sscanf(*p, "%[^\"]", loc);
12687 while (**p && **p != '\"') (*p)++;
12688 if (**p == '\"') (*p)++;
12689 sprintf(buf, "accepted %s\n", name);
12690 SendToProgram(buf, cps);
12697 ParseOption(Option *opt, ChessProgramState *cps)
12698 // [HGM] options: process the string that defines an engine option, and determine
12699 // name, type, default value, and allowed value range
12701 char *p, *q, buf[MSG_SIZ];
12702 int n, min = (-1)<<31, max = 1<<31, def;
12704 if(p = strstr(opt->name, " -spin ")) {
12705 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12706 if(max < min) max = min; // enforce consistency
12707 if(def < min) def = min;
12708 if(def > max) def = max;
12713 } else if((p = strstr(opt->name, " -slider "))) {
12714 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12715 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12716 if(max < min) max = min; // enforce consistency
12717 if(def < min) def = min;
12718 if(def > max) def = max;
12722 opt->type = Spin; // Slider;
12723 } else if((p = strstr(opt->name, " -string "))) {
12724 opt->textValue = p+9;
12725 opt->type = TextBox;
12726 } else if((p = strstr(opt->name, " -file "))) {
12727 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12728 opt->textValue = p+7;
12729 opt->type = TextBox; // FileName;
12730 } else if((p = strstr(opt->name, " -path "))) {
12731 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12732 opt->textValue = p+7;
12733 opt->type = TextBox; // PathName;
12734 } else if(p = strstr(opt->name, " -check ")) {
12735 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12736 opt->value = (def != 0);
12737 opt->type = CheckBox;
12738 } else if(p = strstr(opt->name, " -combo ")) {
12739 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12740 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12741 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12742 opt->value = n = 0;
12743 while(q = StrStr(q, " /// ")) {
12744 n++; *q = 0; // count choices, and null-terminate each of them
12746 if(*q == '*') { // remember default, which is marked with * prefix
12750 cps->comboList[cps->comboCnt++] = q;
12752 cps->comboList[cps->comboCnt++] = NULL;
12754 opt->type = ComboBox;
12755 } else if(p = strstr(opt->name, " -button")) {
12756 opt->type = Button;
12757 } else if(p = strstr(opt->name, " -save")) {
12758 opt->type = SaveButton;
12759 } else return FALSE;
12760 *p = 0; // terminate option name
12761 // now look if the command-line options define a setting for this engine option.
12762 if(cps->optionSettings && cps->optionSettings[0])
12763 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12764 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12765 sprintf(buf, "option %s", p);
12766 if(p = strstr(buf, ",")) *p = 0;
12768 SendToProgram(buf, cps);
12774 FeatureDone(cps, val)
12775 ChessProgramState* cps;
12778 DelayedEventCallback cb = GetDelayedEvent();
12779 if ((cb == InitBackEnd3 && cps == &first) ||
12780 (cb == TwoMachinesEventIfReady && cps == &second)) {
12781 CancelDelayedEvent();
12782 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12784 cps->initDone = val;
12787 /* Parse feature command from engine */
12789 ParseFeatures(args, cps)
12791 ChessProgramState *cps;
12799 while (*p == ' ') p++;
12800 if (*p == NULLCHAR) return;
12802 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12803 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12804 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12805 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12806 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12807 if (BoolFeature(&p, "reuse", &val, cps)) {
12808 /* Engine can disable reuse, but can't enable it if user said no */
12809 if (!val) cps->reuse = FALSE;
12812 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12813 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12814 if (gameMode == TwoMachinesPlay) {
12815 DisplayTwoMachinesTitle();
12821 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12822 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12823 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12824 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12825 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12826 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12827 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12828 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12829 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12830 if (IntFeature(&p, "done", &val, cps)) {
12831 FeatureDone(cps, val);
12834 /* Added by Tord: */
12835 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12836 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12837 /* End of additions by Tord */
12839 /* [HGM] added features: */
12840 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12841 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12842 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12843 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12844 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12845 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12846 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12847 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12848 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12849 SendToProgram(buf, cps);
12852 if(cps->nrOptions >= MAX_OPTIONS) {
12854 sprintf(buf, "%s engine has too many options\n", cps->which);
12855 DisplayError(buf, 0);
12859 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12860 /* End of additions by HGM */
12862 /* unknown feature: complain and skip */
12864 while (*q && *q != '=') q++;
12865 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12866 SendToProgram(buf, cps);
12872 while (*p && *p != '\"') p++;
12873 if (*p == '\"') p++;
12875 while (*p && *p != ' ') p++;
12883 PeriodicUpdatesEvent(newState)
12886 if (newState == appData.periodicUpdates)
12889 appData.periodicUpdates=newState;
12891 /* Display type changes, so update it now */
12892 // DisplayAnalysis();
12894 /* Get the ball rolling again... */
12896 AnalysisPeriodicEvent(1);
12897 StartAnalysisClock();
12902 PonderNextMoveEvent(newState)
12905 if (newState == appData.ponderNextMove) return;
12906 if (gameMode == EditPosition) EditPositionDone(TRUE);
12908 SendToProgram("hard\n", &first);
12909 if (gameMode == TwoMachinesPlay) {
12910 SendToProgram("hard\n", &second);
12913 SendToProgram("easy\n", &first);
12914 thinkOutput[0] = NULLCHAR;
12915 if (gameMode == TwoMachinesPlay) {
12916 SendToProgram("easy\n", &second);
12919 appData.ponderNextMove = newState;
12923 NewSettingEvent(option, command, value)
12929 if (gameMode == EditPosition) EditPositionDone(TRUE);
12930 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12931 SendToProgram(buf, &first);
12932 if (gameMode == TwoMachinesPlay) {
12933 SendToProgram(buf, &second);
12938 ShowThinkingEvent()
12939 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12941 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12942 int newState = appData.showThinking
12943 // [HGM] thinking: other features now need thinking output as well
12944 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12946 if (oldState == newState) return;
12947 oldState = newState;
12948 if (gameMode == EditPosition) EditPositionDone(TRUE);
12950 SendToProgram("post\n", &first);
12951 if (gameMode == TwoMachinesPlay) {
12952 SendToProgram("post\n", &second);
12955 SendToProgram("nopost\n", &first);
12956 thinkOutput[0] = NULLCHAR;
12957 if (gameMode == TwoMachinesPlay) {
12958 SendToProgram("nopost\n", &second);
12961 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12965 AskQuestionEvent(title, question, replyPrefix, which)
12966 char *title; char *question; char *replyPrefix; char *which;
12968 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12969 if (pr == NoProc) return;
12970 AskQuestion(title, question, replyPrefix, pr);
12974 DisplayMove(moveNumber)
12977 char message[MSG_SIZ];
12979 char cpThinkOutput[MSG_SIZ];
12981 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12983 if (moveNumber == forwardMostMove - 1 ||
12984 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12986 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12988 if (strchr(cpThinkOutput, '\n')) {
12989 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12992 *cpThinkOutput = NULLCHAR;
12995 /* [AS] Hide thinking from human user */
12996 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12997 *cpThinkOutput = NULLCHAR;
12998 if( thinkOutput[0] != NULLCHAR ) {
13001 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13002 cpThinkOutput[i] = '.';
13004 cpThinkOutput[i] = NULLCHAR;
13005 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13009 if (moveNumber == forwardMostMove - 1 &&
13010 gameInfo.resultDetails != NULL) {
13011 if (gameInfo.resultDetails[0] == NULLCHAR) {
13012 sprintf(res, " %s", PGNResult(gameInfo.result));
13014 sprintf(res, " {%s} %s",
13015 gameInfo.resultDetails, PGNResult(gameInfo.result));
13021 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13022 DisplayMessage(res, cpThinkOutput);
13024 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13025 WhiteOnMove(moveNumber) ? " " : ".. ",
13026 parseList[moveNumber], res);
13027 DisplayMessage(message, cpThinkOutput);
13032 DisplayComment(moveNumber, text)
13036 char title[MSG_SIZ];
13037 char buf[8000]; // comment can be long!
13040 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13041 strcpy(title, "Comment");
13043 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13044 WhiteOnMove(moveNumber) ? " " : ".. ",
13045 parseList[moveNumber]);
13047 // [HGM] PV info: display PV info together with (or as) comment
13048 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13049 if(text == NULL) text = "";
13050 score = pvInfoList[moveNumber].score;
13051 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13052 depth, (pvInfoList[moveNumber].time+50)/100, text);
13055 if (text != NULL && (appData.autoDisplayComment || commentUp))
13056 CommentPopUp(title, text);
13059 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13060 * might be busy thinking or pondering. It can be omitted if your
13061 * gnuchess is configured to stop thinking immediately on any user
13062 * input. However, that gnuchess feature depends on the FIONREAD
13063 * ioctl, which does not work properly on some flavors of Unix.
13067 ChessProgramState *cps;
13070 if (!cps->useSigint) return;
13071 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13072 switch (gameMode) {
13073 case MachinePlaysWhite:
13074 case MachinePlaysBlack:
13075 case TwoMachinesPlay:
13076 case IcsPlayingWhite:
13077 case IcsPlayingBlack:
13080 /* Skip if we know it isn't thinking */
13081 if (!cps->maybeThinking) return;
13082 if (appData.debugMode)
13083 fprintf(debugFP, "Interrupting %s\n", cps->which);
13084 InterruptChildProcess(cps->pr);
13085 cps->maybeThinking = FALSE;
13090 #endif /*ATTENTION*/
13096 if (whiteTimeRemaining <= 0) {
13099 if (appData.icsActive) {
13100 if (appData.autoCallFlag &&
13101 gameMode == IcsPlayingBlack && !blackFlag) {
13102 SendToICS(ics_prefix);
13103 SendToICS("flag\n");
13107 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13109 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13110 if (appData.autoCallFlag) {
13111 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13118 if (blackTimeRemaining <= 0) {
13121 if (appData.icsActive) {
13122 if (appData.autoCallFlag &&
13123 gameMode == IcsPlayingWhite && !whiteFlag) {
13124 SendToICS(ics_prefix);
13125 SendToICS("flag\n");
13129 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13131 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13132 if (appData.autoCallFlag) {
13133 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13146 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13147 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13150 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13152 if ( !WhiteOnMove(forwardMostMove) )
13153 /* White made time control */
13154 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13155 /* [HGM] time odds: correct new time quota for time odds! */
13156 / WhitePlayer()->timeOdds;
13158 /* Black made time control */
13159 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13160 / WhitePlayer()->other->timeOdds;
13164 DisplayBothClocks()
13166 int wom = gameMode == EditPosition ?
13167 !blackPlaysFirst : WhiteOnMove(currentMove);
13168 DisplayWhiteClock(whiteTimeRemaining, wom);
13169 DisplayBlackClock(blackTimeRemaining, !wom);
13173 /* Timekeeping seems to be a portability nightmare. I think everyone
13174 has ftime(), but I'm really not sure, so I'm including some ifdefs
13175 to use other calls if you don't. Clocks will be less accurate if
13176 you have neither ftime nor gettimeofday.
13179 /* VS 2008 requires the #include outside of the function */
13180 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13181 #include <sys/timeb.h>
13184 /* Get the current time as a TimeMark */
13189 #if HAVE_GETTIMEOFDAY
13191 struct timeval timeVal;
13192 struct timezone timeZone;
13194 gettimeofday(&timeVal, &timeZone);
13195 tm->sec = (long) timeVal.tv_sec;
13196 tm->ms = (int) (timeVal.tv_usec / 1000L);
13198 #else /*!HAVE_GETTIMEOFDAY*/
13201 // include <sys/timeb.h> / moved to just above start of function
13202 struct timeb timeB;
13205 tm->sec = (long) timeB.time;
13206 tm->ms = (int) timeB.millitm;
13208 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13209 tm->sec = (long) time(NULL);
13215 /* Return the difference in milliseconds between two
13216 time marks. We assume the difference will fit in a long!
13219 SubtractTimeMarks(tm2, tm1)
13220 TimeMark *tm2, *tm1;
13222 return 1000L*(tm2->sec - tm1->sec) +
13223 (long) (tm2->ms - tm1->ms);
13228 * Code to manage the game clocks.
13230 * In tournament play, black starts the clock and then white makes a move.
13231 * We give the human user a slight advantage if he is playing white---the
13232 * clocks don't run until he makes his first move, so it takes zero time.
13233 * Also, we don't account for network lag, so we could get out of sync
13234 * with GNU Chess's clock -- but then, referees are always right.
13237 static TimeMark tickStartTM;
13238 static long intendedTickLength;
13241 NextTickLength(timeRemaining)
13242 long timeRemaining;
13244 long nominalTickLength, nextTickLength;
13246 if (timeRemaining > 0L && timeRemaining <= 10000L)
13247 nominalTickLength = 100L;
13249 nominalTickLength = 1000L;
13250 nextTickLength = timeRemaining % nominalTickLength;
13251 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13253 return nextTickLength;
13256 /* Adjust clock one minute up or down */
13258 AdjustClock(Boolean which, int dir)
13260 if(which) blackTimeRemaining += 60000*dir;
13261 else whiteTimeRemaining += 60000*dir;
13262 DisplayBothClocks();
13265 /* Stop clocks and reset to a fresh time control */
13269 (void) StopClockTimer();
13270 if (appData.icsActive) {
13271 whiteTimeRemaining = blackTimeRemaining = 0;
13272 } else if (searchTime) {
13273 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13274 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13275 } else { /* [HGM] correct new time quote for time odds */
13276 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13277 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13279 if (whiteFlag || blackFlag) {
13281 whiteFlag = blackFlag = FALSE;
13283 DisplayBothClocks();
13286 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13288 /* Decrement running clock by amount of time that has passed */
13292 long timeRemaining;
13293 long lastTickLength, fudge;
13296 if (!appData.clockMode) return;
13297 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13301 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13303 /* Fudge if we woke up a little too soon */
13304 fudge = intendedTickLength - lastTickLength;
13305 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13307 if (WhiteOnMove(forwardMostMove)) {
13308 if(whiteNPS >= 0) lastTickLength = 0;
13309 timeRemaining = whiteTimeRemaining -= lastTickLength;
13310 DisplayWhiteClock(whiteTimeRemaining - fudge,
13311 WhiteOnMove(currentMove));
13313 if(blackNPS >= 0) lastTickLength = 0;
13314 timeRemaining = blackTimeRemaining -= lastTickLength;
13315 DisplayBlackClock(blackTimeRemaining - fudge,
13316 !WhiteOnMove(currentMove));
13319 if (CheckFlags()) return;
13322 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13323 StartClockTimer(intendedTickLength);
13325 /* if the time remaining has fallen below the alarm threshold, sound the
13326 * alarm. if the alarm has sounded and (due to a takeback or time control
13327 * with increment) the time remaining has increased to a level above the
13328 * threshold, reset the alarm so it can sound again.
13331 if (appData.icsActive && appData.icsAlarm) {
13333 /* make sure we are dealing with the user's clock */
13334 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13335 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13338 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13339 alarmSounded = FALSE;
13340 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13342 alarmSounded = TRUE;
13348 /* A player has just moved, so stop the previously running
13349 clock and (if in clock mode) start the other one.
13350 We redisplay both clocks in case we're in ICS mode, because
13351 ICS gives us an update to both clocks after every move.
13352 Note that this routine is called *after* forwardMostMove
13353 is updated, so the last fractional tick must be subtracted
13354 from the color that is *not* on move now.
13359 long lastTickLength;
13361 int flagged = FALSE;
13365 if (StopClockTimer() && appData.clockMode) {
13366 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13367 if (WhiteOnMove(forwardMostMove)) {
13368 if(blackNPS >= 0) lastTickLength = 0;
13369 blackTimeRemaining -= lastTickLength;
13370 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13371 // if(pvInfoList[forwardMostMove-1].time == -1)
13372 pvInfoList[forwardMostMove-1].time = // use GUI time
13373 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13375 if(whiteNPS >= 0) lastTickLength = 0;
13376 whiteTimeRemaining -= lastTickLength;
13377 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13378 // if(pvInfoList[forwardMostMove-1].time == -1)
13379 pvInfoList[forwardMostMove-1].time =
13380 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13382 flagged = CheckFlags();
13384 CheckTimeControl();
13386 if (flagged || !appData.clockMode) return;
13388 switch (gameMode) {
13389 case MachinePlaysBlack:
13390 case MachinePlaysWhite:
13391 case BeginningOfGame:
13392 if (pausing) return;
13396 case PlayFromGameFile:
13404 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13405 if(WhiteOnMove(forwardMostMove))
13406 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13407 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13411 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13412 whiteTimeRemaining : blackTimeRemaining);
13413 StartClockTimer(intendedTickLength);
13417 /* Stop both clocks */
13421 long lastTickLength;
13424 if (!StopClockTimer()) return;
13425 if (!appData.clockMode) return;
13429 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13430 if (WhiteOnMove(forwardMostMove)) {
13431 if(whiteNPS >= 0) lastTickLength = 0;
13432 whiteTimeRemaining -= lastTickLength;
13433 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13435 if(blackNPS >= 0) lastTickLength = 0;
13436 blackTimeRemaining -= lastTickLength;
13437 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13442 /* Start clock of player on move. Time may have been reset, so
13443 if clock is already running, stop and restart it. */
13447 (void) StopClockTimer(); /* in case it was running already */
13448 DisplayBothClocks();
13449 if (CheckFlags()) return;
13451 if (!appData.clockMode) return;
13452 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13454 GetTimeMark(&tickStartTM);
13455 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13456 whiteTimeRemaining : blackTimeRemaining);
13458 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13459 whiteNPS = blackNPS = -1;
13460 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13461 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13462 whiteNPS = first.nps;
13463 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13464 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13465 blackNPS = first.nps;
13466 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13467 whiteNPS = second.nps;
13468 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13469 blackNPS = second.nps;
13470 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13472 StartClockTimer(intendedTickLength);
13479 long second, minute, hour, day;
13481 static char buf[32];
13483 if (ms > 0 && ms <= 9900) {
13484 /* convert milliseconds to tenths, rounding up */
13485 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13487 sprintf(buf, " %03.1f ", tenths/10.0);
13491 /* convert milliseconds to seconds, rounding up */
13492 /* use floating point to avoid strangeness of integer division
13493 with negative dividends on many machines */
13494 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13501 day = second / (60 * 60 * 24);
13502 second = second % (60 * 60 * 24);
13503 hour = second / (60 * 60);
13504 second = second % (60 * 60);
13505 minute = second / 60;
13506 second = second % 60;
13509 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13510 sign, day, hour, minute, second);
13512 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13514 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13521 * This is necessary because some C libraries aren't ANSI C compliant yet.
13524 StrStr(string, match)
13525 char *string, *match;
13529 length = strlen(match);
13531 for (i = strlen(string) - length; i >= 0; i--, string++)
13532 if (!strncmp(match, string, length))
13539 StrCaseStr(string, match)
13540 char *string, *match;
13544 length = strlen(match);
13546 for (i = strlen(string) - length; i >= 0; i--, string++) {
13547 for (j = 0; j < length; j++) {
13548 if (ToLower(match[j]) != ToLower(string[j]))
13551 if (j == length) return string;
13565 c1 = ToLower(*s1++);
13566 c2 = ToLower(*s2++);
13567 if (c1 > c2) return 1;
13568 if (c1 < c2) return -1;
13569 if (c1 == NULLCHAR) return 0;
13578 return isupper(c) ? tolower(c) : c;
13586 return islower(c) ? toupper(c) : c;
13588 #endif /* !_amigados */
13596 if ((ret = (char *) malloc(strlen(s) + 1))) {
13603 StrSavePtr(s, savePtr)
13604 char *s, **savePtr;
13609 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13610 strcpy(*savePtr, s);
13622 clock = time((time_t *)NULL);
13623 tm = localtime(&clock);
13624 sprintf(buf, "%04d.%02d.%02d",
13625 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13626 return StrSave(buf);
13631 PositionToFEN(move, overrideCastling)
13633 char *overrideCastling;
13635 int i, j, fromX, fromY, toX, toY;
13642 whiteToPlay = (gameMode == EditPosition) ?
13643 !blackPlaysFirst : (move % 2 == 0);
13646 /* Piece placement data */
13647 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13649 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13650 if (boards[move][i][j] == EmptySquare) {
13652 } else { ChessSquare piece = boards[move][i][j];
13653 if (emptycount > 0) {
13654 if(emptycount<10) /* [HGM] can be >= 10 */
13655 *p++ = '0' + emptycount;
13656 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13659 if(PieceToChar(piece) == '+') {
13660 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13662 piece = (ChessSquare)(DEMOTED piece);
13664 *p++ = PieceToChar(piece);
13666 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13667 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13672 if (emptycount > 0) {
13673 if(emptycount<10) /* [HGM] can be >= 10 */
13674 *p++ = '0' + emptycount;
13675 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13682 /* [HGM] print Crazyhouse or Shogi holdings */
13683 if( gameInfo.holdingsWidth ) {
13684 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13686 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13687 piece = boards[move][i][BOARD_WIDTH-1];
13688 if( piece != EmptySquare )
13689 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13690 *p++ = PieceToChar(piece);
13692 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13693 piece = boards[move][BOARD_HEIGHT-i-1][0];
13694 if( piece != EmptySquare )
13695 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13696 *p++ = PieceToChar(piece);
13699 if( q == p ) *p++ = '-';
13705 *p++ = whiteToPlay ? 'w' : 'b';
13708 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13709 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13711 if(nrCastlingRights) {
13713 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13714 /* [HGM] write directly from rights */
13715 if(boards[move][CASTLING][2] != NoRights &&
13716 boards[move][CASTLING][0] != NoRights )
13717 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13718 if(boards[move][CASTLING][2] != NoRights &&
13719 boards[move][CASTLING][1] != NoRights )
13720 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13721 if(boards[move][CASTLING][5] != NoRights &&
13722 boards[move][CASTLING][3] != NoRights )
13723 *p++ = boards[move][CASTLING][3] + AAA;
13724 if(boards[move][CASTLING][5] != NoRights &&
13725 boards[move][CASTLING][4] != NoRights )
13726 *p++ = boards[move][CASTLING][4] + AAA;
13729 /* [HGM] write true castling rights */
13730 if( nrCastlingRights == 6 ) {
13731 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13732 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13733 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13734 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13735 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13736 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13737 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13738 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13741 if (q == p) *p++ = '-'; /* No castling rights */
13745 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13746 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13747 /* En passant target square */
13748 if (move > backwardMostMove) {
13749 fromX = moveList[move - 1][0] - AAA;
13750 fromY = moveList[move - 1][1] - ONE;
13751 toX = moveList[move - 1][2] - AAA;
13752 toY = moveList[move - 1][3] - ONE;
13753 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13754 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13755 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13757 /* 2-square pawn move just happened */
13759 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13763 } else if(move == backwardMostMove) {
13764 // [HGM] perhaps we should always do it like this, and forget the above?
13765 if((signed char)boards[move][EP_STATUS] >= 0) {
13766 *p++ = boards[move][EP_STATUS] + AAA;
13767 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13778 /* [HGM] find reversible plies */
13779 { int i = 0, j=move;
13781 if (appData.debugMode) { int k;
13782 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13783 for(k=backwardMostMove; k<=forwardMostMove; k++)
13784 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13788 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13789 if( j == backwardMostMove ) i += initialRulePlies;
13790 sprintf(p, "%d ", i);
13791 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13793 /* Fullmove number */
13794 sprintf(p, "%d", (move / 2) + 1);
13796 return StrSave(buf);
13800 ParseFEN(board, blackPlaysFirst, fen)
13802 int *blackPlaysFirst;
13812 /* [HGM] by default clear Crazyhouse holdings, if present */
13813 if(gameInfo.holdingsWidth) {
13814 for(i=0; i<BOARD_HEIGHT; i++) {
13815 board[i][0] = EmptySquare; /* black holdings */
13816 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13817 board[i][1] = (ChessSquare) 0; /* black counts */
13818 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13822 /* Piece placement data */
13823 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13826 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13827 if (*p == '/') p++;
13828 emptycount = gameInfo.boardWidth - j;
13829 while (emptycount--)
13830 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13832 #if(BOARD_FILES >= 10)
13833 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13834 p++; emptycount=10;
13835 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13836 while (emptycount--)
13837 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13839 } else if (isdigit(*p)) {
13840 emptycount = *p++ - '0';
13841 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13842 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13843 while (emptycount--)
13844 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13845 } else if (*p == '+' || isalpha(*p)) {
13846 if (j >= gameInfo.boardWidth) return FALSE;
13848 piece = CharToPiece(*++p);
13849 if(piece == EmptySquare) return FALSE; /* unknown piece */
13850 piece = (ChessSquare) (PROMOTED piece ); p++;
13851 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13852 } else piece = CharToPiece(*p++);
13854 if(piece==EmptySquare) return FALSE; /* unknown piece */
13855 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13856 piece = (ChessSquare) (PROMOTED piece);
13857 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13860 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13866 while (*p == '/' || *p == ' ') p++;
13868 /* [HGM] look for Crazyhouse holdings here */
13869 while(*p==' ') p++;
13870 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13872 if(*p == '-' ) *p++; /* empty holdings */ else {
13873 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13874 /* if we would allow FEN reading to set board size, we would */
13875 /* have to add holdings and shift the board read so far here */
13876 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13878 if((int) piece >= (int) BlackPawn ) {
13879 i = (int)piece - (int)BlackPawn;
13880 i = PieceToNumber((ChessSquare)i);
13881 if( i >= gameInfo.holdingsSize ) return FALSE;
13882 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13883 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13885 i = (int)piece - (int)WhitePawn;
13886 i = PieceToNumber((ChessSquare)i);
13887 if( i >= gameInfo.holdingsSize ) return FALSE;
13888 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13889 board[i][BOARD_WIDTH-2]++; /* black holdings */
13893 if(*p == ']') *p++;
13896 while(*p == ' ') p++;
13901 *blackPlaysFirst = FALSE;
13904 *blackPlaysFirst = TRUE;
13910 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13911 /* return the extra info in global variiables */
13913 /* set defaults in case FEN is incomplete */
13914 board[EP_STATUS] = EP_UNKNOWN;
13915 for(i=0; i<nrCastlingRights; i++ ) {
13916 board[CASTLING][i] =
13917 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13918 } /* assume possible unless obviously impossible */
13919 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13920 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13921 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13922 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13923 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13924 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13927 while(*p==' ') p++;
13928 if(nrCastlingRights) {
13929 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13930 /* castling indicator present, so default becomes no castlings */
13931 for(i=0; i<nrCastlingRights; i++ ) {
13932 board[CASTLING][i] = NoRights;
13935 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13936 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13937 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13938 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13939 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13941 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13942 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13943 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13947 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13948 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13949 board[CASTLING][2] = whiteKingFile;
13952 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13953 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13954 board[CASTLING][2] = whiteKingFile;
13957 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13958 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13959 board[CASTLING][5] = blackKingFile;
13962 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13963 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13964 board[CASTLING][5] = blackKingFile;
13967 default: /* FRC castlings */
13968 if(c >= 'a') { /* black rights */
13969 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13970 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13971 if(i == BOARD_RGHT) break;
13972 board[CASTLING][5] = i;
13974 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13975 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13977 board[CASTLING][3] = c;
13979 board[CASTLING][4] = c;
13980 } else { /* white rights */
13981 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13982 if(board[0][i] == WhiteKing) break;
13983 if(i == BOARD_RGHT) break;
13984 board[CASTLING][2] = i;
13985 c -= AAA - 'a' + 'A';
13986 if(board[0][c] >= WhiteKing) break;
13988 board[CASTLING][0] = c;
13990 board[CASTLING][1] = c;
13994 if (appData.debugMode) {
13995 fprintf(debugFP, "FEN castling rights:");
13996 for(i=0; i<nrCastlingRights; i++)
13997 fprintf(debugFP, " %d", board[CASTLING][i]);
13998 fprintf(debugFP, "\n");
14001 while(*p==' ') p++;
14004 /* read e.p. field in games that know e.p. capture */
14005 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14006 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14008 p++; board[EP_STATUS] = EP_NONE;
14010 char c = *p++ - AAA;
14012 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14013 if(*p >= '0' && *p <='9') *p++;
14014 board[EP_STATUS] = c;
14019 if(sscanf(p, "%d", &i) == 1) {
14020 FENrulePlies = i; /* 50-move ply counter */
14021 /* (The move number is still ignored) */
14028 EditPositionPasteFEN(char *fen)
14031 Board initial_position;
14033 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14034 DisplayError(_("Bad FEN position in clipboard"), 0);
14037 int savedBlackPlaysFirst = blackPlaysFirst;
14038 EditPositionEvent();
14039 blackPlaysFirst = savedBlackPlaysFirst;
14040 CopyBoard(boards[0], initial_position);
14041 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14042 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14043 DisplayBothClocks();
14044 DrawPosition(FALSE, boards[currentMove]);
14049 static char cseq[12] = "\\ ";
14051 Boolean set_cont_sequence(char *new_seq)
14056 // handle bad attempts to set the sequence
14058 return 0; // acceptable error - no debug
14060 len = strlen(new_seq);
14061 ret = (len > 0) && (len < sizeof(cseq));
14063 strcpy(cseq, new_seq);
14064 else if (appData.debugMode)
14065 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14070 reformat a source message so words don't cross the width boundary. internal
14071 newlines are not removed. returns the wrapped size (no null character unless
14072 included in source message). If dest is NULL, only calculate the size required
14073 for the dest buffer. lp argument indicats line position upon entry, and it's
14074 passed back upon exit.
14076 int wrap(char *dest, char *src, int count, int width, int *lp)
14078 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14080 cseq_len = strlen(cseq);
14081 old_line = line = *lp;
14082 ansi = len = clen = 0;
14084 for (i=0; i < count; i++)
14086 if (src[i] == '\033')
14089 // if we hit the width, back up
14090 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14092 // store i & len in case the word is too long
14093 old_i = i, old_len = len;
14095 // find the end of the last word
14096 while (i && src[i] != ' ' && src[i] != '\n')
14102 // word too long? restore i & len before splitting it
14103 if ((old_i-i+clen) >= width)
14110 if (i && src[i-1] == ' ')
14113 if (src[i] != ' ' && src[i] != '\n')
14120 // now append the newline and continuation sequence
14125 strncpy(dest+len, cseq, cseq_len);
14133 dest[len] = src[i];
14137 if (src[i] == '\n')
14142 if (dest && appData.debugMode)
14144 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14145 count, width, line, len, *lp);
14146 show_bytes(debugFP, src, count);
14147 fprintf(debugFP, "\ndest: ");
14148 show_bytes(debugFP, dest, len);
14149 fprintf(debugFP, "\n");
14151 *lp = dest ? line : old_line;
14156 // [HGM] vari: routines for shelving variations
14159 PushTail(int firstMove, int lastMove)
14161 int i, j, nrMoves = lastMove - firstMove;
14163 if(appData.icsActive) { // only in local mode
14164 forwardMostMove = currentMove; // mimic old ICS behavior
14167 if(storedGames >= MAX_VARIATIONS-1) return;
14169 // push current tail of game on stack
14170 savedResult[storedGames] = gameInfo.result;
14171 savedDetails[storedGames] = gameInfo.resultDetails;
14172 gameInfo.resultDetails = NULL;
14173 savedFirst[storedGames] = firstMove;
14174 savedLast [storedGames] = lastMove;
14175 savedFramePtr[storedGames] = framePtr;
14176 framePtr -= nrMoves; // reserve space for the boards
14177 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14178 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14179 for(j=0; j<MOVE_LEN; j++)
14180 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14181 for(j=0; j<2*MOVE_LEN; j++)
14182 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14183 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14184 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14185 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14186 pvInfoList[firstMove+i-1].depth = 0;
14187 commentList[framePtr+i] = commentList[firstMove+i];
14188 commentList[firstMove+i] = NULL;
14192 forwardMostMove = currentMove; // truncte game so we can start variation
14193 if(storedGames == 1) GreyRevert(FALSE);
14197 PopTail(Boolean annotate)
14200 char buf[8000], moveBuf[20];
14202 if(appData.icsActive) return FALSE; // only in local mode
14203 if(!storedGames) return FALSE; // sanity
14206 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14207 nrMoves = savedLast[storedGames] - currentMove;
14210 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14211 else strcpy(buf, "(");
14212 for(i=currentMove; i<forwardMostMove; i++) {
14214 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14215 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14216 strcat(buf, moveBuf);
14217 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14221 for(i=1; i<nrMoves; i++) { // copy last variation back
14222 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14223 for(j=0; j<MOVE_LEN; j++)
14224 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14225 for(j=0; j<2*MOVE_LEN; j++)
14226 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14227 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14228 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14229 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14230 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14231 commentList[currentMove+i] = commentList[framePtr+i];
14232 commentList[framePtr+i] = NULL;
14234 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14235 framePtr = savedFramePtr[storedGames];
14236 gameInfo.result = savedResult[storedGames];
14237 if(gameInfo.resultDetails != NULL) {
14238 free(gameInfo.resultDetails);
14240 gameInfo.resultDetails = savedDetails[storedGames];
14241 forwardMostMove = currentMove + nrMoves;
14242 if(storedGames == 0) GreyRevert(TRUE);
14248 { // remove all shelved variations
14250 for(i=0; i<storedGames; i++) {
14251 if(savedDetails[i])
14252 free(savedDetails[i]);
14253 savedDetails[i] = NULL;
14255 for(i=framePtr; i<MAX_MOVES; i++) {
14256 if(commentList[i]) free(commentList[i]);
14257 commentList[i] = NULL;
14259 framePtr = MAX_MOVES-1;