2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
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 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237 VariantClass startVariant; /* [HGM] nicks: initial variant */
239 extern int tinyLayout, smallLayout;
240 ChessProgramStats programStats;
241 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
243 static int exiting = 0; /* [HGM] moved to top */
244 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
245 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
246 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
247 int partnerHighlight[2];
248 Boolean partnerBoardValid = 0;
249 char partnerStatus[MSG_SIZ];
251 Boolean originalFlip;
252 Boolean twoBoards = 0;
253 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
254 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
255 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
256 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
257 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
258 int opponentKibitzes;
259 int lastSavedGame; /* [HGM] save: ID of game */
260 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
261 extern int chatCount;
263 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
265 /* States for ics_getting_history */
267 #define H_REQUESTED 1
268 #define H_GOT_REQ_HEADER 2
269 #define H_GOT_UNREQ_HEADER 3
270 #define H_GETTING_MOVES 4
271 #define H_GOT_UNWANTED_HEADER 5
273 /* whosays values for GameEnds */
282 /* Maximum number of games in a cmail message */
283 #define CMAIL_MAX_GAMES 20
285 /* Different types of move when calling RegisterMove */
287 #define CMAIL_RESIGN 1
289 #define CMAIL_ACCEPT 3
291 /* Different types of result to remember for each game */
292 #define CMAIL_NOT_RESULT 0
293 #define CMAIL_OLD_RESULT 1
294 #define CMAIL_NEW_RESULT 2
296 /* Telnet protocol constants */
307 static char * safeStrCpy( char * dst, const char * src, size_t count )
309 assert( dst != NULL );
310 assert( src != NULL );
313 strncpy( dst, src, count );
314 dst[ count-1 ] = '\0';
318 /* Some compiler can't cast u64 to double
319 * This function do the job for us:
321 * We use the highest bit for cast, this only
322 * works if the highest bit is not
323 * in use (This should not happen)
325 * We used this for all compiler
328 u64ToDouble(u64 value)
331 u64 tmp = value & u64Const(0x7fffffffffffffff);
332 r = (double)(s64)tmp;
333 if (value & u64Const(0x8000000000000000))
334 r += 9.2233720368547758080e18; /* 2^63 */
338 /* Fake up flags for now, as we aren't keeping track of castling
339 availability yet. [HGM] Change of logic: the flag now only
340 indicates the type of castlings allowed by the rule of the game.
341 The actual rights themselves are maintained in the array
342 castlingRights, as part of the game history, and are not probed
348 int flags = F_ALL_CASTLE_OK;
349 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
350 switch (gameInfo.variant) {
352 flags &= ~F_ALL_CASTLE_OK;
353 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
354 flags |= F_IGNORE_CHECK;
356 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
359 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
361 case VariantKriegspiel:
362 flags |= F_KRIEGSPIEL_CAPTURE;
364 case VariantCapaRandom:
365 case VariantFischeRandom:
366 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
367 case VariantNoCastle:
368 case VariantShatranj:
371 flags &= ~F_ALL_CASTLE_OK;
379 FILE *gameFileFP, *debugFP;
382 [AS] Note: sometimes, the sscanf() function is used to parse the input
383 into a fixed-size buffer. Because of this, we must be prepared to
384 receive strings as long as the size of the input buffer, which is currently
385 set to 4K for Windows and 8K for the rest.
386 So, we must either allocate sufficiently large buffers here, or
387 reduce the size of the input buffer in the input reading part.
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
394 ChessProgramState first, second;
396 /* premove variables */
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
446 /* animateTraining preserves the state of appData.animate
447 * when Training mode is activated. This allows the
448 * response to be animated when appData.animate == TRUE and
449 * appData.animateDragging == TRUE.
451 Boolean animateTraining;
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
460 signed char initialRights[BOARD_FILES];
461 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
462 int initialRulePlies, FENrulePlies;
463 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
466 int mute; // mute all sounds
468 // [HGM] vari: next 12 to save and restore variations
469 #define MAX_VARIATIONS 10
470 int framePtr = MAX_MOVES-1; // points to free stack entry
472 int savedFirst[MAX_VARIATIONS];
473 int savedLast[MAX_VARIATIONS];
474 int savedFramePtr[MAX_VARIATIONS];
475 char *savedDetails[MAX_VARIATIONS];
476 ChessMove savedResult[MAX_VARIATIONS];
478 void PushTail P((int firstMove, int lastMove));
479 Boolean PopTail P((Boolean annotate));
480 void CleanupTail P((void));
482 ChessSquare FIDEArray[2][BOARD_FILES] = {
483 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
484 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
485 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
486 BlackKing, BlackBishop, BlackKnight, BlackRook }
489 ChessSquare twoKingsArray[2][BOARD_FILES] = {
490 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
491 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
492 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
493 BlackKing, BlackKing, BlackKnight, BlackRook }
496 ChessSquare KnightmateArray[2][BOARD_FILES] = {
497 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
498 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
499 { BlackRook, BlackMan, BlackBishop, BlackQueen,
500 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
503 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
504 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
505 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
506 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
507 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
510 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
511 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
512 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
513 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
514 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
517 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
518 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
519 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackMan, BlackFerz,
521 BlackKing, BlackMan, BlackKnight, BlackRook }
525 #if (BOARD_FILES>=10)
526 ChessSquare ShogiArray[2][BOARD_FILES] = {
527 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
528 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
529 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
530 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
533 ChessSquare XiangqiArray[2][BOARD_FILES] = {
534 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
535 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
537 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
540 ChessSquare CapablancaArray[2][BOARD_FILES] = {
541 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
542 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
544 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
547 ChessSquare GreatArray[2][BOARD_FILES] = {
548 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
549 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
550 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
551 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
554 ChessSquare JanusArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
556 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
557 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
558 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
562 ChessSquare GothicArray[2][BOARD_FILES] = {
563 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
564 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
566 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
569 #define GothicArray CapablancaArray
573 ChessSquare FalconArray[2][BOARD_FILES] = {
574 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
575 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
577 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
580 #define FalconArray CapablancaArray
583 #else // !(BOARD_FILES>=10)
584 #define XiangqiPosition FIDEArray
585 #define CapablancaArray FIDEArray
586 #define GothicArray FIDEArray
587 #define GreatArray FIDEArray
588 #endif // !(BOARD_FILES>=10)
590 #if (BOARD_FILES>=12)
591 ChessSquare CourierArray[2][BOARD_FILES] = {
592 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
593 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
594 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
595 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
597 #else // !(BOARD_FILES>=12)
598 #define CourierArray CapablancaArray
599 #endif // !(BOARD_FILES>=12)
602 Board initialPosition;
605 /* Convert str to a rating. Checks for special cases of "----",
607 "++++", etc. Also strips ()'s */
609 string_to_rating(str)
612 while(*str && !isdigit(*str)) ++str;
614 return 0; /* One of the special "no rating" cases */
622 /* Init programStats */
623 programStats.movelist[0] = 0;
624 programStats.depth = 0;
625 programStats.nr_moves = 0;
626 programStats.moves_left = 0;
627 programStats.nodes = 0;
628 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
629 programStats.score = 0;
630 programStats.got_only_move = 0;
631 programStats.got_fail = 0;
632 programStats.line_is_book = 0;
638 int matched, min, sec;
640 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
641 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
643 GetTimeMark(&programStartTime);
644 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
647 programStats.ok_to_send = 1;
648 programStats.seen_stat = 0;
651 * Initialize game list
657 * Internet chess server status
659 if (appData.icsActive) {
660 appData.matchMode = FALSE;
661 appData.matchGames = 0;
663 appData.noChessProgram = !appData.zippyPlay;
665 appData.zippyPlay = FALSE;
666 appData.zippyTalk = FALSE;
667 appData.noChessProgram = TRUE;
669 if (*appData.icsHelper != NULLCHAR) {
670 appData.useTelnet = TRUE;
671 appData.telnetProgram = appData.icsHelper;
674 appData.zippyTalk = appData.zippyPlay = FALSE;
677 /* [AS] Initialize pv info list [HGM] and game state */
681 for( i=0; i<=framePtr; i++ ) {
682 pvInfoList[i].depth = -1;
683 boards[i][EP_STATUS] = EP_NONE;
684 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
689 * Parse timeControl resource
691 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
692 appData.movesPerSession)) {
694 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
695 DisplayFatalError(buf, 0, 2);
699 * Parse searchTime resource
701 if (*appData.searchTime != NULLCHAR) {
702 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
704 searchTime = min * 60;
705 } else if (matched == 2) {
706 searchTime = min * 60 + sec;
709 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
710 DisplayFatalError(buf, 0, 2);
714 /* [AS] Adjudication threshold */
715 adjudicateLossThreshold = appData.adjudicateLossThreshold;
717 first.which = "first";
718 second.which = "second";
719 first.maybeThinking = second.maybeThinking = FALSE;
720 first.pr = second.pr = NoProc;
721 first.isr = second.isr = NULL;
722 first.sendTime = second.sendTime = 2;
723 first.sendDrawOffers = 1;
724 if (appData.firstPlaysBlack) {
725 first.twoMachinesColor = "black\n";
726 second.twoMachinesColor = "white\n";
728 first.twoMachinesColor = "white\n";
729 second.twoMachinesColor = "black\n";
731 first.program = appData.firstChessProgram;
732 second.program = appData.secondChessProgram;
733 first.host = appData.firstHost;
734 second.host = appData.secondHost;
735 first.dir = appData.firstDirectory;
736 second.dir = appData.secondDirectory;
737 first.other = &second;
738 second.other = &first;
739 first.initString = appData.initString;
740 second.initString = appData.secondInitString;
741 first.computerString = appData.firstComputerString;
742 second.computerString = appData.secondComputerString;
743 first.useSigint = second.useSigint = TRUE;
744 first.useSigterm = second.useSigterm = TRUE;
745 first.reuse = appData.reuseFirst;
746 second.reuse = appData.reuseSecond;
747 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
748 second.nps = appData.secondNPS;
749 first.useSetboard = second.useSetboard = FALSE;
750 first.useSAN = second.useSAN = FALSE;
751 first.usePing = second.usePing = FALSE;
752 first.lastPing = second.lastPing = 0;
753 first.lastPong = second.lastPong = 0;
754 first.usePlayother = second.usePlayother = FALSE;
755 first.useColors = second.useColors = TRUE;
756 first.useUsermove = second.useUsermove = FALSE;
757 first.sendICS = second.sendICS = FALSE;
758 first.sendName = second.sendName = appData.icsActive;
759 first.sdKludge = second.sdKludge = FALSE;
760 first.stKludge = second.stKludge = FALSE;
761 TidyProgramName(first.program, first.host, first.tidy);
762 TidyProgramName(second.program, second.host, second.tidy);
763 first.matchWins = second.matchWins = 0;
764 strcpy(first.variants, appData.variant);
765 strcpy(second.variants, appData.variant);
766 first.analysisSupport = second.analysisSupport = 2; /* detect */
767 first.analyzing = second.analyzing = FALSE;
768 first.initDone = second.initDone = FALSE;
770 /* New features added by Tord: */
771 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
772 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
773 /* End of new features added by Tord. */
774 first.fenOverride = appData.fenOverride1;
775 second.fenOverride = appData.fenOverride2;
777 /* [HGM] time odds: set factor for each machine */
778 first.timeOdds = appData.firstTimeOdds;
779 second.timeOdds = appData.secondTimeOdds;
781 if(appData.timeOddsMode) {
782 norm = first.timeOdds;
783 if(norm > second.timeOdds) norm = second.timeOdds;
785 first.timeOdds /= norm;
786 second.timeOdds /= norm;
789 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
790 first.accumulateTC = appData.firstAccumulateTC;
791 second.accumulateTC = appData.secondAccumulateTC;
792 first.maxNrOfSessions = second.maxNrOfSessions = 1;
795 first.debug = second.debug = FALSE;
796 first.supportsNPS = second.supportsNPS = UNKNOWN;
799 first.optionSettings = appData.firstOptions;
800 second.optionSettings = appData.secondOptions;
802 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
803 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
804 first.isUCI = appData.firstIsUCI; /* [AS] */
805 second.isUCI = appData.secondIsUCI; /* [AS] */
806 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
807 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
809 if (appData.firstProtocolVersion > PROTOVER ||
810 appData.firstProtocolVersion < 1) {
812 sprintf(buf, _("protocol version %d not supported"),
813 appData.firstProtocolVersion);
814 DisplayFatalError(buf, 0, 2);
816 first.protocolVersion = appData.firstProtocolVersion;
819 if (appData.secondProtocolVersion > PROTOVER ||
820 appData.secondProtocolVersion < 1) {
822 sprintf(buf, _("protocol version %d not supported"),
823 appData.secondProtocolVersion);
824 DisplayFatalError(buf, 0, 2);
826 second.protocolVersion = appData.secondProtocolVersion;
829 if (appData.icsActive) {
830 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
831 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
832 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
833 appData.clockMode = FALSE;
834 first.sendTime = second.sendTime = 0;
838 /* Override some settings from environment variables, for backward
839 compatibility. Unfortunately it's not feasible to have the env
840 vars just set defaults, at least in xboard. Ugh.
842 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
847 if (appData.noChessProgram) {
848 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
849 sprintf(programVersion, "%s", PACKAGE_STRING);
851 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
852 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
853 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
856 if (!appData.icsActive) {
858 /* Check for variants that are supported only in ICS mode,
859 or not at all. Some that are accepted here nevertheless
860 have bugs; see comments below.
862 VariantClass variant = StringToVariant(appData.variant);
864 case VariantBughouse: /* need four players and two boards */
865 case VariantKriegspiel: /* need to hide pieces and move details */
866 /* case VariantFischeRandom: (Fabien: moved below) */
867 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
868 DisplayFatalError(buf, 0, 2);
872 case VariantLoadable:
882 sprintf(buf, _("Unknown variant name %s"), appData.variant);
883 DisplayFatalError(buf, 0, 2);
886 case VariantXiangqi: /* [HGM] repetition rules not implemented */
887 case VariantFairy: /* [HGM] TestLegality definitely off! */
888 case VariantGothic: /* [HGM] should work */
889 case VariantCapablanca: /* [HGM] should work */
890 case VariantCourier: /* [HGM] initial forced moves not implemented */
891 case VariantShogi: /* [HGM] drops not tested for legality */
892 case VariantKnightmate: /* [HGM] should work */
893 case VariantCylinder: /* [HGM] untested */
894 case VariantFalcon: /* [HGM] untested */
895 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
896 offboard interposition not understood */
897 case VariantNormal: /* definitely works! */
898 case VariantWildCastle: /* pieces not automatically shuffled */
899 case VariantNoCastle: /* pieces not automatically shuffled */
900 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
901 case VariantLosers: /* should work except for win condition,
902 and doesn't know captures are mandatory */
903 case VariantSuicide: /* should work except for win condition,
904 and doesn't know captures are mandatory */
905 case VariantGiveaway: /* should work except for win condition,
906 and doesn't know captures are mandatory */
907 case VariantTwoKings: /* should work */
908 case VariantAtomic: /* should work except for win condition */
909 case Variant3Check: /* should work except for win condition */
910 case VariantShatranj: /* should work except for all win conditions */
911 case VariantMakruk: /* should work except for daw countdown */
912 case VariantBerolina: /* might work if TestLegality is off */
913 case VariantCapaRandom: /* should work */
914 case VariantJanus: /* should work */
915 case VariantSuper: /* experimental */
916 case VariantGreat: /* experimental, requires legality testing to be off */
921 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
922 InitEngineUCI( installDir, &second );
925 int NextIntegerFromString( char ** str, long * value )
930 while( *s == ' ' || *s == '\t' ) {
936 if( *s >= '0' && *s <= '9' ) {
937 while( *s >= '0' && *s <= '9' ) {
938 *value = *value * 10 + (*s - '0');
950 int NextTimeControlFromString( char ** str, long * value )
953 int result = NextIntegerFromString( str, &temp );
956 *value = temp * 60; /* Minutes */
959 result = NextIntegerFromString( str, &temp );
960 *value += temp; /* Seconds */
967 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
968 { /* [HGM] routine added to read '+moves/time' for secondary time control */
969 int result = -1; long temp, temp2;
971 if(**str != '+') return -1; // old params remain in force!
973 if( NextTimeControlFromString( str, &temp ) ) return -1;
976 /* time only: incremental or sudden-death time control */
977 if(**str == '+') { /* increment follows; read it */
979 if(result = NextIntegerFromString( str, &temp2)) return -1;
982 *moves = 0; *tc = temp * 1000;
984 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
986 (*str)++; /* classical time control */
987 result = NextTimeControlFromString( str, &temp2);
996 int GetTimeQuota(int movenr)
997 { /* [HGM] get time to add from the multi-session time-control string */
998 int moves=1; /* kludge to force reading of first session */
999 long time, increment;
1000 char *s = fullTimeControlString;
1002 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1004 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1005 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1006 if(movenr == -1) return time; /* last move before new session */
1007 if(!moves) return increment; /* current session is incremental */
1008 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1009 } while(movenr >= -1); /* try again for next session */
1011 return 0; // no new time quota on this move
1015 ParseTimeControl(tc, ti, mps)
1024 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1027 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1028 else sprintf(buf, "+%s+%d", tc, ti);
1031 sprintf(buf, "+%d/%s", mps, tc);
1032 else sprintf(buf, "+%s", tc);
1034 fullTimeControlString = StrSave(buf);
1036 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1041 /* Parse second time control */
1044 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1052 timeControl_2 = tc2 * 1000;
1062 timeControl = tc1 * 1000;
1065 timeIncrement = ti * 1000; /* convert to ms */
1066 movesPerSession = 0;
1069 movesPerSession = mps;
1077 if (appData.debugMode) {
1078 fprintf(debugFP, "%s\n", programVersion);
1081 set_cont_sequence(appData.wrapContSeq);
1082 if (appData.matchGames > 0) {
1083 appData.matchMode = TRUE;
1084 } else if (appData.matchMode) {
1085 appData.matchGames = 1;
1087 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1088 appData.matchGames = appData.sameColorGames;
1089 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1090 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1091 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1094 if (appData.noChessProgram || first.protocolVersion == 1) {
1097 /* kludge: allow timeout for initial "feature" commands */
1099 DisplayMessage("", _("Starting chess program"));
1100 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1105 InitBackEnd3 P((void))
1107 GameMode initialMode;
1111 InitChessProgram(&first, startedFromSetupPosition);
1113 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1114 free(programVersion);
1115 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1116 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1119 if (appData.icsActive) {
1121 /* [DM] Make a console window if needed [HGM] merged ifs */
1126 if (*appData.icsCommPort != NULLCHAR) {
1127 sprintf(buf, _("Could not open comm port %s"),
1128 appData.icsCommPort);
1130 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1131 appData.icsHost, appData.icsPort);
1133 DisplayFatalError(buf, err, 1);
1138 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1140 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1142 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1143 } else if (appData.noChessProgram) {
1149 if (*appData.cmailGameName != NULLCHAR) {
1151 OpenLoopback(&cmailPR);
1153 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1157 DisplayMessage("", "");
1158 if (StrCaseCmp(appData.initialMode, "") == 0) {
1159 initialMode = BeginningOfGame;
1160 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1161 initialMode = TwoMachinesPlay;
1162 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1163 initialMode = AnalyzeFile;
1164 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1165 initialMode = AnalyzeMode;
1166 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1167 initialMode = MachinePlaysWhite;
1168 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1169 initialMode = MachinePlaysBlack;
1170 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1171 initialMode = EditGame;
1172 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1173 initialMode = EditPosition;
1174 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1175 initialMode = Training;
1177 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1178 DisplayFatalError(buf, 0, 2);
1182 if (appData.matchMode) {
1183 /* Set up machine vs. machine match */
1184 if (appData.noChessProgram) {
1185 DisplayFatalError(_("Can't have a match with no chess programs"),
1191 if (*appData.loadGameFile != NULLCHAR) {
1192 int index = appData.loadGameIndex; // [HGM] autoinc
1193 if(index<0) lastIndex = index = 1;
1194 if (!LoadGameFromFile(appData.loadGameFile,
1196 appData.loadGameFile, FALSE)) {
1197 DisplayFatalError(_("Bad game file"), 0, 1);
1200 } else if (*appData.loadPositionFile != NULLCHAR) {
1201 int index = appData.loadPositionIndex; // [HGM] autoinc
1202 if(index<0) lastIndex = index = 1;
1203 if (!LoadPositionFromFile(appData.loadPositionFile,
1205 appData.loadPositionFile)) {
1206 DisplayFatalError(_("Bad position file"), 0, 1);
1211 } else if (*appData.cmailGameName != NULLCHAR) {
1212 /* Set up cmail mode */
1213 ReloadCmailMsgEvent(TRUE);
1215 /* Set up other modes */
1216 if (initialMode == AnalyzeFile) {
1217 if (*appData.loadGameFile == NULLCHAR) {
1218 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1222 if (*appData.loadGameFile != NULLCHAR) {
1223 (void) LoadGameFromFile(appData.loadGameFile,
1224 appData.loadGameIndex,
1225 appData.loadGameFile, TRUE);
1226 } else if (*appData.loadPositionFile != NULLCHAR) {
1227 (void) LoadPositionFromFile(appData.loadPositionFile,
1228 appData.loadPositionIndex,
1229 appData.loadPositionFile);
1230 /* [HGM] try to make self-starting even after FEN load */
1231 /* to allow automatic setup of fairy variants with wtm */
1232 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1233 gameMode = BeginningOfGame;
1234 setboardSpoiledMachineBlack = 1;
1236 /* [HGM] loadPos: make that every new game uses the setup */
1237 /* from file as long as we do not switch variant */
1238 if(!blackPlaysFirst) {
1239 startedFromPositionFile = TRUE;
1240 CopyBoard(filePosition, boards[0]);
1243 if (initialMode == AnalyzeMode) {
1244 if (appData.noChessProgram) {
1245 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1248 if (appData.icsActive) {
1249 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1253 } else if (initialMode == AnalyzeFile) {
1254 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1255 ShowThinkingEvent();
1257 AnalysisPeriodicEvent(1);
1258 } else if (initialMode == MachinePlaysWhite) {
1259 if (appData.noChessProgram) {
1260 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1264 if (appData.icsActive) {
1265 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1269 MachineWhiteEvent();
1270 } else if (initialMode == MachinePlaysBlack) {
1271 if (appData.noChessProgram) {
1272 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1276 if (appData.icsActive) {
1277 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1281 MachineBlackEvent();
1282 } else if (initialMode == TwoMachinesPlay) {
1283 if (appData.noChessProgram) {
1284 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1288 if (appData.icsActive) {
1289 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1294 } else if (initialMode == EditGame) {
1296 } else if (initialMode == EditPosition) {
1297 EditPositionEvent();
1298 } else if (initialMode == Training) {
1299 if (*appData.loadGameFile == NULLCHAR) {
1300 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1309 * Establish will establish a contact to a remote host.port.
1310 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1311 * used to talk to the host.
1312 * Returns 0 if okay, error code if not.
1319 if (*appData.icsCommPort != NULLCHAR) {
1320 /* Talk to the host through a serial comm port */
1321 return OpenCommPort(appData.icsCommPort, &icsPR);
1323 } else if (*appData.gateway != NULLCHAR) {
1324 if (*appData.remoteShell == NULLCHAR) {
1325 /* Use the rcmd protocol to run telnet program on a gateway host */
1326 snprintf(buf, sizeof(buf), "%s %s %s",
1327 appData.telnetProgram, appData.icsHost, appData.icsPort);
1328 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1331 /* Use the rsh program to run telnet program on a gateway host */
1332 if (*appData.remoteUser == NULLCHAR) {
1333 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1334 appData.gateway, appData.telnetProgram,
1335 appData.icsHost, appData.icsPort);
1337 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1338 appData.remoteShell, appData.gateway,
1339 appData.remoteUser, appData.telnetProgram,
1340 appData.icsHost, appData.icsPort);
1342 return StartChildProcess(buf, "", &icsPR);
1345 } else if (appData.useTelnet) {
1346 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1349 /* TCP socket interface differs somewhat between
1350 Unix and NT; handle details in the front end.
1352 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1356 void EscapeExpand(char *p, char *q)
1357 { // [HGM] initstring: routine to shape up string arguments
1358 while(*p++ = *q++) if(p[-1] == '\\')
1360 case 'n': p[-1] = '\n'; break;
1361 case 'r': p[-1] = '\r'; break;
1362 case 't': p[-1] = '\t'; break;
1363 case '\\': p[-1] = '\\'; break;
1364 case 0: *p = 0; return;
1365 default: p[-1] = q[-1]; break;
1370 show_bytes(fp, buf, count)
1376 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1377 fprintf(fp, "\\%03o", *buf & 0xff);
1386 /* Returns an errno value */
1388 OutputMaybeTelnet(pr, message, count, outError)
1394 char buf[8192], *p, *q, *buflim;
1395 int left, newcount, outcount;
1397 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1398 *appData.gateway != NULLCHAR) {
1399 if (appData.debugMode) {
1400 fprintf(debugFP, ">ICS: ");
1401 show_bytes(debugFP, message, count);
1402 fprintf(debugFP, "\n");
1404 return OutputToProcess(pr, message, count, outError);
1407 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1414 if (appData.debugMode) {
1415 fprintf(debugFP, ">ICS: ");
1416 show_bytes(debugFP, buf, newcount);
1417 fprintf(debugFP, "\n");
1419 outcount = OutputToProcess(pr, buf, newcount, outError);
1420 if (outcount < newcount) return -1; /* to be sure */
1427 } else if (((unsigned char) *p) == TN_IAC) {
1428 *q++ = (char) TN_IAC;
1435 if (appData.debugMode) {
1436 fprintf(debugFP, ">ICS: ");
1437 show_bytes(debugFP, buf, newcount);
1438 fprintf(debugFP, "\n");
1440 outcount = OutputToProcess(pr, buf, newcount, outError);
1441 if (outcount < newcount) return -1; /* to be sure */
1446 read_from_player(isr, closure, message, count, error)
1453 int outError, outCount;
1454 static int gotEof = 0;
1456 /* Pass data read from player on to ICS */
1459 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1460 if (outCount < count) {
1461 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463 } else if (count < 0) {
1464 RemoveInputSource(isr);
1465 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1466 } else if (gotEof++ > 0) {
1467 RemoveInputSource(isr);
1468 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1474 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1475 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1476 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1477 SendToICS("date\n");
1478 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1481 /* added routine for printf style output to ics */
1482 void ics_printf(char *format, ...)
1484 char buffer[MSG_SIZ];
1487 va_start(args, format);
1488 vsnprintf(buffer, sizeof(buffer), format, args);
1489 buffer[sizeof(buffer)-1] = '\0';
1498 int count, outCount, outError;
1500 if (icsPR == NULL) return;
1503 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1504 if (outCount < count) {
1505 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1509 /* This is used for sending logon scripts to the ICS. Sending
1510 without a delay causes problems when using timestamp on ICC
1511 (at least on my machine). */
1513 SendToICSDelayed(s,msdelay)
1517 int count, outCount, outError;
1519 if (icsPR == NULL) return;
1522 if (appData.debugMode) {
1523 fprintf(debugFP, ">ICS: ");
1524 show_bytes(debugFP, s, count);
1525 fprintf(debugFP, "\n");
1527 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1529 if (outCount < count) {
1530 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1535 /* Remove all highlighting escape sequences in s
1536 Also deletes any suffix starting with '('
1539 StripHighlightAndTitle(s)
1542 static char retbuf[MSG_SIZ];
1545 while (*s != NULLCHAR) {
1546 while (*s == '\033') {
1547 while (*s != NULLCHAR && !isalpha(*s)) s++;
1548 if (*s != NULLCHAR) s++;
1550 while (*s != NULLCHAR && *s != '\033') {
1551 if (*s == '(' || *s == '[') {
1562 /* Remove all highlighting escape sequences in s */
1567 static char retbuf[MSG_SIZ];
1570 while (*s != NULLCHAR) {
1571 while (*s == '\033') {
1572 while (*s != NULLCHAR && !isalpha(*s)) s++;
1573 if (*s != NULLCHAR) s++;
1575 while (*s != NULLCHAR && *s != '\033') {
1583 char *variantNames[] = VARIANT_NAMES;
1588 return variantNames[v];
1592 /* Identify a variant from the strings the chess servers use or the
1593 PGN Variant tag names we use. */
1600 VariantClass v = VariantNormal;
1601 int i, found = FALSE;
1606 /* [HGM] skip over optional board-size prefixes */
1607 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1608 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1609 while( *e++ != '_');
1612 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1616 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1617 if (StrCaseStr(e, variantNames[i])) {
1618 v = (VariantClass) i;
1625 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1626 || StrCaseStr(e, "wild/fr")
1627 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1628 v = VariantFischeRandom;
1629 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1630 (i = 1, p = StrCaseStr(e, "w"))) {
1632 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1639 case 0: /* FICS only, actually */
1641 /* Castling legal even if K starts on d-file */
1642 v = VariantWildCastle;
1647 /* Castling illegal even if K & R happen to start in
1648 normal positions. */
1649 v = VariantNoCastle;
1662 /* Castling legal iff K & R start in normal positions */
1668 /* Special wilds for position setup; unclear what to do here */
1669 v = VariantLoadable;
1672 /* Bizarre ICC game */
1673 v = VariantTwoKings;
1676 v = VariantKriegspiel;
1682 v = VariantFischeRandom;
1685 v = VariantCrazyhouse;
1688 v = VariantBughouse;
1694 /* Not quite the same as FICS suicide! */
1695 v = VariantGiveaway;
1701 v = VariantShatranj;
1704 /* Temporary names for future ICC types. The name *will* change in
1705 the next xboard/WinBoard release after ICC defines it. */
1743 v = VariantCapablanca;
1746 v = VariantKnightmate;
1752 v = VariantCylinder;
1758 v = VariantCapaRandom;
1761 v = VariantBerolina;
1773 /* Found "wild" or "w" in the string but no number;
1774 must assume it's normal chess. */
1778 sprintf(buf, _("Unknown wild type %d"), wnum);
1779 DisplayError(buf, 0);
1785 if (appData.debugMode) {
1786 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1787 e, wnum, VariantName(v));
1792 static int leftover_start = 0, leftover_len = 0;
1793 char star_match[STAR_MATCH_N][MSG_SIZ];
1795 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1796 advance *index beyond it, and set leftover_start to the new value of
1797 *index; else return FALSE. If pattern contains the character '*', it
1798 matches any sequence of characters not containing '\r', '\n', or the
1799 character following the '*' (if any), and the matched sequence(s) are
1800 copied into star_match.
1803 looking_at(buf, index, pattern)
1808 char *bufp = &buf[*index], *patternp = pattern;
1810 char *matchp = star_match[0];
1813 if (*patternp == NULLCHAR) {
1814 *index = leftover_start = bufp - buf;
1818 if (*bufp == NULLCHAR) return FALSE;
1819 if (*patternp == '*') {
1820 if (*bufp == *(patternp + 1)) {
1822 matchp = star_match[++star_count];
1826 } else if (*bufp == '\n' || *bufp == '\r') {
1828 if (*patternp == NULLCHAR)
1833 *matchp++ = *bufp++;
1837 if (*patternp != *bufp) return FALSE;
1844 SendToPlayer(data, length)
1848 int error, outCount;
1849 outCount = OutputToProcess(NoProc, data, length, &error);
1850 if (outCount < length) {
1851 DisplayFatalError(_("Error writing to display"), error, 1);
1856 PackHolding(packed, holding)
1868 switch (runlength) {
1879 sprintf(q, "%d", runlength);
1891 /* Telnet protocol requests from the front end */
1893 TelnetRequest(ddww, option)
1894 unsigned char ddww, option;
1896 unsigned char msg[3];
1897 int outCount, outError;
1899 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1901 if (appData.debugMode) {
1902 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1918 sprintf(buf1, "%d", ddww);
1927 sprintf(buf2, "%d", option);
1930 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1935 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1937 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1944 if (!appData.icsActive) return;
1945 TelnetRequest(TN_DO, TN_ECHO);
1951 if (!appData.icsActive) return;
1952 TelnetRequest(TN_DONT, TN_ECHO);
1956 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1958 /* put the holdings sent to us by the server on the board holdings area */
1959 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1963 if(gameInfo.holdingsWidth < 2) return;
1964 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1965 return; // prevent overwriting by pre-board holdings
1967 if( (int)lowestPiece >= BlackPawn ) {
1970 holdingsStartRow = BOARD_HEIGHT-1;
1973 holdingsColumn = BOARD_WIDTH-1;
1974 countsColumn = BOARD_WIDTH-2;
1975 holdingsStartRow = 0;
1979 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1980 board[i][holdingsColumn] = EmptySquare;
1981 board[i][countsColumn] = (ChessSquare) 0;
1983 while( (p=*holdings++) != NULLCHAR ) {
1984 piece = CharToPiece( ToUpper(p) );
1985 if(piece == EmptySquare) continue;
1986 /*j = (int) piece - (int) WhitePawn;*/
1987 j = PieceToNumber(piece);
1988 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1989 if(j < 0) continue; /* should not happen */
1990 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1991 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1992 board[holdingsStartRow+j*direction][countsColumn]++;
1998 VariantSwitch(Board board, VariantClass newVariant)
2000 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2001 static Board oldBoard;
2003 startedFromPositionFile = FALSE;
2004 if(gameInfo.variant == newVariant) return;
2006 /* [HGM] This routine is called each time an assignment is made to
2007 * gameInfo.variant during a game, to make sure the board sizes
2008 * are set to match the new variant. If that means adding or deleting
2009 * holdings, we shift the playing board accordingly
2010 * This kludge is needed because in ICS observe mode, we get boards
2011 * of an ongoing game without knowing the variant, and learn about the
2012 * latter only later. This can be because of the move list we requested,
2013 * in which case the game history is refilled from the beginning anyway,
2014 * but also when receiving holdings of a crazyhouse game. In the latter
2015 * case we want to add those holdings to the already received position.
2019 if (appData.debugMode) {
2020 fprintf(debugFP, "Switch board from %s to %s\n",
2021 VariantName(gameInfo.variant), VariantName(newVariant));
2022 setbuf(debugFP, NULL);
2024 shuffleOpenings = 0; /* [HGM] shuffle */
2025 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2029 newWidth = 9; newHeight = 9;
2030 gameInfo.holdingsSize = 7;
2031 case VariantBughouse:
2032 case VariantCrazyhouse:
2033 newHoldingsWidth = 2; break;
2037 newHoldingsWidth = 2;
2038 gameInfo.holdingsSize = 8;
2041 case VariantCapablanca:
2042 case VariantCapaRandom:
2045 newHoldingsWidth = gameInfo.holdingsSize = 0;
2048 if(newWidth != gameInfo.boardWidth ||
2049 newHeight != gameInfo.boardHeight ||
2050 newHoldingsWidth != gameInfo.holdingsWidth ) {
2052 /* shift position to new playing area, if needed */
2053 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2054 for(i=0; i<BOARD_HEIGHT; i++)
2055 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2056 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2058 for(i=0; i<newHeight; i++) {
2059 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2060 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2062 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2063 for(i=0; i<BOARD_HEIGHT; i++)
2064 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2065 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2068 gameInfo.boardWidth = newWidth;
2069 gameInfo.boardHeight = newHeight;
2070 gameInfo.holdingsWidth = newHoldingsWidth;
2071 gameInfo.variant = newVariant;
2072 InitDrawingSizes(-2, 0);
2073 } else gameInfo.variant = newVariant;
2074 CopyBoard(oldBoard, board); // remember correctly formatted board
2075 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2076 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2079 static int loggedOn = FALSE;
2081 /*-- Game start info cache: --*/
2083 char gs_kind[MSG_SIZ];
2084 static char player1Name[128] = "";
2085 static char player2Name[128] = "";
2086 static char cont_seq[] = "\n\\ ";
2087 static int player1Rating = -1;
2088 static int player2Rating = -1;
2089 /*----------------------------*/
2091 ColorClass curColor = ColorNormal;
2092 int suppressKibitz = 0;
2095 Boolean soughtPending = FALSE;
2096 Boolean seekGraphUp;
2097 #define MAX_SEEK_ADS 200
2099 char *seekAdList[MAX_SEEK_ADS];
2100 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2101 float tcList[MAX_SEEK_ADS];
2102 char colorList[MAX_SEEK_ADS];
2103 int nrOfSeekAds = 0;
2104 int minRating = 1010, maxRating = 2800;
2105 int hMargin = 10, vMargin = 20, h, w;
2106 extern int squareSize, lineGap;
2111 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2112 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2113 if(r < minRating+100 && r >=0 ) r = minRating+100;
2114 if(r > maxRating) r = maxRating;
2115 if(tc < 1.) tc = 1.;
2116 if(tc > 95.) tc = 95.;
2117 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2118 y = ((double)r - minRating)/(maxRating - minRating)
2119 * (h-vMargin-squareSize/8-1) + vMargin;
2120 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2121 if(strstr(seekAdList[i], " u ")) color = 1;
2122 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2123 !strstr(seekAdList[i], "bullet") &&
2124 !strstr(seekAdList[i], "blitz") &&
2125 !strstr(seekAdList[i], "standard") ) color = 2;
2126 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2127 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2131 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2133 char buf[MSG_SIZ], *ext = "";
2134 VariantClass v = StringToVariant(type);
2135 if(strstr(type, "wild")) {
2136 ext = type + 4; // append wild number
2137 if(v == VariantFischeRandom) type = "chess960"; else
2138 if(v == VariantLoadable) type = "setup"; else
2139 type = VariantName(v);
2141 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2142 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2143 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2144 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2145 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2146 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2147 seekNrList[nrOfSeekAds] = nr;
2148 zList[nrOfSeekAds] = 0;
2149 seekAdList[nrOfSeekAds++] = StrSave(buf);
2150 if(plot) PlotSeekAd(nrOfSeekAds-1);
2157 int x = xList[i], y = yList[i], d=squareSize/4, k;
2158 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2159 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2160 // now replot every dot that overlapped
2161 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2162 int xx = xList[k], yy = yList[k];
2163 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2164 DrawSeekDot(xx, yy, colorList[k]);
2169 RemoveSeekAd(int nr)
2172 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2174 if(seekAdList[i]) free(seekAdList[i]);
2175 seekAdList[i] = seekAdList[--nrOfSeekAds];
2176 seekNrList[i] = seekNrList[nrOfSeekAds];
2177 ratingList[i] = ratingList[nrOfSeekAds];
2178 colorList[i] = colorList[nrOfSeekAds];
2179 tcList[i] = tcList[nrOfSeekAds];
2180 xList[i] = xList[nrOfSeekAds];
2181 yList[i] = yList[nrOfSeekAds];
2182 zList[i] = zList[nrOfSeekAds];
2183 seekAdList[nrOfSeekAds] = NULL;
2189 MatchSoughtLine(char *line)
2191 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2192 int nr, base, inc, u=0; char dummy;
2194 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2195 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2197 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2198 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2199 // match: compact and save the line
2200 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2209 if(!seekGraphUp) return FALSE;
2211 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2212 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2214 DrawSeekBackground(0, 0, w, h);
2215 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2216 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2217 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2218 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2220 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2223 sprintf(buf, "%d", i);
2224 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2227 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2228 for(i=1; i<100; i+=(i<10?1:5)) {
2229 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2230 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2231 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2233 sprintf(buf, "%d", i);
2234 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2237 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2241 int SeekGraphClick(ClickType click, int x, int y, int moving)
2243 static int lastDown = 0, displayed = 0, lastSecond;
2244 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2245 if(click == Release || moving) return FALSE;
2247 soughtPending = TRUE;
2248 SendToICS(ics_prefix);
2249 SendToICS("sought\n"); // should this be "sought all"?
2250 } else { // issue challenge based on clicked ad
2251 int dist = 10000; int i, closest = 0, second = 0;
2252 for(i=0; i<nrOfSeekAds; i++) {
2253 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2254 if(d < dist) { dist = d; closest = i; }
2255 second += (d - zList[i] < 120); // count in-range ads
2256 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2260 second = (second > 1);
2261 if(displayed != closest || second != lastSecond) {
2262 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2263 lastSecond = second; displayed = closest;
2265 if(click == Press) {
2266 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2269 } // on press 'hit', only show info
2270 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2271 sprintf(buf, "play %d\n", seekNrList[closest]);
2272 SendToICS(ics_prefix);
2274 return TRUE; // let incoming board of started game pop down the graph
2275 } else if(click == Release) { // release 'miss' is ignored
2276 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2277 if(moving == 2) { // right up-click
2278 nrOfSeekAds = 0; // refresh graph
2279 soughtPending = TRUE;
2280 SendToICS(ics_prefix);
2281 SendToICS("sought\n"); // should this be "sought all"?
2284 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2285 // press miss or release hit 'pop down' seek graph
2286 seekGraphUp = FALSE;
2287 DrawPosition(TRUE, NULL);
2293 read_from_ics(isr, closure, data, count, error)
2300 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2301 #define STARTED_NONE 0
2302 #define STARTED_MOVES 1
2303 #define STARTED_BOARD 2
2304 #define STARTED_OBSERVE 3
2305 #define STARTED_HOLDINGS 4
2306 #define STARTED_CHATTER 5
2307 #define STARTED_COMMENT 6
2308 #define STARTED_MOVES_NOHIDE 7
2310 static int started = STARTED_NONE;
2311 static char parse[20000];
2312 static int parse_pos = 0;
2313 static char buf[BUF_SIZE + 1];
2314 static int firstTime = TRUE, intfSet = FALSE;
2315 static ColorClass prevColor = ColorNormal;
2316 static int savingComment = FALSE;
2317 static int cmatch = 0; // continuation sequence match
2324 int backup; /* [DM] For zippy color lines */
2326 char talker[MSG_SIZ]; // [HGM] chat
2329 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2331 if (appData.debugMode) {
2333 fprintf(debugFP, "<ICS: ");
2334 show_bytes(debugFP, data, count);
2335 fprintf(debugFP, "\n");
2339 if (appData.debugMode) { int f = forwardMostMove;
2340 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2341 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2342 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2345 /* If last read ended with a partial line that we couldn't parse,
2346 prepend it to the new read and try again. */
2347 if (leftover_len > 0) {
2348 for (i=0; i<leftover_len; i++)
2349 buf[i] = buf[leftover_start + i];
2352 /* copy new characters into the buffer */
2353 bp = buf + leftover_len;
2354 buf_len=leftover_len;
2355 for (i=0; i<count; i++)
2358 if (data[i] == '\r')
2361 // join lines split by ICS?
2362 if (!appData.noJoin)
2365 Joining just consists of finding matches against the
2366 continuation sequence, and discarding that sequence
2367 if found instead of copying it. So, until a match
2368 fails, there's nothing to do since it might be the
2369 complete sequence, and thus, something we don't want
2372 if (data[i] == cont_seq[cmatch])
2375 if (cmatch == strlen(cont_seq))
2377 cmatch = 0; // complete match. just reset the counter
2380 it's possible for the ICS to not include the space
2381 at the end of the last word, making our [correct]
2382 join operation fuse two separate words. the server
2383 does this when the space occurs at the width setting.
2385 if (!buf_len || buf[buf_len-1] != ' ')
2396 match failed, so we have to copy what matched before
2397 falling through and copying this character. In reality,
2398 this will only ever be just the newline character, but
2399 it doesn't hurt to be precise.
2401 strncpy(bp, cont_seq, cmatch);
2413 buf[buf_len] = NULLCHAR;
2414 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2419 while (i < buf_len) {
2420 /* Deal with part of the TELNET option negotiation
2421 protocol. We refuse to do anything beyond the
2422 defaults, except that we allow the WILL ECHO option,
2423 which ICS uses to turn off password echoing when we are
2424 directly connected to it. We reject this option
2425 if localLineEditing mode is on (always on in xboard)
2426 and we are talking to port 23, which might be a real
2427 telnet server that will try to keep WILL ECHO on permanently.
2429 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2430 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2431 unsigned char option;
2433 switch ((unsigned char) buf[++i]) {
2435 if (appData.debugMode)
2436 fprintf(debugFP, "\n<WILL ");
2437 switch (option = (unsigned char) buf[++i]) {
2439 if (appData.debugMode)
2440 fprintf(debugFP, "ECHO ");
2441 /* Reply only if this is a change, according
2442 to the protocol rules. */
2443 if (remoteEchoOption) break;
2444 if (appData.localLineEditing &&
2445 atoi(appData.icsPort) == TN_PORT) {
2446 TelnetRequest(TN_DONT, TN_ECHO);
2449 TelnetRequest(TN_DO, TN_ECHO);
2450 remoteEchoOption = TRUE;
2454 if (appData.debugMode)
2455 fprintf(debugFP, "%d ", option);
2456 /* Whatever this is, we don't want it. */
2457 TelnetRequest(TN_DONT, option);
2462 if (appData.debugMode)
2463 fprintf(debugFP, "\n<WONT ");
2464 switch (option = (unsigned char) buf[++i]) {
2466 if (appData.debugMode)
2467 fprintf(debugFP, "ECHO ");
2468 /* Reply only if this is a change, according
2469 to the protocol rules. */
2470 if (!remoteEchoOption) break;
2472 TelnetRequest(TN_DONT, TN_ECHO);
2473 remoteEchoOption = FALSE;
2476 if (appData.debugMode)
2477 fprintf(debugFP, "%d ", (unsigned char) option);
2478 /* Whatever this is, it must already be turned
2479 off, because we never agree to turn on
2480 anything non-default, so according to the
2481 protocol rules, we don't reply. */
2486 if (appData.debugMode)
2487 fprintf(debugFP, "\n<DO ");
2488 switch (option = (unsigned char) buf[++i]) {
2490 /* Whatever this is, we refuse to do it. */
2491 if (appData.debugMode)
2492 fprintf(debugFP, "%d ", option);
2493 TelnetRequest(TN_WONT, option);
2498 if (appData.debugMode)
2499 fprintf(debugFP, "\n<DONT ");
2500 switch (option = (unsigned char) buf[++i]) {
2502 if (appData.debugMode)
2503 fprintf(debugFP, "%d ", option);
2504 /* Whatever this is, we are already not doing
2505 it, because we never agree to do anything
2506 non-default, so according to the protocol
2507 rules, we don't reply. */
2512 if (appData.debugMode)
2513 fprintf(debugFP, "\n<IAC ");
2514 /* Doubled IAC; pass it through */
2518 if (appData.debugMode)
2519 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2520 /* Drop all other telnet commands on the floor */
2523 if (oldi > next_out)
2524 SendToPlayer(&buf[next_out], oldi - next_out);
2530 /* OK, this at least will *usually* work */
2531 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2535 if (loggedOn && !intfSet) {
2536 if (ics_type == ICS_ICC) {
2538 "/set-quietly interface %s\n/set-quietly style 12\n",
2540 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2541 strcat(str, "/set-2 51 1\n/set seek 1\n");
2542 } else if (ics_type == ICS_CHESSNET) {
2543 sprintf(str, "/style 12\n");
2545 strcpy(str, "alias $ @\n$set interface ");
2546 strcat(str, programVersion);
2547 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2548 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2549 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2551 strcat(str, "$iset nohighlight 1\n");
2553 strcat(str, "$iset lock 1\n$style 12\n");
2556 NotifyFrontendLogin();
2560 if (started == STARTED_COMMENT) {
2561 /* Accumulate characters in comment */
2562 parse[parse_pos++] = buf[i];
2563 if (buf[i] == '\n') {
2564 parse[parse_pos] = NULLCHAR;
2565 if(chattingPartner>=0) {
2567 sprintf(mess, "%s%s", talker, parse);
2568 OutputChatMessage(chattingPartner, mess);
2569 chattingPartner = -1;
2570 next_out = i+1; // [HGM] suppress printing in ICS window
2572 if(!suppressKibitz) // [HGM] kibitz
2573 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2574 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2575 int nrDigit = 0, nrAlph = 0, j;
2576 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2577 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2578 parse[parse_pos] = NULLCHAR;
2579 // try to be smart: if it does not look like search info, it should go to
2580 // ICS interaction window after all, not to engine-output window.
2581 for(j=0; j<parse_pos; j++) { // count letters and digits
2582 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2583 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2584 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2586 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2587 int depth=0; float score;
2588 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2589 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2590 pvInfoList[forwardMostMove-1].depth = depth;
2591 pvInfoList[forwardMostMove-1].score = 100*score;
2593 OutputKibitz(suppressKibitz, parse);
2596 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2597 SendToPlayer(tmp, strlen(tmp));
2599 next_out = i+1; // [HGM] suppress printing in ICS window
2601 started = STARTED_NONE;
2603 /* Don't match patterns against characters in comment */
2608 if (started == STARTED_CHATTER) {
2609 if (buf[i] != '\n') {
2610 /* Don't match patterns against characters in chatter */
2614 started = STARTED_NONE;
2615 if(suppressKibitz) next_out = i+1;
2618 /* Kludge to deal with rcmd protocol */
2619 if (firstTime && looking_at(buf, &i, "\001*")) {
2620 DisplayFatalError(&buf[1], 0, 1);
2626 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2629 if (appData.debugMode)
2630 fprintf(debugFP, "ics_type %d\n", ics_type);
2633 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2634 ics_type = ICS_FICS;
2636 if (appData.debugMode)
2637 fprintf(debugFP, "ics_type %d\n", ics_type);
2640 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2641 ics_type = ICS_CHESSNET;
2643 if (appData.debugMode)
2644 fprintf(debugFP, "ics_type %d\n", ics_type);
2649 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2650 looking_at(buf, &i, "Logging you in as \"*\"") ||
2651 looking_at(buf, &i, "will be \"*\""))) {
2652 strcpy(ics_handle, star_match[0]);
2656 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2658 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2659 DisplayIcsInteractionTitle(buf);
2660 have_set_title = TRUE;
2663 /* skip finger notes */
2664 if (started == STARTED_NONE &&
2665 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2666 (buf[i] == '1' && buf[i+1] == '0')) &&
2667 buf[i+2] == ':' && buf[i+3] == ' ') {
2668 started = STARTED_CHATTER;
2674 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2675 if(appData.seekGraph) {
2676 if(soughtPending && MatchSoughtLine(buf+i)) {
2677 i = strstr(buf+i, "rated") - buf;
2678 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2679 next_out = leftover_start = i;
2680 started = STARTED_CHATTER;
2681 suppressKibitz = TRUE;
2684 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2685 && looking_at(buf, &i, "* ads displayed")) {
2686 soughtPending = FALSE;
2691 if(appData.autoRefresh) {
2692 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2693 int s = (ics_type == ICS_ICC); // ICC format differs
2695 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2696 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2697 looking_at(buf, &i, "*% "); // eat prompt
2698 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2699 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2700 next_out = i; // suppress
2703 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2704 char *p = star_match[0];
2706 if(seekGraphUp) RemoveSeekAd(atoi(p));
2707 while(*p && *p++ != ' '); // next
2709 looking_at(buf, &i, "*% "); // eat prompt
2710 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2717 /* skip formula vars */
2718 if (started == STARTED_NONE &&
2719 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2720 started = STARTED_CHATTER;
2725 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2726 if (appData.autoKibitz && started == STARTED_NONE &&
2727 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2728 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2729 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2730 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2731 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2732 suppressKibitz = TRUE;
2733 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2735 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2736 && (gameMode == IcsPlayingWhite)) ||
2737 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2738 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2739 started = STARTED_CHATTER; // own kibitz we simply discard
2741 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2742 parse_pos = 0; parse[0] = NULLCHAR;
2743 savingComment = TRUE;
2744 suppressKibitz = gameMode != IcsObserving ? 2 :
2745 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2749 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2750 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2751 && atoi(star_match[0])) {
2752 // suppress the acknowledgements of our own autoKibitz
2754 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2755 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2756 SendToPlayer(star_match[0], strlen(star_match[0]));
2757 if(looking_at(buf, &i, "*% ")) // eat prompt
2758 suppressKibitz = FALSE;
2762 } // [HGM] kibitz: end of patch
2764 // [HGM] chat: intercept tells by users for which we have an open chat window
2766 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2767 looking_at(buf, &i, "* whispers:") ||
2768 looking_at(buf, &i, "* kibitzes:") ||
2769 looking_at(buf, &i, "* shouts:") ||
2770 looking_at(buf, &i, "* c-shouts:") ||
2771 looking_at(buf, &i, "--> * ") ||
2772 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2773 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2774 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2775 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2777 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2778 chattingPartner = -1;
2780 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2781 for(p=0; p<MAX_CHAT; p++) {
2782 if(channel == atoi(chatPartner[p])) {
2783 talker[0] = '['; strcat(talker, "] ");
2784 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2785 chattingPartner = p; break;
2788 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2789 for(p=0; p<MAX_CHAT; p++) {
2790 if(!strcmp("kibitzes", chatPartner[p])) {
2791 talker[0] = '['; strcat(talker, "] ");
2792 chattingPartner = p; break;
2795 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2796 for(p=0; p<MAX_CHAT; p++) {
2797 if(!strcmp("whispers", chatPartner[p])) {
2798 talker[0] = '['; strcat(talker, "] ");
2799 chattingPartner = p; break;
2802 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2803 if(buf[i-8] == '-' && buf[i-3] == 't')
2804 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2805 if(!strcmp("c-shouts", chatPartner[p])) {
2806 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2807 chattingPartner = p; break;
2810 if(chattingPartner < 0)
2811 for(p=0; p<MAX_CHAT; p++) {
2812 if(!strcmp("shouts", chatPartner[p])) {
2813 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2814 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2815 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2816 chattingPartner = p; break;
2820 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2821 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2822 talker[0] = 0; Colorize(ColorTell, FALSE);
2823 chattingPartner = p; break;
2825 if(chattingPartner<0) i = oldi; else {
2826 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2827 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2828 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829 started = STARTED_COMMENT;
2830 parse_pos = 0; parse[0] = NULLCHAR;
2831 savingComment = 3 + chattingPartner; // counts as TRUE
2832 suppressKibitz = TRUE;
2835 } // [HGM] chat: end of patch
2837 if (appData.zippyTalk || appData.zippyPlay) {
2838 /* [DM] Backup address for color zippy lines */
2842 if (loggedOn == TRUE)
2843 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2844 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2846 if (ZippyControl(buf, &i) ||
2847 ZippyConverse(buf, &i) ||
2848 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2850 if (!appData.colorize) continue;
2854 } // [DM] 'else { ' deleted
2856 /* Regular tells and says */
2857 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2858 looking_at(buf, &i, "* (your partner) tells you: ") ||
2859 looking_at(buf, &i, "* says: ") ||
2860 /* Don't color "message" or "messages" output */
2861 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2862 looking_at(buf, &i, "*. * at *:*: ") ||
2863 looking_at(buf, &i, "--* (*:*): ") ||
2864 /* Message notifications (same color as tells) */
2865 looking_at(buf, &i, "* has left a message ") ||
2866 looking_at(buf, &i, "* just sent you a message:\n") ||
2867 /* Whispers and kibitzes */
2868 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2869 looking_at(buf, &i, "* kibitzes: ") ||
2871 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2873 if (tkind == 1 && strchr(star_match[0], ':')) {
2874 /* Avoid "tells you:" spoofs in channels */
2877 if (star_match[0][0] == NULLCHAR ||
2878 strchr(star_match[0], ' ') ||
2879 (tkind == 3 && strchr(star_match[1], ' '))) {
2880 /* Reject bogus matches */
2883 if (appData.colorize) {
2884 if (oldi > next_out) {
2885 SendToPlayer(&buf[next_out], oldi - next_out);
2890 Colorize(ColorTell, FALSE);
2891 curColor = ColorTell;
2894 Colorize(ColorKibitz, FALSE);
2895 curColor = ColorKibitz;
2898 p = strrchr(star_match[1], '(');
2905 Colorize(ColorChannel1, FALSE);
2906 curColor = ColorChannel1;
2908 Colorize(ColorChannel, FALSE);
2909 curColor = ColorChannel;
2913 curColor = ColorNormal;
2917 if (started == STARTED_NONE && appData.autoComment &&
2918 (gameMode == IcsObserving ||
2919 gameMode == IcsPlayingWhite ||
2920 gameMode == IcsPlayingBlack)) {
2921 parse_pos = i - oldi;
2922 memcpy(parse, &buf[oldi], parse_pos);
2923 parse[parse_pos] = NULLCHAR;
2924 started = STARTED_COMMENT;
2925 savingComment = TRUE;
2927 started = STARTED_CHATTER;
2928 savingComment = FALSE;
2935 if (looking_at(buf, &i, "* s-shouts: ") ||
2936 looking_at(buf, &i, "* c-shouts: ")) {
2937 if (appData.colorize) {
2938 if (oldi > next_out) {
2939 SendToPlayer(&buf[next_out], oldi - next_out);
2942 Colorize(ColorSShout, FALSE);
2943 curColor = ColorSShout;
2946 started = STARTED_CHATTER;
2950 if (looking_at(buf, &i, "--->")) {
2955 if (looking_at(buf, &i, "* shouts: ") ||
2956 looking_at(buf, &i, "--> ")) {
2957 if (appData.colorize) {
2958 if (oldi > next_out) {
2959 SendToPlayer(&buf[next_out], oldi - next_out);
2962 Colorize(ColorShout, FALSE);
2963 curColor = ColorShout;
2966 started = STARTED_CHATTER;
2970 if (looking_at( buf, &i, "Challenge:")) {
2971 if (appData.colorize) {
2972 if (oldi > next_out) {
2973 SendToPlayer(&buf[next_out], oldi - next_out);
2976 Colorize(ColorChallenge, FALSE);
2977 curColor = ColorChallenge;
2983 if (looking_at(buf, &i, "* offers you") ||
2984 looking_at(buf, &i, "* offers to be") ||
2985 looking_at(buf, &i, "* would like to") ||
2986 looking_at(buf, &i, "* requests to") ||
2987 looking_at(buf, &i, "Your opponent offers") ||
2988 looking_at(buf, &i, "Your opponent requests")) {
2990 if (appData.colorize) {
2991 if (oldi > next_out) {
2992 SendToPlayer(&buf[next_out], oldi - next_out);
2995 Colorize(ColorRequest, FALSE);
2996 curColor = ColorRequest;
3001 if (looking_at(buf, &i, "* (*) seeking")) {
3002 if (appData.colorize) {
3003 if (oldi > next_out) {
3004 SendToPlayer(&buf[next_out], oldi - next_out);
3007 Colorize(ColorSeek, FALSE);
3008 curColor = ColorSeek;
3013 if (looking_at(buf, &i, "\\ ")) {
3014 if (prevColor != ColorNormal) {
3015 if (oldi > next_out) {
3016 SendToPlayer(&buf[next_out], oldi - next_out);
3019 Colorize(prevColor, TRUE);
3020 curColor = prevColor;
3022 if (savingComment) {
3023 parse_pos = i - oldi;
3024 memcpy(parse, &buf[oldi], parse_pos);
3025 parse[parse_pos] = NULLCHAR;
3026 started = STARTED_COMMENT;
3027 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3028 chattingPartner = savingComment - 3; // kludge to remember the box
3030 started = STARTED_CHATTER;
3035 if (looking_at(buf, &i, "Black Strength :") ||
3036 looking_at(buf, &i, "<<< style 10 board >>>") ||
3037 looking_at(buf, &i, "<10>") ||
3038 looking_at(buf, &i, "#@#")) {
3039 /* Wrong board style */
3041 SendToICS(ics_prefix);
3042 SendToICS("set style 12\n");
3043 SendToICS(ics_prefix);
3044 SendToICS("refresh\n");
3048 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3050 have_sent_ICS_logon = 1;
3054 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3055 (looking_at(buf, &i, "\n<12> ") ||
3056 looking_at(buf, &i, "<12> "))) {
3058 if (oldi > next_out) {
3059 SendToPlayer(&buf[next_out], oldi - next_out);
3062 started = STARTED_BOARD;
3067 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3068 looking_at(buf, &i, "<b1> ")) {
3069 if (oldi > next_out) {
3070 SendToPlayer(&buf[next_out], oldi - next_out);
3073 started = STARTED_HOLDINGS;
3078 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3080 /* Header for a move list -- first line */
3082 switch (ics_getting_history) {
3086 case BeginningOfGame:
3087 /* User typed "moves" or "oldmoves" while we
3088 were idle. Pretend we asked for these
3089 moves and soak them up so user can step
3090 through them and/or save them.
3093 gameMode = IcsObserving;
3096 ics_getting_history = H_GOT_UNREQ_HEADER;
3098 case EditGame: /*?*/
3099 case EditPosition: /*?*/
3100 /* Should above feature work in these modes too? */
3101 /* For now it doesn't */
3102 ics_getting_history = H_GOT_UNWANTED_HEADER;
3105 ics_getting_history = H_GOT_UNWANTED_HEADER;
3110 /* Is this the right one? */
3111 if (gameInfo.white && gameInfo.black &&
3112 strcmp(gameInfo.white, star_match[0]) == 0 &&
3113 strcmp(gameInfo.black, star_match[2]) == 0) {
3115 ics_getting_history = H_GOT_REQ_HEADER;
3118 case H_GOT_REQ_HEADER:
3119 case H_GOT_UNREQ_HEADER:
3120 case H_GOT_UNWANTED_HEADER:
3121 case H_GETTING_MOVES:
3122 /* Should not happen */
3123 DisplayError(_("Error gathering move list: two headers"), 0);
3124 ics_getting_history = H_FALSE;
3128 /* Save player ratings into gameInfo if needed */
3129 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3130 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3131 (gameInfo.whiteRating == -1 ||
3132 gameInfo.blackRating == -1)) {
3134 gameInfo.whiteRating = string_to_rating(star_match[1]);
3135 gameInfo.blackRating = string_to_rating(star_match[3]);
3136 if (appData.debugMode)
3137 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3138 gameInfo.whiteRating, gameInfo.blackRating);
3143 if (looking_at(buf, &i,
3144 "* * match, initial time: * minute*, increment: * second")) {
3145 /* Header for a move list -- second line */
3146 /* Initial board will follow if this is a wild game */
3147 if (gameInfo.event != NULL) free(gameInfo.event);
3148 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3149 gameInfo.event = StrSave(str);
3150 /* [HGM] we switched variant. Translate boards if needed. */
3151 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3155 if (looking_at(buf, &i, "Move ")) {
3156 /* Beginning of a move list */
3157 switch (ics_getting_history) {
3159 /* Normally should not happen */
3160 /* Maybe user hit reset while we were parsing */
3163 /* Happens if we are ignoring a move list that is not
3164 * the one we just requested. Common if the user
3165 * tries to observe two games without turning off
3168 case H_GETTING_MOVES:
3169 /* Should not happen */
3170 DisplayError(_("Error gathering move list: nested"), 0);
3171 ics_getting_history = H_FALSE;
3173 case H_GOT_REQ_HEADER:
3174 ics_getting_history = H_GETTING_MOVES;
3175 started = STARTED_MOVES;
3177 if (oldi > next_out) {
3178 SendToPlayer(&buf[next_out], oldi - next_out);
3181 case H_GOT_UNREQ_HEADER:
3182 ics_getting_history = H_GETTING_MOVES;
3183 started = STARTED_MOVES_NOHIDE;
3186 case H_GOT_UNWANTED_HEADER:
3187 ics_getting_history = H_FALSE;
3193 if (looking_at(buf, &i, "% ") ||
3194 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3195 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3196 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3197 soughtPending = FALSE;
3201 if(suppressKibitz) next_out = i;
3202 savingComment = FALSE;
3206 case STARTED_MOVES_NOHIDE:
3207 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3208 parse[parse_pos + i - oldi] = NULLCHAR;
3209 ParseGameHistory(parse);
3211 if (appData.zippyPlay && first.initDone) {
3212 FeedMovesToProgram(&first, forwardMostMove);
3213 if (gameMode == IcsPlayingWhite) {
3214 if (WhiteOnMove(forwardMostMove)) {
3215 if (first.sendTime) {
3216 if (first.useColors) {
3217 SendToProgram("black\n", &first);
3219 SendTimeRemaining(&first, TRUE);
3221 if (first.useColors) {
3222 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3224 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3225 first.maybeThinking = TRUE;
3227 if (first.usePlayother) {
3228 if (first.sendTime) {
3229 SendTimeRemaining(&first, TRUE);
3231 SendToProgram("playother\n", &first);
3237 } else if (gameMode == IcsPlayingBlack) {
3238 if (!WhiteOnMove(forwardMostMove)) {
3239 if (first.sendTime) {
3240 if (first.useColors) {
3241 SendToProgram("white\n", &first);
3243 SendTimeRemaining(&first, FALSE);
3245 if (first.useColors) {
3246 SendToProgram("black\n", &first);
3248 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3249 first.maybeThinking = TRUE;
3251 if (first.usePlayother) {
3252 if (first.sendTime) {
3253 SendTimeRemaining(&first, FALSE);
3255 SendToProgram("playother\n", &first);
3264 if (gameMode == IcsObserving && ics_gamenum == -1) {
3265 /* Moves came from oldmoves or moves command
3266 while we weren't doing anything else.
3268 currentMove = forwardMostMove;
3269 ClearHighlights();/*!!could figure this out*/
3270 flipView = appData.flipView;
3271 DrawPosition(TRUE, boards[currentMove]);
3272 DisplayBothClocks();
3273 sprintf(str, "%s vs. %s",
3274 gameInfo.white, gameInfo.black);
3278 /* Moves were history of an active game */
3279 if (gameInfo.resultDetails != NULL) {
3280 free(gameInfo.resultDetails);
3281 gameInfo.resultDetails = NULL;
3284 HistorySet(parseList, backwardMostMove,
3285 forwardMostMove, currentMove-1);
3286 DisplayMove(currentMove - 1);
3287 if (started == STARTED_MOVES) next_out = i;
3288 started = STARTED_NONE;
3289 ics_getting_history = H_FALSE;
3292 case STARTED_OBSERVE:
3293 started = STARTED_NONE;
3294 SendToICS(ics_prefix);
3295 SendToICS("refresh\n");
3301 if(bookHit) { // [HGM] book: simulate book reply
3302 static char bookMove[MSG_SIZ]; // a bit generous?
3304 programStats.nodes = programStats.depth = programStats.time =
3305 programStats.score = programStats.got_only_move = 0;
3306 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3308 strcpy(bookMove, "move ");
3309 strcat(bookMove, bookHit);
3310 HandleMachineMove(bookMove, &first);
3315 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3316 started == STARTED_HOLDINGS ||
3317 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3318 /* Accumulate characters in move list or board */
3319 parse[parse_pos++] = buf[i];
3322 /* Start of game messages. Mostly we detect start of game
3323 when the first board image arrives. On some versions
3324 of the ICS, though, we need to do a "refresh" after starting
3325 to observe in order to get the current board right away. */
3326 if (looking_at(buf, &i, "Adding game * to observation list")) {
3327 started = STARTED_OBSERVE;
3331 /* Handle auto-observe */
3332 if (appData.autoObserve &&
3333 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3334 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3336 /* Choose the player that was highlighted, if any. */
3337 if (star_match[0][0] == '\033' ||
3338 star_match[1][0] != '\033') {
3339 player = star_match[0];
3341 player = star_match[2];
3343 sprintf(str, "%sobserve %s\n",
3344 ics_prefix, StripHighlightAndTitle(player));
3347 /* Save ratings from notify string */
3348 strcpy(player1Name, star_match[0]);
3349 player1Rating = string_to_rating(star_match[1]);
3350 strcpy(player2Name, star_match[2]);
3351 player2Rating = string_to_rating(star_match[3]);
3353 if (appData.debugMode)
3355 "Ratings from 'Game notification:' %s %d, %s %d\n",
3356 player1Name, player1Rating,
3357 player2Name, player2Rating);
3362 /* Deal with automatic examine mode after a game,
3363 and with IcsObserving -> IcsExamining transition */
3364 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3365 looking_at(buf, &i, "has made you an examiner of game *")) {
3367 int gamenum = atoi(star_match[0]);
3368 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3369 gamenum == ics_gamenum) {
3370 /* We were already playing or observing this game;
3371 no need to refetch history */
3372 gameMode = IcsExamining;
3374 pauseExamForwardMostMove = forwardMostMove;
3375 } else if (currentMove < forwardMostMove) {
3376 ForwardInner(forwardMostMove);
3379 /* I don't think this case really can happen */
3380 SendToICS(ics_prefix);
3381 SendToICS("refresh\n");
3386 /* Error messages */
3387 // if (ics_user_moved) {
3388 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3389 if (looking_at(buf, &i, "Illegal move") ||
3390 looking_at(buf, &i, "Not a legal move") ||
3391 looking_at(buf, &i, "Your king is in check") ||
3392 looking_at(buf, &i, "It isn't your turn") ||
3393 looking_at(buf, &i, "It is not your move")) {
3395 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3396 currentMove = forwardMostMove-1;
3397 DisplayMove(currentMove - 1); /* before DMError */
3398 DrawPosition(FALSE, boards[currentMove]);
3399 SwitchClocks(forwardMostMove-1); // [HGM] race
3400 DisplayBothClocks();
3402 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3408 if (looking_at(buf, &i, "still have time") ||
3409 looking_at(buf, &i, "not out of time") ||
3410 looking_at(buf, &i, "either player is out of time") ||
3411 looking_at(buf, &i, "has timeseal; checking")) {
3412 /* We must have called his flag a little too soon */
3413 whiteFlag = blackFlag = FALSE;
3417 if (looking_at(buf, &i, "added * seconds to") ||
3418 looking_at(buf, &i, "seconds were added to")) {
3419 /* Update the clocks */
3420 SendToICS(ics_prefix);
3421 SendToICS("refresh\n");
3425 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3426 ics_clock_paused = TRUE;
3431 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3432 ics_clock_paused = FALSE;
3437 /* Grab player ratings from the Creating: message.
3438 Note we have to check for the special case when
3439 the ICS inserts things like [white] or [black]. */
3440 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3441 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3443 0 player 1 name (not necessarily white)
3445 2 empty, white, or black (IGNORED)
3446 3 player 2 name (not necessarily black)
3449 The names/ratings are sorted out when the game
3450 actually starts (below).
3452 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3453 player1Rating = string_to_rating(star_match[1]);
3454 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3455 player2Rating = string_to_rating(star_match[4]);
3457 if (appData.debugMode)
3459 "Ratings from 'Creating:' %s %d, %s %d\n",
3460 player1Name, player1Rating,
3461 player2Name, player2Rating);
3466 /* Improved generic start/end-of-game messages */
3467 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3468 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3469 /* If tkind == 0: */
3470 /* star_match[0] is the game number */
3471 /* [1] is the white player's name */
3472 /* [2] is the black player's name */
3473 /* For end-of-game: */
3474 /* [3] is the reason for the game end */
3475 /* [4] is a PGN end game-token, preceded by " " */
3476 /* For start-of-game: */
3477 /* [3] begins with "Creating" or "Continuing" */
3478 /* [4] is " *" or empty (don't care). */
3479 int gamenum = atoi(star_match[0]);
3480 char *whitename, *blackname, *why, *endtoken;
3481 ChessMove endtype = (ChessMove) 0;
3484 whitename = star_match[1];
3485 blackname = star_match[2];
3486 why = star_match[3];
3487 endtoken = star_match[4];
3489 whitename = star_match[1];
3490 blackname = star_match[3];
3491 why = star_match[5];
3492 endtoken = star_match[6];
3495 /* Game start messages */
3496 if (strncmp(why, "Creating ", 9) == 0 ||
3497 strncmp(why, "Continuing ", 11) == 0) {
3498 gs_gamenum = gamenum;
3499 strcpy(gs_kind, strchr(why, ' ') + 1);
3500 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3502 if (appData.zippyPlay) {
3503 ZippyGameStart(whitename, blackname);
3506 partnerBoardValid = FALSE; // [HGM] bughouse
3510 /* Game end messages */
3511 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3512 ics_gamenum != gamenum) {
3515 while (endtoken[0] == ' ') endtoken++;
3516 switch (endtoken[0]) {
3519 endtype = GameUnfinished;
3522 endtype = BlackWins;
3525 if (endtoken[1] == '/')
3526 endtype = GameIsDrawn;
3528 endtype = WhiteWins;
3531 GameEnds(endtype, why, GE_ICS);
3533 if (appData.zippyPlay && first.initDone) {
3534 ZippyGameEnd(endtype, why);
3535 if (first.pr == NULL) {
3536 /* Start the next process early so that we'll
3537 be ready for the next challenge */
3538 StartChessProgram(&first);
3540 /* Send "new" early, in case this command takes
3541 a long time to finish, so that we'll be ready
3542 for the next challenge. */
3543 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3547 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3551 if (looking_at(buf, &i, "Removing game * from observation") ||
3552 looking_at(buf, &i, "no longer observing game *") ||
3553 looking_at(buf, &i, "Game * (*) has no examiners")) {
3554 if (gameMode == IcsObserving &&
3555 atoi(star_match[0]) == ics_gamenum)
3557 /* icsEngineAnalyze */
3558 if (appData.icsEngineAnalyze) {
3565 ics_user_moved = FALSE;
3570 if (looking_at(buf, &i, "no longer examining game *")) {
3571 if (gameMode == IcsExamining &&
3572 atoi(star_match[0]) == ics_gamenum)
3576 ics_user_moved = FALSE;
3581 /* Advance leftover_start past any newlines we find,
3582 so only partial lines can get reparsed */
3583 if (looking_at(buf, &i, "\n")) {
3584 prevColor = curColor;
3585 if (curColor != ColorNormal) {
3586 if (oldi > next_out) {
3587 SendToPlayer(&buf[next_out], oldi - next_out);
3590 Colorize(ColorNormal, FALSE);
3591 curColor = ColorNormal;
3593 if (started == STARTED_BOARD) {
3594 started = STARTED_NONE;
3595 parse[parse_pos] = NULLCHAR;
3596 ParseBoard12(parse);
3599 /* Send premove here */
3600 if (appData.premove) {
3602 if (currentMove == 0 &&
3603 gameMode == IcsPlayingWhite &&
3604 appData.premoveWhite) {
3605 sprintf(str, "%s\n", appData.premoveWhiteText);
3606 if (appData.debugMode)
3607 fprintf(debugFP, "Sending premove:\n");
3609 } else if (currentMove == 1 &&
3610 gameMode == IcsPlayingBlack &&
3611 appData.premoveBlack) {
3612 sprintf(str, "%s\n", appData.premoveBlackText);
3613 if (appData.debugMode)
3614 fprintf(debugFP, "Sending premove:\n");
3616 } else if (gotPremove) {
3618 ClearPremoveHighlights();
3619 if (appData.debugMode)
3620 fprintf(debugFP, "Sending premove:\n");
3621 UserMoveEvent(premoveFromX, premoveFromY,
3622 premoveToX, premoveToY,
3627 /* Usually suppress following prompt */
3628 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3629 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3630 if (looking_at(buf, &i, "*% ")) {
3631 savingComment = FALSE;
3636 } else if (started == STARTED_HOLDINGS) {
3638 char new_piece[MSG_SIZ];
3639 started = STARTED_NONE;
3640 parse[parse_pos] = NULLCHAR;
3641 if (appData.debugMode)
3642 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3643 parse, currentMove);
3644 if (sscanf(parse, " game %d", &gamenum) == 1) {
3645 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3646 if (gameInfo.variant == VariantNormal) {
3647 /* [HGM] We seem to switch variant during a game!
3648 * Presumably no holdings were displayed, so we have
3649 * to move the position two files to the right to
3650 * create room for them!
3652 VariantClass newVariant;
3653 switch(gameInfo.boardWidth) { // base guess on board width
3654 case 9: newVariant = VariantShogi; break;
3655 case 10: newVariant = VariantGreat; break;
3656 default: newVariant = VariantCrazyhouse; break;
3658 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3659 /* Get a move list just to see the header, which
3660 will tell us whether this is really bug or zh */
3661 if (ics_getting_history == H_FALSE) {
3662 ics_getting_history = H_REQUESTED;
3663 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3667 new_piece[0] = NULLCHAR;
3668 sscanf(parse, "game %d white [%s black [%s <- %s",
3669 &gamenum, white_holding, black_holding,
3671 white_holding[strlen(white_holding)-1] = NULLCHAR;
3672 black_holding[strlen(black_holding)-1] = NULLCHAR;
3673 /* [HGM] copy holdings to board holdings area */
3674 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3675 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3676 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3678 if (appData.zippyPlay && first.initDone) {
3679 ZippyHoldings(white_holding, black_holding,
3683 if (tinyLayout || smallLayout) {
3684 char wh[16], bh[16];
3685 PackHolding(wh, white_holding);
3686 PackHolding(bh, black_holding);
3687 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3688 gameInfo.white, gameInfo.black);
3690 sprintf(str, "%s [%s] vs. %s [%s]",
3691 gameInfo.white, white_holding,
3692 gameInfo.black, black_holding);
3694 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3695 DrawPosition(FALSE, boards[currentMove]);
3697 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3698 sscanf(parse, "game %d white [%s black [%s <- %s",
3699 &gamenum, white_holding, black_holding,
3701 white_holding[strlen(white_holding)-1] = NULLCHAR;
3702 black_holding[strlen(black_holding)-1] = NULLCHAR;
3703 /* [HGM] copy holdings to partner-board holdings area */
3704 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3705 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3706 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3707 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3708 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3711 /* Suppress following prompt */
3712 if (looking_at(buf, &i, "*% ")) {
3713 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3714 savingComment = FALSE;
3722 i++; /* skip unparsed character and loop back */
3725 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3726 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3727 // SendToPlayer(&buf[next_out], i - next_out);
3728 started != STARTED_HOLDINGS && leftover_start > next_out) {
3729 SendToPlayer(&buf[next_out], leftover_start - next_out);
3733 leftover_len = buf_len - leftover_start;
3734 /* if buffer ends with something we couldn't parse,
3735 reparse it after appending the next read */
3737 } else if (count == 0) {
3738 RemoveInputSource(isr);
3739 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3741 DisplayFatalError(_("Error reading from ICS"), error, 1);
3746 /* Board style 12 looks like this:
3748 <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
3750 * The "<12> " is stripped before it gets to this routine. The two
3751 * trailing 0's (flip state and clock ticking) are later addition, and
3752 * some chess servers may not have them, or may have only the first.
3753 * Additional trailing fields may be added in the future.
3756 #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"
3758 #define RELATION_OBSERVING_PLAYED 0
3759 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3760 #define RELATION_PLAYING_MYMOVE 1
3761 #define RELATION_PLAYING_NOTMYMOVE -1
3762 #define RELATION_EXAMINING 2
3763 #define RELATION_ISOLATED_BOARD -3
3764 #define RELATION_STARTING_POSITION -4 /* FICS only */
3767 ParseBoard12(string)
3770 GameMode newGameMode;
3771 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3772 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3773 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3774 char to_play, board_chars[200];
3775 char move_str[500], str[500], elapsed_time[500];
3776 char black[32], white[32];
3778 int prevMove = currentMove;
3781 int fromX, fromY, toX, toY;
3783 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3784 char *bookHit = NULL; // [HGM] book
3785 Boolean weird = FALSE, reqFlag = FALSE;
3787 fromX = fromY = toX = toY = -1;
3791 if (appData.debugMode)
3792 fprintf(debugFP, _("Parsing board: %s\n"), string);
3794 move_str[0] = NULLCHAR;
3795 elapsed_time[0] = NULLCHAR;
3796 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3798 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3799 if(string[i] == ' ') { ranks++; files = 0; }
3801 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3804 for(j = 0; j <i; j++) board_chars[j] = string[j];
3805 board_chars[i] = '\0';
3808 n = sscanf(string, PATTERN, &to_play, &double_push,
3809 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3810 &gamenum, white, black, &relation, &basetime, &increment,
3811 &white_stren, &black_stren, &white_time, &black_time,
3812 &moveNum, str, elapsed_time, move_str, &ics_flip,
3816 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3817 DisplayError(str, 0);
3821 /* Convert the move number to internal form */
3822 moveNum = (moveNum - 1) * 2;
3823 if (to_play == 'B') moveNum++;
3824 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3825 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3831 case RELATION_OBSERVING_PLAYED:
3832 case RELATION_OBSERVING_STATIC:
3833 if (gamenum == -1) {
3834 /* Old ICC buglet */
3835 relation = RELATION_OBSERVING_STATIC;
3837 newGameMode = IcsObserving;
3839 case RELATION_PLAYING_MYMOVE:
3840 case RELATION_PLAYING_NOTMYMOVE:
3842 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3843 IcsPlayingWhite : IcsPlayingBlack;
3845 case RELATION_EXAMINING:
3846 newGameMode = IcsExamining;
3848 case RELATION_ISOLATED_BOARD:
3850 /* Just display this board. If user was doing something else,
3851 we will forget about it until the next board comes. */
3852 newGameMode = IcsIdle;
3854 case RELATION_STARTING_POSITION:
3855 newGameMode = gameMode;
3859 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3860 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3861 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3863 for (k = 0; k < ranks; k++) {
3864 for (j = 0; j < files; j++)
3865 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3866 if(gameInfo.holdingsWidth > 1) {
3867 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3868 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3871 CopyBoard(partnerBoard, board);
3872 if(toSqr = strchr(str, '/')) { // extract highlights from long move
3873 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3874 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3875 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3876 if(toSqr = strchr(str, '-')) {
3877 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3878 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3879 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3880 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3881 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3882 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3883 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3884 sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3885 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3886 DisplayMessage(partnerStatus, "");
3887 partnerBoardValid = TRUE;
3891 /* Modify behavior for initial board display on move listing
3894 switch (ics_getting_history) {
3898 case H_GOT_REQ_HEADER:
3899 case H_GOT_UNREQ_HEADER:
3900 /* This is the initial position of the current game */
3901 gamenum = ics_gamenum;
3902 moveNum = 0; /* old ICS bug workaround */
3903 if (to_play == 'B') {
3904 startedFromSetupPosition = TRUE;
3905 blackPlaysFirst = TRUE;
3907 if (forwardMostMove == 0) forwardMostMove = 1;
3908 if (backwardMostMove == 0) backwardMostMove = 1;
3909 if (currentMove == 0) currentMove = 1;
3911 newGameMode = gameMode;
3912 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3914 case H_GOT_UNWANTED_HEADER:
3915 /* This is an initial board that we don't want */
3917 case H_GETTING_MOVES:
3918 /* Should not happen */
3919 DisplayError(_("Error gathering move list: extra board"), 0);
3920 ics_getting_history = H_FALSE;
3924 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3925 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3926 /* [HGM] We seem to have switched variant unexpectedly
3927 * Try to guess new variant from board size
3929 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3930 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3931 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3932 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3933 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3934 if(!weird) newVariant = VariantNormal;
3935 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3936 /* Get a move list just to see the header, which
3937 will tell us whether this is really bug or zh */
3938 if (ics_getting_history == H_FALSE) {
3939 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3940 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3945 /* Take action if this is the first board of a new game, or of a
3946 different game than is currently being displayed. */
3947 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3948 relation == RELATION_ISOLATED_BOARD) {
3950 /* Forget the old game and get the history (if any) of the new one */
3951 if (gameMode != BeginningOfGame) {
3955 if (appData.autoRaiseBoard) BoardToTop();
3957 if (gamenum == -1) {
3958 newGameMode = IcsIdle;
3959 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3960 appData.getMoveList && !reqFlag) {
3961 /* Need to get game history */
3962 ics_getting_history = H_REQUESTED;
3963 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3967 /* Initially flip the board to have black on the bottom if playing
3968 black or if the ICS flip flag is set, but let the user change
3969 it with the Flip View button. */
3970 flipView = appData.autoFlipView ?
3971 (newGameMode == IcsPlayingBlack) || ics_flip :
3974 /* Done with values from previous mode; copy in new ones */
3975 gameMode = newGameMode;
3977 ics_gamenum = gamenum;
3978 if (gamenum == gs_gamenum) {
3979 int klen = strlen(gs_kind);
3980 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3981 sprintf(str, "ICS %s", gs_kind);
3982 gameInfo.event = StrSave(str);
3984 gameInfo.event = StrSave("ICS game");
3986 gameInfo.site = StrSave(appData.icsHost);
3987 gameInfo.date = PGNDate();
3988 gameInfo.round = StrSave("-");
3989 gameInfo.white = StrSave(white);
3990 gameInfo.black = StrSave(black);
3991 timeControl = basetime * 60 * 1000;
3993 timeIncrement = increment * 1000;
3994 movesPerSession = 0;
3995 gameInfo.timeControl = TimeControlTagValue();
3996 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3997 if (appData.debugMode) {
3998 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3999 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4000 setbuf(debugFP, NULL);
4003 gameInfo.outOfBook = NULL;
4005 /* Do we have the ratings? */
4006 if (strcmp(player1Name, white) == 0 &&
4007 strcmp(player2Name, black) == 0) {
4008 if (appData.debugMode)
4009 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4010 player1Rating, player2Rating);
4011 gameInfo.whiteRating = player1Rating;
4012 gameInfo.blackRating = player2Rating;
4013 } else if (strcmp(player2Name, white) == 0 &&
4014 strcmp(player1Name, black) == 0) {
4015 if (appData.debugMode)
4016 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4017 player2Rating, player1Rating);
4018 gameInfo.whiteRating = player2Rating;
4019 gameInfo.blackRating = player1Rating;
4021 player1Name[0] = player2Name[0] = NULLCHAR;
4023 /* Silence shouts if requested */
4024 if (appData.quietPlay &&
4025 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4026 SendToICS(ics_prefix);
4027 SendToICS("set shout 0\n");
4031 /* Deal with midgame name changes */
4033 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4034 if (gameInfo.white) free(gameInfo.white);
4035 gameInfo.white = StrSave(white);
4037 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4038 if (gameInfo.black) free(gameInfo.black);
4039 gameInfo.black = StrSave(black);
4043 /* Throw away game result if anything actually changes in examine mode */
4044 if (gameMode == IcsExamining && !newGame) {
4045 gameInfo.result = GameUnfinished;
4046 if (gameInfo.resultDetails != NULL) {
4047 free(gameInfo.resultDetails);
4048 gameInfo.resultDetails = NULL;
4052 /* In pausing && IcsExamining mode, we ignore boards coming
4053 in if they are in a different variation than we are. */
4054 if (pauseExamInvalid) return;
4055 if (pausing && gameMode == IcsExamining) {
4056 if (moveNum <= pauseExamForwardMostMove) {
4057 pauseExamInvalid = TRUE;
4058 forwardMostMove = pauseExamForwardMostMove;
4063 if (appData.debugMode) {
4064 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4066 /* Parse the board */
4067 for (k = 0; k < ranks; k++) {
4068 for (j = 0; j < files; j++)
4069 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4070 if(gameInfo.holdingsWidth > 1) {
4071 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4072 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4075 CopyBoard(boards[moveNum], board);
4076 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4078 startedFromSetupPosition =
4079 !CompareBoards(board, initialPosition);
4080 if(startedFromSetupPosition)
4081 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4084 /* [HGM] Set castling rights. Take the outermost Rooks,
4085 to make it also work for FRC opening positions. Note that board12
4086 is really defective for later FRC positions, as it has no way to
4087 indicate which Rook can castle if they are on the same side of King.
4088 For the initial position we grant rights to the outermost Rooks,
4089 and remember thos rights, and we then copy them on positions
4090 later in an FRC game. This means WB might not recognize castlings with
4091 Rooks that have moved back to their original position as illegal,
4092 but in ICS mode that is not its job anyway.
4094 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4095 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4097 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4098 if(board[0][i] == WhiteRook) j = i;
4099 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4100 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4101 if(board[0][i] == WhiteRook) j = i;
4102 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4103 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4104 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4105 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4106 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4107 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4108 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4110 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4111 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4112 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4113 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4114 if(board[BOARD_HEIGHT-1][k] == bKing)
4115 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4116 if(gameInfo.variant == VariantTwoKings) {
4117 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4118 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4119 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4122 r = boards[moveNum][CASTLING][0] = initialRights[0];
4123 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4124 r = boards[moveNum][CASTLING][1] = initialRights[1];
4125 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4126 r = boards[moveNum][CASTLING][3] = initialRights[3];
4127 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4128 r = boards[moveNum][CASTLING][4] = initialRights[4];
4129 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4130 /* wildcastle kludge: always assume King has rights */
4131 r = boards[moveNum][CASTLING][2] = initialRights[2];
4132 r = boards[moveNum][CASTLING][5] = initialRights[5];
4134 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4135 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4138 if (ics_getting_history == H_GOT_REQ_HEADER ||
4139 ics_getting_history == H_GOT_UNREQ_HEADER) {
4140 /* This was an initial position from a move list, not
4141 the current position */
4145 /* Update currentMove and known move number limits */
4146 newMove = newGame || moveNum > forwardMostMove;
4149 forwardMostMove = backwardMostMove = currentMove = moveNum;
4150 if (gameMode == IcsExamining && moveNum == 0) {
4151 /* Workaround for ICS limitation: we are not told the wild
4152 type when starting to examine a game. But if we ask for
4153 the move list, the move list header will tell us */
4154 ics_getting_history = H_REQUESTED;
4155 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4158 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4159 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4161 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4162 /* [HGM] applied this also to an engine that is silently watching */
4163 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4164 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4165 gameInfo.variant == currentlyInitializedVariant) {
4166 takeback = forwardMostMove - moveNum;
4167 for (i = 0; i < takeback; i++) {
4168 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4169 SendToProgram("undo\n", &first);
4174 forwardMostMove = moveNum;
4175 if (!pausing || currentMove > forwardMostMove)
4176 currentMove = forwardMostMove;
4178 /* New part of history that is not contiguous with old part */
4179 if (pausing && gameMode == IcsExamining) {
4180 pauseExamInvalid = TRUE;
4181 forwardMostMove = pauseExamForwardMostMove;
4184 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4186 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4187 // [HGM] when we will receive the move list we now request, it will be
4188 // fed to the engine from the first move on. So if the engine is not
4189 // in the initial position now, bring it there.
4190 InitChessProgram(&first, 0);
4193 ics_getting_history = H_REQUESTED;
4194 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4197 forwardMostMove = backwardMostMove = currentMove = moveNum;
4200 /* Update the clocks */
4201 if (strchr(elapsed_time, '.')) {
4203 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4204 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4206 /* Time is in seconds */
4207 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4208 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4213 if (appData.zippyPlay && newGame &&
4214 gameMode != IcsObserving && gameMode != IcsIdle &&
4215 gameMode != IcsExamining)
4216 ZippyFirstBoard(moveNum, basetime, increment);
4219 /* Put the move on the move list, first converting
4220 to canonical algebraic form. */
4222 if (appData.debugMode) {
4223 if (appData.debugMode) { int f = forwardMostMove;
4224 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4225 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4226 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4228 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4229 fprintf(debugFP, "moveNum = %d\n", moveNum);
4230 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4231 setbuf(debugFP, NULL);
4233 if (moveNum <= backwardMostMove) {
4234 /* We don't know what the board looked like before
4236 strcpy(parseList[moveNum - 1], move_str);
4237 strcat(parseList[moveNum - 1], " ");
4238 strcat(parseList[moveNum - 1], elapsed_time);
4239 moveList[moveNum - 1][0] = NULLCHAR;
4240 } else if (strcmp(move_str, "none") == 0) {
4241 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4242 /* Again, we don't know what the board looked like;
4243 this is really the start of the game. */
4244 parseList[moveNum - 1][0] = NULLCHAR;
4245 moveList[moveNum - 1][0] = NULLCHAR;
4246 backwardMostMove = moveNum;
4247 startedFromSetupPosition = TRUE;
4248 fromX = fromY = toX = toY = -1;
4250 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4251 // So we parse the long-algebraic move string in stead of the SAN move
4252 int valid; char buf[MSG_SIZ], *prom;
4254 // str looks something like "Q/a1-a2"; kill the slash
4256 sprintf(buf, "%c%s", str[0], str+2);
4257 else strcpy(buf, str); // might be castling
4258 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4259 strcat(buf, prom); // long move lacks promo specification!
4260 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4261 if(appData.debugMode)
4262 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4263 strcpy(move_str, buf);
4265 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4266 &fromX, &fromY, &toX, &toY, &promoChar)
4267 || ParseOneMove(buf, moveNum - 1, &moveType,
4268 &fromX, &fromY, &toX, &toY, &promoChar);
4269 // end of long SAN patch
4271 (void) CoordsToAlgebraic(boards[moveNum - 1],
4272 PosFlags(moveNum - 1),
4273 fromY, fromX, toY, toX, promoChar,
4274 parseList[moveNum-1]);
4275 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4281 if(gameInfo.variant != VariantShogi)
4282 strcat(parseList[moveNum - 1], "+");
4285 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4286 strcat(parseList[moveNum - 1], "#");
4289 strcat(parseList[moveNum - 1], " ");
4290 strcat(parseList[moveNum - 1], elapsed_time);
4291 /* currentMoveString is set as a side-effect of ParseOneMove */
4292 strcpy(moveList[moveNum - 1], currentMoveString);
4293 strcat(moveList[moveNum - 1], "\n");
4295 /* Move from ICS was illegal!? Punt. */
4296 if (appData.debugMode) {
4297 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4298 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4300 strcpy(parseList[moveNum - 1], move_str);
4301 strcat(parseList[moveNum - 1], " ");
4302 strcat(parseList[moveNum - 1], elapsed_time);
4303 moveList[moveNum - 1][0] = NULLCHAR;
4304 fromX = fromY = toX = toY = -1;
4307 if (appData.debugMode) {
4308 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4309 setbuf(debugFP, NULL);
4313 /* Send move to chess program (BEFORE animating it). */
4314 if (appData.zippyPlay && !newGame && newMove &&
4315 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4317 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4318 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4319 if (moveList[moveNum - 1][0] == NULLCHAR) {
4320 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4322 DisplayError(str, 0);
4324 if (first.sendTime) {
4325 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4327 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4328 if (firstMove && !bookHit) {
4330 if (first.useColors) {
4331 SendToProgram(gameMode == IcsPlayingWhite ?
4333 "black\ngo\n", &first);
4335 SendToProgram("go\n", &first);
4337 first.maybeThinking = TRUE;
4340 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4341 if (moveList[moveNum - 1][0] == NULLCHAR) {
4342 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4343 DisplayError(str, 0);
4345 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4346 SendMoveToProgram(moveNum - 1, &first);
4353 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4354 /* If move comes from a remote source, animate it. If it
4355 isn't remote, it will have already been animated. */
4356 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4357 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4359 if (!pausing && appData.highlightLastMove) {
4360 SetHighlights(fromX, fromY, toX, toY);
4364 /* Start the clocks */
4365 whiteFlag = blackFlag = FALSE;
4366 appData.clockMode = !(basetime == 0 && increment == 0);
4368 ics_clock_paused = TRUE;
4370 } else if (ticking == 1) {
4371 ics_clock_paused = FALSE;
4373 if (gameMode == IcsIdle ||
4374 relation == RELATION_OBSERVING_STATIC ||
4375 relation == RELATION_EXAMINING ||
4377 DisplayBothClocks();
4381 /* Display opponents and material strengths */
4382 if (gameInfo.variant != VariantBughouse &&
4383 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4384 if (tinyLayout || smallLayout) {
4385 if(gameInfo.variant == VariantNormal)
4386 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4387 gameInfo.white, white_stren, gameInfo.black, black_stren,
4388 basetime, increment);
4390 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4391 gameInfo.white, white_stren, gameInfo.black, black_stren,
4392 basetime, increment, (int) gameInfo.variant);
4394 if(gameInfo.variant == VariantNormal)
4395 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4396 gameInfo.white, white_stren, gameInfo.black, black_stren,
4397 basetime, increment);
4399 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4400 gameInfo.white, white_stren, gameInfo.black, black_stren,
4401 basetime, increment, VariantName(gameInfo.variant));
4404 if (appData.debugMode) {
4405 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4410 /* Display the board */
4411 if (!pausing && !appData.noGUI) {
4413 if (appData.premove)
4415 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4416 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4417 ClearPremoveHighlights();
4419 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4420 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4421 DrawPosition(j, boards[currentMove]);
4423 DisplayMove(moveNum - 1);
4424 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4425 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4426 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4427 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4431 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4433 if(bookHit) { // [HGM] book: simulate book reply
4434 static char bookMove[MSG_SIZ]; // a bit generous?
4436 programStats.nodes = programStats.depth = programStats.time =
4437 programStats.score = programStats.got_only_move = 0;
4438 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4440 strcpy(bookMove, "move ");
4441 strcat(bookMove, bookHit);
4442 HandleMachineMove(bookMove, &first);
4451 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4452 ics_getting_history = H_REQUESTED;
4453 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4459 AnalysisPeriodicEvent(force)
4462 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4463 && !force) || !appData.periodicUpdates)
4466 /* Send . command to Crafty to collect stats */
4467 SendToProgram(".\n", &first);
4469 /* Don't send another until we get a response (this makes
4470 us stop sending to old Crafty's which don't understand
4471 the "." command (sending illegal cmds resets node count & time,
4472 which looks bad)) */
4473 programStats.ok_to_send = 0;
4476 void ics_update_width(new_width)
4479 ics_printf("set width %d\n", new_width);
4483 SendMoveToProgram(moveNum, cps)
4485 ChessProgramState *cps;
4489 if (cps->useUsermove) {
4490 SendToProgram("usermove ", cps);
4494 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4495 int len = space - parseList[moveNum];
4496 memcpy(buf, parseList[moveNum], len);
4498 buf[len] = NULLCHAR;
4500 sprintf(buf, "%s\n", parseList[moveNum]);
4502 SendToProgram(buf, cps);
4504 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4505 AlphaRank(moveList[moveNum], 4);
4506 SendToProgram(moveList[moveNum], cps);
4507 AlphaRank(moveList[moveNum], 4); // and back
4509 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4510 * the engine. It would be nice to have a better way to identify castle
4512 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4513 && cps->useOOCastle) {
4514 int fromX = moveList[moveNum][0] - AAA;
4515 int fromY = moveList[moveNum][1] - ONE;
4516 int toX = moveList[moveNum][2] - AAA;
4517 int toY = moveList[moveNum][3] - ONE;
4518 if((boards[moveNum][fromY][fromX] == WhiteKing
4519 && boards[moveNum][toY][toX] == WhiteRook)
4520 || (boards[moveNum][fromY][fromX] == BlackKing
4521 && boards[moveNum][toY][toX] == BlackRook)) {
4522 if(toX > fromX) SendToProgram("O-O\n", cps);
4523 else SendToProgram("O-O-O\n", cps);
4525 else SendToProgram(moveList[moveNum], cps);
4527 else SendToProgram(moveList[moveNum], cps);
4528 /* End of additions by Tord */
4531 /* [HGM] setting up the opening has brought engine in force mode! */
4532 /* Send 'go' if we are in a mode where machine should play. */
4533 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4534 (gameMode == TwoMachinesPlay ||
4536 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4538 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4539 SendToProgram("go\n", cps);
4540 if (appData.debugMode) {
4541 fprintf(debugFP, "(extra)\n");
4544 setboardSpoiledMachineBlack = 0;
4548 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4550 int fromX, fromY, toX, toY;
4552 char user_move[MSG_SIZ];
4556 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4557 (int)moveType, fromX, fromY, toX, toY);
4558 DisplayError(user_move + strlen("say "), 0);
4560 case WhiteKingSideCastle:
4561 case BlackKingSideCastle:
4562 case WhiteQueenSideCastleWild:
4563 case BlackQueenSideCastleWild:
4565 case WhiteHSideCastleFR:
4566 case BlackHSideCastleFR:
4568 sprintf(user_move, "o-o\n");
4570 case WhiteQueenSideCastle:
4571 case BlackQueenSideCastle:
4572 case WhiteKingSideCastleWild:
4573 case BlackKingSideCastleWild:
4575 case WhiteASideCastleFR:
4576 case BlackASideCastleFR:
4578 sprintf(user_move, "o-o-o\n");
4580 case WhitePromotionQueen:
4581 case BlackPromotionQueen:
4582 case WhitePromotionRook:
4583 case BlackPromotionRook:
4584 case WhitePromotionBishop:
4585 case BlackPromotionBishop:
4586 case WhitePromotionKnight:
4587 case BlackPromotionKnight:
4588 case WhitePromotionKing:
4589 case BlackPromotionKing:
4590 case WhitePromotionChancellor:
4591 case BlackPromotionChancellor:
4592 case WhitePromotionArchbishop:
4593 case BlackPromotionArchbishop:
4594 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4595 sprintf(user_move, "%c%c%c%c=%c\n",
4596 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4597 PieceToChar(WhiteFerz));
4598 else if(gameInfo.variant == VariantGreat)
4599 sprintf(user_move, "%c%c%c%c=%c\n",
4600 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4601 PieceToChar(WhiteMan));
4603 sprintf(user_move, "%c%c%c%c=%c\n",
4604 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4605 PieceToChar(PromoPiece(moveType)));
4609 sprintf(user_move, "%c@%c%c\n",
4610 ToUpper(PieceToChar((ChessSquare) fromX)),
4611 AAA + toX, ONE + toY);
4614 case WhiteCapturesEnPassant:
4615 case BlackCapturesEnPassant:
4616 case IllegalMove: /* could be a variant we don't quite understand */
4617 sprintf(user_move, "%c%c%c%c\n",
4618 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4621 SendToICS(user_move);
4622 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4623 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4628 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4629 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4630 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4631 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4632 DisplayError("You cannot do this while you are playing or observing", 0);
4635 if(gameMode != IcsExamining) { // is this ever not the case?
4636 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4638 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4639 sprintf(command, "match %s", ics_handle);
4640 } else { // on FICS we must first go to general examine mode
4641 strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4643 if(gameInfo.variant != VariantNormal) {
4644 // try figure out wild number, as xboard names are not always valid on ICS
4645 for(i=1; i<=36; i++) {
4646 sprintf(buf, "wild/%d", i);
4647 if(StringToVariant(buf) == gameInfo.variant) break;
4649 if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4650 else if(i == 22) sprintf(buf, "%s fr\n", command);
4651 else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4652 } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4653 SendToICS(ics_prefix);
4655 if(startedFromSetupPosition || backwardMostMove != 0) {
4656 fen = PositionToFEN(backwardMostMove, NULL);
4657 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4658 sprintf(buf, "loadfen %s\n", fen);
4660 } else { // FICS: everything has to set by separate bsetup commands
4661 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4662 sprintf(buf, "bsetup fen %s\n", fen);
4664 if(!WhiteOnMove(backwardMostMove)) {
4665 SendToICS("bsetup tomove black\n");
4667 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4668 sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4670 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4671 sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4673 i = boards[backwardMostMove][EP_STATUS];
4674 if(i >= 0) { // set e.p.
4675 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4681 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4682 SendToICS("bsetup done\n"); // switch to normal examining.
4684 for(i = backwardMostMove; i<last; i++) {
4686 sprintf(buf, "%s\n", parseList[i]);
4689 SendToICS(ics_prefix);
4690 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4694 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4699 if (rf == DROP_RANK) {
4700 sprintf(move, "%c@%c%c\n",
4701 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4703 if (promoChar == 'x' || promoChar == NULLCHAR) {
4704 sprintf(move, "%c%c%c%c\n",
4705 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4707 sprintf(move, "%c%c%c%c%c\n",
4708 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4714 ProcessICSInitScript(f)
4719 while (fgets(buf, MSG_SIZ, f)) {
4720 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4727 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4729 AlphaRank(char *move, int n)
4731 // char *p = move, c; int x, y;
4733 if (appData.debugMode) {
4734 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4738 move[2]>='0' && move[2]<='9' &&
4739 move[3]>='a' && move[3]<='x' ) {
4741 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4742 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4744 if(move[0]>='0' && move[0]<='9' &&
4745 move[1]>='a' && move[1]<='x' &&
4746 move[2]>='0' && move[2]<='9' &&
4747 move[3]>='a' && move[3]<='x' ) {
4748 /* input move, Shogi -> normal */
4749 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4750 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4751 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4752 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4755 move[3]>='0' && move[3]<='9' &&
4756 move[2]>='a' && move[2]<='x' ) {
4758 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4759 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4762 move[0]>='a' && move[0]<='x' &&
4763 move[3]>='0' && move[3]<='9' &&
4764 move[2]>='a' && move[2]<='x' ) {
4765 /* output move, normal -> Shogi */
4766 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4767 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4768 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4769 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4770 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4772 if (appData.debugMode) {
4773 fprintf(debugFP, " out = '%s'\n", move);
4777 char yy_textstr[8000];
4779 /* Parser for moves from gnuchess, ICS, or user typein box */
4781 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4784 ChessMove *moveType;
4785 int *fromX, *fromY, *toX, *toY;
4788 if (appData.debugMode) {
4789 fprintf(debugFP, "move to parse: %s\n", move);
4791 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4793 switch (*moveType) {
4794 case WhitePromotionChancellor:
4795 case BlackPromotionChancellor:
4796 case WhitePromotionArchbishop:
4797 case BlackPromotionArchbishop:
4798 case WhitePromotionQueen:
4799 case BlackPromotionQueen:
4800 case WhitePromotionRook:
4801 case BlackPromotionRook:
4802 case WhitePromotionBishop:
4803 case BlackPromotionBishop:
4804 case WhitePromotionKnight:
4805 case BlackPromotionKnight:
4806 case WhitePromotionKing:
4807 case BlackPromotionKing:
4809 case WhiteCapturesEnPassant:
4810 case BlackCapturesEnPassant:
4811 case WhiteKingSideCastle:
4812 case WhiteQueenSideCastle:
4813 case BlackKingSideCastle:
4814 case BlackQueenSideCastle:
4815 case WhiteKingSideCastleWild:
4816 case WhiteQueenSideCastleWild:
4817 case BlackKingSideCastleWild:
4818 case BlackQueenSideCastleWild:
4819 /* Code added by Tord: */
4820 case WhiteHSideCastleFR:
4821 case WhiteASideCastleFR:
4822 case BlackHSideCastleFR:
4823 case BlackASideCastleFR:
4824 /* End of code added by Tord */
4825 case IllegalMove: /* bug or odd chess variant */
4826 *fromX = currentMoveString[0] - AAA;
4827 *fromY = currentMoveString[1] - ONE;
4828 *toX = currentMoveString[2] - AAA;
4829 *toY = currentMoveString[3] - ONE;
4830 *promoChar = currentMoveString[4];
4831 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4832 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4833 if (appData.debugMode) {
4834 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4836 *fromX = *fromY = *toX = *toY = 0;
4839 if (appData.testLegality) {
4840 return (*moveType != IllegalMove);
4842 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4843 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4848 *fromX = *moveType == WhiteDrop ?
4849 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4850 (int) CharToPiece(ToLower(currentMoveString[0]));
4852 *toX = currentMoveString[2] - AAA;
4853 *toY = currentMoveString[3] - ONE;
4854 *promoChar = NULLCHAR;
4858 case ImpossibleMove:
4859 case (ChessMove) 0: /* end of file */
4868 if (appData.debugMode) {
4869 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4872 *fromX = *fromY = *toX = *toY = 0;
4873 *promoChar = NULLCHAR;
4880 ParsePV(char *pv, Boolean storeComments)
4881 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4882 int fromX, fromY, toX, toY; char promoChar;
4887 endPV = forwardMostMove;
4889 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4890 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4891 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4892 if(appData.debugMode){
4893 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4895 if(!valid && nr == 0 &&
4896 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4897 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4898 // Hande case where played move is different from leading PV move
4899 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4900 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4901 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4902 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4903 endPV += 2; // if position different, keep this
4904 moveList[endPV-1][0] = fromX + AAA;
4905 moveList[endPV-1][1] = fromY + ONE;
4906 moveList[endPV-1][2] = toX + AAA;
4907 moveList[endPV-1][3] = toY + ONE;
4908 parseList[endPV-1][0] = NULLCHAR;
4909 strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4912 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4913 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4914 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4915 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4916 valid++; // allow comments in PV
4920 if(endPV+1 > framePtr) break; // no space, truncate
4923 CopyBoard(boards[endPV], boards[endPV-1]);
4924 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4925 moveList[endPV-1][0] = fromX + AAA;
4926 moveList[endPV-1][1] = fromY + ONE;
4927 moveList[endPV-1][2] = toX + AAA;
4928 moveList[endPV-1][3] = toY + ONE;
4930 CoordsToAlgebraic(boards[endPV - 1],
4931 PosFlags(endPV - 1),
4932 fromY, fromX, toY, toX, promoChar,
4933 parseList[endPV - 1]);
4935 parseList[endPV-1][0] = NULLCHAR;
4937 currentMove = endPV;
4938 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4939 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4940 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4941 DrawPosition(TRUE, boards[currentMove]);
4944 static int lastX, lastY;
4947 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4952 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4953 lastX = x; lastY = y;
4954 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4956 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4957 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4959 do{ while(buf[index] && buf[index] != '\n') index++;
4960 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4962 ParsePV(buf+startPV, FALSE);
4963 *start = startPV; *end = index-1;
4968 LoadPV(int x, int y)
4969 { // called on right mouse click to load PV
4970 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4971 lastX = x; lastY = y;
4972 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4979 if(endPV < 0) return;
4981 currentMove = forwardMostMove;
4982 ClearPremoveHighlights();
4983 DrawPosition(TRUE, boards[currentMove]);
4987 MovePV(int x, int y, int h)
4988 { // step through PV based on mouse coordinates (called on mouse move)
4989 int margin = h>>3, step = 0;
4991 if(endPV < 0) return;
4992 // we must somehow check if right button is still down (might be released off board!)
4993 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4994 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4995 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4997 lastX = x; lastY = y;
4998 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4999 currentMove += step;
5000 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5001 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5002 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5003 DrawPosition(FALSE, boards[currentMove]);
5007 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5008 // All positions will have equal probability, but the current method will not provide a unique
5009 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5015 int piecesLeft[(int)BlackPawn];
5016 int seed, nrOfShuffles;
5018 void GetPositionNumber()
5019 { // sets global variable seed
5022 seed = appData.defaultFrcPosition;
5023 if(seed < 0) { // randomize based on time for negative FRC position numbers
5024 for(i=0; i<50; i++) seed += random();
5025 seed = random() ^ random() >> 8 ^ random() << 8;
5026 if(seed<0) seed = -seed;
5030 int put(Board board, int pieceType, int rank, int n, int shade)
5031 // put the piece on the (n-1)-th empty squares of the given shade
5035 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5036 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5037 board[rank][i] = (ChessSquare) pieceType;
5038 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5040 piecesLeft[pieceType]--;
5048 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5049 // calculate where the next piece goes, (any empty square), and put it there
5053 i = seed % squaresLeft[shade];
5054 nrOfShuffles *= squaresLeft[shade];
5055 seed /= squaresLeft[shade];
5056 put(board, pieceType, rank, i, shade);
5059 void AddTwoPieces(Board board, int pieceType, int rank)
5060 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5062 int i, n=squaresLeft[ANY], j=n-1, k;
5064 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5065 i = seed % k; // pick one
5068 while(i >= j) i -= j--;
5069 j = n - 1 - j; i += j;
5070 put(board, pieceType, rank, j, ANY);
5071 put(board, pieceType, rank, i, ANY);
5074 void SetUpShuffle(Board board, int number)
5078 GetPositionNumber(); nrOfShuffles = 1;
5080 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5081 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5082 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5084 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5086 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5087 p = (int) board[0][i];
5088 if(p < (int) BlackPawn) piecesLeft[p] ++;
5089 board[0][i] = EmptySquare;
5092 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5093 // shuffles restricted to allow normal castling put KRR first
5094 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5095 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5096 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5097 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5098 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5099 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5100 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5101 put(board, WhiteRook, 0, 0, ANY);
5102 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5105 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5106 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5107 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5108 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5109 while(piecesLeft[p] >= 2) {
5110 AddOnePiece(board, p, 0, LITE);
5111 AddOnePiece(board, p, 0, DARK);
5113 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5116 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5117 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5118 // but we leave King and Rooks for last, to possibly obey FRC restriction
5119 if(p == (int)WhiteRook) continue;
5120 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5121 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5124 // now everything is placed, except perhaps King (Unicorn) and Rooks
5126 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5127 // Last King gets castling rights
5128 while(piecesLeft[(int)WhiteUnicorn]) {
5129 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5130 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5133 while(piecesLeft[(int)WhiteKing]) {
5134 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5135 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5140 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5141 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5144 // Only Rooks can be left; simply place them all
5145 while(piecesLeft[(int)WhiteRook]) {
5146 i = put(board, WhiteRook, 0, 0, ANY);
5147 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5150 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5152 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5155 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5156 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5159 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5162 int SetCharTable( char *table, const char * map )
5163 /* [HGM] moved here from winboard.c because of its general usefulness */
5164 /* Basically a safe strcpy that uses the last character as King */
5166 int result = FALSE; int NrPieces;
5168 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5169 && NrPieces >= 12 && !(NrPieces&1)) {
5170 int i; /* [HGM] Accept even length from 12 to 34 */
5172 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5173 for( i=0; i<NrPieces/2-1; i++ ) {
5175 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5177 table[(int) WhiteKing] = map[NrPieces/2-1];
5178 table[(int) BlackKing] = map[NrPieces-1];
5186 void Prelude(Board board)
5187 { // [HGM] superchess: random selection of exo-pieces
5188 int i, j, k; ChessSquare p;
5189 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5191 GetPositionNumber(); // use FRC position number
5193 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5194 SetCharTable(pieceToChar, appData.pieceToCharTable);
5195 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5196 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5199 j = seed%4; seed /= 4;
5200 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5201 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5202 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5203 j = seed%3 + (seed%3 >= j); seed /= 3;
5204 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5205 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5206 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5207 j = seed%3; seed /= 3;
5208 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5209 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5210 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5211 j = seed%2 + (seed%2 >= j); seed /= 2;
5212 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5213 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5214 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5215 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5216 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5217 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5218 put(board, exoPieces[0], 0, 0, ANY);
5219 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5223 InitPosition(redraw)
5226 ChessSquare (* pieces)[BOARD_FILES];
5227 int i, j, pawnRow, overrule,
5228 oldx = gameInfo.boardWidth,
5229 oldy = gameInfo.boardHeight,
5230 oldh = gameInfo.holdingsWidth,
5231 oldv = gameInfo.variant;
5233 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5235 /* [AS] Initialize pv info list [HGM] and game status */
5237 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5238 pvInfoList[i].depth = 0;
5239 boards[i][EP_STATUS] = EP_NONE;
5240 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5243 initialRulePlies = 0; /* 50-move counter start */
5245 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5246 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5250 /* [HGM] logic here is completely changed. In stead of full positions */
5251 /* the initialized data only consist of the two backranks. The switch */
5252 /* selects which one we will use, which is than copied to the Board */
5253 /* initialPosition, which for the rest is initialized by Pawns and */
5254 /* empty squares. This initial position is then copied to boards[0], */
5255 /* possibly after shuffling, so that it remains available. */
5257 gameInfo.holdingsWidth = 0; /* default board sizes */
5258 gameInfo.boardWidth = 8;
5259 gameInfo.boardHeight = 8;
5260 gameInfo.holdingsSize = 0;
5261 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5262 for(i=0; i<BOARD_FILES-2; i++)
5263 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5264 initialPosition[EP_STATUS] = EP_NONE;
5265 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5266 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5267 SetCharTable(pieceNickName, appData.pieceNickNames);
5268 else SetCharTable(pieceNickName, "............");
5270 switch (gameInfo.variant) {
5271 case VariantFischeRandom:
5272 shuffleOpenings = TRUE;
5276 case VariantShatranj:
5277 pieces = ShatranjArray;
5278 nrCastlingRights = 0;
5279 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5282 pieces = makrukArray;
5283 nrCastlingRights = 0;
5284 startedFromSetupPosition = TRUE;
5285 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5287 case VariantTwoKings:
5288 pieces = twoKingsArray;
5290 case VariantCapaRandom:
5291 shuffleOpenings = TRUE;
5292 case VariantCapablanca:
5293 pieces = CapablancaArray;
5294 gameInfo.boardWidth = 10;
5295 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5298 pieces = GothicArray;
5299 gameInfo.boardWidth = 10;
5300 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5303 pieces = JanusArray;
5304 gameInfo.boardWidth = 10;
5305 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5306 nrCastlingRights = 6;
5307 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5308 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5309 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5310 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5311 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5312 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5315 pieces = FalconArray;
5316 gameInfo.boardWidth = 10;
5317 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5319 case VariantXiangqi:
5320 pieces = XiangqiArray;
5321 gameInfo.boardWidth = 9;
5322 gameInfo.boardHeight = 10;
5323 nrCastlingRights = 0;
5324 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5327 pieces = ShogiArray;
5328 gameInfo.boardWidth = 9;
5329 gameInfo.boardHeight = 9;
5330 gameInfo.holdingsSize = 7;
5331 nrCastlingRights = 0;
5332 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5334 case VariantCourier:
5335 pieces = CourierArray;
5336 gameInfo.boardWidth = 12;
5337 nrCastlingRights = 0;
5338 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5340 case VariantKnightmate:
5341 pieces = KnightmateArray;
5342 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5345 pieces = fairyArray;
5346 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5349 pieces = GreatArray;
5350 gameInfo.boardWidth = 10;
5351 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5352 gameInfo.holdingsSize = 8;
5356 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5357 gameInfo.holdingsSize = 8;
5358 startedFromSetupPosition = TRUE;
5360 case VariantCrazyhouse:
5361 case VariantBughouse:
5363 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5364 gameInfo.holdingsSize = 5;
5366 case VariantWildCastle:
5368 /* !!?shuffle with kings guaranteed to be on d or e file */
5369 shuffleOpenings = 1;
5371 case VariantNoCastle:
5373 nrCastlingRights = 0;
5374 /* !!?unconstrained back-rank shuffle */
5375 shuffleOpenings = 1;
5380 if(appData.NrFiles >= 0) {
5381 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5382 gameInfo.boardWidth = appData.NrFiles;
5384 if(appData.NrRanks >= 0) {
5385 gameInfo.boardHeight = appData.NrRanks;
5387 if(appData.holdingsSize >= 0) {
5388 i = appData.holdingsSize;
5389 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5390 gameInfo.holdingsSize = i;
5392 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5393 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5394 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5396 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5397 if(pawnRow < 1) pawnRow = 1;
5398 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5400 /* User pieceToChar list overrules defaults */
5401 if(appData.pieceToCharTable != NULL)
5402 SetCharTable(pieceToChar, appData.pieceToCharTable);
5404 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5406 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5407 s = (ChessSquare) 0; /* account holding counts in guard band */
5408 for( i=0; i<BOARD_HEIGHT; i++ )
5409 initialPosition[i][j] = s;
5411 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5412 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5413 initialPosition[pawnRow][j] = WhitePawn;
5414 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5415 if(gameInfo.variant == VariantXiangqi) {
5417 initialPosition[pawnRow][j] =
5418 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5419 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5420 initialPosition[2][j] = WhiteCannon;
5421 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5425 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5427 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5430 initialPosition[1][j] = WhiteBishop;
5431 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5433 initialPosition[1][j] = WhiteRook;
5434 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5437 if( nrCastlingRights == -1) {
5438 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5439 /* This sets default castling rights from none to normal corners */
5440 /* Variants with other castling rights must set them themselves above */
5441 nrCastlingRights = 6;
5443 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5444 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5445 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5446 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5447 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5448 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5451 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5452 if(gameInfo.variant == VariantGreat) { // promotion commoners
5453 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5454 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5455 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5456 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5458 if (appData.debugMode) {
5459 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5461 if(shuffleOpenings) {
5462 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5463 startedFromSetupPosition = TRUE;
5465 if(startedFromPositionFile) {
5466 /* [HGM] loadPos: use PositionFile for every new game */
5467 CopyBoard(initialPosition, filePosition);
5468 for(i=0; i<nrCastlingRights; i++)
5469 initialRights[i] = filePosition[CASTLING][i];
5470 startedFromSetupPosition = TRUE;
5473 CopyBoard(boards[0], initialPosition);
5475 if(oldx != gameInfo.boardWidth ||
5476 oldy != gameInfo.boardHeight ||
5477 oldh != gameInfo.holdingsWidth
5479 || oldv == VariantGothic || // For licensing popups
5480 gameInfo.variant == VariantGothic
5483 || oldv == VariantFalcon ||
5484 gameInfo.variant == VariantFalcon
5487 InitDrawingSizes(-2 ,0);
5490 DrawPosition(TRUE, boards[currentMove]);
5494 SendBoard(cps, moveNum)
5495 ChessProgramState *cps;
5498 char message[MSG_SIZ];
5500 if (cps->useSetboard) {
5501 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5502 sprintf(message, "setboard %s\n", fen);
5503 SendToProgram(message, cps);
5509 /* Kludge to set black to move, avoiding the troublesome and now
5510 * deprecated "black" command.
5512 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5514 SendToProgram("edit\n", cps);
5515 SendToProgram("#\n", cps);
5516 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5517 bp = &boards[moveNum][i][BOARD_LEFT];
5518 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5519 if ((int) *bp < (int) BlackPawn) {
5520 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5522 if(message[0] == '+' || message[0] == '~') {
5523 sprintf(message, "%c%c%c+\n",
5524 PieceToChar((ChessSquare)(DEMOTED *bp)),
5527 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5528 message[1] = BOARD_RGHT - 1 - j + '1';
5529 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5531 SendToProgram(message, cps);
5536 SendToProgram("c\n", cps);
5537 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5538 bp = &boards[moveNum][i][BOARD_LEFT];
5539 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5540 if (((int) *bp != (int) EmptySquare)
5541 && ((int) *bp >= (int) BlackPawn)) {
5542 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5544 if(message[0] == '+' || message[0] == '~') {
5545 sprintf(message, "%c%c%c+\n",
5546 PieceToChar((ChessSquare)(DEMOTED *bp)),
5549 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5550 message[1] = BOARD_RGHT - 1 - j + '1';
5551 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5553 SendToProgram(message, cps);
5558 SendToProgram(".\n", cps);
5560 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5563 static int autoQueen; // [HGM] oneclick
5566 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5568 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5569 /* [HGM] add Shogi promotions */
5570 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5575 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5576 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5578 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5579 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5582 piece = boards[currentMove][fromY][fromX];
5583 if(gameInfo.variant == VariantShogi) {
5584 promotionZoneSize = 3;
5585 highestPromotingPiece = (int)WhiteFerz;
5586 } else if(gameInfo.variant == VariantMakruk) {
5587 promotionZoneSize = 3;
5590 // next weed out all moves that do not touch the promotion zone at all
5591 if((int)piece >= BlackPawn) {
5592 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5594 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5596 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5597 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5600 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5602 // weed out mandatory Shogi promotions
5603 if(gameInfo.variant == VariantShogi) {
5604 if(piece >= BlackPawn) {
5605 if(toY == 0 && piece == BlackPawn ||
5606 toY == 0 && piece == BlackQueen ||
5607 toY <= 1 && piece == BlackKnight) {
5612 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5613 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5614 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5621 // weed out obviously illegal Pawn moves
5622 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5623 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5624 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5625 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5626 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5627 // note we are not allowed to test for valid (non-)capture, due to premove
5630 // we either have a choice what to promote to, or (in Shogi) whether to promote
5631 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5632 *promoChoice = PieceToChar(BlackFerz); // no choice
5635 if(autoQueen) { // predetermined
5636 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5637 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5638 else *promoChoice = PieceToChar(BlackQueen);
5642 // suppress promotion popup on illegal moves that are not premoves
5643 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5644 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5645 if(appData.testLegality && !premove) {
5646 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5647 fromY, fromX, toY, toX, NULLCHAR);
5648 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5649 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5657 InPalace(row, column)
5659 { /* [HGM] for Xiangqi */
5660 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5661 column < (BOARD_WIDTH + 4)/2 &&
5662 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5667 PieceForSquare (x, y)
5671 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5674 return boards[currentMove][y][x];
5678 OKToStartUserMove(x, y)
5681 ChessSquare from_piece;
5684 if (matchMode) return FALSE;
5685 if (gameMode == EditPosition) return TRUE;
5687 if (x >= 0 && y >= 0)
5688 from_piece = boards[currentMove][y][x];
5690 from_piece = EmptySquare;
5692 if (from_piece == EmptySquare) return FALSE;
5694 white_piece = (int)from_piece >= (int)WhitePawn &&
5695 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5698 case PlayFromGameFile:
5700 case TwoMachinesPlay:
5708 case MachinePlaysWhite:
5709 case IcsPlayingBlack:
5710 if (appData.zippyPlay) return FALSE;
5712 DisplayMoveError(_("You are playing Black"));
5717 case MachinePlaysBlack:
5718 case IcsPlayingWhite:
5719 if (appData.zippyPlay) return FALSE;
5721 DisplayMoveError(_("You are playing White"));
5727 if (!white_piece && WhiteOnMove(currentMove)) {
5728 DisplayMoveError(_("It is White's turn"));
5731 if (white_piece && !WhiteOnMove(currentMove)) {
5732 DisplayMoveError(_("It is Black's turn"));
5735 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5736 /* Editing correspondence game history */
5737 /* Could disallow this or prompt for confirmation */
5742 case BeginningOfGame:
5743 if (appData.icsActive) return FALSE;
5744 if (!appData.noChessProgram) {
5746 DisplayMoveError(_("You are playing White"));
5753 if (!white_piece && WhiteOnMove(currentMove)) {
5754 DisplayMoveError(_("It is White's turn"));
5757 if (white_piece && !WhiteOnMove(currentMove)) {
5758 DisplayMoveError(_("It is Black's turn"));
5767 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5768 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5769 && gameMode != AnalyzeFile && gameMode != Training) {
5770 DisplayMoveError(_("Displayed position is not current"));
5777 OnlyMove(int *x, int *y, Boolean captures) {
5778 DisambiguateClosure cl;
5779 if (appData.zippyPlay) return FALSE;
5781 case MachinePlaysBlack:
5782 case IcsPlayingWhite:
5783 case BeginningOfGame:
5784 if(!WhiteOnMove(currentMove)) return FALSE;
5786 case MachinePlaysWhite:
5787 case IcsPlayingBlack:
5788 if(WhiteOnMove(currentMove)) return FALSE;
5793 cl.pieceIn = EmptySquare;
5798 cl.promoCharIn = NULLCHAR;
5799 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5800 if( cl.kind == NormalMove ||
5801 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5802 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5803 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5804 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5811 if(cl.kind != ImpossibleMove) return FALSE;
5812 cl.pieceIn = EmptySquare;
5817 cl.promoCharIn = NULLCHAR;
5818 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5819 if( cl.kind == NormalMove ||
5820 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5821 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5822 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5823 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5828 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5834 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5835 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5836 int lastLoadGameUseList = FALSE;
5837 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5838 ChessMove lastLoadGameStart = (ChessMove) 0;
5841 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5842 int fromX, fromY, toX, toY;
5847 ChessSquare pdown, pup;
5849 /* Check if the user is playing in turn. This is complicated because we
5850 let the user "pick up" a piece before it is his turn. So the piece he
5851 tried to pick up may have been captured by the time he puts it down!
5852 Therefore we use the color the user is supposed to be playing in this
5853 test, not the color of the piece that is currently on the starting
5854 square---except in EditGame mode, where the user is playing both
5855 sides; fortunately there the capture race can't happen. (It can
5856 now happen in IcsExamining mode, but that's just too bad. The user
5857 will get a somewhat confusing message in that case.)
5861 case PlayFromGameFile:
5863 case TwoMachinesPlay:
5867 /* We switched into a game mode where moves are not accepted,
5868 perhaps while the mouse button was down. */
5869 return ImpossibleMove;
5871 case MachinePlaysWhite:
5872 /* User is moving for Black */
5873 if (WhiteOnMove(currentMove)) {
5874 DisplayMoveError(_("It is White's turn"));
5875 return ImpossibleMove;
5879 case MachinePlaysBlack:
5880 /* User is moving for White */
5881 if (!WhiteOnMove(currentMove)) {
5882 DisplayMoveError(_("It is Black's turn"));
5883 return ImpossibleMove;
5889 case BeginningOfGame:
5892 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5893 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5894 /* User is moving for Black */
5895 if (WhiteOnMove(currentMove)) {
5896 DisplayMoveError(_("It is White's turn"));
5897 return ImpossibleMove;
5900 /* User is moving for White */
5901 if (!WhiteOnMove(currentMove)) {
5902 DisplayMoveError(_("It is Black's turn"));
5903 return ImpossibleMove;
5908 case IcsPlayingBlack:
5909 /* User is moving for Black */
5910 if (WhiteOnMove(currentMove)) {
5911 if (!appData.premove) {
5912 DisplayMoveError(_("It is White's turn"));
5913 } else if (toX >= 0 && toY >= 0) {
5916 premoveFromX = fromX;
5917 premoveFromY = fromY;
5918 premovePromoChar = promoChar;
5920 if (appData.debugMode)
5921 fprintf(debugFP, "Got premove: fromX %d,"
5922 "fromY %d, toX %d, toY %d\n",
5923 fromX, fromY, toX, toY);
5925 return ImpossibleMove;
5929 case IcsPlayingWhite:
5930 /* User is moving for White */
5931 if (!WhiteOnMove(currentMove)) {
5932 if (!appData.premove) {
5933 DisplayMoveError(_("It is Black's turn"));
5934 } else if (toX >= 0 && toY >= 0) {
5937 premoveFromX = fromX;
5938 premoveFromY = fromY;
5939 premovePromoChar = promoChar;
5941 if (appData.debugMode)
5942 fprintf(debugFP, "Got premove: fromX %d,"
5943 "fromY %d, toX %d, toY %d\n",
5944 fromX, fromY, toX, toY);
5946 return ImpossibleMove;
5954 /* EditPosition, empty square, or different color piece;
5955 click-click move is possible */
5956 if (toX == -2 || toY == -2) {
5957 boards[0][fromY][fromX] = EmptySquare;
5958 return AmbiguousMove;
5959 } else if (toX >= 0 && toY >= 0) {
5960 boards[0][toY][toX] = boards[0][fromY][fromX];
5961 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5962 if(boards[0][fromY][0] != EmptySquare) {
5963 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5964 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5967 if(fromX == BOARD_RGHT+1) {
5968 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5969 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5970 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5973 boards[0][fromY][fromX] = EmptySquare;
5974 return AmbiguousMove;
5976 return ImpossibleMove;
5979 if(toX < 0 || toY < 0) return ImpossibleMove;
5980 pdown = boards[currentMove][fromY][fromX];
5981 pup = boards[currentMove][toY][toX];
5983 /* [HGM] If move started in holdings, it means a drop */
5984 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5985 if( pup != EmptySquare ) return ImpossibleMove;
5986 if(appData.testLegality) {
5987 /* it would be more logical if LegalityTest() also figured out
5988 * which drops are legal. For now we forbid pawns on back rank.
5989 * Shogi is on its own here...
5991 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5992 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5993 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5995 return WhiteDrop; /* Not needed to specify white or black yet */
5998 /* [HGM] always test for legality, to get promotion info */
5999 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6000 fromY, fromX, toY, toX, promoChar);
6001 /* [HGM] but possibly ignore an IllegalMove result */
6002 if (appData.testLegality) {
6003 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6004 DisplayMoveError(_("Illegal move"));
6005 return ImpossibleMove;
6010 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6011 function is made into one that returns an OK move type if FinishMove
6012 should be called. This to give the calling driver routine the
6013 opportunity to finish the userMove input with a promotion popup,
6014 without bothering the user with this for invalid or illegal moves */
6016 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6019 /* Common tail of UserMoveEvent and DropMenuEvent */
6021 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6023 int fromX, fromY, toX, toY;
6024 /*char*/int promoChar;
6028 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6029 // [HGM] superchess: suppress promotions to non-available piece
6030 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6031 if(WhiteOnMove(currentMove)) {
6032 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6034 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6038 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6039 move type in caller when we know the move is a legal promotion */
6040 if(moveType == NormalMove && promoChar)
6041 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6043 /* [HGM] convert drag-and-drop piece drops to standard form */
6044 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6045 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6046 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6047 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6048 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6049 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6050 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6051 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6055 /* [HGM] <popupFix> The following if has been moved here from
6056 UserMoveEvent(). Because it seemed to belong here (why not allow
6057 piece drops in training games?), and because it can only be
6058 performed after it is known to what we promote. */
6059 if (gameMode == Training) {
6060 /* compare the move played on the board to the next move in the
6061 * game. If they match, display the move and the opponent's response.
6062 * If they don't match, display an error message.
6066 CopyBoard(testBoard, boards[currentMove]);
6067 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6069 if (CompareBoards(testBoard, boards[currentMove+1])) {
6070 ForwardInner(currentMove+1);
6072 /* Autoplay the opponent's response.
6073 * if appData.animate was TRUE when Training mode was entered,
6074 * the response will be animated.
6076 saveAnimate = appData.animate;
6077 appData.animate = animateTraining;
6078 ForwardInner(currentMove+1);
6079 appData.animate = saveAnimate;
6081 /* check for the end of the game */
6082 if (currentMove >= forwardMostMove) {
6083 gameMode = PlayFromGameFile;
6085 SetTrainingModeOff();
6086 DisplayInformation(_("End of game"));
6089 DisplayError(_("Incorrect move"), 0);
6094 /* Ok, now we know that the move is good, so we can kill
6095 the previous line in Analysis Mode */
6096 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6097 && currentMove < forwardMostMove) {
6098 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6101 /* If we need the chess program but it's dead, restart it */
6102 ResurrectChessProgram();
6104 /* A user move restarts a paused game*/
6108 thinkOutput[0] = NULLCHAR;
6110 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6112 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6114 if (gameMode == BeginningOfGame) {
6115 if (appData.noChessProgram) {
6116 gameMode = EditGame;
6120 gameMode = MachinePlaysBlack;
6123 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6125 if (first.sendName) {
6126 sprintf(buf, "name %s\n", gameInfo.white);
6127 SendToProgram(buf, &first);
6134 /* Relay move to ICS or chess engine */
6135 if (appData.icsActive) {
6136 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6137 gameMode == IcsExamining) {
6138 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6139 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6141 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6143 // also send plain move, in case ICS does not understand atomic claims
6144 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6148 if (first.sendTime && (gameMode == BeginningOfGame ||
6149 gameMode == MachinePlaysWhite ||
6150 gameMode == MachinePlaysBlack)) {
6151 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6153 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6154 // [HGM] book: if program might be playing, let it use book
6155 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6156 first.maybeThinking = TRUE;
6157 } else SendMoveToProgram(forwardMostMove-1, &first);
6158 if (currentMove == cmailOldMove + 1) {
6159 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6163 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6167 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6173 if (WhiteOnMove(currentMove)) {
6174 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6176 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6180 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6185 case MachinePlaysBlack:
6186 case MachinePlaysWhite:
6187 /* disable certain menu options while machine is thinking */
6188 SetMachineThinkingEnables();
6195 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6197 if(bookHit) { // [HGM] book: simulate book reply
6198 static char bookMove[MSG_SIZ]; // a bit generous?
6200 programStats.nodes = programStats.depth = programStats.time =
6201 programStats.score = programStats.got_only_move = 0;
6202 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6204 strcpy(bookMove, "move ");
6205 strcat(bookMove, bookHit);
6206 HandleMachineMove(bookMove, &first);
6212 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6213 int fromX, fromY, toX, toY;
6216 /* [HGM] This routine was added to allow calling of its two logical
6217 parts from other modules in the old way. Before, UserMoveEvent()
6218 automatically called FinishMove() if the move was OK, and returned
6219 otherwise. I separated the two, in order to make it possible to
6220 slip a promotion popup in between. But that it always needs two
6221 calls, to the first part, (now called UserMoveTest() ), and to
6222 FinishMove if the first part succeeded. Calls that do not need
6223 to do anything in between, can call this routine the old way.
6225 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6226 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6227 if(moveType == AmbiguousMove)
6228 DrawPosition(FALSE, boards[currentMove]);
6229 else if(moveType != ImpossibleMove && moveType != Comment)
6230 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6234 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6241 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6242 Markers *m = (Markers *) closure;
6243 if(rf == fromY && ff == fromX)
6244 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6245 || kind == WhiteCapturesEnPassant
6246 || kind == BlackCapturesEnPassant);
6247 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6251 MarkTargetSquares(int clear)
6254 if(!appData.markers || !appData.highlightDragging ||
6255 !appData.testLegality || gameMode == EditPosition) return;
6257 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6260 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6261 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6262 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6264 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6267 DrawPosition(TRUE, NULL);
6270 void LeftClick(ClickType clickType, int xPix, int yPix)
6273 Boolean saveAnimate;
6274 static int second = 0, promotionChoice = 0, dragging = 0;
6275 char promoChoice = NULLCHAR;
6277 if(appData.seekGraph && appData.icsActive && loggedOn &&
6278 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6279 SeekGraphClick(clickType, xPix, yPix, 0);
6283 if (clickType == Press) ErrorPopDown();
6284 MarkTargetSquares(1);
6286 x = EventToSquare(xPix, BOARD_WIDTH);
6287 y = EventToSquare(yPix, BOARD_HEIGHT);
6288 if (!flipView && y >= 0) {
6289 y = BOARD_HEIGHT - 1 - y;
6291 if (flipView && x >= 0) {
6292 x = BOARD_WIDTH - 1 - x;
6295 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6296 if(clickType == Release) return; // ignore upclick of click-click destination
6297 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6298 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6299 if(gameInfo.holdingsWidth &&
6300 (WhiteOnMove(currentMove)
6301 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6302 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6303 // click in right holdings, for determining promotion piece
6304 ChessSquare p = boards[currentMove][y][x];
6305 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6306 if(p != EmptySquare) {
6307 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6312 DrawPosition(FALSE, boards[currentMove]);
6316 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6317 if(clickType == Press
6318 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6319 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6320 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6323 autoQueen = appData.alwaysPromoteToQueen;
6326 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6327 if (clickType == Press) {
6329 if (OKToStartUserMove(x, y)) {
6333 MarkTargetSquares(0);
6334 DragPieceBegin(xPix, yPix); dragging = 1;
6335 if (appData.highlightDragging) {
6336 SetHighlights(x, y, -1, -1);
6339 } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6340 DragPieceEnd(xPix, yPix); dragging = 0;
6341 DrawPosition(FALSE, NULL);
6348 if (clickType == Press && gameMode != EditPosition) {
6353 // ignore off-board to clicks
6354 if(y < 0 || x < 0) return;
6356 /* Check if clicking again on the same color piece */
6357 fromP = boards[currentMove][fromY][fromX];
6358 toP = boards[currentMove][y][x];
6359 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6360 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6361 WhitePawn <= toP && toP <= WhiteKing &&
6362 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6363 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6364 (BlackPawn <= fromP && fromP <= BlackKing &&
6365 BlackPawn <= toP && toP <= BlackKing &&
6366 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6367 !(fromP == BlackKing && toP == BlackRook && frc))) {
6368 /* Clicked again on same color piece -- changed his mind */
6369 second = (x == fromX && y == fromY);
6370 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6371 if (appData.highlightDragging) {
6372 SetHighlights(x, y, -1, -1);
6376 if (OKToStartUserMove(x, y)) {
6378 fromY = y; dragging = 1;
6379 MarkTargetSquares(0);
6380 DragPieceBegin(xPix, yPix);
6385 // ignore clicks on holdings
6386 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6389 if (clickType == Release && x == fromX && y == fromY) {
6390 DragPieceEnd(xPix, yPix); dragging = 0;
6391 if (appData.animateDragging) {
6392 /* Undo animation damage if any */
6393 DrawPosition(FALSE, NULL);
6396 /* Second up/down in same square; just abort move */
6401 ClearPremoveHighlights();
6403 /* First upclick in same square; start click-click mode */
6404 SetHighlights(x, y, -1, -1);
6409 /* we now have a different from- and (possibly off-board) to-square */
6410 /* Completed move */
6413 saveAnimate = appData.animate;
6414 if (clickType == Press) {
6415 /* Finish clickclick move */
6416 if (appData.animate || appData.highlightLastMove) {
6417 SetHighlights(fromX, fromY, toX, toY);
6422 /* Finish drag move */
6423 if (appData.highlightLastMove) {
6424 SetHighlights(fromX, fromY, toX, toY);
6428 DragPieceEnd(xPix, yPix); dragging = 0;
6429 /* Don't animate move and drag both */
6430 appData.animate = FALSE;
6433 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6434 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6435 ChessSquare piece = boards[currentMove][fromY][fromX];
6436 if(gameMode == EditPosition && piece != EmptySquare &&
6437 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6440 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6441 n = PieceToNumber(piece - (int)BlackPawn);
6442 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6443 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6444 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6446 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6447 n = PieceToNumber(piece);
6448 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6449 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6450 boards[currentMove][n][BOARD_WIDTH-2]++;
6452 boards[currentMove][fromY][fromX] = EmptySquare;
6456 DrawPosition(TRUE, boards[currentMove]);
6460 // off-board moves should not be highlighted
6461 if(x < 0 || x < 0) ClearHighlights();
6463 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6464 SetHighlights(fromX, fromY, toX, toY);
6465 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6466 // [HGM] super: promotion to captured piece selected from holdings
6467 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6468 promotionChoice = TRUE;
6469 // kludge follows to temporarily execute move on display, without promoting yet
6470 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6471 boards[currentMove][toY][toX] = p;
6472 DrawPosition(FALSE, boards[currentMove]);
6473 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6474 boards[currentMove][toY][toX] = q;
6475 DisplayMessage("Click in holdings to choose piece", "");
6480 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6481 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6482 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6485 appData.animate = saveAnimate;
6486 if (appData.animate || appData.animateDragging) {
6487 /* Undo animation damage if needed */
6488 DrawPosition(FALSE, NULL);
6492 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6493 { // front-end-free part taken out of PieceMenuPopup
6494 int whichMenu; int xSqr, ySqr;
6496 if(seekGraphUp) { // [HGM] seekgraph
6497 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6498 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6502 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6503 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6504 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6505 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6506 if(action == Press) {
6507 originalFlip = flipView;
6508 flipView = !flipView; // temporarily flip board to see game from partners perspective
6509 DrawPosition(TRUE, partnerBoard);
6510 DisplayMessage(partnerStatus, "");
6512 } else if(action == Release) {
6513 flipView = originalFlip;
6514 DrawPosition(TRUE, boards[currentMove]);
6520 xSqr = EventToSquare(x, BOARD_WIDTH);
6521 ySqr = EventToSquare(y, BOARD_HEIGHT);
6522 if (action == Release) UnLoadPV(); // [HGM] pv
6523 if (action != Press) return -2; // return code to be ignored
6526 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6528 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6529 if (xSqr < 0 || ySqr < 0) return -1;
\r
6530 whichMenu = 0; // edit-position menu
6533 if(!appData.icsEngineAnalyze) return -1;
6534 case IcsPlayingWhite:
6535 case IcsPlayingBlack:
6536 if(!appData.zippyPlay) goto noZip;
6539 case MachinePlaysWhite:
6540 case MachinePlaysBlack:
6541 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6542 if (!appData.dropMenu) {
6544 return 2; // flag front-end to grab mouse events
6546 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6547 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6550 if (xSqr < 0 || ySqr < 0) return -1;
6551 if (!appData.dropMenu || appData.testLegality &&
6552 gameInfo.variant != VariantBughouse &&
6553 gameInfo.variant != VariantCrazyhouse) return -1;
6554 whichMenu = 1; // drop menu
6560 if (((*fromX = xSqr) < 0) ||
6561 ((*fromY = ySqr) < 0)) {
6562 *fromX = *fromY = -1;
6566 *fromX = BOARD_WIDTH - 1 - *fromX;
6568 *fromY = BOARD_HEIGHT - 1 - *fromY;
6573 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6575 // char * hint = lastHint;
6576 FrontEndProgramStats stats;
6578 stats.which = cps == &first ? 0 : 1;
6579 stats.depth = cpstats->depth;
6580 stats.nodes = cpstats->nodes;
6581 stats.score = cpstats->score;
6582 stats.time = cpstats->time;
6583 stats.pv = cpstats->movelist;
6584 stats.hint = lastHint;
6585 stats.an_move_index = 0;
6586 stats.an_move_count = 0;
6588 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6589 stats.hint = cpstats->move_name;
6590 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6591 stats.an_move_count = cpstats->nr_moves;
6594 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6596 SetProgramStats( &stats );
6600 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6601 { // count all piece types
6603 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6604 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6605 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6607 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6608 if(p == BlackPawn && r == 0) (*bStale)++; else pCnt[p]++; // count last-Rank Pawns (XQ) separately
6609 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6610 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6611 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6612 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6617 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6619 VariantClass v = gameInfo.variant;
6621 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6622 if(v == VariantShatranj) return TRUE; // always winnable through baring
6623 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6624 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6626 if(v == VariantXiangqi) {
6627 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6629 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6630 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6631 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6632 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6633 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6634 if(stale) // we have at least one last-rank P plus perhaps C
6635 return majors // KPKX
6636 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6638 return pCnt[WhiteFerz+side] // KCAK
6639 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6640 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6641 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6643 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6644 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6646 if(nMine == 1) return FALSE; // bare King
6647 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6648 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6649 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6650 // by now we have King + 1 piece (or multiple Bishops on the same color)
6651 if(pCnt[WhiteKnight+side])
6652 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6653 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6654 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6656 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6657 if(pCnt[WhiteAlfil+side])
6658 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6659 if(pCnt[WhiteWazir+side])
6660 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6667 Adjudicate(ChessProgramState *cps)
6668 { // [HGM] some adjudications useful with buggy engines
6669 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6670 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6671 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6672 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6673 int k, count = 0; static int bare = 1;
6674 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6675 Boolean canAdjudicate = !appData.icsActive;
6677 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6678 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6679 if( appData.testLegality )
6680 { /* [HGM] Some more adjudications for obstinate engines */
6681 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6682 static int moveCount = 6;
6684 char *reason = NULL;
6686 /* Count what is on board. */
6687 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6689 /* Some material-based adjudications that have to be made before stalemate test */
6690 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6691 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6692 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6693 if(canAdjudicate && appData.checkMates) {
6695 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6696 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6697 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6698 "Xboard adjudication: King destroyed", GE_XBOARD );
6703 /* Bare King in Shatranj (loses) or Losers (wins) */
6704 if( nrW == 1 || nrB == 1) {
6705 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6706 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6707 if(canAdjudicate && appData.checkMates) {
6709 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6710 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6711 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6712 "Xboard adjudication: Bare king", GE_XBOARD );
6716 if( gameInfo.variant == VariantShatranj && --bare < 0)
6718 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6719 if(canAdjudicate && appData.checkMates) {
6720 /* but only adjudicate if adjudication enabled */
6722 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6723 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6724 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6725 "Xboard adjudication: Bare king", GE_XBOARD );
6732 // don't wait for engine to announce game end if we can judge ourselves
6733 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6735 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6736 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6737 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6738 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6741 reason = "Xboard adjudication: 3rd check";
6742 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6752 reason = "Xboard adjudication: Stalemate";
6753 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6754 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6755 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6756 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6757 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6758 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6759 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6760 EP_CHECKMATE : EP_WINS);
6761 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6762 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6766 reason = "Xboard adjudication: Checkmate";
6767 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6771 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6773 result = GameIsDrawn; break;
6775 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6777 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6779 result = (ChessMove) 0;
6781 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6783 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6784 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6785 GameEnds( result, reason, GE_XBOARD );
6789 /* Next absolutely insufficient mating material. */
6790 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6791 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6792 { /* includes KBK, KNK, KK of KBKB with like Bishops */
6794 /* always flag draws, for judging claims */
6795 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6797 if(canAdjudicate && appData.materialDraws) {
6798 /* but only adjudicate them if adjudication enabled */
6799 if(engineOpponent) {
6800 SendToProgram("force\n", engineOpponent); // suppress reply
6801 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6803 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6804 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6809 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6810 if(nrW + nrB == 4 &&
6811 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6812 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
6813 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
6814 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6816 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6817 { /* if the first 3 moves do not show a tactical win, declare draw */
6818 if(engineOpponent) {
6819 SendToProgram("force\n", engineOpponent); // suppress reply
6820 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6822 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6823 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6826 } else moveCount = 6;
6830 if (appData.debugMode) { int i;
6831 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6832 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6833 appData.drawRepeats);
6834 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6835 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6839 // Repetition draws and 50-move rule can be applied independently of legality testing
6841 /* Check for rep-draws */
6843 for(k = forwardMostMove-2;
6844 k>=backwardMostMove && k>=forwardMostMove-100 &&
6845 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6846 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6849 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6850 /* compare castling rights */
6851 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6852 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6853 rights++; /* King lost rights, while rook still had them */
6854 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6855 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6856 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6857 rights++; /* but at least one rook lost them */
6859 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6860 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6862 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6863 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6864 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6867 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6868 && appData.drawRepeats > 1) {
6869 /* adjudicate after user-specified nr of repeats */
6870 int result = GameIsDrawn;
6871 char *details = "XBoard adjudication: repetition draw";
6872 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6873 // [HGM] xiangqi: check for forbidden perpetuals
6874 int m, ourPerpetual = 1, hisPerpetual = 1;
6875 for(m=forwardMostMove; m>k; m-=2) {
6876 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6877 ourPerpetual = 0; // the current mover did not always check
6878 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6879 hisPerpetual = 0; // the opponent did not always check
6881 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6882 ourPerpetual, hisPerpetual);
6883 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6884 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6885 details = "Xboard adjudication: perpetual checking";
6887 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6888 break; // (or we would have caught him before). Abort repetition-checking loop.
6890 // Now check for perpetual chases
6891 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6892 hisPerpetual = PerpetualChase(k, forwardMostMove);
6893 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6894 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6895 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6896 details = "Xboard adjudication: perpetual chasing";
6898 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6899 break; // Abort repetition-checking loop.
6901 // if neither of us is checking or chasing all the time, or both are, it is draw
6903 if(engineOpponent) {
6904 SendToProgram("force\n", engineOpponent); // suppress reply
6905 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6907 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6908 GameEnds( result, details, GE_XBOARD );
6911 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6912 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6916 /* Now we test for 50-move draws. Determine ply count */
6917 count = forwardMostMove;
6918 /* look for last irreversble move */
6919 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6921 /* if we hit starting position, add initial plies */
6922 if( count == backwardMostMove )
6923 count -= initialRulePlies;
6924 count = forwardMostMove - count;
6925 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6926 // adjust reversible move counter for checks in Xiangqi
6927 int i = forwardMostMove - count, inCheck = 0, lastCheck;
6928 if(i < backwardMostMove) i = backwardMostMove;
6929 while(i <= forwardMostMove) {
6930 lastCheck = inCheck; // check evasion does not count
6931 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6932 if(inCheck || lastCheck) count--; // check does not count
6937 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6938 /* this is used to judge if draw claims are legal */
6939 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6940 if(engineOpponent) {
6941 SendToProgram("force\n", engineOpponent); // suppress reply
6942 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6944 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6945 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6949 /* if draw offer is pending, treat it as a draw claim
6950 * when draw condition present, to allow engines a way to
6951 * claim draws before making their move to avoid a race
6952 * condition occurring after their move
6954 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6956 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6957 p = "Draw claim: 50-move rule";
6958 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6959 p = "Draw claim: 3-fold repetition";
6960 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6961 p = "Draw claim: insufficient mating material";
6962 if( p != NULL && canAdjudicate) {
6963 if(engineOpponent) {
6964 SendToProgram("force\n", engineOpponent); // suppress reply
6965 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6967 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6968 GameEnds( GameIsDrawn, p, GE_XBOARD );
6973 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6974 if(engineOpponent) {
6975 SendToProgram("force\n", engineOpponent); // suppress reply
6976 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6978 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6979 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6985 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6986 { // [HGM] book: this routine intercepts moves to simulate book replies
6987 char *bookHit = NULL;
6989 //first determine if the incoming move brings opponent into his book
6990 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6991 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6992 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6993 if(bookHit != NULL && !cps->bookSuspend) {
6994 // make sure opponent is not going to reply after receiving move to book position
6995 SendToProgram("force\n", cps);
6996 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6998 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6999 // now arrange restart after book miss
7001 // after a book hit we never send 'go', and the code after the call to this routine
7002 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7004 sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7005 SendToProgram(buf, cps);
7006 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7007 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7008 SendToProgram("go\n", cps);
7009 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7010 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7011 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7012 SendToProgram("go\n", cps);
7013 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7015 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7019 ChessProgramState *savedState;
7020 void DeferredBookMove(void)
7022 if(savedState->lastPing != savedState->lastPong)
7023 ScheduleDelayedEvent(DeferredBookMove, 10);
7025 HandleMachineMove(savedMessage, savedState);
7029 HandleMachineMove(message, cps)
7031 ChessProgramState *cps;
7033 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7034 char realname[MSG_SIZ];
7035 int fromX, fromY, toX, toY;
7044 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7046 * Kludge to ignore BEL characters
7048 while (*message == '\007') message++;
7051 * [HGM] engine debug message: ignore lines starting with '#' character
7053 if(cps->debug && *message == '#') return;
7056 * Look for book output
7058 if (cps == &first && bookRequested) {
7059 if (message[0] == '\t' || message[0] == ' ') {
7060 /* Part of the book output is here; append it */
7061 strcat(bookOutput, message);
7062 strcat(bookOutput, " \n");
7064 } else if (bookOutput[0] != NULLCHAR) {
7065 /* All of book output has arrived; display it */
7066 char *p = bookOutput;
7067 while (*p != NULLCHAR) {
7068 if (*p == '\t') *p = ' ';
7071 DisplayInformation(bookOutput);
7072 bookRequested = FALSE;
7073 /* Fall through to parse the current output */
7078 * Look for machine move.
7080 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7081 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7083 /* This method is only useful on engines that support ping */
7084 if (cps->lastPing != cps->lastPong) {
7085 if (gameMode == BeginningOfGame) {
7086 /* Extra move from before last new; ignore */
7087 if (appData.debugMode) {
7088 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7091 if (appData.debugMode) {
7092 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7093 cps->which, gameMode);
7096 SendToProgram("undo\n", cps);
7102 case BeginningOfGame:
7103 /* Extra move from before last reset; ignore */
7104 if (appData.debugMode) {
7105 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7112 /* Extra move after we tried to stop. The mode test is
7113 not a reliable way of detecting this problem, but it's
7114 the best we can do on engines that don't support ping.
7116 if (appData.debugMode) {
7117 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7118 cps->which, gameMode);
7120 SendToProgram("undo\n", cps);
7123 case MachinePlaysWhite:
7124 case IcsPlayingWhite:
7125 machineWhite = TRUE;
7128 case MachinePlaysBlack:
7129 case IcsPlayingBlack:
7130 machineWhite = FALSE;
7133 case TwoMachinesPlay:
7134 machineWhite = (cps->twoMachinesColor[0] == 'w');
7137 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7138 if (appData.debugMode) {
7140 "Ignoring move out of turn by %s, gameMode %d"
7141 ", forwardMost %d\n",
7142 cps->which, gameMode, forwardMostMove);
7147 if (appData.debugMode) { int f = forwardMostMove;
7148 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7149 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7150 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7152 if(cps->alphaRank) AlphaRank(machineMove, 4);
7153 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7154 &fromX, &fromY, &toX, &toY, &promoChar)) {
7155 /* Machine move could not be parsed; ignore it. */
7156 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7157 machineMove, cps->which);
7158 DisplayError(buf1, 0);
7159 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7160 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7161 if (gameMode == TwoMachinesPlay) {
7162 GameEnds(machineWhite ? BlackWins : WhiteWins,
7168 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7169 /* So we have to redo legality test with true e.p. status here, */
7170 /* to make sure an illegal e.p. capture does not slip through, */
7171 /* to cause a forfeit on a justified illegal-move complaint */
7172 /* of the opponent. */
7173 if( gameMode==TwoMachinesPlay && appData.testLegality
7174 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7177 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7178 fromY, fromX, toY, toX, promoChar);
7179 if (appData.debugMode) {
7181 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7182 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7183 fprintf(debugFP, "castling rights\n");
7185 if(moveType == IllegalMove) {
7186 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7187 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7188 GameEnds(machineWhite ? BlackWins : WhiteWins,
7191 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7192 /* [HGM] Kludge to handle engines that send FRC-style castling
7193 when they shouldn't (like TSCP-Gothic) */
7195 case WhiteASideCastleFR:
7196 case BlackASideCastleFR:
7198 currentMoveString[2]++;
7200 case WhiteHSideCastleFR:
7201 case BlackHSideCastleFR:
7203 currentMoveString[2]--;
7205 default: ; // nothing to do, but suppresses warning of pedantic compilers
7208 hintRequested = FALSE;
7209 lastHint[0] = NULLCHAR;
7210 bookRequested = FALSE;
7211 /* Program may be pondering now */
7212 cps->maybeThinking = TRUE;
7213 if (cps->sendTime == 2) cps->sendTime = 1;
7214 if (cps->offeredDraw) cps->offeredDraw--;
7216 /* currentMoveString is set as a side-effect of ParseOneMove */
7217 strcpy(machineMove, currentMoveString);
7218 strcat(machineMove, "\n");
7219 strcpy(moveList[forwardMostMove], machineMove);
7221 /* [AS] Save move info*/
7222 pvInfoList[ forwardMostMove ].score = programStats.score;
7223 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7224 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7226 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7228 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7229 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7232 while( count < adjudicateLossPlies ) {
7233 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7236 score = -score; /* Flip score for winning side */
7239 if( score > adjudicateLossThreshold ) {
7246 if( count >= adjudicateLossPlies ) {
7247 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7249 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7250 "Xboard adjudication",
7257 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7260 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7262 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7263 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7265 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7267 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7269 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7270 char buf[3*MSG_SIZ];
7272 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7273 programStats.score / 100.,
7275 programStats.time / 100.,
7276 (unsigned int)programStats.nodes,
7277 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7278 programStats.movelist);
7280 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7285 /* [AS] Clear stats for next move */
7286 ClearProgramStats();
7287 thinkOutput[0] = NULLCHAR;
7288 hiddenThinkOutputState = 0;
7291 if (gameMode == TwoMachinesPlay) {
7292 /* [HGM] relaying draw offers moved to after reception of move */
7293 /* and interpreting offer as claim if it brings draw condition */
7294 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7295 SendToProgram("draw\n", cps->other);
7297 if (cps->other->sendTime) {
7298 SendTimeRemaining(cps->other,
7299 cps->other->twoMachinesColor[0] == 'w');
7301 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7302 if (firstMove && !bookHit) {
7304 if (cps->other->useColors) {
7305 SendToProgram(cps->other->twoMachinesColor, cps->other);
7307 SendToProgram("go\n", cps->other);
7309 cps->other->maybeThinking = TRUE;
7312 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7314 if (!pausing && appData.ringBellAfterMoves) {
7319 * Reenable menu items that were disabled while
7320 * machine was thinking
7322 if (gameMode != TwoMachinesPlay)
7323 SetUserThinkingEnables();
7325 // [HGM] book: after book hit opponent has received move and is now in force mode
7326 // force the book reply into it, and then fake that it outputted this move by jumping
7327 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7329 static char bookMove[MSG_SIZ]; // a bit generous?
7331 strcpy(bookMove, "move ");
7332 strcat(bookMove, bookHit);
7335 programStats.nodes = programStats.depth = programStats.time =
7336 programStats.score = programStats.got_only_move = 0;
7337 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7339 if(cps->lastPing != cps->lastPong) {
7340 savedMessage = message; // args for deferred call
7342 ScheduleDelayedEvent(DeferredBookMove, 10);
7351 /* Set special modes for chess engines. Later something general
7352 * could be added here; for now there is just one kludge feature,
7353 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7354 * when "xboard" is given as an interactive command.
7356 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7357 cps->useSigint = FALSE;
7358 cps->useSigterm = FALSE;
7360 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7361 ParseFeatures(message+8, cps);
7362 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7365 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7366 * want this, I was asked to put it in, and obliged.
7368 if (!strncmp(message, "setboard ", 9)) {
7369 Board initial_position;
7371 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7373 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7374 DisplayError(_("Bad FEN received from engine"), 0);
7378 CopyBoard(boards[0], initial_position);
7379 initialRulePlies = FENrulePlies;
7380 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7381 else gameMode = MachinePlaysBlack;
7382 DrawPosition(FALSE, boards[currentMove]);
7388 * Look for communication commands
7390 if (!strncmp(message, "telluser ", 9)) {
7391 EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7392 DisplayNote(message + 9);
7395 if (!strncmp(message, "tellusererror ", 14)) {
7397 EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7398 DisplayError(message + 14, 0);
7401 if (!strncmp(message, "tellopponent ", 13)) {
7402 if (appData.icsActive) {
7404 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7408 DisplayNote(message + 13);
7412 if (!strncmp(message, "tellothers ", 11)) {
7413 if (appData.icsActive) {
7415 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7421 if (!strncmp(message, "tellall ", 8)) {
7422 if (appData.icsActive) {
7424 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7428 DisplayNote(message + 8);
7432 if (strncmp(message, "warning", 7) == 0) {
7433 /* Undocumented feature, use tellusererror in new code */
7434 DisplayError(message, 0);
7437 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7438 strcpy(realname, cps->tidy);
7439 strcat(realname, " query");
7440 AskQuestion(realname, buf2, buf1, cps->pr);
7443 /* Commands from the engine directly to ICS. We don't allow these to be
7444 * sent until we are logged on. Crafty kibitzes have been known to
7445 * interfere with the login process.
7448 if (!strncmp(message, "tellics ", 8)) {
7449 SendToICS(message + 8);
7453 if (!strncmp(message, "tellicsnoalias ", 15)) {
7454 SendToICS(ics_prefix);
7455 SendToICS(message + 15);
7459 /* The following are for backward compatibility only */
7460 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7461 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7462 SendToICS(ics_prefix);
7468 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7472 * If the move is illegal, cancel it and redraw the board.
7473 * Also deal with other error cases. Matching is rather loose
7474 * here to accommodate engines written before the spec.
7476 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7477 strncmp(message, "Error", 5) == 0) {
7478 if (StrStr(message, "name") ||
7479 StrStr(message, "rating") || StrStr(message, "?") ||
7480 StrStr(message, "result") || StrStr(message, "board") ||
7481 StrStr(message, "bk") || StrStr(message, "computer") ||
7482 StrStr(message, "variant") || StrStr(message, "hint") ||
7483 StrStr(message, "random") || StrStr(message, "depth") ||
7484 StrStr(message, "accepted")) {
7487 if (StrStr(message, "protover")) {
7488 /* Program is responding to input, so it's apparently done
7489 initializing, and this error message indicates it is
7490 protocol version 1. So we don't need to wait any longer
7491 for it to initialize and send feature commands. */
7492 FeatureDone(cps, 1);
7493 cps->protocolVersion = 1;
7496 cps->maybeThinking = FALSE;
7498 if (StrStr(message, "draw")) {
7499 /* Program doesn't have "draw" command */
7500 cps->sendDrawOffers = 0;
7503 if (cps->sendTime != 1 &&
7504 (StrStr(message, "time") || StrStr(message, "otim"))) {
7505 /* Program apparently doesn't have "time" or "otim" command */
7509 if (StrStr(message, "analyze")) {
7510 cps->analysisSupport = FALSE;
7511 cps->analyzing = FALSE;
7513 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7514 DisplayError(buf2, 0);
7517 if (StrStr(message, "(no matching move)st")) {
7518 /* Special kludge for GNU Chess 4 only */
7519 cps->stKludge = TRUE;
7520 SendTimeControl(cps, movesPerSession, timeControl,
7521 timeIncrement, appData.searchDepth,
7525 if (StrStr(message, "(no matching move)sd")) {
7526 /* Special kludge for GNU Chess 4 only */
7527 cps->sdKludge = TRUE;
7528 SendTimeControl(cps, movesPerSession, timeControl,
7529 timeIncrement, appData.searchDepth,
7533 if (!StrStr(message, "llegal")) {
7536 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7537 gameMode == IcsIdle) return;
7538 if (forwardMostMove <= backwardMostMove) return;
7539 if (pausing) PauseEvent();
7540 if(appData.forceIllegal) {
7541 // [HGM] illegal: machine refused move; force position after move into it
7542 SendToProgram("force\n", cps);
7543 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7544 // we have a real problem now, as SendBoard will use the a2a3 kludge
7545 // when black is to move, while there might be nothing on a2 or black
7546 // might already have the move. So send the board as if white has the move.
7547 // But first we must change the stm of the engine, as it refused the last move
7548 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7549 if(WhiteOnMove(forwardMostMove)) {
7550 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7551 SendBoard(cps, forwardMostMove); // kludgeless board
7553 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7554 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7555 SendBoard(cps, forwardMostMove+1); // kludgeless board
7557 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7558 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7559 gameMode == TwoMachinesPlay)
7560 SendToProgram("go\n", cps);
7563 if (gameMode == PlayFromGameFile) {
7564 /* Stop reading this game file */
7565 gameMode = EditGame;
7568 currentMove = forwardMostMove-1;
7569 DisplayMove(currentMove-1); /* before DisplayMoveError */
7570 SwitchClocks(forwardMostMove-1); // [HGM] race
7571 DisplayBothClocks();
7572 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7573 parseList[currentMove], cps->which);
7574 DisplayMoveError(buf1);
7575 DrawPosition(FALSE, boards[currentMove]);
7577 /* [HGM] illegal-move claim should forfeit game when Xboard */
7578 /* only passes fully legal moves */
7579 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7580 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7581 "False illegal-move claim", GE_XBOARD );
7585 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7586 /* Program has a broken "time" command that
7587 outputs a string not ending in newline.
7593 * If chess program startup fails, exit with an error message.
7594 * Attempts to recover here are futile.
7596 if ((StrStr(message, "unknown host") != NULL)
7597 || (StrStr(message, "No remote directory") != NULL)
7598 || (StrStr(message, "not found") != NULL)
7599 || (StrStr(message, "No such file") != NULL)
7600 || (StrStr(message, "can't alloc") != NULL)
7601 || (StrStr(message, "Permission denied") != NULL)) {
7603 cps->maybeThinking = FALSE;
7604 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7605 cps->which, cps->program, cps->host, message);
7606 RemoveInputSource(cps->isr);
7607 DisplayFatalError(buf1, 0, 1);
7612 * Look for hint output
7614 if (sscanf(message, "Hint: %s", buf1) == 1) {
7615 if (cps == &first && hintRequested) {
7616 hintRequested = FALSE;
7617 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7618 &fromX, &fromY, &toX, &toY, &promoChar)) {
7619 (void) CoordsToAlgebraic(boards[forwardMostMove],
7620 PosFlags(forwardMostMove),
7621 fromY, fromX, toY, toX, promoChar, buf1);
7622 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7623 DisplayInformation(buf2);
7625 /* Hint move could not be parsed!? */
7626 snprintf(buf2, sizeof(buf2),
7627 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7629 DisplayError(buf2, 0);
7632 strcpy(lastHint, buf1);
7638 * Ignore other messages if game is not in progress
7640 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7641 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7644 * look for win, lose, draw, or draw offer
7646 if (strncmp(message, "1-0", 3) == 0) {
7647 char *p, *q, *r = "";
7648 p = strchr(message, '{');
7656 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7658 } else if (strncmp(message, "0-1", 3) == 0) {
7659 char *p, *q, *r = "";
7660 p = strchr(message, '{');
7668 /* Kludge for Arasan 4.1 bug */
7669 if (strcmp(r, "Black resigns") == 0) {
7670 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7673 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7675 } else if (strncmp(message, "1/2", 3) == 0) {
7676 char *p, *q, *r = "";
7677 p = strchr(message, '{');
7686 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7689 } else if (strncmp(message, "White resign", 12) == 0) {
7690 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7692 } else if (strncmp(message, "Black resign", 12) == 0) {
7693 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7695 } else if (strncmp(message, "White matches", 13) == 0 ||
7696 strncmp(message, "Black matches", 13) == 0 ) {
7697 /* [HGM] ignore GNUShogi noises */
7699 } else if (strncmp(message, "White", 5) == 0 &&
7700 message[5] != '(' &&
7701 StrStr(message, "Black") == NULL) {
7702 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7704 } else if (strncmp(message, "Black", 5) == 0 &&
7705 message[5] != '(') {
7706 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7708 } else if (strcmp(message, "resign") == 0 ||
7709 strcmp(message, "computer resigns") == 0) {
7711 case MachinePlaysBlack:
7712 case IcsPlayingBlack:
7713 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7715 case MachinePlaysWhite:
7716 case IcsPlayingWhite:
7717 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7719 case TwoMachinesPlay:
7720 if (cps->twoMachinesColor[0] == 'w')
7721 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7723 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7730 } else if (strncmp(message, "opponent mates", 14) == 0) {
7732 case MachinePlaysBlack:
7733 case IcsPlayingBlack:
7734 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7736 case MachinePlaysWhite:
7737 case IcsPlayingWhite:
7738 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7740 case TwoMachinesPlay:
7741 if (cps->twoMachinesColor[0] == 'w')
7742 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7744 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7751 } else if (strncmp(message, "computer mates", 14) == 0) {
7753 case MachinePlaysBlack:
7754 case IcsPlayingBlack:
7755 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7757 case MachinePlaysWhite:
7758 case IcsPlayingWhite:
7759 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7761 case TwoMachinesPlay:
7762 if (cps->twoMachinesColor[0] == 'w')
7763 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7765 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7772 } else if (strncmp(message, "checkmate", 9) == 0) {
7773 if (WhiteOnMove(forwardMostMove)) {
7774 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7776 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7779 } else if (strstr(message, "Draw") != NULL ||
7780 strstr(message, "game is a draw") != NULL) {
7781 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7783 } else if (strstr(message, "offer") != NULL &&
7784 strstr(message, "draw") != NULL) {
7786 if (appData.zippyPlay && first.initDone) {
7787 /* Relay offer to ICS */
7788 SendToICS(ics_prefix);
7789 SendToICS("draw\n");
7792 cps->offeredDraw = 2; /* valid until this engine moves twice */
7793 if (gameMode == TwoMachinesPlay) {
7794 if (cps->other->offeredDraw) {
7795 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7796 /* [HGM] in two-machine mode we delay relaying draw offer */
7797 /* until after we also have move, to see if it is really claim */
7799 } else if (gameMode == MachinePlaysWhite ||
7800 gameMode == MachinePlaysBlack) {
7801 if (userOfferedDraw) {
7802 DisplayInformation(_("Machine accepts your draw offer"));
7803 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7805 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7812 * Look for thinking output
7814 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7815 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7817 int plylev, mvleft, mvtot, curscore, time;
7818 char mvname[MOVE_LEN];
7822 int prefixHint = FALSE;
7823 mvname[0] = NULLCHAR;
7826 case MachinePlaysBlack:
7827 case IcsPlayingBlack:
7828 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7830 case MachinePlaysWhite:
7831 case IcsPlayingWhite:
7832 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7837 case IcsObserving: /* [DM] icsEngineAnalyze */
7838 if (!appData.icsEngineAnalyze) ignore = TRUE;
7840 case TwoMachinesPlay:
7841 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7851 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7853 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7854 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7856 if (plyext != ' ' && plyext != '\t') {
7860 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7861 if( cps->scoreIsAbsolute &&
7862 ( gameMode == MachinePlaysBlack ||
7863 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7864 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7865 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7866 !WhiteOnMove(currentMove)
7869 curscore = -curscore;
7873 tempStats.depth = plylev;
7874 tempStats.nodes = nodes;
7875 tempStats.time = time;
7876 tempStats.score = curscore;
7877 tempStats.got_only_move = 0;
7879 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7882 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7883 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7884 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7885 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7886 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7887 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7888 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7889 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7892 /* Buffer overflow protection */
7893 if (buf1[0] != NULLCHAR) {
7894 if (strlen(buf1) >= sizeof(tempStats.movelist)
7895 && appData.debugMode) {
7897 "PV is too long; using the first %u bytes.\n",
7898 (unsigned) sizeof(tempStats.movelist) - 1);
7901 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7903 sprintf(tempStats.movelist, " no PV\n");
7906 if (tempStats.seen_stat) {
7907 tempStats.ok_to_send = 1;
7910 if (strchr(tempStats.movelist, '(') != NULL) {
7911 tempStats.line_is_book = 1;
7912 tempStats.nr_moves = 0;
7913 tempStats.moves_left = 0;
7915 tempStats.line_is_book = 0;
7918 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7919 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7921 SendProgramStatsToFrontend( cps, &tempStats );
7924 [AS] Protect the thinkOutput buffer from overflow... this
7925 is only useful if buf1 hasn't overflowed first!
7927 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7929 (gameMode == TwoMachinesPlay ?
7930 ToUpper(cps->twoMachinesColor[0]) : ' '),
7931 ((double) curscore) / 100.0,
7932 prefixHint ? lastHint : "",
7933 prefixHint ? " " : "" );
7935 if( buf1[0] != NULLCHAR ) {
7936 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7938 if( strlen(buf1) > max_len ) {
7939 if( appData.debugMode) {
7940 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7942 buf1[max_len+1] = '\0';
7945 strcat( thinkOutput, buf1 );
7948 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7949 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7950 DisplayMove(currentMove - 1);
7954 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7955 /* crafty (9.25+) says "(only move) <move>"
7956 * if there is only 1 legal move
7958 sscanf(p, "(only move) %s", buf1);
7959 sprintf(thinkOutput, "%s (only move)", buf1);
7960 sprintf(programStats.movelist, "%s (only move)", buf1);
7961 programStats.depth = 1;
7962 programStats.nr_moves = 1;
7963 programStats.moves_left = 1;
7964 programStats.nodes = 1;
7965 programStats.time = 1;
7966 programStats.got_only_move = 1;
7968 /* Not really, but we also use this member to
7969 mean "line isn't going to change" (Crafty
7970 isn't searching, so stats won't change) */
7971 programStats.line_is_book = 1;
7973 SendProgramStatsToFrontend( cps, &programStats );
7975 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7976 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7977 DisplayMove(currentMove - 1);
7980 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7981 &time, &nodes, &plylev, &mvleft,
7982 &mvtot, mvname) >= 5) {
7983 /* The stat01: line is from Crafty (9.29+) in response
7984 to the "." command */
7985 programStats.seen_stat = 1;
7986 cps->maybeThinking = TRUE;
7988 if (programStats.got_only_move || !appData.periodicUpdates)
7991 programStats.depth = plylev;
7992 programStats.time = time;
7993 programStats.nodes = nodes;
7994 programStats.moves_left = mvleft;
7995 programStats.nr_moves = mvtot;
7996 strcpy(programStats.move_name, mvname);
7997 programStats.ok_to_send = 1;
7998 programStats.movelist[0] = '\0';
8000 SendProgramStatsToFrontend( cps, &programStats );
8004 } else if (strncmp(message,"++",2) == 0) {
8005 /* Crafty 9.29+ outputs this */
8006 programStats.got_fail = 2;
8009 } else if (strncmp(message,"--",2) == 0) {
8010 /* Crafty 9.29+ outputs this */
8011 programStats.got_fail = 1;
8014 } else if (thinkOutput[0] != NULLCHAR &&
8015 strncmp(message, " ", 4) == 0) {
8016 unsigned message_len;
8019 while (*p && *p == ' ') p++;
8021 message_len = strlen( p );
8023 /* [AS] Avoid buffer overflow */
8024 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8025 strcat(thinkOutput, " ");
8026 strcat(thinkOutput, p);
8029 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8030 strcat(programStats.movelist, " ");
8031 strcat(programStats.movelist, p);
8034 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8035 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8036 DisplayMove(currentMove - 1);
8044 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8045 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8047 ChessProgramStats cpstats;
8049 if (plyext != ' ' && plyext != '\t') {
8053 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8054 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8055 curscore = -curscore;
8058 cpstats.depth = plylev;
8059 cpstats.nodes = nodes;
8060 cpstats.time = time;
8061 cpstats.score = curscore;
8062 cpstats.got_only_move = 0;
8063 cpstats.movelist[0] = '\0';
8065 if (buf1[0] != NULLCHAR) {
8066 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8069 cpstats.ok_to_send = 0;
8070 cpstats.line_is_book = 0;
8071 cpstats.nr_moves = 0;
8072 cpstats.moves_left = 0;
8074 SendProgramStatsToFrontend( cps, &cpstats );
8081 /* Parse a game score from the character string "game", and
8082 record it as the history of the current game. The game
8083 score is NOT assumed to start from the standard position.
8084 The display is not updated in any way.
8087 ParseGameHistory(game)
8091 int fromX, fromY, toX, toY, boardIndex;
8096 if (appData.debugMode)
8097 fprintf(debugFP, "Parsing game history: %s\n", game);
8099 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8100 gameInfo.site = StrSave(appData.icsHost);
8101 gameInfo.date = PGNDate();
8102 gameInfo.round = StrSave("-");
8104 /* Parse out names of players */
8105 while (*game == ' ') game++;
8107 while (*game != ' ') *p++ = *game++;
8109 gameInfo.white = StrSave(buf);
8110 while (*game == ' ') game++;
8112 while (*game != ' ' && *game != '\n') *p++ = *game++;
8114 gameInfo.black = StrSave(buf);
8117 boardIndex = blackPlaysFirst ? 1 : 0;
8120 yyboardindex = boardIndex;
8121 moveType = (ChessMove) yylex();
8123 case IllegalMove: /* maybe suicide chess, etc. */
8124 if (appData.debugMode) {
8125 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8126 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8127 setbuf(debugFP, NULL);
8129 case WhitePromotionChancellor:
8130 case BlackPromotionChancellor:
8131 case WhitePromotionArchbishop:
8132 case BlackPromotionArchbishop:
8133 case WhitePromotionQueen:
8134 case BlackPromotionQueen:
8135 case WhitePromotionRook:
8136 case BlackPromotionRook:
8137 case WhitePromotionBishop:
8138 case BlackPromotionBishop:
8139 case WhitePromotionKnight:
8140 case BlackPromotionKnight:
8141 case WhitePromotionKing:
8142 case BlackPromotionKing:
8144 case WhiteCapturesEnPassant:
8145 case BlackCapturesEnPassant:
8146 case WhiteKingSideCastle:
8147 case WhiteQueenSideCastle:
8148 case BlackKingSideCastle:
8149 case BlackQueenSideCastle:
8150 case WhiteKingSideCastleWild:
8151 case WhiteQueenSideCastleWild:
8152 case BlackKingSideCastleWild:
8153 case BlackQueenSideCastleWild:
8155 case WhiteHSideCastleFR:
8156 case WhiteASideCastleFR:
8157 case BlackHSideCastleFR:
8158 case BlackASideCastleFR:
8160 fromX = currentMoveString[0] - AAA;
8161 fromY = currentMoveString[1] - ONE;
8162 toX = currentMoveString[2] - AAA;
8163 toY = currentMoveString[3] - ONE;
8164 promoChar = currentMoveString[4];
8168 fromX = moveType == WhiteDrop ?
8169 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8170 (int) CharToPiece(ToLower(currentMoveString[0]));
8172 toX = currentMoveString[2] - AAA;
8173 toY = currentMoveString[3] - ONE;
8174 promoChar = NULLCHAR;
8178 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8179 if (appData.debugMode) {
8180 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8181 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8182 setbuf(debugFP, NULL);
8184 DisplayError(buf, 0);
8186 case ImpossibleMove:
8188 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8189 if (appData.debugMode) {
8190 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8191 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8192 setbuf(debugFP, NULL);
8194 DisplayError(buf, 0);
8196 case (ChessMove) 0: /* end of file */
8197 if (boardIndex < backwardMostMove) {
8198 /* Oops, gap. How did that happen? */
8199 DisplayError(_("Gap in move list"), 0);
8202 backwardMostMove = blackPlaysFirst ? 1 : 0;
8203 if (boardIndex > forwardMostMove) {
8204 forwardMostMove = boardIndex;
8208 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8209 strcat(parseList[boardIndex-1], " ");
8210 strcat(parseList[boardIndex-1], yy_text);
8222 case GameUnfinished:
8223 if (gameMode == IcsExamining) {
8224 if (boardIndex < backwardMostMove) {
8225 /* Oops, gap. How did that happen? */
8228 backwardMostMove = blackPlaysFirst ? 1 : 0;
8231 gameInfo.result = moveType;
8232 p = strchr(yy_text, '{');
8233 if (p == NULL) p = strchr(yy_text, '(');
8236 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8238 q = strchr(p, *p == '{' ? '}' : ')');
8239 if (q != NULL) *q = NULLCHAR;
8242 gameInfo.resultDetails = StrSave(p);
8245 if (boardIndex >= forwardMostMove &&
8246 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8247 backwardMostMove = blackPlaysFirst ? 1 : 0;
8250 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8251 fromY, fromX, toY, toX, promoChar,
8252 parseList[boardIndex]);
8253 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8254 /* currentMoveString is set as a side-effect of yylex */
8255 strcpy(moveList[boardIndex], currentMoveString);
8256 strcat(moveList[boardIndex], "\n");
8258 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8259 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8265 if(gameInfo.variant != VariantShogi)
8266 strcat(parseList[boardIndex - 1], "+");
8270 strcat(parseList[boardIndex - 1], "#");
8277 /* Apply a move to the given board */
8279 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8280 int fromX, fromY, toX, toY;
8284 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8285 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8287 /* [HGM] compute & store e.p. status and castling rights for new position */
8288 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8291 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8292 oldEP = (signed char)board[EP_STATUS];
8293 board[EP_STATUS] = EP_NONE;
8295 if( board[toY][toX] != EmptySquare )
8296 board[EP_STATUS] = EP_CAPTURE;
8298 if( board[fromY][fromX] == WhitePawn ) {
8299 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8300 board[EP_STATUS] = EP_PAWN_MOVE;
8302 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8303 gameInfo.variant != VariantBerolina || toX < fromX)
8304 board[EP_STATUS] = toX | berolina;
8305 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8306 gameInfo.variant != VariantBerolina || toX > fromX)
8307 board[EP_STATUS] = toX;
8310 if( board[fromY][fromX] == BlackPawn ) {
8311 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8312 board[EP_STATUS] = EP_PAWN_MOVE;
8313 if( toY-fromY== -2) {
8314 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8315 gameInfo.variant != VariantBerolina || toX < fromX)
8316 board[EP_STATUS] = toX | berolina;
8317 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8318 gameInfo.variant != VariantBerolina || toX > fromX)
8319 board[EP_STATUS] = toX;
8323 for(i=0; i<nrCastlingRights; i++) {
8324 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8325 board[CASTLING][i] == toX && castlingRank[i] == toY
8326 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8331 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8332 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8333 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8335 if (fromX == toX && fromY == toY) return;
8337 if (fromY == DROP_RANK) {
8339 piece = board[toY][toX] = (ChessSquare) fromX;
8341 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8342 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8343 if(gameInfo.variant == VariantKnightmate)
8344 king += (int) WhiteUnicorn - (int) WhiteKing;
8346 /* Code added by Tord: */
8347 /* FRC castling assumed when king captures friendly rook. */
8348 if (board[fromY][fromX] == WhiteKing &&
8349 board[toY][toX] == WhiteRook) {
8350 board[fromY][fromX] = EmptySquare;
8351 board[toY][toX] = EmptySquare;
8353 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8355 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8357 } else if (board[fromY][fromX] == BlackKing &&
8358 board[toY][toX] == BlackRook) {
8359 board[fromY][fromX] = EmptySquare;
8360 board[toY][toX] = EmptySquare;
8362 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8364 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8366 /* End of code added by Tord */
8368 } else if (board[fromY][fromX] == king
8369 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8370 && toY == fromY && toX > fromX+1) {
8371 board[fromY][fromX] = EmptySquare;
8372 board[toY][toX] = king;
8373 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8374 board[fromY][BOARD_RGHT-1] = EmptySquare;
8375 } else if (board[fromY][fromX] == king
8376 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8377 && toY == fromY && toX < fromX-1) {
8378 board[fromY][fromX] = EmptySquare;
8379 board[toY][toX] = king;
8380 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8381 board[fromY][BOARD_LEFT] = EmptySquare;
8382 } else if (board[fromY][fromX] == WhitePawn
8383 && toY >= BOARD_HEIGHT-promoRank
8384 && gameInfo.variant != VariantXiangqi
8386 /* white pawn promotion */
8387 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8388 if (board[toY][toX] == EmptySquare) {
8389 board[toY][toX] = WhiteQueen;
8391 if(gameInfo.variant==VariantBughouse ||
8392 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8393 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8394 board[fromY][fromX] = EmptySquare;
8395 } else if ((fromY == BOARD_HEIGHT-4)
8397 && gameInfo.variant != VariantXiangqi
8398 && gameInfo.variant != VariantBerolina
8399 && (board[fromY][fromX] == WhitePawn)
8400 && (board[toY][toX] == EmptySquare)) {
8401 board[fromY][fromX] = EmptySquare;
8402 board[toY][toX] = WhitePawn;
8403 captured = board[toY - 1][toX];
8404 board[toY - 1][toX] = EmptySquare;
8405 } else if ((fromY == BOARD_HEIGHT-4)
8407 && gameInfo.variant == VariantBerolina
8408 && (board[fromY][fromX] == WhitePawn)
8409 && (board[toY][toX] == EmptySquare)) {
8410 board[fromY][fromX] = EmptySquare;
8411 board[toY][toX] = WhitePawn;
8412 if(oldEP & EP_BEROLIN_A) {
8413 captured = board[fromY][fromX-1];
8414 board[fromY][fromX-1] = EmptySquare;
8415 }else{ captured = board[fromY][fromX+1];
8416 board[fromY][fromX+1] = EmptySquare;
8418 } else if (board[fromY][fromX] == king
8419 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8420 && toY == fromY && toX > fromX+1) {
8421 board[fromY][fromX] = EmptySquare;
8422 board[toY][toX] = king;
8423 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8424 board[fromY][BOARD_RGHT-1] = EmptySquare;
8425 } else if (board[fromY][fromX] == king
8426 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8427 && toY == fromY && toX < fromX-1) {
8428 board[fromY][fromX] = EmptySquare;
8429 board[toY][toX] = king;
8430 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8431 board[fromY][BOARD_LEFT] = EmptySquare;
8432 } else if (fromY == 7 && fromX == 3
8433 && board[fromY][fromX] == BlackKing
8434 && toY == 7 && toX == 5) {
8435 board[fromY][fromX] = EmptySquare;
8436 board[toY][toX] = BlackKing;
8437 board[fromY][7] = EmptySquare;
8438 board[toY][4] = BlackRook;
8439 } else if (fromY == 7 && fromX == 3
8440 && board[fromY][fromX] == BlackKing
8441 && toY == 7 && toX == 1) {
8442 board[fromY][fromX] = EmptySquare;
8443 board[toY][toX] = BlackKing;
8444 board[fromY][0] = EmptySquare;
8445 board[toY][2] = BlackRook;
8446 } else if (board[fromY][fromX] == BlackPawn
8448 && gameInfo.variant != VariantXiangqi
8450 /* black pawn promotion */
8451 board[toY][toX] = CharToPiece(ToLower(promoChar));
8452 if (board[toY][toX] == EmptySquare) {
8453 board[toY][toX] = BlackQueen;
8455 if(gameInfo.variant==VariantBughouse ||
8456 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8457 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8458 board[fromY][fromX] = EmptySquare;
8459 } else if ((fromY == 3)
8461 && gameInfo.variant != VariantXiangqi
8462 && gameInfo.variant != VariantBerolina
8463 && (board[fromY][fromX] == BlackPawn)
8464 && (board[toY][toX] == EmptySquare)) {
8465 board[fromY][fromX] = EmptySquare;
8466 board[toY][toX] = BlackPawn;
8467 captured = board[toY + 1][toX];
8468 board[toY + 1][toX] = EmptySquare;
8469 } else if ((fromY == 3)
8471 && gameInfo.variant == VariantBerolina
8472 && (board[fromY][fromX] == BlackPawn)
8473 && (board[toY][toX] == EmptySquare)) {
8474 board[fromY][fromX] = EmptySquare;
8475 board[toY][toX] = BlackPawn;
8476 if(oldEP & EP_BEROLIN_A) {
8477 captured = board[fromY][fromX-1];
8478 board[fromY][fromX-1] = EmptySquare;
8479 }else{ captured = board[fromY][fromX+1];
8480 board[fromY][fromX+1] = EmptySquare;
8483 board[toY][toX] = board[fromY][fromX];
8484 board[fromY][fromX] = EmptySquare;
8487 /* [HGM] now we promote for Shogi, if needed */
8488 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8489 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8492 if (gameInfo.holdingsWidth != 0) {
8494 /* !!A lot more code needs to be written to support holdings */
8495 /* [HGM] OK, so I have written it. Holdings are stored in the */
8496 /* penultimate board files, so they are automaticlly stored */
8497 /* in the game history. */
8498 if (fromY == DROP_RANK) {
8499 /* Delete from holdings, by decreasing count */
8500 /* and erasing image if necessary */
8502 if(p < (int) BlackPawn) { /* white drop */
8503 p -= (int)WhitePawn;
8504 p = PieceToNumber((ChessSquare)p);
8505 if(p >= gameInfo.holdingsSize) p = 0;
8506 if(--board[p][BOARD_WIDTH-2] <= 0)
8507 board[p][BOARD_WIDTH-1] = EmptySquare;
8508 if((int)board[p][BOARD_WIDTH-2] < 0)
8509 board[p][BOARD_WIDTH-2] = 0;
8510 } else { /* black drop */
8511 p -= (int)BlackPawn;
8512 p = PieceToNumber((ChessSquare)p);
8513 if(p >= gameInfo.holdingsSize) p = 0;
8514 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8515 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8516 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8517 board[BOARD_HEIGHT-1-p][1] = 0;
8520 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8521 && gameInfo.variant != VariantBughouse ) {
8522 /* [HGM] holdings: Add to holdings, if holdings exist */
8523 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8524 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8525 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8528 if (p >= (int) BlackPawn) {
8529 p -= (int)BlackPawn;
8530 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8531 /* in Shogi restore piece to its original first */
8532 captured = (ChessSquare) (DEMOTED captured);
8535 p = PieceToNumber((ChessSquare)p);
8536 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8537 board[p][BOARD_WIDTH-2]++;
8538 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8540 p -= (int)WhitePawn;
8541 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8542 captured = (ChessSquare) (DEMOTED captured);
8545 p = PieceToNumber((ChessSquare)p);
8546 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8547 board[BOARD_HEIGHT-1-p][1]++;
8548 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8551 } else if (gameInfo.variant == VariantAtomic) {
8552 if (captured != EmptySquare) {
8554 for (y = toY-1; y <= toY+1; y++) {
8555 for (x = toX-1; x <= toX+1; x++) {
8556 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8557 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8558 board[y][x] = EmptySquare;
8562 board[toY][toX] = EmptySquare;
8565 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8566 /* [HGM] Shogi promotions */
8567 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8570 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8571 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8572 // [HGM] superchess: take promotion piece out of holdings
8573 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8574 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8575 if(!--board[k][BOARD_WIDTH-2])
8576 board[k][BOARD_WIDTH-1] = EmptySquare;
8578 if(!--board[BOARD_HEIGHT-1-k][1])
8579 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8585 /* Updates forwardMostMove */
8587 MakeMove(fromX, fromY, toX, toY, promoChar)
8588 int fromX, fromY, toX, toY;
8591 // forwardMostMove++; // [HGM] bare: moved downstream
8593 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8594 int timeLeft; static int lastLoadFlag=0; int king, piece;
8595 piece = boards[forwardMostMove][fromY][fromX];
8596 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8597 if(gameInfo.variant == VariantKnightmate)
8598 king += (int) WhiteUnicorn - (int) WhiteKing;
8599 if(forwardMostMove == 0) {
8601 fprintf(serverMoves, "%s;", second.tidy);
8602 fprintf(serverMoves, "%s;", first.tidy);
8603 if(!blackPlaysFirst)
8604 fprintf(serverMoves, "%s;", second.tidy);
8605 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8606 lastLoadFlag = loadFlag;
8608 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8609 // print castling suffix
8610 if( toY == fromY && piece == king ) {
8612 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8614 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8617 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8618 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8619 boards[forwardMostMove][toY][toX] == EmptySquare
8620 && fromX != toX && fromY != toY)
8621 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8623 if(promoChar != NULLCHAR)
8624 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8626 fprintf(serverMoves, "/%d/%d",
8627 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8628 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8629 else timeLeft = blackTimeRemaining/1000;
8630 fprintf(serverMoves, "/%d", timeLeft);
8632 fflush(serverMoves);
8635 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8636 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8640 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8641 if (commentList[forwardMostMove+1] != NULL) {
8642 free(commentList[forwardMostMove+1]);
8643 commentList[forwardMostMove+1] = NULL;
8645 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8646 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8647 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8648 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8649 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8650 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8651 gameInfo.result = GameUnfinished;
8652 if (gameInfo.resultDetails != NULL) {
8653 free(gameInfo.resultDetails);
8654 gameInfo.resultDetails = NULL;
8656 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8657 moveList[forwardMostMove - 1]);
8658 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8659 PosFlags(forwardMostMove - 1),
8660 fromY, fromX, toY, toX, promoChar,
8661 parseList[forwardMostMove - 1]);
8662 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8668 if(gameInfo.variant != VariantShogi)
8669 strcat(parseList[forwardMostMove - 1], "+");
8673 strcat(parseList[forwardMostMove - 1], "#");
8676 if (appData.debugMode) {
8677 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8682 /* Updates currentMove if not pausing */
8684 ShowMove(fromX, fromY, toX, toY)
8686 int instant = (gameMode == PlayFromGameFile) ?
8687 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8688 if(appData.noGUI) return;
8689 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8691 if (forwardMostMove == currentMove + 1) {
8692 AnimateMove(boards[forwardMostMove - 1],
8693 fromX, fromY, toX, toY);
8695 if (appData.highlightLastMove) {
8696 SetHighlights(fromX, fromY, toX, toY);
8699 currentMove = forwardMostMove;
8702 if (instant) return;
8704 DisplayMove(currentMove - 1);
8705 DrawPosition(FALSE, boards[currentMove]);
8706 DisplayBothClocks();
8707 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8710 void SendEgtPath(ChessProgramState *cps)
8711 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8712 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8714 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8717 char c, *q = name+1, *r, *s;
8719 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8720 while(*p && *p != ',') *q++ = *p++;
8722 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8723 strcmp(name, ",nalimov:") == 0 ) {
8724 // take nalimov path from the menu-changeable option first, if it is defined
8725 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8726 SendToProgram(buf,cps); // send egtbpath command for nalimov
8728 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8729 (s = StrStr(appData.egtFormats, name)) != NULL) {
8730 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8731 s = r = StrStr(s, ":") + 1; // beginning of path info
8732 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8733 c = *r; *r = 0; // temporarily null-terminate path info
8734 *--q = 0; // strip of trailig ':' from name
8735 sprintf(buf, "egtpath %s %s\n", name+1, s);
8737 SendToProgram(buf,cps); // send egtbpath command for this format
8739 if(*p == ',') p++; // read away comma to position for next format name
8744 InitChessProgram(cps, setup)
8745 ChessProgramState *cps;
8746 int setup; /* [HGM] needed to setup FRC opening position */
8748 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8749 if (appData.noChessProgram) return;
8750 hintRequested = FALSE;
8751 bookRequested = FALSE;
8753 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8754 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8755 if(cps->memSize) { /* [HGM] memory */
8756 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8757 SendToProgram(buf, cps);
8759 SendEgtPath(cps); /* [HGM] EGT */
8760 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8761 sprintf(buf, "cores %d\n", appData.smpCores);
8762 SendToProgram(buf, cps);
8765 SendToProgram(cps->initString, cps);
8766 if (gameInfo.variant != VariantNormal &&
8767 gameInfo.variant != VariantLoadable
8768 /* [HGM] also send variant if board size non-standard */
8769 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8771 char *v = VariantName(gameInfo.variant);
8772 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8773 /* [HGM] in protocol 1 we have to assume all variants valid */
8774 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8775 DisplayFatalError(buf, 0, 1);
8779 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8780 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8781 if( gameInfo.variant == VariantXiangqi )
8782 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8783 if( gameInfo.variant == VariantShogi )
8784 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8785 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8786 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8787 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8788 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8789 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8790 if( gameInfo.variant == VariantCourier )
8791 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8792 if( gameInfo.variant == VariantSuper )
8793 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8794 if( gameInfo.variant == VariantGreat )
8795 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8798 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8799 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8800 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8801 if(StrStr(cps->variants, b) == NULL) {
8802 // specific sized variant not known, check if general sizing allowed
8803 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8804 if(StrStr(cps->variants, "boardsize") == NULL) {
8805 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8806 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8807 DisplayFatalError(buf, 0, 1);
8810 /* [HGM] here we really should compare with the maximum supported board size */
8813 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8814 sprintf(buf, "variant %s\n", b);
8815 SendToProgram(buf, cps);
8817 currentlyInitializedVariant = gameInfo.variant;
8819 /* [HGM] send opening position in FRC to first engine */
8821 SendToProgram("force\n", cps);
8823 /* engine is now in force mode! Set flag to wake it up after first move. */
8824 setboardSpoiledMachineBlack = 1;
8828 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8829 SendToProgram(buf, cps);
8831 cps->maybeThinking = FALSE;
8832 cps->offeredDraw = 0;
8833 if (!appData.icsActive) {
8834 SendTimeControl(cps, movesPerSession, timeControl,
8835 timeIncrement, appData.searchDepth,
8838 if (appData.showThinking
8839 // [HGM] thinking: four options require thinking output to be sent
8840 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8842 SendToProgram("post\n", cps);
8844 SendToProgram("hard\n", cps);
8845 if (!appData.ponderNextMove) {
8846 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8847 it without being sure what state we are in first. "hard"
8848 is not a toggle, so that one is OK.
8850 SendToProgram("easy\n", cps);
8853 sprintf(buf, "ping %d\n", ++cps->lastPing);
8854 SendToProgram(buf, cps);
8856 cps->initDone = TRUE;
8861 StartChessProgram(cps)
8862 ChessProgramState *cps;
8867 if (appData.noChessProgram) return;
8868 cps->initDone = FALSE;
8870 if (strcmp(cps->host, "localhost") == 0) {
8871 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8872 } else if (*appData.remoteShell == NULLCHAR) {
8873 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8875 if (*appData.remoteUser == NULLCHAR) {
8876 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8879 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8880 cps->host, appData.remoteUser, cps->program);
8882 err = StartChildProcess(buf, "", &cps->pr);
8886 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8887 DisplayFatalError(buf, err, 1);
8893 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8894 if (cps->protocolVersion > 1) {
8895 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8896 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8897 cps->comboCnt = 0; // and values of combo boxes
8898 SendToProgram(buf, cps);
8900 SendToProgram("xboard\n", cps);
8906 TwoMachinesEventIfReady P((void))
8908 if (first.lastPing != first.lastPong) {
8909 DisplayMessage("", _("Waiting for first chess program"));
8910 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8913 if (second.lastPing != second.lastPong) {
8914 DisplayMessage("", _("Waiting for second chess program"));
8915 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8923 NextMatchGame P((void))
8925 int index; /* [HGM] autoinc: step load index during match */
8927 if (*appData.loadGameFile != NULLCHAR) {
8928 index = appData.loadGameIndex;
8929 if(index < 0) { // [HGM] autoinc
8930 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8931 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8933 LoadGameFromFile(appData.loadGameFile,
8935 appData.loadGameFile, FALSE);
8936 } else if (*appData.loadPositionFile != NULLCHAR) {
8937 index = appData.loadPositionIndex;
8938 if(index < 0) { // [HGM] autoinc
8939 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8940 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8942 LoadPositionFromFile(appData.loadPositionFile,
8944 appData.loadPositionFile);
8946 TwoMachinesEventIfReady();
8949 void UserAdjudicationEvent( int result )
8951 ChessMove gameResult = GameIsDrawn;
8954 gameResult = WhiteWins;
8956 else if( result < 0 ) {
8957 gameResult = BlackWins;
8960 if( gameMode == TwoMachinesPlay ) {
8961 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8966 // [HGM] save: calculate checksum of game to make games easily identifiable
8967 int StringCheckSum(char *s)
8970 if(s==NULL) return 0;
8971 while(*s) i = i*259 + *s++;
8978 for(i=backwardMostMove; i<forwardMostMove; i++) {
8979 sum += pvInfoList[i].depth;
8980 sum += StringCheckSum(parseList[i]);
8981 sum += StringCheckSum(commentList[i]);
8984 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8985 return sum + StringCheckSum(commentList[i]);
8986 } // end of save patch
8989 GameEnds(result, resultDetails, whosays)
8991 char *resultDetails;
8994 GameMode nextGameMode;
8996 char buf[MSG_SIZ], popupRequested = 0;
8998 if(endingGame) return; /* [HGM] crash: forbid recursion */
9000 if(twoBoards) { // [HGM] dual: switch back to one board
9001 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9002 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9004 if (appData.debugMode) {
9005 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9006 result, resultDetails ? resultDetails : "(null)", whosays);
9009 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9011 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9012 /* If we are playing on ICS, the server decides when the
9013 game is over, but the engine can offer to draw, claim
9017 if (appData.zippyPlay && first.initDone) {
9018 if (result == GameIsDrawn) {
9019 /* In case draw still needs to be claimed */
9020 SendToICS(ics_prefix);
9021 SendToICS("draw\n");
9022 } else if (StrCaseStr(resultDetails, "resign")) {
9023 SendToICS(ics_prefix);
9024 SendToICS("resign\n");
9028 endingGame = 0; /* [HGM] crash */
9032 /* If we're loading the game from a file, stop */
9033 if (whosays == GE_FILE) {
9034 (void) StopLoadGameTimer();
9038 /* Cancel draw offers */
9039 first.offeredDraw = second.offeredDraw = 0;
9041 /* If this is an ICS game, only ICS can really say it's done;
9042 if not, anyone can. */
9043 isIcsGame = (gameMode == IcsPlayingWhite ||
9044 gameMode == IcsPlayingBlack ||
9045 gameMode == IcsObserving ||
9046 gameMode == IcsExamining);
9048 if (!isIcsGame || whosays == GE_ICS) {
9049 /* OK -- not an ICS game, or ICS said it was done */
9051 if (!isIcsGame && !appData.noChessProgram)
9052 SetUserThinkingEnables();
9054 /* [HGM] if a machine claims the game end we verify this claim */
9055 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9056 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9058 ChessMove trueResult = (ChessMove) -1;
9060 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9061 first.twoMachinesColor[0] :
9062 second.twoMachinesColor[0] ;
9064 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9065 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9066 /* [HGM] verify: engine mate claims accepted if they were flagged */
9067 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9069 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9070 /* [HGM] verify: engine mate claims accepted if they were flagged */
9071 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9073 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9074 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9077 // now verify win claims, but not in drop games, as we don't understand those yet
9078 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9079 || gameInfo.variant == VariantGreat) &&
9080 (result == WhiteWins && claimer == 'w' ||
9081 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9082 if (appData.debugMode) {
9083 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9084 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9086 if(result != trueResult) {
9087 sprintf(buf, "False win claim: '%s'", resultDetails);
9088 result = claimer == 'w' ? BlackWins : WhiteWins;
9089 resultDetails = buf;
9092 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9093 && (forwardMostMove <= backwardMostMove ||
9094 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9095 (claimer=='b')==(forwardMostMove&1))
9097 /* [HGM] verify: draws that were not flagged are false claims */
9098 sprintf(buf, "False draw claim: '%s'", resultDetails);
9099 result = claimer == 'w' ? BlackWins : WhiteWins;
9100 resultDetails = buf;
9102 /* (Claiming a loss is accepted no questions asked!) */
9104 /* [HGM] bare: don't allow bare King to win */
9105 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9106 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9107 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9108 && result != GameIsDrawn)
9109 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9110 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9111 int p = (signed char)boards[forwardMostMove][i][j] - color;
9112 if(p >= 0 && p <= (int)WhiteKing) k++;
9114 if (appData.debugMode) {
9115 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9116 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9119 result = GameIsDrawn;
9120 sprintf(buf, "%s but bare king", resultDetails);
9121 resultDetails = buf;
9127 if(serverMoves != NULL && !loadFlag) { char c = '=';
9128 if(result==WhiteWins) c = '+';
9129 if(result==BlackWins) c = '-';
9130 if(resultDetails != NULL)
9131 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9133 if (resultDetails != NULL) {
9134 gameInfo.result = result;
9135 gameInfo.resultDetails = StrSave(resultDetails);
9137 /* display last move only if game was not loaded from file */
9138 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9139 DisplayMove(currentMove - 1);
9141 if (forwardMostMove != 0) {
9142 if (gameMode != PlayFromGameFile && gameMode != EditGame
9143 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9145 if (*appData.saveGameFile != NULLCHAR) {
9146 SaveGameToFile(appData.saveGameFile, TRUE);
9147 } else if (appData.autoSaveGames) {
9150 if (*appData.savePositionFile != NULLCHAR) {
9151 SavePositionToFile(appData.savePositionFile);
9156 /* Tell program how game ended in case it is learning */
9157 /* [HGM] Moved this to after saving the PGN, just in case */
9158 /* engine died and we got here through time loss. In that */
9159 /* case we will get a fatal error writing the pipe, which */
9160 /* would otherwise lose us the PGN. */
9161 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9162 /* output during GameEnds should never be fatal anymore */
9163 if (gameMode == MachinePlaysWhite ||
9164 gameMode == MachinePlaysBlack ||
9165 gameMode == TwoMachinesPlay ||
9166 gameMode == IcsPlayingWhite ||
9167 gameMode == IcsPlayingBlack ||
9168 gameMode == BeginningOfGame) {
9170 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9172 if (first.pr != NoProc) {
9173 SendToProgram(buf, &first);
9175 if (second.pr != NoProc &&
9176 gameMode == TwoMachinesPlay) {
9177 SendToProgram(buf, &second);
9182 if (appData.icsActive) {
9183 if (appData.quietPlay &&
9184 (gameMode == IcsPlayingWhite ||
9185 gameMode == IcsPlayingBlack)) {
9186 SendToICS(ics_prefix);
9187 SendToICS("set shout 1\n");
9189 nextGameMode = IcsIdle;
9190 ics_user_moved = FALSE;
9191 /* clean up premove. It's ugly when the game has ended and the
9192 * premove highlights are still on the board.
9196 ClearPremoveHighlights();
9197 DrawPosition(FALSE, boards[currentMove]);
9199 if (whosays == GE_ICS) {
9202 if (gameMode == IcsPlayingWhite)
9204 else if(gameMode == IcsPlayingBlack)
9208 if (gameMode == IcsPlayingBlack)
9210 else if(gameMode == IcsPlayingWhite)
9217 PlayIcsUnfinishedSound();
9220 } else if (gameMode == EditGame ||
9221 gameMode == PlayFromGameFile ||
9222 gameMode == AnalyzeMode ||
9223 gameMode == AnalyzeFile) {
9224 nextGameMode = gameMode;
9226 nextGameMode = EndOfGame;
9231 nextGameMode = gameMode;
9234 if (appData.noChessProgram) {
9235 gameMode = nextGameMode;
9237 endingGame = 0; /* [HGM] crash */
9242 /* Put first chess program into idle state */
9243 if (first.pr != NoProc &&
9244 (gameMode == MachinePlaysWhite ||
9245 gameMode == MachinePlaysBlack ||
9246 gameMode == TwoMachinesPlay ||
9247 gameMode == IcsPlayingWhite ||
9248 gameMode == IcsPlayingBlack ||
9249 gameMode == BeginningOfGame)) {
9250 SendToProgram("force\n", &first);
9251 if (first.usePing) {
9253 sprintf(buf, "ping %d\n", ++first.lastPing);
9254 SendToProgram(buf, &first);
9257 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9258 /* Kill off first chess program */
9259 if (first.isr != NULL)
9260 RemoveInputSource(first.isr);
9263 if (first.pr != NoProc) {
9265 DoSleep( appData.delayBeforeQuit );
9266 SendToProgram("quit\n", &first);
9267 DoSleep( appData.delayAfterQuit );
9268 DestroyChildProcess(first.pr, first.useSigterm);
9273 /* Put second chess program into idle state */
9274 if (second.pr != NoProc &&
9275 gameMode == TwoMachinesPlay) {
9276 SendToProgram("force\n", &second);
9277 if (second.usePing) {
9279 sprintf(buf, "ping %d\n", ++second.lastPing);
9280 SendToProgram(buf, &second);
9283 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9284 /* Kill off second chess program */
9285 if (second.isr != NULL)
9286 RemoveInputSource(second.isr);
9289 if (second.pr != NoProc) {
9290 DoSleep( appData.delayBeforeQuit );
9291 SendToProgram("quit\n", &second);
9292 DoSleep( appData.delayAfterQuit );
9293 DestroyChildProcess(second.pr, second.useSigterm);
9298 if (matchMode && gameMode == TwoMachinesPlay) {
9301 if (first.twoMachinesColor[0] == 'w') {
9308 if (first.twoMachinesColor[0] == 'b') {
9317 if (matchGame < appData.matchGames) {
9319 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9320 tmp = first.twoMachinesColor;
9321 first.twoMachinesColor = second.twoMachinesColor;
9322 second.twoMachinesColor = tmp;
9324 gameMode = nextGameMode;
9326 if(appData.matchPause>10000 || appData.matchPause<10)
9327 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9328 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9329 endingGame = 0; /* [HGM] crash */
9333 gameMode = nextGameMode;
9334 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9335 first.tidy, second.tidy,
9336 first.matchWins, second.matchWins,
9337 appData.matchGames - (first.matchWins + second.matchWins));
9338 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9341 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9342 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9344 gameMode = nextGameMode;
9346 endingGame = 0; /* [HGM] crash */
9347 if(popupRequested) DisplayFatalError(buf, 0, 0); // [HGM] crash: this call GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9350 /* Assumes program was just initialized (initString sent).
9351 Leaves program in force mode. */
9353 FeedMovesToProgram(cps, upto)
9354 ChessProgramState *cps;
9359 if (appData.debugMode)
9360 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9361 startedFromSetupPosition ? "position and " : "",
9362 backwardMostMove, upto, cps->which);
9363 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9364 // [HGM] variantswitch: make engine aware of new variant
9365 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9366 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9367 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9368 SendToProgram(buf, cps);
9369 currentlyInitializedVariant = gameInfo.variant;
9371 SendToProgram("force\n", cps);
9372 if (startedFromSetupPosition) {
9373 SendBoard(cps, backwardMostMove);
9374 if (appData.debugMode) {
9375 fprintf(debugFP, "feedMoves\n");
9378 for (i = backwardMostMove; i < upto; i++) {
9379 SendMoveToProgram(i, cps);
9385 ResurrectChessProgram()
9387 /* The chess program may have exited.
9388 If so, restart it and feed it all the moves made so far. */
9390 if (appData.noChessProgram || first.pr != NoProc) return;
9392 StartChessProgram(&first);
9393 InitChessProgram(&first, FALSE);
9394 FeedMovesToProgram(&first, currentMove);
9396 if (!first.sendTime) {
9397 /* can't tell gnuchess what its clock should read,
9398 so we bow to its notion. */
9400 timeRemaining[0][currentMove] = whiteTimeRemaining;
9401 timeRemaining[1][currentMove] = blackTimeRemaining;
9404 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9405 appData.icsEngineAnalyze) && first.analysisSupport) {
9406 SendToProgram("analyze\n", &first);
9407 first.analyzing = TRUE;
9420 if (appData.debugMode) {
9421 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9422 redraw, init, gameMode);
9424 CleanupTail(); // [HGM] vari: delete any stored variations
9425 pausing = pauseExamInvalid = FALSE;
9426 startedFromSetupPosition = blackPlaysFirst = FALSE;
9428 whiteFlag = blackFlag = FALSE;
9429 userOfferedDraw = FALSE;
9430 hintRequested = bookRequested = FALSE;
9431 first.maybeThinking = FALSE;
9432 second.maybeThinking = FALSE;
9433 first.bookSuspend = FALSE; // [HGM] book
9434 second.bookSuspend = FALSE;
9435 thinkOutput[0] = NULLCHAR;
9436 lastHint[0] = NULLCHAR;
9437 ClearGameInfo(&gameInfo);
9438 gameInfo.variant = StringToVariant(appData.variant);
9439 ics_user_moved = ics_clock_paused = FALSE;
9440 ics_getting_history = H_FALSE;
9442 white_holding[0] = black_holding[0] = NULLCHAR;
9443 ClearProgramStats();
9444 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9448 flipView = appData.flipView;
9449 ClearPremoveHighlights();
9451 alarmSounded = FALSE;
9453 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9454 if(appData.serverMovesName != NULL) {
9455 /* [HGM] prepare to make moves file for broadcasting */
9456 clock_t t = clock();
9457 if(serverMoves != NULL) fclose(serverMoves);
9458 serverMoves = fopen(appData.serverMovesName, "r");
9459 if(serverMoves != NULL) {
9460 fclose(serverMoves);
9461 /* delay 15 sec before overwriting, so all clients can see end */
9462 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9464 serverMoves = fopen(appData.serverMovesName, "w");
9468 gameMode = BeginningOfGame;
9470 if(appData.icsActive) gameInfo.variant = VariantNormal;
9471 currentMove = forwardMostMove = backwardMostMove = 0;
9472 InitPosition(redraw);
9473 for (i = 0; i < MAX_MOVES; i++) {
9474 if (commentList[i] != NULL) {
9475 free(commentList[i]);
9476 commentList[i] = NULL;
9480 timeRemaining[0][0] = whiteTimeRemaining;
9481 timeRemaining[1][0] = blackTimeRemaining;
9482 if (first.pr == NULL) {
9483 StartChessProgram(&first);
9486 InitChessProgram(&first, startedFromSetupPosition);
9489 DisplayMessage("", "");
9490 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9491 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9498 if (!AutoPlayOneMove())
9500 if (matchMode || appData.timeDelay == 0)
9502 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9504 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9513 int fromX, fromY, toX, toY;
9515 if (appData.debugMode) {
9516 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9519 if (gameMode != PlayFromGameFile)
9522 if (currentMove >= forwardMostMove) {
9523 gameMode = EditGame;
9526 /* [AS] Clear current move marker at the end of a game */
9527 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9532 toX = moveList[currentMove][2] - AAA;
9533 toY = moveList[currentMove][3] - ONE;
9535 if (moveList[currentMove][1] == '@') {
9536 if (appData.highlightLastMove) {
9537 SetHighlights(-1, -1, toX, toY);
9540 fromX = moveList[currentMove][0] - AAA;
9541 fromY = moveList[currentMove][1] - ONE;
9543 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9545 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9547 if (appData.highlightLastMove) {
9548 SetHighlights(fromX, fromY, toX, toY);
9551 DisplayMove(currentMove);
9552 SendMoveToProgram(currentMove++, &first);
9553 DisplayBothClocks();
9554 DrawPosition(FALSE, boards[currentMove]);
9555 // [HGM] PV info: always display, routine tests if empty
9556 DisplayComment(currentMove - 1, commentList[currentMove]);
9562 LoadGameOneMove(readAhead)
9563 ChessMove readAhead;
9565 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9566 char promoChar = NULLCHAR;
9571 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9572 gameMode != AnalyzeMode && gameMode != Training) {
9577 yyboardindex = forwardMostMove;
9578 if (readAhead != (ChessMove)0) {
9579 moveType = readAhead;
9581 if (gameFileFP == NULL)
9583 moveType = (ChessMove) yylex();
9589 if (appData.debugMode)
9590 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9593 /* append the comment but don't display it */
9594 AppendComment(currentMove, p, FALSE);
9597 case WhiteCapturesEnPassant:
9598 case BlackCapturesEnPassant:
9599 case WhitePromotionChancellor:
9600 case BlackPromotionChancellor:
9601 case WhitePromotionArchbishop:
9602 case BlackPromotionArchbishop:
9603 case WhitePromotionCentaur:
9604 case BlackPromotionCentaur:
9605 case WhitePromotionQueen:
9606 case BlackPromotionQueen:
9607 case WhitePromotionRook:
9608 case BlackPromotionRook:
9609 case WhitePromotionBishop:
9610 case BlackPromotionBishop:
9611 case WhitePromotionKnight:
9612 case BlackPromotionKnight:
9613 case WhitePromotionKing:
9614 case BlackPromotionKing:
9616 case WhiteKingSideCastle:
9617 case WhiteQueenSideCastle:
9618 case BlackKingSideCastle:
9619 case BlackQueenSideCastle:
9620 case WhiteKingSideCastleWild:
9621 case WhiteQueenSideCastleWild:
9622 case BlackKingSideCastleWild:
9623 case BlackQueenSideCastleWild:
9625 case WhiteHSideCastleFR:
9626 case WhiteASideCastleFR:
9627 case BlackHSideCastleFR:
9628 case BlackASideCastleFR:
9630 if (appData.debugMode)
9631 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9632 fromX = currentMoveString[0] - AAA;
9633 fromY = currentMoveString[1] - ONE;
9634 toX = currentMoveString[2] - AAA;
9635 toY = currentMoveString[3] - ONE;
9636 promoChar = currentMoveString[4];
9641 if (appData.debugMode)
9642 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9643 fromX = moveType == WhiteDrop ?
9644 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9645 (int) CharToPiece(ToLower(currentMoveString[0]));
9647 toX = currentMoveString[2] - AAA;
9648 toY = currentMoveString[3] - ONE;
9654 case GameUnfinished:
9655 if (appData.debugMode)
9656 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9657 p = strchr(yy_text, '{');
9658 if (p == NULL) p = strchr(yy_text, '(');
9661 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9663 q = strchr(p, *p == '{' ? '}' : ')');
9664 if (q != NULL) *q = NULLCHAR;
9667 GameEnds(moveType, p, GE_FILE);
9669 if (cmailMsgLoaded) {
9671 flipView = WhiteOnMove(currentMove);
9672 if (moveType == GameUnfinished) flipView = !flipView;
9673 if (appData.debugMode)
9674 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9678 case (ChessMove) 0: /* end of file */
9679 if (appData.debugMode)
9680 fprintf(debugFP, "Parser hit end of file\n");
9681 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9687 if (WhiteOnMove(currentMove)) {
9688 GameEnds(BlackWins, "Black mates", GE_FILE);
9690 GameEnds(WhiteWins, "White mates", GE_FILE);
9694 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9701 if (lastLoadGameStart == GNUChessGame) {
9702 /* GNUChessGames have numbers, but they aren't move numbers */
9703 if (appData.debugMode)
9704 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9705 yy_text, (int) moveType);
9706 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9708 /* else fall thru */
9713 /* Reached start of next game in file */
9714 if (appData.debugMode)
9715 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9716 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9722 if (WhiteOnMove(currentMove)) {
9723 GameEnds(BlackWins, "Black mates", GE_FILE);
9725 GameEnds(WhiteWins, "White mates", GE_FILE);
9729 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9735 case PositionDiagram: /* should not happen; ignore */
9736 case ElapsedTime: /* ignore */
9737 case NAG: /* ignore */
9738 if (appData.debugMode)
9739 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9740 yy_text, (int) moveType);
9741 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9744 if (appData.testLegality) {
9745 if (appData.debugMode)
9746 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9747 sprintf(move, _("Illegal move: %d.%s%s"),
9748 (forwardMostMove / 2) + 1,
9749 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9750 DisplayError(move, 0);
9753 if (appData.debugMode)
9754 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9755 yy_text, currentMoveString);
9756 fromX = currentMoveString[0] - AAA;
9757 fromY = currentMoveString[1] - ONE;
9758 toX = currentMoveString[2] - AAA;
9759 toY = currentMoveString[3] - ONE;
9760 promoChar = currentMoveString[4];
9765 if (appData.debugMode)
9766 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9767 sprintf(move, _("Ambiguous move: %d.%s%s"),
9768 (forwardMostMove / 2) + 1,
9769 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9770 DisplayError(move, 0);
9775 case ImpossibleMove:
9776 if (appData.debugMode)
9777 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9778 sprintf(move, _("Illegal move: %d.%s%s"),
9779 (forwardMostMove / 2) + 1,
9780 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9781 DisplayError(move, 0);
9787 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9788 DrawPosition(FALSE, boards[currentMove]);
9789 DisplayBothClocks();
9790 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9791 DisplayComment(currentMove - 1, commentList[currentMove]);
9793 (void) StopLoadGameTimer();
9795 cmailOldMove = forwardMostMove;
9798 /* currentMoveString is set as a side-effect of yylex */
9799 strcat(currentMoveString, "\n");
9800 strcpy(moveList[forwardMostMove], currentMoveString);
9802 thinkOutput[0] = NULLCHAR;
9803 MakeMove(fromX, fromY, toX, toY, promoChar);
9804 currentMove = forwardMostMove;
9809 /* Load the nth game from the given file */
9811 LoadGameFromFile(filename, n, title, useList)
9815 /*Boolean*/ int useList;
9820 if (strcmp(filename, "-") == 0) {
9824 f = fopen(filename, "rb");
9826 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9827 DisplayError(buf, errno);
9831 if (fseek(f, 0, 0) == -1) {
9832 /* f is not seekable; probably a pipe */
9835 if (useList && n == 0) {
9836 int error = GameListBuild(f);
9838 DisplayError(_("Cannot build game list"), error);
9839 } else if (!ListEmpty(&gameList) &&
9840 ((ListGame *) gameList.tailPred)->number > 1) {
9841 GameListPopUp(f, title);
9848 return LoadGame(f, n, title, FALSE);
9853 MakeRegisteredMove()
9855 int fromX, fromY, toX, toY;
9857 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9858 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9861 if (appData.debugMode)
9862 fprintf(debugFP, "Restoring %s for game %d\n",
9863 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9865 thinkOutput[0] = NULLCHAR;
9866 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9867 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9868 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9869 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9870 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9871 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9872 MakeMove(fromX, fromY, toX, toY, promoChar);
9873 ShowMove(fromX, fromY, toX, toY);
9875 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9882 if (WhiteOnMove(currentMove)) {
9883 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9885 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9890 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9897 if (WhiteOnMove(currentMove)) {
9898 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9900 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9905 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9916 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9918 CmailLoadGame(f, gameNumber, title, useList)
9926 if (gameNumber > nCmailGames) {
9927 DisplayError(_("No more games in this message"), 0);
9930 if (f == lastLoadGameFP) {
9931 int offset = gameNumber - lastLoadGameNumber;
9933 cmailMsg[0] = NULLCHAR;
9934 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9935 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9936 nCmailMovesRegistered--;
9938 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9939 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9940 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9943 if (! RegisterMove()) return FALSE;
9947 retVal = LoadGame(f, gameNumber, title, useList);
9949 /* Make move registered during previous look at this game, if any */
9950 MakeRegisteredMove();
9952 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9953 commentList[currentMove]
9954 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9955 DisplayComment(currentMove - 1, commentList[currentMove]);
9961 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9966 int gameNumber = lastLoadGameNumber + offset;
9967 if (lastLoadGameFP == NULL) {
9968 DisplayError(_("No game has been loaded yet"), 0);
9971 if (gameNumber <= 0) {
9972 DisplayError(_("Can't back up any further"), 0);
9975 if (cmailMsgLoaded) {
9976 return CmailLoadGame(lastLoadGameFP, gameNumber,
9977 lastLoadGameTitle, lastLoadGameUseList);
9979 return LoadGame(lastLoadGameFP, gameNumber,
9980 lastLoadGameTitle, lastLoadGameUseList);
9986 /* Load the nth game from open file f */
9988 LoadGame(f, gameNumber, title, useList)
9996 int gn = gameNumber;
9997 ListGame *lg = NULL;
10000 GameMode oldGameMode;
10001 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10003 if (appData.debugMode)
10004 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10006 if (gameMode == Training )
10007 SetTrainingModeOff();
10009 oldGameMode = gameMode;
10010 if (gameMode != BeginningOfGame) {
10011 Reset(FALSE, TRUE);
10015 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10016 fclose(lastLoadGameFP);
10020 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10023 fseek(f, lg->offset, 0);
10024 GameListHighlight(gameNumber);
10028 DisplayError(_("Game number out of range"), 0);
10033 if (fseek(f, 0, 0) == -1) {
10034 if (f == lastLoadGameFP ?
10035 gameNumber == lastLoadGameNumber + 1 :
10039 DisplayError(_("Can't seek on game file"), 0);
10044 lastLoadGameFP = f;
10045 lastLoadGameNumber = gameNumber;
10046 strcpy(lastLoadGameTitle, title);
10047 lastLoadGameUseList = useList;
10051 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10052 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10053 lg->gameInfo.black);
10055 } else if (*title != NULLCHAR) {
10056 if (gameNumber > 1) {
10057 sprintf(buf, "%s %d", title, gameNumber);
10060 DisplayTitle(title);
10064 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10065 gameMode = PlayFromGameFile;
10069 currentMove = forwardMostMove = backwardMostMove = 0;
10070 CopyBoard(boards[0], initialPosition);
10074 * Skip the first gn-1 games in the file.
10075 * Also skip over anything that precedes an identifiable
10076 * start of game marker, to avoid being confused by
10077 * garbage at the start of the file. Currently
10078 * recognized start of game markers are the move number "1",
10079 * the pattern "gnuchess .* game", the pattern
10080 * "^[#;%] [^ ]* game file", and a PGN tag block.
10081 * A game that starts with one of the latter two patterns
10082 * will also have a move number 1, possibly
10083 * following a position diagram.
10084 * 5-4-02: Let's try being more lenient and allowing a game to
10085 * start with an unnumbered move. Does that break anything?
10087 cm = lastLoadGameStart = (ChessMove) 0;
10089 yyboardindex = forwardMostMove;
10090 cm = (ChessMove) yylex();
10092 case (ChessMove) 0:
10093 if (cmailMsgLoaded) {
10094 nCmailGames = CMAIL_MAX_GAMES - gn;
10097 DisplayError(_("Game not found in file"), 0);
10104 lastLoadGameStart = cm;
10107 case MoveNumberOne:
10108 switch (lastLoadGameStart) {
10113 case MoveNumberOne:
10114 case (ChessMove) 0:
10115 gn--; /* count this game */
10116 lastLoadGameStart = cm;
10125 switch (lastLoadGameStart) {
10128 case MoveNumberOne:
10129 case (ChessMove) 0:
10130 gn--; /* count this game */
10131 lastLoadGameStart = cm;
10134 lastLoadGameStart = cm; /* game counted already */
10142 yyboardindex = forwardMostMove;
10143 cm = (ChessMove) yylex();
10144 } while (cm == PGNTag || cm == Comment);
10151 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10152 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10153 != CMAIL_OLD_RESULT) {
10155 cmailResult[ CMAIL_MAX_GAMES
10156 - gn - 1] = CMAIL_OLD_RESULT;
10162 /* Only a NormalMove can be at the start of a game
10163 * without a position diagram. */
10164 if (lastLoadGameStart == (ChessMove) 0) {
10166 lastLoadGameStart = MoveNumberOne;
10175 if (appData.debugMode)
10176 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10178 if (cm == XBoardGame) {
10179 /* Skip any header junk before position diagram and/or move 1 */
10181 yyboardindex = forwardMostMove;
10182 cm = (ChessMove) yylex();
10184 if (cm == (ChessMove) 0 ||
10185 cm == GNUChessGame || cm == XBoardGame) {
10186 /* Empty game; pretend end-of-file and handle later */
10187 cm = (ChessMove) 0;
10191 if (cm == MoveNumberOne || cm == PositionDiagram ||
10192 cm == PGNTag || cm == Comment)
10195 } else if (cm == GNUChessGame) {
10196 if (gameInfo.event != NULL) {
10197 free(gameInfo.event);
10199 gameInfo.event = StrSave(yy_text);
10202 startedFromSetupPosition = FALSE;
10203 while (cm == PGNTag) {
10204 if (appData.debugMode)
10205 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10206 err = ParsePGNTag(yy_text, &gameInfo);
10207 if (!err) numPGNTags++;
10209 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10210 if(gameInfo.variant != oldVariant) {
10211 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10212 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10213 InitPosition(TRUE);
10214 oldVariant = gameInfo.variant;
10215 if (appData.debugMode)
10216 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10220 if (gameInfo.fen != NULL) {
10221 Board initial_position;
10222 startedFromSetupPosition = TRUE;
10223 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10225 DisplayError(_("Bad FEN position in file"), 0);
10228 CopyBoard(boards[0], initial_position);
10229 if (blackPlaysFirst) {
10230 currentMove = forwardMostMove = backwardMostMove = 1;
10231 CopyBoard(boards[1], initial_position);
10232 strcpy(moveList[0], "");
10233 strcpy(parseList[0], "");
10234 timeRemaining[0][1] = whiteTimeRemaining;
10235 timeRemaining[1][1] = blackTimeRemaining;
10236 if (commentList[0] != NULL) {
10237 commentList[1] = commentList[0];
10238 commentList[0] = NULL;
10241 currentMove = forwardMostMove = backwardMostMove = 0;
10243 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10245 initialRulePlies = FENrulePlies;
10246 for( i=0; i< nrCastlingRights; i++ )
10247 initialRights[i] = initial_position[CASTLING][i];
10249 yyboardindex = forwardMostMove;
10250 free(gameInfo.fen);
10251 gameInfo.fen = NULL;
10254 yyboardindex = forwardMostMove;
10255 cm = (ChessMove) yylex();
10257 /* Handle comments interspersed among the tags */
10258 while (cm == Comment) {
10260 if (appData.debugMode)
10261 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10263 AppendComment(currentMove, p, FALSE);
10264 yyboardindex = forwardMostMove;
10265 cm = (ChessMove) yylex();
10269 /* don't rely on existence of Event tag since if game was
10270 * pasted from clipboard the Event tag may not exist
10272 if (numPGNTags > 0){
10274 if (gameInfo.variant == VariantNormal) {
10275 VariantClass v = StringToVariant(gameInfo.event);
10276 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10277 if(v < VariantShogi) gameInfo.variant = v;
10280 if( appData.autoDisplayTags ) {
10281 tags = PGNTags(&gameInfo);
10282 TagsPopUp(tags, CmailMsg());
10287 /* Make something up, but don't display it now */
10292 if (cm == PositionDiagram) {
10295 Board initial_position;
10297 if (appData.debugMode)
10298 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10300 if (!startedFromSetupPosition) {
10302 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10303 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10313 initial_position[i][j++] = CharToPiece(*p);
10316 while (*p == ' ' || *p == '\t' ||
10317 *p == '\n' || *p == '\r') p++;
10319 if (strncmp(p, "black", strlen("black"))==0)
10320 blackPlaysFirst = TRUE;
10322 blackPlaysFirst = FALSE;
10323 startedFromSetupPosition = TRUE;
10325 CopyBoard(boards[0], initial_position);
10326 if (blackPlaysFirst) {
10327 currentMove = forwardMostMove = backwardMostMove = 1;
10328 CopyBoard(boards[1], initial_position);
10329 strcpy(moveList[0], "");
10330 strcpy(parseList[0], "");
10331 timeRemaining[0][1] = whiteTimeRemaining;
10332 timeRemaining[1][1] = blackTimeRemaining;
10333 if (commentList[0] != NULL) {
10334 commentList[1] = commentList[0];
10335 commentList[0] = NULL;
10338 currentMove = forwardMostMove = backwardMostMove = 0;
10341 yyboardindex = forwardMostMove;
10342 cm = (ChessMove) yylex();
10345 if (first.pr == NoProc) {
10346 StartChessProgram(&first);
10348 InitChessProgram(&first, FALSE);
10349 SendToProgram("force\n", &first);
10350 if (startedFromSetupPosition) {
10351 SendBoard(&first, forwardMostMove);
10352 if (appData.debugMode) {
10353 fprintf(debugFP, "Load Game\n");
10355 DisplayBothClocks();
10358 /* [HGM] server: flag to write setup moves in broadcast file as one */
10359 loadFlag = appData.suppressLoadMoves;
10361 while (cm == Comment) {
10363 if (appData.debugMode)
10364 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10366 AppendComment(currentMove, p, FALSE);
10367 yyboardindex = forwardMostMove;
10368 cm = (ChessMove) yylex();
10371 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10372 cm == WhiteWins || cm == BlackWins ||
10373 cm == GameIsDrawn || cm == GameUnfinished) {
10374 DisplayMessage("", _("No moves in game"));
10375 if (cmailMsgLoaded) {
10376 if (appData.debugMode)
10377 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10381 DrawPosition(FALSE, boards[currentMove]);
10382 DisplayBothClocks();
10383 gameMode = EditGame;
10390 // [HGM] PV info: routine tests if comment empty
10391 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10392 DisplayComment(currentMove - 1, commentList[currentMove]);
10394 if (!matchMode && appData.timeDelay != 0)
10395 DrawPosition(FALSE, boards[currentMove]);
10397 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10398 programStats.ok_to_send = 1;
10401 /* if the first token after the PGN tags is a move
10402 * and not move number 1, retrieve it from the parser
10404 if (cm != MoveNumberOne)
10405 LoadGameOneMove(cm);
10407 /* load the remaining moves from the file */
10408 while (LoadGameOneMove((ChessMove)0)) {
10409 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10410 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10413 /* rewind to the start of the game */
10414 currentMove = backwardMostMove;
10416 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10418 if (oldGameMode == AnalyzeFile ||
10419 oldGameMode == AnalyzeMode) {
10420 AnalyzeFileEvent();
10423 if (matchMode || appData.timeDelay == 0) {
10425 gameMode = EditGame;
10427 } else if (appData.timeDelay > 0) {
10428 AutoPlayGameLoop();
10431 if (appData.debugMode)
10432 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10434 loadFlag = 0; /* [HGM] true game starts */
10438 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10440 ReloadPosition(offset)
10443 int positionNumber = lastLoadPositionNumber + offset;
10444 if (lastLoadPositionFP == NULL) {
10445 DisplayError(_("No position has been loaded yet"), 0);
10448 if (positionNumber <= 0) {
10449 DisplayError(_("Can't back up any further"), 0);
10452 return LoadPosition(lastLoadPositionFP, positionNumber,
10453 lastLoadPositionTitle);
10456 /* Load the nth position from the given file */
10458 LoadPositionFromFile(filename, n, title)
10466 if (strcmp(filename, "-") == 0) {
10467 return LoadPosition(stdin, n, "stdin");
10469 f = fopen(filename, "rb");
10471 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10472 DisplayError(buf, errno);
10475 return LoadPosition(f, n, title);
10480 /* Load the nth position from the given open file, and close it */
10482 LoadPosition(f, positionNumber, title)
10484 int positionNumber;
10487 char *p, line[MSG_SIZ];
10488 Board initial_position;
10489 int i, j, fenMode, pn;
10491 if (gameMode == Training )
10492 SetTrainingModeOff();
10494 if (gameMode != BeginningOfGame) {
10495 Reset(FALSE, TRUE);
10497 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10498 fclose(lastLoadPositionFP);
10500 if (positionNumber == 0) positionNumber = 1;
10501 lastLoadPositionFP = f;
10502 lastLoadPositionNumber = positionNumber;
10503 strcpy(lastLoadPositionTitle, title);
10504 if (first.pr == NoProc) {
10505 StartChessProgram(&first);
10506 InitChessProgram(&first, FALSE);
10508 pn = positionNumber;
10509 if (positionNumber < 0) {
10510 /* Negative position number means to seek to that byte offset */
10511 if (fseek(f, -positionNumber, 0) == -1) {
10512 DisplayError(_("Can't seek on position file"), 0);
10517 if (fseek(f, 0, 0) == -1) {
10518 if (f == lastLoadPositionFP ?
10519 positionNumber == lastLoadPositionNumber + 1 :
10520 positionNumber == 1) {
10523 DisplayError(_("Can't seek on position file"), 0);
10528 /* See if this file is FEN or old-style xboard */
10529 if (fgets(line, MSG_SIZ, f) == NULL) {
10530 DisplayError(_("Position not found in file"), 0);
10533 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10534 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10537 if (fenMode || line[0] == '#') pn--;
10539 /* skip positions before number pn */
10540 if (fgets(line, MSG_SIZ, f) == NULL) {
10542 DisplayError(_("Position not found in file"), 0);
10545 if (fenMode || line[0] == '#') pn--;
10550 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10551 DisplayError(_("Bad FEN position in file"), 0);
10555 (void) fgets(line, MSG_SIZ, f);
10556 (void) fgets(line, MSG_SIZ, f);
10558 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10559 (void) fgets(line, MSG_SIZ, f);
10560 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10563 initial_position[i][j++] = CharToPiece(*p);
10567 blackPlaysFirst = FALSE;
10569 (void) fgets(line, MSG_SIZ, f);
10570 if (strncmp(line, "black", strlen("black"))==0)
10571 blackPlaysFirst = TRUE;
10574 startedFromSetupPosition = TRUE;
10576 SendToProgram("force\n", &first);
10577 CopyBoard(boards[0], initial_position);
10578 if (blackPlaysFirst) {
10579 currentMove = forwardMostMove = backwardMostMove = 1;
10580 strcpy(moveList[0], "");
10581 strcpy(parseList[0], "");
10582 CopyBoard(boards[1], initial_position);
10583 DisplayMessage("", _("Black to play"));
10585 currentMove = forwardMostMove = backwardMostMove = 0;
10586 DisplayMessage("", _("White to play"));
10588 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10589 SendBoard(&first, forwardMostMove);
10590 if (appData.debugMode) {
10592 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10593 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10594 fprintf(debugFP, "Load Position\n");
10597 if (positionNumber > 1) {
10598 sprintf(line, "%s %d", title, positionNumber);
10599 DisplayTitle(line);
10601 DisplayTitle(title);
10603 gameMode = EditGame;
10606 timeRemaining[0][1] = whiteTimeRemaining;
10607 timeRemaining[1][1] = blackTimeRemaining;
10608 DrawPosition(FALSE, boards[currentMove]);
10615 CopyPlayerNameIntoFileName(dest, src)
10618 while (*src != NULLCHAR && *src != ',') {
10623 *(*dest)++ = *src++;
10628 char *DefaultFileName(ext)
10631 static char def[MSG_SIZ];
10634 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10636 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10638 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10647 /* Save the current game to the given file */
10649 SaveGameToFile(filename, append)
10656 if (strcmp(filename, "-") == 0) {
10657 return SaveGame(stdout, 0, NULL);
10659 f = fopen(filename, append ? "a" : "w");
10661 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10662 DisplayError(buf, errno);
10665 return SaveGame(f, 0, NULL);
10674 static char buf[MSG_SIZ];
10677 p = strchr(str, ' ');
10678 if (p == NULL) return str;
10679 strncpy(buf, str, p - str);
10680 buf[p - str] = NULLCHAR;
10684 #define PGN_MAX_LINE 75
10686 #define PGN_SIDE_WHITE 0
10687 #define PGN_SIDE_BLACK 1
10690 static int FindFirstMoveOutOfBook( int side )
10694 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10695 int index = backwardMostMove;
10696 int has_book_hit = 0;
10698 if( (index % 2) != side ) {
10702 while( index < forwardMostMove ) {
10703 /* Check to see if engine is in book */
10704 int depth = pvInfoList[index].depth;
10705 int score = pvInfoList[index].score;
10711 else if( score == 0 && depth == 63 ) {
10712 in_book = 1; /* Zappa */
10714 else if( score == 2 && depth == 99 ) {
10715 in_book = 1; /* Abrok */
10718 has_book_hit += in_book;
10734 void GetOutOfBookInfo( char * buf )
10738 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10740 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10741 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10745 if( oob[0] >= 0 || oob[1] >= 0 ) {
10746 for( i=0; i<2; i++ ) {
10750 if( i > 0 && oob[0] >= 0 ) {
10751 strcat( buf, " " );
10754 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10755 sprintf( buf+strlen(buf), "%s%.2f",
10756 pvInfoList[idx].score >= 0 ? "+" : "",
10757 pvInfoList[idx].score / 100.0 );
10763 /* Save game in PGN style and close the file */
10768 int i, offset, linelen, newblock;
10772 int movelen, numlen, blank;
10773 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10775 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10777 tm = time((time_t *) NULL);
10779 PrintPGNTags(f, &gameInfo);
10781 if (backwardMostMove > 0 || startedFromSetupPosition) {
10782 char *fen = PositionToFEN(backwardMostMove, NULL);
10783 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10784 fprintf(f, "\n{--------------\n");
10785 PrintPosition(f, backwardMostMove);
10786 fprintf(f, "--------------}\n");
10790 /* [AS] Out of book annotation */
10791 if( appData.saveOutOfBookInfo ) {
10794 GetOutOfBookInfo( buf );
10796 if( buf[0] != '\0' ) {
10797 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10804 i = backwardMostMove;
10808 while (i < forwardMostMove) {
10809 /* Print comments preceding this move */
10810 if (commentList[i] != NULL) {
10811 if (linelen > 0) fprintf(f, "\n");
10812 fprintf(f, "%s", commentList[i]);
10817 /* Format move number */
10818 if ((i % 2) == 0) {
10819 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10822 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10824 numtext[0] = NULLCHAR;
10827 numlen = strlen(numtext);
10830 /* Print move number */
10831 blank = linelen > 0 && numlen > 0;
10832 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10841 fprintf(f, "%s", numtext);
10845 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10846 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10849 blank = linelen > 0 && movelen > 0;
10850 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10859 fprintf(f, "%s", move_buffer);
10860 linelen += movelen;
10862 /* [AS] Add PV info if present */
10863 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10864 /* [HGM] add time */
10865 char buf[MSG_SIZ]; int seconds;
10867 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10869 if( seconds <= 0) buf[0] = 0; else
10870 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10871 seconds = (seconds + 4)/10; // round to full seconds
10872 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10873 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10876 sprintf( move_buffer, "{%s%.2f/%d%s}",
10877 pvInfoList[i].score >= 0 ? "+" : "",
10878 pvInfoList[i].score / 100.0,
10879 pvInfoList[i].depth,
10882 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10884 /* Print score/depth */
10885 blank = linelen > 0 && movelen > 0;
10886 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10895 fprintf(f, "%s", move_buffer);
10896 linelen += movelen;
10902 /* Start a new line */
10903 if (linelen > 0) fprintf(f, "\n");
10905 /* Print comments after last move */
10906 if (commentList[i] != NULL) {
10907 fprintf(f, "%s\n", commentList[i]);
10911 if (gameInfo.resultDetails != NULL &&
10912 gameInfo.resultDetails[0] != NULLCHAR) {
10913 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10914 PGNResult(gameInfo.result));
10916 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10920 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10924 /* Save game in old style and close the file */
10926 SaveGameOldStyle(f)
10932 tm = time((time_t *) NULL);
10934 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10937 if (backwardMostMove > 0 || startedFromSetupPosition) {
10938 fprintf(f, "\n[--------------\n");
10939 PrintPosition(f, backwardMostMove);
10940 fprintf(f, "--------------]\n");
10945 i = backwardMostMove;
10946 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10948 while (i < forwardMostMove) {
10949 if (commentList[i] != NULL) {
10950 fprintf(f, "[%s]\n", commentList[i]);
10953 if ((i % 2) == 1) {
10954 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10957 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10959 if (commentList[i] != NULL) {
10963 if (i >= forwardMostMove) {
10967 fprintf(f, "%s\n", parseList[i]);
10972 if (commentList[i] != NULL) {
10973 fprintf(f, "[%s]\n", commentList[i]);
10976 /* This isn't really the old style, but it's close enough */
10977 if (gameInfo.resultDetails != NULL &&
10978 gameInfo.resultDetails[0] != NULLCHAR) {
10979 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10980 gameInfo.resultDetails);
10982 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10989 /* Save the current game to open file f and close the file */
10991 SaveGame(f, dummy, dummy2)
10996 if (gameMode == EditPosition) EditPositionDone(TRUE);
10997 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10998 if (appData.oldSaveStyle)
10999 return SaveGameOldStyle(f);
11001 return SaveGamePGN(f);
11004 /* Save the current position to the given file */
11006 SavePositionToFile(filename)
11012 if (strcmp(filename, "-") == 0) {
11013 return SavePosition(stdout, 0, NULL);
11015 f = fopen(filename, "a");
11017 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11018 DisplayError(buf, errno);
11021 SavePosition(f, 0, NULL);
11027 /* Save the current position to the given open file and close the file */
11029 SavePosition(f, dummy, dummy2)
11037 if (gameMode == EditPosition) EditPositionDone(TRUE);
11038 if (appData.oldSaveStyle) {
11039 tm = time((time_t *) NULL);
11041 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11043 fprintf(f, "[--------------\n");
11044 PrintPosition(f, currentMove);
11045 fprintf(f, "--------------]\n");
11047 fen = PositionToFEN(currentMove, NULL);
11048 fprintf(f, "%s\n", fen);
11056 ReloadCmailMsgEvent(unregister)
11060 static char *inFilename = NULL;
11061 static char *outFilename;
11063 struct stat inbuf, outbuf;
11066 /* Any registered moves are unregistered if unregister is set, */
11067 /* i.e. invoked by the signal handler */
11069 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11070 cmailMoveRegistered[i] = FALSE;
11071 if (cmailCommentList[i] != NULL) {
11072 free(cmailCommentList[i]);
11073 cmailCommentList[i] = NULL;
11076 nCmailMovesRegistered = 0;
11079 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11080 cmailResult[i] = CMAIL_NOT_RESULT;
11084 if (inFilename == NULL) {
11085 /* Because the filenames are static they only get malloced once */
11086 /* and they never get freed */
11087 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11088 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11090 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11091 sprintf(outFilename, "%s.out", appData.cmailGameName);
11094 status = stat(outFilename, &outbuf);
11096 cmailMailedMove = FALSE;
11098 status = stat(inFilename, &inbuf);
11099 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11102 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11103 counts the games, notes how each one terminated, etc.
11105 It would be nice to remove this kludge and instead gather all
11106 the information while building the game list. (And to keep it
11107 in the game list nodes instead of having a bunch of fixed-size
11108 parallel arrays.) Note this will require getting each game's
11109 termination from the PGN tags, as the game list builder does
11110 not process the game moves. --mann
11112 cmailMsgLoaded = TRUE;
11113 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11115 /* Load first game in the file or popup game menu */
11116 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11118 #endif /* !WIN32 */
11126 char string[MSG_SIZ];
11128 if ( cmailMailedMove
11129 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11130 return TRUE; /* Allow free viewing */
11133 /* Unregister move to ensure that we don't leave RegisterMove */
11134 /* with the move registered when the conditions for registering no */
11136 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11137 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11138 nCmailMovesRegistered --;
11140 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11142 free(cmailCommentList[lastLoadGameNumber - 1]);
11143 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11147 if (cmailOldMove == -1) {
11148 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11152 if (currentMove > cmailOldMove + 1) {
11153 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11157 if (currentMove < cmailOldMove) {
11158 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11162 if (forwardMostMove > currentMove) {
11163 /* Silently truncate extra moves */
11167 if ( (currentMove == cmailOldMove + 1)
11168 || ( (currentMove == cmailOldMove)
11169 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11170 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11171 if (gameInfo.result != GameUnfinished) {
11172 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11175 if (commentList[currentMove] != NULL) {
11176 cmailCommentList[lastLoadGameNumber - 1]
11177 = StrSave(commentList[currentMove]);
11179 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11181 if (appData.debugMode)
11182 fprintf(debugFP, "Saving %s for game %d\n",
11183 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11186 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11188 f = fopen(string, "w");
11189 if (appData.oldSaveStyle) {
11190 SaveGameOldStyle(f); /* also closes the file */
11192 sprintf(string, "%s.pos.out", appData.cmailGameName);
11193 f = fopen(string, "w");
11194 SavePosition(f, 0, NULL); /* also closes the file */
11196 fprintf(f, "{--------------\n");
11197 PrintPosition(f, currentMove);
11198 fprintf(f, "--------------}\n\n");
11200 SaveGame(f, 0, NULL); /* also closes the file*/
11203 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11204 nCmailMovesRegistered ++;
11205 } else if (nCmailGames == 1) {
11206 DisplayError(_("You have not made a move yet"), 0);
11217 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11218 FILE *commandOutput;
11219 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11220 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11226 if (! cmailMsgLoaded) {
11227 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11231 if (nCmailGames == nCmailResults) {
11232 DisplayError(_("No unfinished games"), 0);
11236 #if CMAIL_PROHIBIT_REMAIL
11237 if (cmailMailedMove) {
11238 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);
11239 DisplayError(msg, 0);
11244 if (! (cmailMailedMove || RegisterMove())) return;
11246 if ( cmailMailedMove
11247 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11248 sprintf(string, partCommandString,
11249 appData.debugMode ? " -v" : "", appData.cmailGameName);
11250 commandOutput = popen(string, "r");
11252 if (commandOutput == NULL) {
11253 DisplayError(_("Failed to invoke cmail"), 0);
11255 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11256 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11258 if (nBuffers > 1) {
11259 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11260 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11261 nBytes = MSG_SIZ - 1;
11263 (void) memcpy(msg, buffer, nBytes);
11265 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11267 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11268 cmailMailedMove = TRUE; /* Prevent >1 moves */
11271 for (i = 0; i < nCmailGames; i ++) {
11272 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11277 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11279 sprintf(buffer, "%s/%s.%s.archive",
11281 appData.cmailGameName,
11283 LoadGameFromFile(buffer, 1, buffer, FALSE);
11284 cmailMsgLoaded = FALSE;
11288 DisplayInformation(msg);
11289 pclose(commandOutput);
11292 if ((*cmailMsg) != '\0') {
11293 DisplayInformation(cmailMsg);
11298 #endif /* !WIN32 */
11307 int prependComma = 0;
11309 char string[MSG_SIZ]; /* Space for game-list */
11312 if (!cmailMsgLoaded) return "";
11314 if (cmailMailedMove) {
11315 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11317 /* Create a list of games left */
11318 sprintf(string, "[");
11319 for (i = 0; i < nCmailGames; i ++) {
11320 if (! ( cmailMoveRegistered[i]
11321 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11322 if (prependComma) {
11323 sprintf(number, ",%d", i + 1);
11325 sprintf(number, "%d", i + 1);
11329 strcat(string, number);
11332 strcat(string, "]");
11334 if (nCmailMovesRegistered + nCmailResults == 0) {
11335 switch (nCmailGames) {
11338 _("Still need to make move for game\n"));
11343 _("Still need to make moves for both games\n"));
11348 _("Still need to make moves for all %d games\n"),
11353 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11356 _("Still need to make a move for game %s\n"),
11361 if (nCmailResults == nCmailGames) {
11362 sprintf(cmailMsg, _("No unfinished games\n"));
11364 sprintf(cmailMsg, _("Ready to send mail\n"));
11370 _("Still need to make moves for games %s\n"),
11382 if (gameMode == Training)
11383 SetTrainingModeOff();
11386 cmailMsgLoaded = FALSE;
11387 if (appData.icsActive) {
11388 SendToICS(ics_prefix);
11389 SendToICS("refresh\n");
11399 /* Give up on clean exit */
11403 /* Keep trying for clean exit */
11407 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11409 if (telnetISR != NULL) {
11410 RemoveInputSource(telnetISR);
11412 if (icsPR != NoProc) {
11413 DestroyChildProcess(icsPR, TRUE);
11416 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11417 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11419 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11420 /* make sure this other one finishes before killing it! */
11421 if(endingGame) { int count = 0;
11422 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11423 while(endingGame && count++ < 10) DoSleep(1);
11424 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11427 /* Kill off chess programs */
11428 if (first.pr != NoProc) {
11431 DoSleep( appData.delayBeforeQuit );
11432 SendToProgram("quit\n", &first);
11433 DoSleep( appData.delayAfterQuit );
11434 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11436 if (second.pr != NoProc) {
11437 DoSleep( appData.delayBeforeQuit );
11438 SendToProgram("quit\n", &second);
11439 DoSleep( appData.delayAfterQuit );
11440 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11442 if (first.isr != NULL) {
11443 RemoveInputSource(first.isr);
11445 if (second.isr != NULL) {
11446 RemoveInputSource(second.isr);
11449 ShutDownFrontEnd();
11456 if (appData.debugMode)
11457 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11461 if (gameMode == MachinePlaysWhite ||
11462 gameMode == MachinePlaysBlack) {
11465 DisplayBothClocks();
11467 if (gameMode == PlayFromGameFile) {
11468 if (appData.timeDelay >= 0)
11469 AutoPlayGameLoop();
11470 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11471 Reset(FALSE, TRUE);
11472 SendToICS(ics_prefix);
11473 SendToICS("refresh\n");
11474 } else if (currentMove < forwardMostMove) {
11475 ForwardInner(forwardMostMove);
11477 pauseExamInvalid = FALSE;
11479 switch (gameMode) {
11483 pauseExamForwardMostMove = forwardMostMove;
11484 pauseExamInvalid = FALSE;
11487 case IcsPlayingWhite:
11488 case IcsPlayingBlack:
11492 case PlayFromGameFile:
11493 (void) StopLoadGameTimer();
11497 case BeginningOfGame:
11498 if (appData.icsActive) return;
11499 /* else fall through */
11500 case MachinePlaysWhite:
11501 case MachinePlaysBlack:
11502 case TwoMachinesPlay:
11503 if (forwardMostMove == 0)
11504 return; /* don't pause if no one has moved */
11505 if ((gameMode == MachinePlaysWhite &&
11506 !WhiteOnMove(forwardMostMove)) ||
11507 (gameMode == MachinePlaysBlack &&
11508 WhiteOnMove(forwardMostMove))) {
11521 char title[MSG_SIZ];
11523 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11524 strcpy(title, _("Edit comment"));
11526 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11527 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11528 parseList[currentMove - 1]);
11531 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11538 char *tags = PGNTags(&gameInfo);
11539 EditTagsPopUp(tags);
11546 if (appData.noChessProgram || gameMode == AnalyzeMode)
11549 if (gameMode != AnalyzeFile) {
11550 if (!appData.icsEngineAnalyze) {
11552 if (gameMode != EditGame) return;
11554 ResurrectChessProgram();
11555 SendToProgram("analyze\n", &first);
11556 first.analyzing = TRUE;
11557 /*first.maybeThinking = TRUE;*/
11558 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11559 EngineOutputPopUp();
11561 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11566 StartAnalysisClock();
11567 GetTimeMark(&lastNodeCountTime);
11574 if (appData.noChessProgram || gameMode == AnalyzeFile)
11577 if (gameMode != AnalyzeMode) {
11579 if (gameMode != EditGame) return;
11580 ResurrectChessProgram();
11581 SendToProgram("analyze\n", &first);
11582 first.analyzing = TRUE;
11583 /*first.maybeThinking = TRUE;*/
11584 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11585 EngineOutputPopUp();
11587 gameMode = AnalyzeFile;
11592 StartAnalysisClock();
11593 GetTimeMark(&lastNodeCountTime);
11598 MachineWhiteEvent()
11601 char *bookHit = NULL;
11603 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11607 if (gameMode == PlayFromGameFile ||
11608 gameMode == TwoMachinesPlay ||
11609 gameMode == Training ||
11610 gameMode == AnalyzeMode ||
11611 gameMode == EndOfGame)
11614 if (gameMode == EditPosition)
11615 EditPositionDone(TRUE);
11617 if (!WhiteOnMove(currentMove)) {
11618 DisplayError(_("It is not White's turn"), 0);
11622 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11625 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11626 gameMode == AnalyzeFile)
11629 ResurrectChessProgram(); /* in case it isn't running */
11630 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11631 gameMode = MachinePlaysWhite;
11634 gameMode = MachinePlaysWhite;
11638 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11640 if (first.sendName) {
11641 sprintf(buf, "name %s\n", gameInfo.black);
11642 SendToProgram(buf, &first);
11644 if (first.sendTime) {
11645 if (first.useColors) {
11646 SendToProgram("black\n", &first); /*gnu kludge*/
11648 SendTimeRemaining(&first, TRUE);
11650 if (first.useColors) {
11651 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11653 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11654 SetMachineThinkingEnables();
11655 first.maybeThinking = TRUE;
11659 if (appData.autoFlipView && !flipView) {
11660 flipView = !flipView;
11661 DrawPosition(FALSE, NULL);
11662 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11665 if(bookHit) { // [HGM] book: simulate book reply
11666 static char bookMove[MSG_SIZ]; // a bit generous?
11668 programStats.nodes = programStats.depth = programStats.time =
11669 programStats.score = programStats.got_only_move = 0;
11670 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11672 strcpy(bookMove, "move ");
11673 strcat(bookMove, bookHit);
11674 HandleMachineMove(bookMove, &first);
11679 MachineBlackEvent()
11682 char *bookHit = NULL;
11684 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11688 if (gameMode == PlayFromGameFile ||
11689 gameMode == TwoMachinesPlay ||
11690 gameMode == Training ||
11691 gameMode == AnalyzeMode ||
11692 gameMode == EndOfGame)
11695 if (gameMode == EditPosition)
11696 EditPositionDone(TRUE);
11698 if (WhiteOnMove(currentMove)) {
11699 DisplayError(_("It is not Black's turn"), 0);
11703 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11706 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11707 gameMode == AnalyzeFile)
11710 ResurrectChessProgram(); /* in case it isn't running */
11711 gameMode = MachinePlaysBlack;
11715 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11717 if (first.sendName) {
11718 sprintf(buf, "name %s\n", gameInfo.white);
11719 SendToProgram(buf, &first);
11721 if (first.sendTime) {
11722 if (first.useColors) {
11723 SendToProgram("white\n", &first); /*gnu kludge*/
11725 SendTimeRemaining(&first, FALSE);
11727 if (first.useColors) {
11728 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11730 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11731 SetMachineThinkingEnables();
11732 first.maybeThinking = TRUE;
11735 if (appData.autoFlipView && flipView) {
11736 flipView = !flipView;
11737 DrawPosition(FALSE, NULL);
11738 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11740 if(bookHit) { // [HGM] book: simulate book reply
11741 static char bookMove[MSG_SIZ]; // a bit generous?
11743 programStats.nodes = programStats.depth = programStats.time =
11744 programStats.score = programStats.got_only_move = 0;
11745 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11747 strcpy(bookMove, "move ");
11748 strcat(bookMove, bookHit);
11749 HandleMachineMove(bookMove, &first);
11755 DisplayTwoMachinesTitle()
11758 if (appData.matchGames > 0) {
11759 if (first.twoMachinesColor[0] == 'w') {
11760 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11761 gameInfo.white, gameInfo.black,
11762 first.matchWins, second.matchWins,
11763 matchGame - 1 - (first.matchWins + second.matchWins));
11765 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11766 gameInfo.white, gameInfo.black,
11767 second.matchWins, first.matchWins,
11768 matchGame - 1 - (first.matchWins + second.matchWins));
11771 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11777 TwoMachinesEvent P((void))
11781 ChessProgramState *onmove;
11782 char *bookHit = NULL;
11784 if (appData.noChessProgram) return;
11786 switch (gameMode) {
11787 case TwoMachinesPlay:
11789 case MachinePlaysWhite:
11790 case MachinePlaysBlack:
11791 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11792 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11796 case BeginningOfGame:
11797 case PlayFromGameFile:
11800 if (gameMode != EditGame) return;
11803 EditPositionDone(TRUE);
11814 // forwardMostMove = currentMove;
11815 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11816 ResurrectChessProgram(); /* in case first program isn't running */
11818 if (second.pr == NULL) {
11819 StartChessProgram(&second);
11820 if (second.protocolVersion == 1) {
11821 TwoMachinesEventIfReady();
11823 /* kludge: allow timeout for initial "feature" command */
11825 DisplayMessage("", _("Starting second chess program"));
11826 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11830 DisplayMessage("", "");
11831 InitChessProgram(&second, FALSE);
11832 SendToProgram("force\n", &second);
11833 if (startedFromSetupPosition) {
11834 SendBoard(&second, backwardMostMove);
11835 if (appData.debugMode) {
11836 fprintf(debugFP, "Two Machines\n");
11839 for (i = backwardMostMove; i < forwardMostMove; i++) {
11840 SendMoveToProgram(i, &second);
11843 gameMode = TwoMachinesPlay;
11847 DisplayTwoMachinesTitle();
11849 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11855 SendToProgram(first.computerString, &first);
11856 if (first.sendName) {
11857 sprintf(buf, "name %s\n", second.tidy);
11858 SendToProgram(buf, &first);
11860 SendToProgram(second.computerString, &second);
11861 if (second.sendName) {
11862 sprintf(buf, "name %s\n", first.tidy);
11863 SendToProgram(buf, &second);
11867 if (!first.sendTime || !second.sendTime) {
11868 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11869 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11871 if (onmove->sendTime) {
11872 if (onmove->useColors) {
11873 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11875 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11877 if (onmove->useColors) {
11878 SendToProgram(onmove->twoMachinesColor, onmove);
11880 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11881 // SendToProgram("go\n", onmove);
11882 onmove->maybeThinking = TRUE;
11883 SetMachineThinkingEnables();
11887 if(bookHit) { // [HGM] book: simulate book reply
11888 static char bookMove[MSG_SIZ]; // a bit generous?
11890 programStats.nodes = programStats.depth = programStats.time =
11891 programStats.score = programStats.got_only_move = 0;
11892 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11894 strcpy(bookMove, "move ");
11895 strcat(bookMove, bookHit);
11896 savedMessage = bookMove; // args for deferred call
11897 savedState = onmove;
11898 ScheduleDelayedEvent(DeferredBookMove, 1);
11905 if (gameMode == Training) {
11906 SetTrainingModeOff();
11907 gameMode = PlayFromGameFile;
11908 DisplayMessage("", _("Training mode off"));
11910 gameMode = Training;
11911 animateTraining = appData.animate;
11913 /* make sure we are not already at the end of the game */
11914 if (currentMove < forwardMostMove) {
11915 SetTrainingModeOn();
11916 DisplayMessage("", _("Training mode on"));
11918 gameMode = PlayFromGameFile;
11919 DisplayError(_("Already at end of game"), 0);
11928 if (!appData.icsActive) return;
11929 switch (gameMode) {
11930 case IcsPlayingWhite:
11931 case IcsPlayingBlack:
11934 case BeginningOfGame:
11942 EditPositionDone(TRUE);
11955 gameMode = IcsIdle;
11966 switch (gameMode) {
11968 SetTrainingModeOff();
11970 case MachinePlaysWhite:
11971 case MachinePlaysBlack:
11972 case BeginningOfGame:
11973 SendToProgram("force\n", &first);
11974 SetUserThinkingEnables();
11976 case PlayFromGameFile:
11977 (void) StopLoadGameTimer();
11978 if (gameFileFP != NULL) {
11983 EditPositionDone(TRUE);
11988 SendToProgram("force\n", &first);
11990 case TwoMachinesPlay:
11991 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11992 ResurrectChessProgram();
11993 SetUserThinkingEnables();
11996 ResurrectChessProgram();
11998 case IcsPlayingBlack:
11999 case IcsPlayingWhite:
12000 DisplayError(_("Warning: You are still playing a game"), 0);
12003 DisplayError(_("Warning: You are still observing a game"), 0);
12006 DisplayError(_("Warning: You are still examining a game"), 0);
12017 first.offeredDraw = second.offeredDraw = 0;
12019 if (gameMode == PlayFromGameFile) {
12020 whiteTimeRemaining = timeRemaining[0][currentMove];
12021 blackTimeRemaining = timeRemaining[1][currentMove];
12025 if (gameMode == MachinePlaysWhite ||
12026 gameMode == MachinePlaysBlack ||
12027 gameMode == TwoMachinesPlay ||
12028 gameMode == EndOfGame) {
12029 i = forwardMostMove;
12030 while (i > currentMove) {
12031 SendToProgram("undo\n", &first);
12034 whiteTimeRemaining = timeRemaining[0][currentMove];
12035 blackTimeRemaining = timeRemaining[1][currentMove];
12036 DisplayBothClocks();
12037 if (whiteFlag || blackFlag) {
12038 whiteFlag = blackFlag = 0;
12043 gameMode = EditGame;
12050 EditPositionEvent()
12052 if (gameMode == EditPosition) {
12058 if (gameMode != EditGame) return;
12060 gameMode = EditPosition;
12063 if (currentMove > 0)
12064 CopyBoard(boards[0], boards[currentMove]);
12066 blackPlaysFirst = !WhiteOnMove(currentMove);
12068 currentMove = forwardMostMove = backwardMostMove = 0;
12069 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12076 /* [DM] icsEngineAnalyze - possible call from other functions */
12077 if (appData.icsEngineAnalyze) {
12078 appData.icsEngineAnalyze = FALSE;
12080 DisplayMessage("",_("Close ICS engine analyze..."));
12082 if (first.analysisSupport && first.analyzing) {
12083 SendToProgram("exit\n", &first);
12084 first.analyzing = FALSE;
12086 thinkOutput[0] = NULLCHAR;
12090 EditPositionDone(Boolean fakeRights)
12092 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12094 startedFromSetupPosition = TRUE;
12095 InitChessProgram(&first, FALSE);
12096 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12097 boards[0][EP_STATUS] = EP_NONE;
12098 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12099 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12100 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12101 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12102 } else boards[0][CASTLING][2] = NoRights;
12103 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12104 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12105 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12106 } else boards[0][CASTLING][5] = NoRights;
12108 SendToProgram("force\n", &first);
12109 if (blackPlaysFirst) {
12110 strcpy(moveList[0], "");
12111 strcpy(parseList[0], "");
12112 currentMove = forwardMostMove = backwardMostMove = 1;
12113 CopyBoard(boards[1], boards[0]);
12115 currentMove = forwardMostMove = backwardMostMove = 0;
12117 SendBoard(&first, forwardMostMove);
12118 if (appData.debugMode) {
12119 fprintf(debugFP, "EditPosDone\n");
12122 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12123 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12124 gameMode = EditGame;
12126 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12127 ClearHighlights(); /* [AS] */
12130 /* Pause for `ms' milliseconds */
12131 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12141 } while (SubtractTimeMarks(&m2, &m1) < ms);
12144 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12146 SendMultiLineToICS(buf)
12149 char temp[MSG_SIZ+1], *p;
12156 strncpy(temp, buf, len);
12161 if (*p == '\n' || *p == '\r')
12166 strcat(temp, "\n");
12168 SendToPlayer(temp, strlen(temp));
12172 SetWhiteToPlayEvent()
12174 if (gameMode == EditPosition) {
12175 blackPlaysFirst = FALSE;
12176 DisplayBothClocks(); /* works because currentMove is 0 */
12177 } else if (gameMode == IcsExamining) {
12178 SendToICS(ics_prefix);
12179 SendToICS("tomove white\n");
12184 SetBlackToPlayEvent()
12186 if (gameMode == EditPosition) {
12187 blackPlaysFirst = TRUE;
12188 currentMove = 1; /* kludge */
12189 DisplayBothClocks();
12191 } else if (gameMode == IcsExamining) {
12192 SendToICS(ics_prefix);
12193 SendToICS("tomove black\n");
12198 EditPositionMenuEvent(selection, x, y)
12199 ChessSquare selection;
12203 ChessSquare piece = boards[0][y][x];
12205 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12207 switch (selection) {
12209 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12210 SendToICS(ics_prefix);
12211 SendToICS("bsetup clear\n");
12212 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12213 SendToICS(ics_prefix);
12214 SendToICS("clearboard\n");
12216 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12217 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12218 for (y = 0; y < BOARD_HEIGHT; y++) {
12219 if (gameMode == IcsExamining) {
12220 if (boards[currentMove][y][x] != EmptySquare) {
12221 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12226 boards[0][y][x] = p;
12231 if (gameMode == EditPosition) {
12232 DrawPosition(FALSE, boards[0]);
12237 SetWhiteToPlayEvent();
12241 SetBlackToPlayEvent();
12245 if (gameMode == IcsExamining) {
12246 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12247 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12250 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12251 if(x == BOARD_LEFT-2) {
12252 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12253 boards[0][y][1] = 0;
12255 if(x == BOARD_RGHT+1) {
12256 if(y >= gameInfo.holdingsSize) break;
12257 boards[0][y][BOARD_WIDTH-2] = 0;
12260 boards[0][y][x] = EmptySquare;
12261 DrawPosition(FALSE, boards[0]);
12266 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12267 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12268 selection = (ChessSquare) (PROMOTED piece);
12269 } else if(piece == EmptySquare) selection = WhiteSilver;
12270 else selection = (ChessSquare)((int)piece - 1);
12274 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12275 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12276 selection = (ChessSquare) (DEMOTED piece);
12277 } else if(piece == EmptySquare) selection = BlackSilver;
12278 else selection = (ChessSquare)((int)piece + 1);
12283 if(gameInfo.variant == VariantShatranj ||
12284 gameInfo.variant == VariantXiangqi ||
12285 gameInfo.variant == VariantCourier ||
12286 gameInfo.variant == VariantMakruk )
12287 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12292 if(gameInfo.variant == VariantXiangqi)
12293 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12294 if(gameInfo.variant == VariantKnightmate)
12295 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12298 if (gameMode == IcsExamining) {
12299 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12300 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12301 PieceToChar(selection), AAA + x, ONE + y);
12304 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12306 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12307 n = PieceToNumber(selection - BlackPawn);
12308 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12309 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12310 boards[0][BOARD_HEIGHT-1-n][1]++;
12312 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12313 n = PieceToNumber(selection);
12314 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12315 boards[0][n][BOARD_WIDTH-1] = selection;
12316 boards[0][n][BOARD_WIDTH-2]++;
12319 boards[0][y][x] = selection;
12320 DrawPosition(TRUE, boards[0]);
12328 DropMenuEvent(selection, x, y)
12329 ChessSquare selection;
12332 ChessMove moveType;
12334 switch (gameMode) {
12335 case IcsPlayingWhite:
12336 case MachinePlaysBlack:
12337 if (!WhiteOnMove(currentMove)) {
12338 DisplayMoveError(_("It is Black's turn"));
12341 moveType = WhiteDrop;
12343 case IcsPlayingBlack:
12344 case MachinePlaysWhite:
12345 if (WhiteOnMove(currentMove)) {
12346 DisplayMoveError(_("It is White's turn"));
12349 moveType = BlackDrop;
12352 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12358 if (moveType == BlackDrop && selection < BlackPawn) {
12359 selection = (ChessSquare) ((int) selection
12360 + (int) BlackPawn - (int) WhitePawn);
12362 if (boards[currentMove][y][x] != EmptySquare) {
12363 DisplayMoveError(_("That square is occupied"));
12367 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12373 /* Accept a pending offer of any kind from opponent */
12375 if (appData.icsActive) {
12376 SendToICS(ics_prefix);
12377 SendToICS("accept\n");
12378 } else if (cmailMsgLoaded) {
12379 if (currentMove == cmailOldMove &&
12380 commentList[cmailOldMove] != NULL &&
12381 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12382 "Black offers a draw" : "White offers a draw")) {
12384 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12385 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12387 DisplayError(_("There is no pending offer on this move"), 0);
12388 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12391 /* Not used for offers from chess program */
12398 /* Decline a pending offer of any kind from opponent */
12400 if (appData.icsActive) {
12401 SendToICS(ics_prefix);
12402 SendToICS("decline\n");
12403 } else if (cmailMsgLoaded) {
12404 if (currentMove == cmailOldMove &&
12405 commentList[cmailOldMove] != NULL &&
12406 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12407 "Black offers a draw" : "White offers a draw")) {
12409 AppendComment(cmailOldMove, "Draw declined", TRUE);
12410 DisplayComment(cmailOldMove - 1, "Draw declined");
12413 DisplayError(_("There is no pending offer on this move"), 0);
12416 /* Not used for offers from chess program */
12423 /* Issue ICS rematch command */
12424 if (appData.icsActive) {
12425 SendToICS(ics_prefix);
12426 SendToICS("rematch\n");
12433 /* Call your opponent's flag (claim a win on time) */
12434 if (appData.icsActive) {
12435 SendToICS(ics_prefix);
12436 SendToICS("flag\n");
12438 switch (gameMode) {
12441 case MachinePlaysWhite:
12444 GameEnds(GameIsDrawn, "Both players ran out of time",
12447 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12449 DisplayError(_("Your opponent is not out of time"), 0);
12452 case MachinePlaysBlack:
12455 GameEnds(GameIsDrawn, "Both players ran out of time",
12458 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12460 DisplayError(_("Your opponent is not out of time"), 0);
12470 /* Offer draw or accept pending draw offer from opponent */
12472 if (appData.icsActive) {
12473 /* Note: tournament rules require draw offers to be
12474 made after you make your move but before you punch
12475 your clock. Currently ICS doesn't let you do that;
12476 instead, you immediately punch your clock after making
12477 a move, but you can offer a draw at any time. */
12479 SendToICS(ics_prefix);
12480 SendToICS("draw\n");
12481 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12482 } else if (cmailMsgLoaded) {
12483 if (currentMove == cmailOldMove &&
12484 commentList[cmailOldMove] != NULL &&
12485 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12486 "Black offers a draw" : "White offers a draw")) {
12487 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12488 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12489 } else if (currentMove == cmailOldMove + 1) {
12490 char *offer = WhiteOnMove(cmailOldMove) ?
12491 "White offers a draw" : "Black offers a draw";
12492 AppendComment(currentMove, offer, TRUE);
12493 DisplayComment(currentMove - 1, offer);
12494 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12496 DisplayError(_("You must make your move before offering a draw"), 0);
12497 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12499 } else if (first.offeredDraw) {
12500 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12502 if (first.sendDrawOffers) {
12503 SendToProgram("draw\n", &first);
12504 userOfferedDraw = TRUE;
12512 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12514 if (appData.icsActive) {
12515 SendToICS(ics_prefix);
12516 SendToICS("adjourn\n");
12518 /* Currently GNU Chess doesn't offer or accept Adjourns */
12526 /* Offer Abort or accept pending Abort offer from opponent */
12528 if (appData.icsActive) {
12529 SendToICS(ics_prefix);
12530 SendToICS("abort\n");
12532 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12539 /* Resign. You can do this even if it's not your turn. */
12541 if (appData.icsActive) {
12542 SendToICS(ics_prefix);
12543 SendToICS("resign\n");
12545 switch (gameMode) {
12546 case MachinePlaysWhite:
12547 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12549 case MachinePlaysBlack:
12550 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12553 if (cmailMsgLoaded) {
12555 if (WhiteOnMove(cmailOldMove)) {
12556 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12558 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12560 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12571 StopObservingEvent()
12573 /* Stop observing current games */
12574 SendToICS(ics_prefix);
12575 SendToICS("unobserve\n");
12579 StopExaminingEvent()
12581 /* Stop observing current game */
12582 SendToICS(ics_prefix);
12583 SendToICS("unexamine\n");
12587 ForwardInner(target)
12592 if (appData.debugMode)
12593 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12594 target, currentMove, forwardMostMove);
12596 if (gameMode == EditPosition)
12599 if (gameMode == PlayFromGameFile && !pausing)
12602 if (gameMode == IcsExamining && pausing)
12603 limit = pauseExamForwardMostMove;
12605 limit = forwardMostMove;
12607 if (target > limit) target = limit;
12609 if (target > 0 && moveList[target - 1][0]) {
12610 int fromX, fromY, toX, toY;
12611 toX = moveList[target - 1][2] - AAA;
12612 toY = moveList[target - 1][3] - ONE;
12613 if (moveList[target - 1][1] == '@') {
12614 if (appData.highlightLastMove) {
12615 SetHighlights(-1, -1, toX, toY);
12618 fromX = moveList[target - 1][0] - AAA;
12619 fromY = moveList[target - 1][1] - ONE;
12620 if (target == currentMove + 1) {
12621 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12623 if (appData.highlightLastMove) {
12624 SetHighlights(fromX, fromY, toX, toY);
12628 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12629 gameMode == Training || gameMode == PlayFromGameFile ||
12630 gameMode == AnalyzeFile) {
12631 while (currentMove < target) {
12632 SendMoveToProgram(currentMove++, &first);
12635 currentMove = target;
12638 if (gameMode == EditGame || gameMode == EndOfGame) {
12639 whiteTimeRemaining = timeRemaining[0][currentMove];
12640 blackTimeRemaining = timeRemaining[1][currentMove];
12642 DisplayBothClocks();
12643 DisplayMove(currentMove - 1);
12644 DrawPosition(FALSE, boards[currentMove]);
12645 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12646 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12647 DisplayComment(currentMove - 1, commentList[currentMove]);
12655 if (gameMode == IcsExamining && !pausing) {
12656 SendToICS(ics_prefix);
12657 SendToICS("forward\n");
12659 ForwardInner(currentMove + 1);
12666 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12667 /* to optimze, we temporarily turn off analysis mode while we feed
12668 * the remaining moves to the engine. Otherwise we get analysis output
12671 if (first.analysisSupport) {
12672 SendToProgram("exit\nforce\n", &first);
12673 first.analyzing = FALSE;
12677 if (gameMode == IcsExamining && !pausing) {
12678 SendToICS(ics_prefix);
12679 SendToICS("forward 999999\n");
12681 ForwardInner(forwardMostMove);
12684 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12685 /* we have fed all the moves, so reactivate analysis mode */
12686 SendToProgram("analyze\n", &first);
12687 first.analyzing = TRUE;
12688 /*first.maybeThinking = TRUE;*/
12689 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12694 BackwardInner(target)
12697 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12699 if (appData.debugMode)
12700 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12701 target, currentMove, forwardMostMove);
12703 if (gameMode == EditPosition) return;
12704 if (currentMove <= backwardMostMove) {
12706 DrawPosition(full_redraw, boards[currentMove]);
12709 if (gameMode == PlayFromGameFile && !pausing)
12712 if (moveList[target][0]) {
12713 int fromX, fromY, toX, toY;
12714 toX = moveList[target][2] - AAA;
12715 toY = moveList[target][3] - ONE;
12716 if (moveList[target][1] == '@') {
12717 if (appData.highlightLastMove) {
12718 SetHighlights(-1, -1, toX, toY);
12721 fromX = moveList[target][0] - AAA;
12722 fromY = moveList[target][1] - ONE;
12723 if (target == currentMove - 1) {
12724 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12726 if (appData.highlightLastMove) {
12727 SetHighlights(fromX, fromY, toX, toY);
12731 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12732 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12733 while (currentMove > target) {
12734 SendToProgram("undo\n", &first);
12738 currentMove = target;
12741 if (gameMode == EditGame || gameMode == EndOfGame) {
12742 whiteTimeRemaining = timeRemaining[0][currentMove];
12743 blackTimeRemaining = timeRemaining[1][currentMove];
12745 DisplayBothClocks();
12746 DisplayMove(currentMove - 1);
12747 DrawPosition(full_redraw, boards[currentMove]);
12748 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12749 // [HGM] PV info: routine tests if comment empty
12750 DisplayComment(currentMove - 1, commentList[currentMove]);
12756 if (gameMode == IcsExamining && !pausing) {
12757 SendToICS(ics_prefix);
12758 SendToICS("backward\n");
12760 BackwardInner(currentMove - 1);
12767 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12768 /* to optimize, we temporarily turn off analysis mode while we undo
12769 * all the moves. Otherwise we get analysis output after each undo.
12771 if (first.analysisSupport) {
12772 SendToProgram("exit\nforce\n", &first);
12773 first.analyzing = FALSE;
12777 if (gameMode == IcsExamining && !pausing) {
12778 SendToICS(ics_prefix);
12779 SendToICS("backward 999999\n");
12781 BackwardInner(backwardMostMove);
12784 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12785 /* we have fed all the moves, so reactivate analysis mode */
12786 SendToProgram("analyze\n", &first);
12787 first.analyzing = TRUE;
12788 /*first.maybeThinking = TRUE;*/
12789 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12796 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12797 if (to >= forwardMostMove) to = forwardMostMove;
12798 if (to <= backwardMostMove) to = backwardMostMove;
12799 if (to < currentMove) {
12807 RevertEvent(Boolean annotate)
12809 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12812 if (gameMode != IcsExamining) {
12813 DisplayError(_("You are not examining a game"), 0);
12817 DisplayError(_("You can't revert while pausing"), 0);
12820 SendToICS(ics_prefix);
12821 SendToICS("revert\n");
12827 switch (gameMode) {
12828 case MachinePlaysWhite:
12829 case MachinePlaysBlack:
12830 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12831 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12834 if (forwardMostMove < 2) return;
12835 currentMove = forwardMostMove = forwardMostMove - 2;
12836 whiteTimeRemaining = timeRemaining[0][currentMove];
12837 blackTimeRemaining = timeRemaining[1][currentMove];
12838 DisplayBothClocks();
12839 DisplayMove(currentMove - 1);
12840 ClearHighlights();/*!! could figure this out*/
12841 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12842 SendToProgram("remove\n", &first);
12843 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12846 case BeginningOfGame:
12850 case IcsPlayingWhite:
12851 case IcsPlayingBlack:
12852 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12853 SendToICS(ics_prefix);
12854 SendToICS("takeback 2\n");
12856 SendToICS(ics_prefix);
12857 SendToICS("takeback 1\n");
12866 ChessProgramState *cps;
12868 switch (gameMode) {
12869 case MachinePlaysWhite:
12870 if (!WhiteOnMove(forwardMostMove)) {
12871 DisplayError(_("It is your turn"), 0);
12876 case MachinePlaysBlack:
12877 if (WhiteOnMove(forwardMostMove)) {
12878 DisplayError(_("It is your turn"), 0);
12883 case TwoMachinesPlay:
12884 if (WhiteOnMove(forwardMostMove) ==
12885 (first.twoMachinesColor[0] == 'w')) {
12891 case BeginningOfGame:
12895 SendToProgram("?\n", cps);
12899 TruncateGameEvent()
12902 if (gameMode != EditGame) return;
12909 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12910 if (forwardMostMove > currentMove) {
12911 if (gameInfo.resultDetails != NULL) {
12912 free(gameInfo.resultDetails);
12913 gameInfo.resultDetails = NULL;
12914 gameInfo.result = GameUnfinished;
12916 forwardMostMove = currentMove;
12917 HistorySet(parseList, backwardMostMove, forwardMostMove,
12925 if (appData.noChessProgram) return;
12926 switch (gameMode) {
12927 case MachinePlaysWhite:
12928 if (WhiteOnMove(forwardMostMove)) {
12929 DisplayError(_("Wait until your turn"), 0);
12933 case BeginningOfGame:
12934 case MachinePlaysBlack:
12935 if (!WhiteOnMove(forwardMostMove)) {
12936 DisplayError(_("Wait until your turn"), 0);
12941 DisplayError(_("No hint available"), 0);
12944 SendToProgram("hint\n", &first);
12945 hintRequested = TRUE;
12951 if (appData.noChessProgram) return;
12952 switch (gameMode) {
12953 case MachinePlaysWhite:
12954 if (WhiteOnMove(forwardMostMove)) {
12955 DisplayError(_("Wait until your turn"), 0);
12959 case BeginningOfGame:
12960 case MachinePlaysBlack:
12961 if (!WhiteOnMove(forwardMostMove)) {
12962 DisplayError(_("Wait until your turn"), 0);
12967 EditPositionDone(TRUE);
12969 case TwoMachinesPlay:
12974 SendToProgram("bk\n", &first);
12975 bookOutput[0] = NULLCHAR;
12976 bookRequested = TRUE;
12982 char *tags = PGNTags(&gameInfo);
12983 TagsPopUp(tags, CmailMsg());
12987 /* end button procedures */
12990 PrintPosition(fp, move)
12996 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12997 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12998 char c = PieceToChar(boards[move][i][j]);
12999 fputc(c == 'x' ? '.' : c, fp);
13000 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13003 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13004 fprintf(fp, "white to play\n");
13006 fprintf(fp, "black to play\n");
13013 if (gameInfo.white != NULL) {
13014 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13020 /* Find last component of program's own name, using some heuristics */
13022 TidyProgramName(prog, host, buf)
13023 char *prog, *host, buf[MSG_SIZ];
13026 int local = (strcmp(host, "localhost") == 0);
13027 while (!local && (p = strchr(prog, ';')) != NULL) {
13029 while (*p == ' ') p++;
13032 if (*prog == '"' || *prog == '\'') {
13033 q = strchr(prog + 1, *prog);
13035 q = strchr(prog, ' ');
13037 if (q == NULL) q = prog + strlen(prog);
13039 while (p >= prog && *p != '/' && *p != '\\') p--;
13041 if(p == prog && *p == '"') p++;
13042 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13043 memcpy(buf, p, q - p);
13044 buf[q - p] = NULLCHAR;
13052 TimeControlTagValue()
13055 if (!appData.clockMode) {
13057 } else if (movesPerSession > 0) {
13058 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13059 } else if (timeIncrement == 0) {
13060 sprintf(buf, "%ld", timeControl/1000);
13062 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13064 return StrSave(buf);
13070 /* This routine is used only for certain modes */
13071 VariantClass v = gameInfo.variant;
13072 ChessMove r = GameUnfinished;
13075 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13076 r = gameInfo.result;
13077 p = gameInfo.resultDetails;
13078 gameInfo.resultDetails = NULL;
13080 ClearGameInfo(&gameInfo);
13081 gameInfo.variant = v;
13083 switch (gameMode) {
13084 case MachinePlaysWhite:
13085 gameInfo.event = StrSave( appData.pgnEventHeader );
13086 gameInfo.site = StrSave(HostName());
13087 gameInfo.date = PGNDate();
13088 gameInfo.round = StrSave("-");
13089 gameInfo.white = StrSave(first.tidy);
13090 gameInfo.black = StrSave(UserName());
13091 gameInfo.timeControl = TimeControlTagValue();
13094 case MachinePlaysBlack:
13095 gameInfo.event = StrSave( appData.pgnEventHeader );
13096 gameInfo.site = StrSave(HostName());
13097 gameInfo.date = PGNDate();
13098 gameInfo.round = StrSave("-");
13099 gameInfo.white = StrSave(UserName());
13100 gameInfo.black = StrSave(first.tidy);
13101 gameInfo.timeControl = TimeControlTagValue();
13104 case TwoMachinesPlay:
13105 gameInfo.event = StrSave( appData.pgnEventHeader );
13106 gameInfo.site = StrSave(HostName());
13107 gameInfo.date = PGNDate();
13108 if (matchGame > 0) {
13110 sprintf(buf, "%d", matchGame);
13111 gameInfo.round = StrSave(buf);
13113 gameInfo.round = StrSave("-");
13115 if (first.twoMachinesColor[0] == 'w') {
13116 gameInfo.white = StrSave(first.tidy);
13117 gameInfo.black = StrSave(second.tidy);
13119 gameInfo.white = StrSave(second.tidy);
13120 gameInfo.black = StrSave(first.tidy);
13122 gameInfo.timeControl = TimeControlTagValue();
13126 gameInfo.event = StrSave("Edited game");
13127 gameInfo.site = StrSave(HostName());
13128 gameInfo.date = PGNDate();
13129 gameInfo.round = StrSave("-");
13130 gameInfo.white = StrSave("-");
13131 gameInfo.black = StrSave("-");
13132 gameInfo.result = r;
13133 gameInfo.resultDetails = p;
13137 gameInfo.event = StrSave("Edited position");
13138 gameInfo.site = StrSave(HostName());
13139 gameInfo.date = PGNDate();
13140 gameInfo.round = StrSave("-");
13141 gameInfo.white = StrSave("-");
13142 gameInfo.black = StrSave("-");
13145 case IcsPlayingWhite:
13146 case IcsPlayingBlack:
13151 case PlayFromGameFile:
13152 gameInfo.event = StrSave("Game from non-PGN file");
13153 gameInfo.site = StrSave(HostName());
13154 gameInfo.date = PGNDate();
13155 gameInfo.round = StrSave("-");
13156 gameInfo.white = StrSave("?");
13157 gameInfo.black = StrSave("?");
13166 ReplaceComment(index, text)
13172 while (*text == '\n') text++;
13173 len = strlen(text);
13174 while (len > 0 && text[len - 1] == '\n') len--;
13176 if (commentList[index] != NULL)
13177 free(commentList[index]);
13180 commentList[index] = NULL;
13183 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13184 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13185 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13186 commentList[index] = (char *) malloc(len + 2);
13187 strncpy(commentList[index], text, len);
13188 commentList[index][len] = '\n';
13189 commentList[index][len + 1] = NULLCHAR;
13191 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13193 commentList[index] = (char *) malloc(len + 6);
13194 strcpy(commentList[index], "{\n");
13195 strncpy(commentList[index]+2, text, len);
13196 commentList[index][len+2] = NULLCHAR;
13197 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13198 strcat(commentList[index], "\n}\n");
13212 if (ch == '\r') continue;
13214 } while (ch != '\0');
13218 AppendComment(index, text, addBraces)
13221 Boolean addBraces; // [HGM] braces: tells if we should add {}
13226 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13227 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13230 while (*text == '\n') text++;
13231 len = strlen(text);
13232 while (len > 0 && text[len - 1] == '\n') len--;
13234 if (len == 0) return;
13236 if (commentList[index] != NULL) {
13237 old = commentList[index];
13238 oldlen = strlen(old);
13239 while(commentList[index][oldlen-1] == '\n')
13240 commentList[index][--oldlen] = NULLCHAR;
13241 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13242 strcpy(commentList[index], old);
13244 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13245 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13246 if(addBraces) addBraces = FALSE; else { text++; len--; }
13247 while (*text == '\n') { text++; len--; }
13248 commentList[index][--oldlen] = NULLCHAR;
13250 if(addBraces) strcat(commentList[index], "\n{\n");
13251 else strcat(commentList[index], "\n");
13252 strcat(commentList[index], text);
13253 if(addBraces) strcat(commentList[index], "\n}\n");
13254 else strcat(commentList[index], "\n");
13256 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13258 strcpy(commentList[index], "{\n");
13259 else commentList[index][0] = NULLCHAR;
13260 strcat(commentList[index], text);
13261 strcat(commentList[index], "\n");
13262 if(addBraces) strcat(commentList[index], "}\n");
13266 static char * FindStr( char * text, char * sub_text )
13268 char * result = strstr( text, sub_text );
13270 if( result != NULL ) {
13271 result += strlen( sub_text );
13277 /* [AS] Try to extract PV info from PGN comment */
13278 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13279 char *GetInfoFromComment( int index, char * text )
13283 if( text != NULL && index > 0 ) {
13286 int time = -1, sec = 0, deci;
13287 char * s_eval = FindStr( text, "[%eval " );
13288 char * s_emt = FindStr( text, "[%emt " );
13290 if( s_eval != NULL || s_emt != NULL ) {
13294 if( s_eval != NULL ) {
13295 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13299 if( delim != ']' ) {
13304 if( s_emt != NULL ) {
13309 /* We expect something like: [+|-]nnn.nn/dd */
13312 if(*text != '{') return text; // [HGM] braces: must be normal comment
13314 sep = strchr( text, '/' );
13315 if( sep == NULL || sep < (text+4) ) {
13319 time = -1; sec = -1; deci = -1;
13320 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13321 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13322 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13323 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13327 if( score_lo < 0 || score_lo >= 100 ) {
13331 if(sec >= 0) time = 600*time + 10*sec; else
13332 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13334 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13336 /* [HGM] PV time: now locate end of PV info */
13337 while( *++sep >= '0' && *sep <= '9'); // strip depth
13339 while( *++sep >= '0' && *sep <= '9'); // strip time
13341 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13343 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13344 while(*sep == ' ') sep++;
13355 pvInfoList[index-1].depth = depth;
13356 pvInfoList[index-1].score = score;
13357 pvInfoList[index-1].time = 10*time; // centi-sec
13358 if(*sep == '}') *sep = 0; else *--sep = '{';
13364 SendToProgram(message, cps)
13366 ChessProgramState *cps;
13368 int count, outCount, error;
13371 if (cps->pr == NULL) return;
13374 if (appData.debugMode) {
13377 fprintf(debugFP, "%ld >%-6s: %s",
13378 SubtractTimeMarks(&now, &programStartTime),
13379 cps->which, message);
13382 count = strlen(message);
13383 outCount = OutputToProcess(cps->pr, message, count, &error);
13384 if (outCount < count && !exiting
13385 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13386 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13387 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13388 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13389 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13390 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13392 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13394 gameInfo.resultDetails = StrSave(buf);
13396 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13401 ReceiveFromProgram(isr, closure, message, count, error)
13402 InputSourceRef isr;
13410 ChessProgramState *cps = (ChessProgramState *)closure;
13412 if (isr != cps->isr) return; /* Killed intentionally */
13416 _("Error: %s chess program (%s) exited unexpectedly"),
13417 cps->which, cps->program);
13418 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13419 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13420 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13421 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13423 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13425 gameInfo.resultDetails = StrSave(buf);
13427 RemoveInputSource(cps->isr);
13428 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13431 _("Error reading from %s chess program (%s)"),
13432 cps->which, cps->program);
13433 RemoveInputSource(cps->isr);
13435 /* [AS] Program is misbehaving badly... kill it */
13436 if( count == -2 ) {
13437 DestroyChildProcess( cps->pr, 9 );
13441 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13446 if ((end_str = strchr(message, '\r')) != NULL)
13447 *end_str = NULLCHAR;
13448 if ((end_str = strchr(message, '\n')) != NULL)
13449 *end_str = NULLCHAR;
13451 if (appData.debugMode) {
13452 TimeMark now; int print = 1;
13453 char *quote = ""; char c; int i;
13455 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13456 char start = message[0];
13457 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13458 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13459 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13460 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13461 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13462 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13463 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13464 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13465 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13466 print = (appData.engineComments >= 2);
13468 message[0] = start; // restore original message
13472 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13473 SubtractTimeMarks(&now, &programStartTime), cps->which,
13479 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13480 if (appData.icsEngineAnalyze) {
13481 if (strstr(message, "whisper") != NULL ||
13482 strstr(message, "kibitz") != NULL ||
13483 strstr(message, "tellics") != NULL) return;
13486 HandleMachineMove(message, cps);
13491 SendTimeControl(cps, mps, tc, inc, sd, st)
13492 ChessProgramState *cps;
13493 int mps, inc, sd, st;
13499 if( timeControl_2 > 0 ) {
13500 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13501 tc = timeControl_2;
13504 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13505 inc /= cps->timeOdds;
13506 st /= cps->timeOdds;
13508 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13511 /* Set exact time per move, normally using st command */
13512 if (cps->stKludge) {
13513 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13515 if (seconds == 0) {
13516 sprintf(buf, "level 1 %d\n", st/60);
13518 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13521 sprintf(buf, "st %d\n", st);
13524 /* Set conventional or incremental time control, using level command */
13525 if (seconds == 0) {
13526 /* Note old gnuchess bug -- minutes:seconds used to not work.
13527 Fixed in later versions, but still avoid :seconds
13528 when seconds is 0. */
13529 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13531 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13532 seconds, inc/1000);
13535 SendToProgram(buf, cps);
13537 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13538 /* Orthogonally, limit search to given depth */
13540 if (cps->sdKludge) {
13541 sprintf(buf, "depth\n%d\n", sd);
13543 sprintf(buf, "sd %d\n", sd);
13545 SendToProgram(buf, cps);
13548 if(cps->nps > 0) { /* [HGM] nps */
13549 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13551 sprintf(buf, "nps %d\n", cps->nps);
13552 SendToProgram(buf, cps);
13557 ChessProgramState *WhitePlayer()
13558 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13560 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13561 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13567 SendTimeRemaining(cps, machineWhite)
13568 ChessProgramState *cps;
13569 int /*boolean*/ machineWhite;
13571 char message[MSG_SIZ];
13574 /* Note: this routine must be called when the clocks are stopped
13575 or when they have *just* been set or switched; otherwise
13576 it will be off by the time since the current tick started.
13578 if (machineWhite) {
13579 time = whiteTimeRemaining / 10;
13580 otime = blackTimeRemaining / 10;
13582 time = blackTimeRemaining / 10;
13583 otime = whiteTimeRemaining / 10;
13585 /* [HGM] translate opponent's time by time-odds factor */
13586 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13587 if (appData.debugMode) {
13588 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13591 if (time <= 0) time = 1;
13592 if (otime <= 0) otime = 1;
13594 sprintf(message, "time %ld\n", time);
13595 SendToProgram(message, cps);
13597 sprintf(message, "otim %ld\n", otime);
13598 SendToProgram(message, cps);
13602 BoolFeature(p, name, loc, cps)
13606 ChessProgramState *cps;
13609 int len = strlen(name);
13611 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13613 sscanf(*p, "%d", &val);
13615 while (**p && **p != ' ') (*p)++;
13616 sprintf(buf, "accepted %s\n", name);
13617 SendToProgram(buf, cps);
13624 IntFeature(p, name, loc, cps)
13628 ChessProgramState *cps;
13631 int len = strlen(name);
13632 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13634 sscanf(*p, "%d", loc);
13635 while (**p && **p != ' ') (*p)++;
13636 sprintf(buf, "accepted %s\n", name);
13637 SendToProgram(buf, cps);
13644 StringFeature(p, name, loc, cps)
13648 ChessProgramState *cps;
13651 int len = strlen(name);
13652 if (strncmp((*p), name, len) == 0
13653 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13655 sscanf(*p, "%[^\"]", loc);
13656 while (**p && **p != '\"') (*p)++;
13657 if (**p == '\"') (*p)++;
13658 sprintf(buf, "accepted %s\n", name);
13659 SendToProgram(buf, cps);
13666 ParseOption(Option *opt, ChessProgramState *cps)
13667 // [HGM] options: process the string that defines an engine option, and determine
13668 // name, type, default value, and allowed value range
13670 char *p, *q, buf[MSG_SIZ];
13671 int n, min = (-1)<<31, max = 1<<31, def;
13673 if(p = strstr(opt->name, " -spin ")) {
13674 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13675 if(max < min) max = min; // enforce consistency
13676 if(def < min) def = min;
13677 if(def > max) def = max;
13682 } else if((p = strstr(opt->name, " -slider "))) {
13683 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13684 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13685 if(max < min) max = min; // enforce consistency
13686 if(def < min) def = min;
13687 if(def > max) def = max;
13691 opt->type = Spin; // Slider;
13692 } else if((p = strstr(opt->name, " -string "))) {
13693 opt->textValue = p+9;
13694 opt->type = TextBox;
13695 } else if((p = strstr(opt->name, " -file "))) {
13696 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13697 opt->textValue = p+7;
13698 opt->type = TextBox; // FileName;
13699 } else if((p = strstr(opt->name, " -path "))) {
13700 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13701 opt->textValue = p+7;
13702 opt->type = TextBox; // PathName;
13703 } else if(p = strstr(opt->name, " -check ")) {
13704 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13705 opt->value = (def != 0);
13706 opt->type = CheckBox;
13707 } else if(p = strstr(opt->name, " -combo ")) {
13708 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13709 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13710 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13711 opt->value = n = 0;
13712 while(q = StrStr(q, " /// ")) {
13713 n++; *q = 0; // count choices, and null-terminate each of them
13715 if(*q == '*') { // remember default, which is marked with * prefix
13719 cps->comboList[cps->comboCnt++] = q;
13721 cps->comboList[cps->comboCnt++] = NULL;
13723 opt->type = ComboBox;
13724 } else if(p = strstr(opt->name, " -button")) {
13725 opt->type = Button;
13726 } else if(p = strstr(opt->name, " -save")) {
13727 opt->type = SaveButton;
13728 } else return FALSE;
13729 *p = 0; // terminate option name
13730 // now look if the command-line options define a setting for this engine option.
13731 if(cps->optionSettings && cps->optionSettings[0])
13732 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13733 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13734 sprintf(buf, "option %s", p);
13735 if(p = strstr(buf, ",")) *p = 0;
13737 SendToProgram(buf, cps);
13743 FeatureDone(cps, val)
13744 ChessProgramState* cps;
13747 DelayedEventCallback cb = GetDelayedEvent();
13748 if ((cb == InitBackEnd3 && cps == &first) ||
13749 (cb == TwoMachinesEventIfReady && cps == &second)) {
13750 CancelDelayedEvent();
13751 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13753 cps->initDone = val;
13756 /* Parse feature command from engine */
13758 ParseFeatures(args, cps)
13760 ChessProgramState *cps;
13768 while (*p == ' ') p++;
13769 if (*p == NULLCHAR) return;
13771 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13772 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13773 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13774 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13775 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13776 if (BoolFeature(&p, "reuse", &val, cps)) {
13777 /* Engine can disable reuse, but can't enable it if user said no */
13778 if (!val) cps->reuse = FALSE;
13781 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13782 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13783 if (gameMode == TwoMachinesPlay) {
13784 DisplayTwoMachinesTitle();
13790 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13791 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13792 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13793 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13794 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13795 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13796 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13797 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13798 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13799 if (IntFeature(&p, "done", &val, cps)) {
13800 FeatureDone(cps, val);
13803 /* Added by Tord: */
13804 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13805 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13806 /* End of additions by Tord */
13808 /* [HGM] added features: */
13809 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13810 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13811 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13812 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13813 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13814 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13815 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13816 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13817 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13818 SendToProgram(buf, cps);
13821 if(cps->nrOptions >= MAX_OPTIONS) {
13823 sprintf(buf, "%s engine has too many options\n", cps->which);
13824 DisplayError(buf, 0);
13828 /* End of additions by HGM */
13830 /* unknown feature: complain and skip */
13832 while (*q && *q != '=') q++;
13833 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13834 SendToProgram(buf, cps);
13840 while (*p && *p != '\"') p++;
13841 if (*p == '\"') p++;
13843 while (*p && *p != ' ') p++;
13851 PeriodicUpdatesEvent(newState)
13854 if (newState == appData.periodicUpdates)
13857 appData.periodicUpdates=newState;
13859 /* Display type changes, so update it now */
13860 // DisplayAnalysis();
13862 /* Get the ball rolling again... */
13864 AnalysisPeriodicEvent(1);
13865 StartAnalysisClock();
13870 PonderNextMoveEvent(newState)
13873 if (newState == appData.ponderNextMove) return;
13874 if (gameMode == EditPosition) EditPositionDone(TRUE);
13876 SendToProgram("hard\n", &first);
13877 if (gameMode == TwoMachinesPlay) {
13878 SendToProgram("hard\n", &second);
13881 SendToProgram("easy\n", &first);
13882 thinkOutput[0] = NULLCHAR;
13883 if (gameMode == TwoMachinesPlay) {
13884 SendToProgram("easy\n", &second);
13887 appData.ponderNextMove = newState;
13891 NewSettingEvent(option, feature, command, value)
13893 int option, value, *feature;
13897 if (gameMode == EditPosition) EditPositionDone(TRUE);
13898 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13899 if(feature == NULL || *feature) SendToProgram(buf, &first);
13900 if (gameMode == TwoMachinesPlay) {
13901 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13906 ShowThinkingEvent()
13907 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13909 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13910 int newState = appData.showThinking
13911 // [HGM] thinking: other features now need thinking output as well
13912 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13914 if (oldState == newState) return;
13915 oldState = newState;
13916 if (gameMode == EditPosition) EditPositionDone(TRUE);
13918 SendToProgram("post\n", &first);
13919 if (gameMode == TwoMachinesPlay) {
13920 SendToProgram("post\n", &second);
13923 SendToProgram("nopost\n", &first);
13924 thinkOutput[0] = NULLCHAR;
13925 if (gameMode == TwoMachinesPlay) {
13926 SendToProgram("nopost\n", &second);
13929 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13933 AskQuestionEvent(title, question, replyPrefix, which)
13934 char *title; char *question; char *replyPrefix; char *which;
13936 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13937 if (pr == NoProc) return;
13938 AskQuestion(title, question, replyPrefix, pr);
13942 DisplayMove(moveNumber)
13945 char message[MSG_SIZ];
13947 char cpThinkOutput[MSG_SIZ];
13949 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13951 if (moveNumber == forwardMostMove - 1 ||
13952 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13954 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13956 if (strchr(cpThinkOutput, '\n')) {
13957 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13960 *cpThinkOutput = NULLCHAR;
13963 /* [AS] Hide thinking from human user */
13964 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13965 *cpThinkOutput = NULLCHAR;
13966 if( thinkOutput[0] != NULLCHAR ) {
13969 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13970 cpThinkOutput[i] = '.';
13972 cpThinkOutput[i] = NULLCHAR;
13973 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13977 if (moveNumber == forwardMostMove - 1 &&
13978 gameInfo.resultDetails != NULL) {
13979 if (gameInfo.resultDetails[0] == NULLCHAR) {
13980 sprintf(res, " %s", PGNResult(gameInfo.result));
13982 sprintf(res, " {%s} %s",
13983 gameInfo.resultDetails, PGNResult(gameInfo.result));
13989 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13990 DisplayMessage(res, cpThinkOutput);
13992 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13993 WhiteOnMove(moveNumber) ? " " : ".. ",
13994 parseList[moveNumber], res);
13995 DisplayMessage(message, cpThinkOutput);
14000 DisplayComment(moveNumber, text)
14004 char title[MSG_SIZ];
14005 char buf[8000]; // comment can be long!
14008 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14009 strcpy(title, "Comment");
14011 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14012 WhiteOnMove(moveNumber) ? " " : ".. ",
14013 parseList[moveNumber]);
14015 // [HGM] PV info: display PV info together with (or as) comment
14016 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14017 if(text == NULL) text = "";
14018 score = pvInfoList[moveNumber].score;
14019 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14020 depth, (pvInfoList[moveNumber].time+50)/100, text);
14023 if (text != NULL && (appData.autoDisplayComment || commentUp))
14024 CommentPopUp(title, text);
14027 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14028 * might be busy thinking or pondering. It can be omitted if your
14029 * gnuchess is configured to stop thinking immediately on any user
14030 * input. However, that gnuchess feature depends on the FIONREAD
14031 * ioctl, which does not work properly on some flavors of Unix.
14035 ChessProgramState *cps;
14038 if (!cps->useSigint) return;
14039 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14040 switch (gameMode) {
14041 case MachinePlaysWhite:
14042 case MachinePlaysBlack:
14043 case TwoMachinesPlay:
14044 case IcsPlayingWhite:
14045 case IcsPlayingBlack:
14048 /* Skip if we know it isn't thinking */
14049 if (!cps->maybeThinking) return;
14050 if (appData.debugMode)
14051 fprintf(debugFP, "Interrupting %s\n", cps->which);
14052 InterruptChildProcess(cps->pr);
14053 cps->maybeThinking = FALSE;
14058 #endif /*ATTENTION*/
14064 if (whiteTimeRemaining <= 0) {
14067 if (appData.icsActive) {
14068 if (appData.autoCallFlag &&
14069 gameMode == IcsPlayingBlack && !blackFlag) {
14070 SendToICS(ics_prefix);
14071 SendToICS("flag\n");
14075 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14077 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14078 if (appData.autoCallFlag) {
14079 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14086 if (blackTimeRemaining <= 0) {
14089 if (appData.icsActive) {
14090 if (appData.autoCallFlag &&
14091 gameMode == IcsPlayingWhite && !whiteFlag) {
14092 SendToICS(ics_prefix);
14093 SendToICS("flag\n");
14097 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14099 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14100 if (appData.autoCallFlag) {
14101 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14114 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14115 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14118 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14120 if ( !WhiteOnMove(forwardMostMove) )
14121 /* White made time control */
14122 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14123 /* [HGM] time odds: correct new time quota for time odds! */
14124 / WhitePlayer()->timeOdds;
14126 /* Black made time control */
14127 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14128 / WhitePlayer()->other->timeOdds;
14132 DisplayBothClocks()
14134 int wom = gameMode == EditPosition ?
14135 !blackPlaysFirst : WhiteOnMove(currentMove);
14136 DisplayWhiteClock(whiteTimeRemaining, wom);
14137 DisplayBlackClock(blackTimeRemaining, !wom);
14141 /* Timekeeping seems to be a portability nightmare. I think everyone
14142 has ftime(), but I'm really not sure, so I'm including some ifdefs
14143 to use other calls if you don't. Clocks will be less accurate if
14144 you have neither ftime nor gettimeofday.
14147 /* VS 2008 requires the #include outside of the function */
14148 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14149 #include <sys/timeb.h>
14152 /* Get the current time as a TimeMark */
14157 #if HAVE_GETTIMEOFDAY
14159 struct timeval timeVal;
14160 struct timezone timeZone;
14162 gettimeofday(&timeVal, &timeZone);
14163 tm->sec = (long) timeVal.tv_sec;
14164 tm->ms = (int) (timeVal.tv_usec / 1000L);
14166 #else /*!HAVE_GETTIMEOFDAY*/
14169 // include <sys/timeb.h> / moved to just above start of function
14170 struct timeb timeB;
14173 tm->sec = (long) timeB.time;
14174 tm->ms = (int) timeB.millitm;
14176 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14177 tm->sec = (long) time(NULL);
14183 /* Return the difference in milliseconds between two
14184 time marks. We assume the difference will fit in a long!
14187 SubtractTimeMarks(tm2, tm1)
14188 TimeMark *tm2, *tm1;
14190 return 1000L*(tm2->sec - tm1->sec) +
14191 (long) (tm2->ms - tm1->ms);
14196 * Code to manage the game clocks.
14198 * In tournament play, black starts the clock and then white makes a move.
14199 * We give the human user a slight advantage if he is playing white---the
14200 * clocks don't run until he makes his first move, so it takes zero time.
14201 * Also, we don't account for network lag, so we could get out of sync
14202 * with GNU Chess's clock -- but then, referees are always right.
14205 static TimeMark tickStartTM;
14206 static long intendedTickLength;
14209 NextTickLength(timeRemaining)
14210 long timeRemaining;
14212 long nominalTickLength, nextTickLength;
14214 if (timeRemaining > 0L && timeRemaining <= 10000L)
14215 nominalTickLength = 100L;
14217 nominalTickLength = 1000L;
14218 nextTickLength = timeRemaining % nominalTickLength;
14219 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14221 return nextTickLength;
14224 /* Adjust clock one minute up or down */
14226 AdjustClock(Boolean which, int dir)
14228 if(which) blackTimeRemaining += 60000*dir;
14229 else whiteTimeRemaining += 60000*dir;
14230 DisplayBothClocks();
14233 /* Stop clocks and reset to a fresh time control */
14237 (void) StopClockTimer();
14238 if (appData.icsActive) {
14239 whiteTimeRemaining = blackTimeRemaining = 0;
14240 } else if (searchTime) {
14241 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14242 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14243 } else { /* [HGM] correct new time quote for time odds */
14244 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14245 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14247 if (whiteFlag || blackFlag) {
14249 whiteFlag = blackFlag = FALSE;
14251 DisplayBothClocks();
14254 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14256 /* Decrement running clock by amount of time that has passed */
14260 long timeRemaining;
14261 long lastTickLength, fudge;
14264 if (!appData.clockMode) return;
14265 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14269 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14271 /* Fudge if we woke up a little too soon */
14272 fudge = intendedTickLength - lastTickLength;
14273 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14275 if (WhiteOnMove(forwardMostMove)) {
14276 if(whiteNPS >= 0) lastTickLength = 0;
14277 timeRemaining = whiteTimeRemaining -= lastTickLength;
14278 DisplayWhiteClock(whiteTimeRemaining - fudge,
14279 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14281 if(blackNPS >= 0) lastTickLength = 0;
14282 timeRemaining = blackTimeRemaining -= lastTickLength;
14283 DisplayBlackClock(blackTimeRemaining - fudge,
14284 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14287 if (CheckFlags()) return;
14290 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14291 StartClockTimer(intendedTickLength);
14293 /* if the time remaining has fallen below the alarm threshold, sound the
14294 * alarm. if the alarm has sounded and (due to a takeback or time control
14295 * with increment) the time remaining has increased to a level above the
14296 * threshold, reset the alarm so it can sound again.
14299 if (appData.icsActive && appData.icsAlarm) {
14301 /* make sure we are dealing with the user's clock */
14302 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14303 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14306 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14307 alarmSounded = FALSE;
14308 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14310 alarmSounded = TRUE;
14316 /* A player has just moved, so stop the previously running
14317 clock and (if in clock mode) start the other one.
14318 We redisplay both clocks in case we're in ICS mode, because
14319 ICS gives us an update to both clocks after every move.
14320 Note that this routine is called *after* forwardMostMove
14321 is updated, so the last fractional tick must be subtracted
14322 from the color that is *not* on move now.
14325 SwitchClocks(int newMoveNr)
14327 long lastTickLength;
14329 int flagged = FALSE;
14333 if (StopClockTimer() && appData.clockMode) {
14334 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14335 if (!WhiteOnMove(forwardMostMove)) {
14336 if(blackNPS >= 0) lastTickLength = 0;
14337 blackTimeRemaining -= lastTickLength;
14338 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14339 // if(pvInfoList[forwardMostMove-1].time == -1)
14340 pvInfoList[forwardMostMove-1].time = // use GUI time
14341 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14343 if(whiteNPS >= 0) lastTickLength = 0;
14344 whiteTimeRemaining -= lastTickLength;
14345 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14346 // if(pvInfoList[forwardMostMove-1].time == -1)
14347 pvInfoList[forwardMostMove-1].time =
14348 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14350 flagged = CheckFlags();
14352 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14353 CheckTimeControl();
14355 if (flagged || !appData.clockMode) return;
14357 switch (gameMode) {
14358 case MachinePlaysBlack:
14359 case MachinePlaysWhite:
14360 case BeginningOfGame:
14361 if (pausing) return;
14365 case PlayFromGameFile:
14373 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14374 if(WhiteOnMove(forwardMostMove))
14375 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14376 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14380 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14381 whiteTimeRemaining : blackTimeRemaining);
14382 StartClockTimer(intendedTickLength);
14386 /* Stop both clocks */
14390 long lastTickLength;
14393 if (!StopClockTimer()) return;
14394 if (!appData.clockMode) return;
14398 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14399 if (WhiteOnMove(forwardMostMove)) {
14400 if(whiteNPS >= 0) lastTickLength = 0;
14401 whiteTimeRemaining -= lastTickLength;
14402 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14404 if(blackNPS >= 0) lastTickLength = 0;
14405 blackTimeRemaining -= lastTickLength;
14406 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14411 /* Start clock of player on move. Time may have been reset, so
14412 if clock is already running, stop and restart it. */
14416 (void) StopClockTimer(); /* in case it was running already */
14417 DisplayBothClocks();
14418 if (CheckFlags()) return;
14420 if (!appData.clockMode) return;
14421 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14423 GetTimeMark(&tickStartTM);
14424 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14425 whiteTimeRemaining : blackTimeRemaining);
14427 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14428 whiteNPS = blackNPS = -1;
14429 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14430 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14431 whiteNPS = first.nps;
14432 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14433 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14434 blackNPS = first.nps;
14435 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14436 whiteNPS = second.nps;
14437 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14438 blackNPS = second.nps;
14439 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14441 StartClockTimer(intendedTickLength);
14448 long second, minute, hour, day;
14450 static char buf[32];
14452 if (ms > 0 && ms <= 9900) {
14453 /* convert milliseconds to tenths, rounding up */
14454 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14456 sprintf(buf, " %03.1f ", tenths/10.0);
14460 /* convert milliseconds to seconds, rounding up */
14461 /* use floating point to avoid strangeness of integer division
14462 with negative dividends on many machines */
14463 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14470 day = second / (60 * 60 * 24);
14471 second = second % (60 * 60 * 24);
14472 hour = second / (60 * 60);
14473 second = second % (60 * 60);
14474 minute = second / 60;
14475 second = second % 60;
14478 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14479 sign, day, hour, minute, second);
14481 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14483 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14490 * This is necessary because some C libraries aren't ANSI C compliant yet.
14493 StrStr(string, match)
14494 char *string, *match;
14498 length = strlen(match);
14500 for (i = strlen(string) - length; i >= 0; i--, string++)
14501 if (!strncmp(match, string, length))
14508 StrCaseStr(string, match)
14509 char *string, *match;
14513 length = strlen(match);
14515 for (i = strlen(string) - length; i >= 0; i--, string++) {
14516 for (j = 0; j < length; j++) {
14517 if (ToLower(match[j]) != ToLower(string[j]))
14520 if (j == length) return string;
14534 c1 = ToLower(*s1++);
14535 c2 = ToLower(*s2++);
14536 if (c1 > c2) return 1;
14537 if (c1 < c2) return -1;
14538 if (c1 == NULLCHAR) return 0;
14547 return isupper(c) ? tolower(c) : c;
14555 return islower(c) ? toupper(c) : c;
14557 #endif /* !_amigados */
14565 if ((ret = (char *) malloc(strlen(s) + 1))) {
14572 StrSavePtr(s, savePtr)
14573 char *s, **savePtr;
14578 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14579 strcpy(*savePtr, s);
14591 clock = time((time_t *)NULL);
14592 tm = localtime(&clock);
14593 sprintf(buf, "%04d.%02d.%02d",
14594 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14595 return StrSave(buf);
14600 PositionToFEN(move, overrideCastling)
14602 char *overrideCastling;
14604 int i, j, fromX, fromY, toX, toY;
14611 whiteToPlay = (gameMode == EditPosition) ?
14612 !blackPlaysFirst : (move % 2 == 0);
14615 /* Piece placement data */
14616 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14618 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14619 if (boards[move][i][j] == EmptySquare) {
14621 } else { ChessSquare piece = boards[move][i][j];
14622 if (emptycount > 0) {
14623 if(emptycount<10) /* [HGM] can be >= 10 */
14624 *p++ = '0' + emptycount;
14625 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14628 if(PieceToChar(piece) == '+') {
14629 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14631 piece = (ChessSquare)(DEMOTED piece);
14633 *p++ = PieceToChar(piece);
14635 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14636 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14641 if (emptycount > 0) {
14642 if(emptycount<10) /* [HGM] can be >= 10 */
14643 *p++ = '0' + emptycount;
14644 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14651 /* [HGM] print Crazyhouse or Shogi holdings */
14652 if( gameInfo.holdingsWidth ) {
14653 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14655 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14656 piece = boards[move][i][BOARD_WIDTH-1];
14657 if( piece != EmptySquare )
14658 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14659 *p++ = PieceToChar(piece);
14661 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14662 piece = boards[move][BOARD_HEIGHT-i-1][0];
14663 if( piece != EmptySquare )
14664 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14665 *p++ = PieceToChar(piece);
14668 if( q == p ) *p++ = '-';
14674 *p++ = whiteToPlay ? 'w' : 'b';
14677 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14678 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14680 if(nrCastlingRights) {
14682 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14683 /* [HGM] write directly from rights */
14684 if(boards[move][CASTLING][2] != NoRights &&
14685 boards[move][CASTLING][0] != NoRights )
14686 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14687 if(boards[move][CASTLING][2] != NoRights &&
14688 boards[move][CASTLING][1] != NoRights )
14689 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14690 if(boards[move][CASTLING][5] != NoRights &&
14691 boards[move][CASTLING][3] != NoRights )
14692 *p++ = boards[move][CASTLING][3] + AAA;
14693 if(boards[move][CASTLING][5] != NoRights &&
14694 boards[move][CASTLING][4] != NoRights )
14695 *p++ = boards[move][CASTLING][4] + AAA;
14698 /* [HGM] write true castling rights */
14699 if( nrCastlingRights == 6 ) {
14700 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14701 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14702 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14703 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14704 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14705 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14706 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14707 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14710 if (q == p) *p++ = '-'; /* No castling rights */
14714 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14715 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14716 /* En passant target square */
14717 if (move > backwardMostMove) {
14718 fromX = moveList[move - 1][0] - AAA;
14719 fromY = moveList[move - 1][1] - ONE;
14720 toX = moveList[move - 1][2] - AAA;
14721 toY = moveList[move - 1][3] - ONE;
14722 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14723 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14724 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14726 /* 2-square pawn move just happened */
14728 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14732 } else if(move == backwardMostMove) {
14733 // [HGM] perhaps we should always do it like this, and forget the above?
14734 if((signed char)boards[move][EP_STATUS] >= 0) {
14735 *p++ = boards[move][EP_STATUS] + AAA;
14736 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14747 /* [HGM] find reversible plies */
14748 { int i = 0, j=move;
14750 if (appData.debugMode) { int k;
14751 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14752 for(k=backwardMostMove; k<=forwardMostMove; k++)
14753 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14757 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14758 if( j == backwardMostMove ) i += initialRulePlies;
14759 sprintf(p, "%d ", i);
14760 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14762 /* Fullmove number */
14763 sprintf(p, "%d", (move / 2) + 1);
14765 return StrSave(buf);
14769 ParseFEN(board, blackPlaysFirst, fen)
14771 int *blackPlaysFirst;
14781 /* [HGM] by default clear Crazyhouse holdings, if present */
14782 if(gameInfo.holdingsWidth) {
14783 for(i=0; i<BOARD_HEIGHT; i++) {
14784 board[i][0] = EmptySquare; /* black holdings */
14785 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14786 board[i][1] = (ChessSquare) 0; /* black counts */
14787 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14791 /* Piece placement data */
14792 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14795 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14796 if (*p == '/') p++;
14797 emptycount = gameInfo.boardWidth - j;
14798 while (emptycount--)
14799 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14801 #if(BOARD_FILES >= 10)
14802 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14803 p++; emptycount=10;
14804 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14805 while (emptycount--)
14806 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14808 } else if (isdigit(*p)) {
14809 emptycount = *p++ - '0';
14810 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14811 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14812 while (emptycount--)
14813 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14814 } else if (*p == '+' || isalpha(*p)) {
14815 if (j >= gameInfo.boardWidth) return FALSE;
14817 piece = CharToPiece(*++p);
14818 if(piece == EmptySquare) return FALSE; /* unknown piece */
14819 piece = (ChessSquare) (PROMOTED piece ); p++;
14820 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14821 } else piece = CharToPiece(*p++);
14823 if(piece==EmptySquare) return FALSE; /* unknown piece */
14824 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14825 piece = (ChessSquare) (PROMOTED piece);
14826 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14829 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14835 while (*p == '/' || *p == ' ') p++;
14837 /* [HGM] look for Crazyhouse holdings here */
14838 while(*p==' ') p++;
14839 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14841 if(*p == '-' ) *p++; /* empty holdings */ else {
14842 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14843 /* if we would allow FEN reading to set board size, we would */
14844 /* have to add holdings and shift the board read so far here */
14845 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14847 if((int) piece >= (int) BlackPawn ) {
14848 i = (int)piece - (int)BlackPawn;
14849 i = PieceToNumber((ChessSquare)i);
14850 if( i >= gameInfo.holdingsSize ) return FALSE;
14851 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14852 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14854 i = (int)piece - (int)WhitePawn;
14855 i = PieceToNumber((ChessSquare)i);
14856 if( i >= gameInfo.holdingsSize ) return FALSE;
14857 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14858 board[i][BOARD_WIDTH-2]++; /* black holdings */
14862 if(*p == ']') *p++;
14865 while(*p == ' ') p++;
14870 *blackPlaysFirst = FALSE;
14873 *blackPlaysFirst = TRUE;
14879 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14880 /* return the extra info in global variiables */
14882 /* set defaults in case FEN is incomplete */
14883 board[EP_STATUS] = EP_UNKNOWN;
14884 for(i=0; i<nrCastlingRights; i++ ) {
14885 board[CASTLING][i] =
14886 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14887 } /* assume possible unless obviously impossible */
14888 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14889 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14890 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14891 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14892 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14893 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14894 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14895 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14898 while(*p==' ') p++;
14899 if(nrCastlingRights) {
14900 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14901 /* castling indicator present, so default becomes no castlings */
14902 for(i=0; i<nrCastlingRights; i++ ) {
14903 board[CASTLING][i] = NoRights;
14906 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14907 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14908 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14909 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14910 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14912 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14913 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14914 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14916 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14917 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14918 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14919 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14920 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14921 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14924 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14925 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14926 board[CASTLING][2] = whiteKingFile;
14929 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14930 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14931 board[CASTLING][2] = whiteKingFile;
14934 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14935 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14936 board[CASTLING][5] = blackKingFile;
14939 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14940 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14941 board[CASTLING][5] = blackKingFile;
14944 default: /* FRC castlings */
14945 if(c >= 'a') { /* black rights */
14946 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14947 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14948 if(i == BOARD_RGHT) break;
14949 board[CASTLING][5] = i;
14951 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14952 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14954 board[CASTLING][3] = c;
14956 board[CASTLING][4] = c;
14957 } else { /* white rights */
14958 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14959 if(board[0][i] == WhiteKing) break;
14960 if(i == BOARD_RGHT) break;
14961 board[CASTLING][2] = i;
14962 c -= AAA - 'a' + 'A';
14963 if(board[0][c] >= WhiteKing) break;
14965 board[CASTLING][0] = c;
14967 board[CASTLING][1] = c;
14971 for(i=0; i<nrCastlingRights; i++)
14972 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14973 if (appData.debugMode) {
14974 fprintf(debugFP, "FEN castling rights:");
14975 for(i=0; i<nrCastlingRights; i++)
14976 fprintf(debugFP, " %d", board[CASTLING][i]);
14977 fprintf(debugFP, "\n");
14980 while(*p==' ') p++;
14983 /* read e.p. field in games that know e.p. capture */
14984 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14985 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14987 p++; board[EP_STATUS] = EP_NONE;
14989 char c = *p++ - AAA;
14991 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14992 if(*p >= '0' && *p <='9') *p++;
14993 board[EP_STATUS] = c;
14998 if(sscanf(p, "%d", &i) == 1) {
14999 FENrulePlies = i; /* 50-move ply counter */
15000 /* (The move number is still ignored) */
15007 EditPositionPasteFEN(char *fen)
15010 Board initial_position;
15012 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15013 DisplayError(_("Bad FEN position in clipboard"), 0);
15016 int savedBlackPlaysFirst = blackPlaysFirst;
15017 EditPositionEvent();
15018 blackPlaysFirst = savedBlackPlaysFirst;
15019 CopyBoard(boards[0], initial_position);
15020 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15021 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15022 DisplayBothClocks();
15023 DrawPosition(FALSE, boards[currentMove]);
15028 static char cseq[12] = "\\ ";
15030 Boolean set_cont_sequence(char *new_seq)
15035 // handle bad attempts to set the sequence
15037 return 0; // acceptable error - no debug
15039 len = strlen(new_seq);
15040 ret = (len > 0) && (len < sizeof(cseq));
15042 strcpy(cseq, new_seq);
15043 else if (appData.debugMode)
15044 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15049 reformat a source message so words don't cross the width boundary. internal
15050 newlines are not removed. returns the wrapped size (no null character unless
15051 included in source message). If dest is NULL, only calculate the size required
15052 for the dest buffer. lp argument indicats line position upon entry, and it's
15053 passed back upon exit.
15055 int wrap(char *dest, char *src, int count, int width, int *lp)
15057 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15059 cseq_len = strlen(cseq);
15060 old_line = line = *lp;
15061 ansi = len = clen = 0;
15063 for (i=0; i < count; i++)
15065 if (src[i] == '\033')
15068 // if we hit the width, back up
15069 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15071 // store i & len in case the word is too long
15072 old_i = i, old_len = len;
15074 // find the end of the last word
15075 while (i && src[i] != ' ' && src[i] != '\n')
15081 // word too long? restore i & len before splitting it
15082 if ((old_i-i+clen) >= width)
15089 if (i && src[i-1] == ' ')
15092 if (src[i] != ' ' && src[i] != '\n')
15099 // now append the newline and continuation sequence
15104 strncpy(dest+len, cseq, cseq_len);
15112 dest[len] = src[i];
15116 if (src[i] == '\n')
15121 if (dest && appData.debugMode)
15123 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15124 count, width, line, len, *lp);
15125 show_bytes(debugFP, src, count);
15126 fprintf(debugFP, "\ndest: ");
15127 show_bytes(debugFP, dest, len);
15128 fprintf(debugFP, "\n");
15130 *lp = dest ? line : old_line;
15135 // [HGM] vari: routines for shelving variations
15138 PushTail(int firstMove, int lastMove)
15140 int i, j, nrMoves = lastMove - firstMove;
15142 if(appData.icsActive) { // only in local mode
15143 forwardMostMove = currentMove; // mimic old ICS behavior
15146 if(storedGames >= MAX_VARIATIONS-1) return;
15148 // push current tail of game on stack
15149 savedResult[storedGames] = gameInfo.result;
15150 savedDetails[storedGames] = gameInfo.resultDetails;
15151 gameInfo.resultDetails = NULL;
15152 savedFirst[storedGames] = firstMove;
15153 savedLast [storedGames] = lastMove;
15154 savedFramePtr[storedGames] = framePtr;
15155 framePtr -= nrMoves; // reserve space for the boards
15156 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15157 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15158 for(j=0; j<MOVE_LEN; j++)
15159 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15160 for(j=0; j<2*MOVE_LEN; j++)
15161 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15162 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15163 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15164 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15165 pvInfoList[firstMove+i-1].depth = 0;
15166 commentList[framePtr+i] = commentList[firstMove+i];
15167 commentList[firstMove+i] = NULL;
15171 forwardMostMove = firstMove; // truncate game so we can start variation
15172 if(storedGames == 1) GreyRevert(FALSE);
15176 PopTail(Boolean annotate)
15179 char buf[8000], moveBuf[20];
15181 if(appData.icsActive) return FALSE; // only in local mode
15182 if(!storedGames) return FALSE; // sanity
15183 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15186 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15187 nrMoves = savedLast[storedGames] - currentMove;
15190 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15191 else strcpy(buf, "(");
15192 for(i=currentMove; i<forwardMostMove; i++) {
15194 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15195 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15196 strcat(buf, moveBuf);
15197 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15198 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15202 for(i=1; i<=nrMoves; i++) { // copy last variation back
15203 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15204 for(j=0; j<MOVE_LEN; j++)
15205 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15206 for(j=0; j<2*MOVE_LEN; j++)
15207 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15208 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15209 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15210 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15211 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15212 commentList[currentMove+i] = commentList[framePtr+i];
15213 commentList[framePtr+i] = NULL;
15215 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15216 framePtr = savedFramePtr[storedGames];
15217 gameInfo.result = savedResult[storedGames];
15218 if(gameInfo.resultDetails != NULL) {
15219 free(gameInfo.resultDetails);
15221 gameInfo.resultDetails = savedDetails[storedGames];
15222 forwardMostMove = currentMove + nrMoves;
15223 if(storedGames == 0) GreyRevert(TRUE);
15229 { // remove all shelved variations
15231 for(i=0; i<storedGames; i++) {
15232 if(savedDetails[i])
15233 free(savedDetails[i]);
15234 savedDetails[i] = NULL;
15236 for(i=framePtr; i<MAX_MOVES; i++) {
15237 if(commentList[i]) free(commentList[i]);
15238 commentList[i] = NULL;
15240 framePtr = MAX_MOVES-1;
15245 LoadVariation(int index, char *text)
15246 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15247 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15248 int level = 0, move;
15250 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15251 // first find outermost bracketing variation
15252 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15253 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15254 if(*p == '{') wait = '}'; else
15255 if(*p == '[') wait = ']'; else
15256 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15257 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15259 if(*p == wait) wait = NULLCHAR; // closing ]} found
15262 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15263 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15264 end[1] = NULLCHAR; // clip off comment beyond variation
15265 ToNrEvent(currentMove-1);
15266 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15267 // kludge: use ParsePV() to append variation to game
15268 move = currentMove;
15269 ParsePV(start, TRUE);
15270 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15271 ClearPremoveHighlights();
15273 ToNrEvent(currentMove+1);