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)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 /* States for ics_getting_history */
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
279 /* whosays values for GameEnds */
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
291 /* Different types of move when calling RegisterMove */
293 #define CMAIL_RESIGN 1
295 #define CMAIL_ACCEPT 3
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
302 /* Telnet protocol constants */
313 static char * safeStrCpy( char * dst, const char * src, size_t count )
315 assert( dst != NULL );
316 assert( src != NULL );
319 strncpy( dst, src, count );
320 dst[ count-1 ] = '\0';
324 /* Some compiler can't cast u64 to double
325 * This function do the job for us:
327 * We use the highest bit for cast, this only
328 * works if the highest bit is not
329 * in use (This should not happen)
331 * We used this for all compiler
334 u64ToDouble(u64 value)
337 u64 tmp = value & u64Const(0x7fffffffffffffff);
338 r = (double)(s64)tmp;
339 if (value & u64Const(0x8000000000000000))
340 r += 9.2233720368547758080e18; /* 2^63 */
344 /* Fake up flags for now, as we aren't keeping track of castling
345 availability yet. [HGM] Change of logic: the flag now only
346 indicates the type of castlings allowed by the rule of the game.
347 The actual rights themselves are maintained in the array
348 castlingRights, as part of the game history, and are not probed
354 int flags = F_ALL_CASTLE_OK;
355 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
356 switch (gameInfo.variant) {
358 flags &= ~F_ALL_CASTLE_OK;
359 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
360 flags |= F_IGNORE_CHECK;
362 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
365 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
367 case VariantKriegspiel:
368 flags |= F_KRIEGSPIEL_CAPTURE;
370 case VariantCapaRandom:
371 case VariantFischeRandom:
372 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
373 case VariantNoCastle:
374 case VariantShatranj:
377 flags &= ~F_ALL_CASTLE_OK;
385 FILE *gameFileFP, *debugFP;
388 [AS] Note: sometimes, the sscanf() function is used to parse the input
389 into a fixed-size buffer. Because of this, we must be prepared to
390 receive strings as long as the size of the input buffer, which is currently
391 set to 4K for Windows and 8K for the rest.
392 So, we must either allocate sufficiently large buffers here, or
393 reduce the size of the input buffer in the input reading part.
396 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
397 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
398 char thinkOutput1[MSG_SIZ*10];
400 ChessProgramState first, second;
402 /* premove variables */
405 int premoveFromX = 0;
406 int premoveFromY = 0;
407 int premovePromoChar = 0;
409 Boolean alarmSounded;
410 /* end premove variables */
412 char *ics_prefix = "$";
413 int ics_type = ICS_GENERIC;
415 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
416 int pauseExamForwardMostMove = 0;
417 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
418 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
419 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
420 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
421 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
422 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
423 int whiteFlag = FALSE, blackFlag = FALSE;
424 int userOfferedDraw = FALSE;
425 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
426 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
427 int cmailMoveType[CMAIL_MAX_GAMES];
428 long ics_clock_paused = 0;
429 ProcRef icsPR = NoProc, cmailPR = NoProc;
430 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
431 GameMode gameMode = BeginningOfGame;
432 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
433 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
434 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
435 int hiddenThinkOutputState = 0; /* [AS] */
436 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
437 int adjudicateLossPlies = 6;
438 char white_holding[64], black_holding[64];
439 TimeMark lastNodeCountTime;
440 long lastNodeCount=0;
441 int have_sent_ICS_logon = 0;
443 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
444 long timeControl_2; /* [AS] Allow separate time controls */
445 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
446 long timeRemaining[2][MAX_MOVES];
448 TimeMark programStartTime;
449 char ics_handle[MSG_SIZ];
450 int have_set_title = 0;
452 /* animateTraining preserves the state of appData.animate
453 * when Training mode is activated. This allows the
454 * response to be animated when appData.animate == TRUE and
455 * appData.animateDragging == TRUE.
457 Boolean animateTraining;
463 Board boards[MAX_MOVES];
464 /* [HGM] Following 7 needed for accurate legality tests: */
465 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
466 signed char initialRights[BOARD_FILES];
467 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
468 int initialRulePlies, FENrulePlies;
469 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
472 int mute; // mute all sounds
474 // [HGM] vari: next 12 to save and restore variations
475 #define MAX_VARIATIONS 10
476 int framePtr = MAX_MOVES-1; // points to free stack entry
478 int savedFirst[MAX_VARIATIONS];
479 int savedLast[MAX_VARIATIONS];
480 int savedFramePtr[MAX_VARIATIONS];
481 char *savedDetails[MAX_VARIATIONS];
482 ChessMove savedResult[MAX_VARIATIONS];
484 void PushTail P((int firstMove, int lastMove));
485 Boolean PopTail P((Boolean annotate));
486 void CleanupTail P((void));
488 ChessSquare FIDEArray[2][BOARD_FILES] = {
489 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
490 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
492 BlackKing, BlackBishop, BlackKnight, BlackRook }
495 ChessSquare twoKingsArray[2][BOARD_FILES] = {
496 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
498 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
499 BlackKing, BlackKing, BlackKnight, BlackRook }
502 ChessSquare KnightmateArray[2][BOARD_FILES] = {
503 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
504 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
505 { BlackRook, BlackMan, BlackBishop, BlackQueen,
506 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
509 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
510 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
511 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
512 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
513 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
516 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
517 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
518 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
519 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
520 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
523 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
524 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
525 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
526 { BlackRook, BlackKnight, BlackMan, BlackFerz,
527 BlackKing, BlackMan, BlackKnight, BlackRook }
531 #if (BOARD_FILES>=10)
532 ChessSquare ShogiArray[2][BOARD_FILES] = {
533 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
534 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
535 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
536 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
539 ChessSquare XiangqiArray[2][BOARD_FILES] = {
540 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
541 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
542 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
543 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 ChessSquare CapablancaArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
548 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
549 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
550 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
553 ChessSquare GreatArray[2][BOARD_FILES] = {
554 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
555 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
556 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
557 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
560 ChessSquare JanusArray[2][BOARD_FILES] = {
561 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
562 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
563 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
564 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
568 ChessSquare GothicArray[2][BOARD_FILES] = {
569 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
570 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
571 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
572 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
575 #define GothicArray CapablancaArray
579 ChessSquare FalconArray[2][BOARD_FILES] = {
580 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
581 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
583 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
586 #define FalconArray CapablancaArray
589 #else // !(BOARD_FILES>=10)
590 #define XiangqiPosition FIDEArray
591 #define CapablancaArray FIDEArray
592 #define GothicArray FIDEArray
593 #define GreatArray FIDEArray
594 #endif // !(BOARD_FILES>=10)
596 #if (BOARD_FILES>=12)
597 ChessSquare CourierArray[2][BOARD_FILES] = {
598 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
599 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
600 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
601 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
603 #else // !(BOARD_FILES>=12)
604 #define CourierArray CapablancaArray
605 #endif // !(BOARD_FILES>=12)
608 Board initialPosition;
611 /* Convert str to a rating. Checks for special cases of "----",
613 "++++", etc. Also strips ()'s */
615 string_to_rating(str)
618 while(*str && !isdigit(*str)) ++str;
620 return 0; /* One of the special "no rating" cases */
628 /* Init programStats */
629 programStats.movelist[0] = 0;
630 programStats.depth = 0;
631 programStats.nr_moves = 0;
632 programStats.moves_left = 0;
633 programStats.nodes = 0;
634 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
635 programStats.score = 0;
636 programStats.got_only_move = 0;
637 programStats.got_fail = 0;
638 programStats.line_is_book = 0;
644 int matched, min, sec;
646 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
647 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
649 GetTimeMark(&programStartTime);
650 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
653 programStats.ok_to_send = 1;
654 programStats.seen_stat = 0;
657 * Initialize game list
663 * Internet chess server status
665 if (appData.icsActive) {
666 appData.matchMode = FALSE;
667 appData.matchGames = 0;
669 appData.noChessProgram = !appData.zippyPlay;
671 appData.zippyPlay = FALSE;
672 appData.zippyTalk = FALSE;
673 appData.noChessProgram = TRUE;
675 if (*appData.icsHelper != NULLCHAR) {
676 appData.useTelnet = TRUE;
677 appData.telnetProgram = appData.icsHelper;
680 appData.zippyTalk = appData.zippyPlay = FALSE;
683 /* [AS] Initialize pv info list [HGM] and game state */
687 for( i=0; i<=framePtr; i++ ) {
688 pvInfoList[i].depth = -1;
689 boards[i][EP_STATUS] = EP_NONE;
690 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
695 * Parse timeControl resource
697 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
698 appData.movesPerSession)) {
700 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
701 DisplayFatalError(buf, 0, 2);
705 * Parse searchTime resource
707 if (*appData.searchTime != NULLCHAR) {
708 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
710 searchTime = min * 60;
711 } else if (matched == 2) {
712 searchTime = min * 60 + sec;
715 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
716 DisplayFatalError(buf, 0, 2);
720 /* [AS] Adjudication threshold */
721 adjudicateLossThreshold = appData.adjudicateLossThreshold;
723 first.which = _("first");
724 second.which = _("second");
725 first.maybeThinking = second.maybeThinking = FALSE;
726 first.pr = second.pr = NoProc;
727 first.isr = second.isr = NULL;
728 first.sendTime = second.sendTime = 2;
729 first.sendDrawOffers = 1;
730 if (appData.firstPlaysBlack) {
731 first.twoMachinesColor = "black\n";
732 second.twoMachinesColor = "white\n";
734 first.twoMachinesColor = "white\n";
735 second.twoMachinesColor = "black\n";
737 first.program = appData.firstChessProgram;
738 second.program = appData.secondChessProgram;
739 first.host = appData.firstHost;
740 second.host = appData.secondHost;
741 first.dir = appData.firstDirectory;
742 second.dir = appData.secondDirectory;
743 first.other = &second;
744 second.other = &first;
745 first.initString = appData.initString;
746 second.initString = appData.secondInitString;
747 first.computerString = appData.firstComputerString;
748 second.computerString = appData.secondComputerString;
749 first.useSigint = second.useSigint = TRUE;
750 first.useSigterm = second.useSigterm = TRUE;
751 first.reuse = appData.reuseFirst;
752 second.reuse = appData.reuseSecond;
753 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
754 second.nps = appData.secondNPS;
755 first.useSetboard = second.useSetboard = FALSE;
756 first.useSAN = second.useSAN = FALSE;
757 first.usePing = second.usePing = FALSE;
758 first.lastPing = second.lastPing = 0;
759 first.lastPong = second.lastPong = 0;
760 first.usePlayother = second.usePlayother = FALSE;
761 first.useColors = second.useColors = TRUE;
762 first.useUsermove = second.useUsermove = FALSE;
763 first.sendICS = second.sendICS = FALSE;
764 first.sendName = second.sendName = appData.icsActive;
765 first.sdKludge = second.sdKludge = FALSE;
766 first.stKludge = second.stKludge = FALSE;
767 TidyProgramName(first.program, first.host, first.tidy);
768 TidyProgramName(second.program, second.host, second.tidy);
769 first.matchWins = second.matchWins = 0;
770 strcpy(first.variants, appData.variant);
771 strcpy(second.variants, appData.variant);
772 first.analysisSupport = second.analysisSupport = 2; /* detect */
773 first.analyzing = second.analyzing = FALSE;
774 first.initDone = second.initDone = FALSE;
776 /* New features added by Tord: */
777 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
778 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
779 /* End of new features added by Tord. */
780 first.fenOverride = appData.fenOverride1;
781 second.fenOverride = appData.fenOverride2;
783 /* [HGM] time odds: set factor for each machine */
784 first.timeOdds = appData.firstTimeOdds;
785 second.timeOdds = appData.secondTimeOdds;
787 if(appData.timeOddsMode) {
788 norm = first.timeOdds;
789 if(norm > second.timeOdds) norm = second.timeOdds;
791 first.timeOdds /= norm;
792 second.timeOdds /= norm;
795 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796 first.accumulateTC = appData.firstAccumulateTC;
797 second.accumulateTC = appData.secondAccumulateTC;
798 first.maxNrOfSessions = second.maxNrOfSessions = 1;
801 first.debug = second.debug = FALSE;
802 first.supportsNPS = second.supportsNPS = UNKNOWN;
805 first.optionSettings = appData.firstOptions;
806 second.optionSettings = appData.secondOptions;
808 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
809 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
810 first.isUCI = appData.firstIsUCI; /* [AS] */
811 second.isUCI = appData.secondIsUCI; /* [AS] */
812 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
813 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
815 if (appData.firstProtocolVersion > PROTOVER ||
816 appData.firstProtocolVersion < 1) {
818 sprintf(buf, _("protocol version %d not supported"),
819 appData.firstProtocolVersion);
820 DisplayFatalError(buf, 0, 2);
822 first.protocolVersion = appData.firstProtocolVersion;
825 if (appData.secondProtocolVersion > PROTOVER ||
826 appData.secondProtocolVersion < 1) {
828 sprintf(buf, _("protocol version %d not supported"),
829 appData.secondProtocolVersion);
830 DisplayFatalError(buf, 0, 2);
832 second.protocolVersion = appData.secondProtocolVersion;
835 if (appData.icsActive) {
836 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
837 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
838 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
839 appData.clockMode = FALSE;
840 first.sendTime = second.sendTime = 0;
844 /* Override some settings from environment variables, for backward
845 compatibility. Unfortunately it's not feasible to have the env
846 vars just set defaults, at least in xboard. Ugh.
848 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
853 if (appData.noChessProgram) {
854 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
855 sprintf(programVersion, "%s", PACKAGE_STRING);
857 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
858 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
859 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
862 if (!appData.icsActive) {
864 /* Check for variants that are supported only in ICS mode,
865 or not at all. Some that are accepted here nevertheless
866 have bugs; see comments below.
868 VariantClass variant = StringToVariant(appData.variant);
870 case VariantBughouse: /* need four players and two boards */
871 case VariantKriegspiel: /* need to hide pieces and move details */
872 /* case VariantFischeRandom: (Fabien: moved below) */
873 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
878 case VariantLoadable:
888 sprintf(buf, _("Unknown variant name %s"), appData.variant);
889 DisplayFatalError(buf, 0, 2);
892 case VariantXiangqi: /* [HGM] repetition rules not implemented */
893 case VariantFairy: /* [HGM] TestLegality definitely off! */
894 case VariantGothic: /* [HGM] should work */
895 case VariantCapablanca: /* [HGM] should work */
896 case VariantCourier: /* [HGM] initial forced moves not implemented */
897 case VariantShogi: /* [HGM] could still mate with pawn drop */
898 case VariantKnightmate: /* [HGM] should work */
899 case VariantCylinder: /* [HGM] untested */
900 case VariantFalcon: /* [HGM] untested */
901 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
902 offboard interposition not understood */
903 case VariantNormal: /* definitely works! */
904 case VariantWildCastle: /* pieces not automatically shuffled */
905 case VariantNoCastle: /* pieces not automatically shuffled */
906 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
907 case VariantLosers: /* should work except for win condition,
908 and doesn't know captures are mandatory */
909 case VariantSuicide: /* should work except for win condition,
910 and doesn't know captures are mandatory */
911 case VariantGiveaway: /* should work except for win condition,
912 and doesn't know captures are mandatory */
913 case VariantTwoKings: /* should work */
914 case VariantAtomic: /* should work except for win condition */
915 case Variant3Check: /* should work except for win condition */
916 case VariantShatranj: /* should work except for all win conditions */
917 case VariantMakruk: /* should work except for daw countdown */
918 case VariantBerolina: /* might work if TestLegality is off */
919 case VariantCapaRandom: /* should work */
920 case VariantJanus: /* should work */
921 case VariantSuper: /* experimental */
922 case VariantGreat: /* experimental, requires legality testing to be off */
927 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
928 InitEngineUCI( installDir, &second );
931 int NextIntegerFromString( char ** str, long * value )
936 while( *s == ' ' || *s == '\t' ) {
942 if( *s >= '0' && *s <= '9' ) {
943 while( *s >= '0' && *s <= '9' ) {
944 *value = *value * 10 + (*s - '0');
956 int NextTimeControlFromString( char ** str, long * value )
959 int result = NextIntegerFromString( str, &temp );
962 *value = temp * 60; /* Minutes */
965 result = NextIntegerFromString( str, &temp );
966 *value += temp; /* Seconds */
973 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
974 { /* [HGM] routine added to read '+moves/time' for secondary time control */
975 int result = -1; long temp, temp2;
977 if(**str != '+') return -1; // old params remain in force!
979 if( NextTimeControlFromString( str, &temp ) ) return -1;
982 /* time only: incremental or sudden-death time control */
983 if(**str == '+') { /* increment follows; read it */
985 if(result = NextIntegerFromString( str, &temp2)) return -1;
988 *moves = 0; *tc = temp * 1000;
990 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
992 (*str)++; /* classical time control */
993 result = NextTimeControlFromString( str, &temp2);
1002 int GetTimeQuota(int movenr)
1003 { /* [HGM] get time to add from the multi-session time-control string */
1004 int moves=1; /* kludge to force reading of first session */
1005 long time, increment;
1006 char *s = fullTimeControlString;
1008 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1010 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1011 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1012 if(movenr == -1) return time; /* last move before new session */
1013 if(!moves) return increment; /* current session is incremental */
1014 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1015 } while(movenr >= -1); /* try again for next session */
1017 return 0; // no new time quota on this move
1021 ParseTimeControl(tc, ti, mps)
1030 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1033 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1034 else sprintf(buf, "+%s+%d", tc, ti);
1037 sprintf(buf, "+%d/%s", mps, tc);
1038 else sprintf(buf, "+%s", tc);
1040 fullTimeControlString = StrSave(buf);
1042 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1047 /* Parse second time control */
1050 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1058 timeControl_2 = tc2 * 1000;
1068 timeControl = tc1 * 1000;
1071 timeIncrement = ti * 1000; /* convert to ms */
1072 movesPerSession = 0;
1075 movesPerSession = mps;
1083 if (appData.debugMode) {
1084 fprintf(debugFP, "%s\n", programVersion);
1087 set_cont_sequence(appData.wrapContSeq);
1088 if (appData.matchGames > 0) {
1089 appData.matchMode = TRUE;
1090 } else if (appData.matchMode) {
1091 appData.matchGames = 1;
1093 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1094 appData.matchGames = appData.sameColorGames;
1095 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1096 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1097 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1100 if (appData.noChessProgram || first.protocolVersion == 1) {
1103 /* kludge: allow timeout for initial "feature" commands */
1105 DisplayMessage("", _("Starting chess program"));
1106 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1111 InitBackEnd3 P((void))
1113 GameMode initialMode;
1117 InitChessProgram(&first, startedFromSetupPosition);
1119 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1120 free(programVersion);
1121 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1122 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1125 if (appData.icsActive) {
1127 /* [DM] Make a console window if needed [HGM] merged ifs */
1132 if (*appData.icsCommPort != NULLCHAR) {
1133 sprintf(buf, _("Could not open comm port %s"),
1134 appData.icsCommPort);
1136 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1137 appData.icsHost, appData.icsPort);
1139 DisplayFatalError(buf, err, 1);
1144 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1146 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1147 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1148 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1149 } else if (appData.noChessProgram) {
1155 if (*appData.cmailGameName != NULLCHAR) {
1157 OpenLoopback(&cmailPR);
1159 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1163 DisplayMessage("", "");
1164 if (StrCaseCmp(appData.initialMode, "") == 0) {
1165 initialMode = BeginningOfGame;
1166 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1167 initialMode = TwoMachinesPlay;
1168 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1169 initialMode = AnalyzeFile;
1170 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1171 initialMode = AnalyzeMode;
1172 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1173 initialMode = MachinePlaysWhite;
1174 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1175 initialMode = MachinePlaysBlack;
1176 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1177 initialMode = EditGame;
1178 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1179 initialMode = EditPosition;
1180 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1181 initialMode = Training;
1183 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1184 DisplayFatalError(buf, 0, 2);
1188 if (appData.matchMode) {
1189 /* Set up machine vs. machine match */
1190 if (appData.noChessProgram) {
1191 DisplayFatalError(_("Can't have a match with no chess programs"),
1197 if (*appData.loadGameFile != NULLCHAR) {
1198 int index = appData.loadGameIndex; // [HGM] autoinc
1199 if(index<0) lastIndex = index = 1;
1200 if (!LoadGameFromFile(appData.loadGameFile,
1202 appData.loadGameFile, FALSE)) {
1203 DisplayFatalError(_("Bad game file"), 0, 1);
1206 } else if (*appData.loadPositionFile != NULLCHAR) {
1207 int index = appData.loadPositionIndex; // [HGM] autoinc
1208 if(index<0) lastIndex = index = 1;
1209 if (!LoadPositionFromFile(appData.loadPositionFile,
1211 appData.loadPositionFile)) {
1212 DisplayFatalError(_("Bad position file"), 0, 1);
1217 } else if (*appData.cmailGameName != NULLCHAR) {
1218 /* Set up cmail mode */
1219 ReloadCmailMsgEvent(TRUE);
1221 /* Set up other modes */
1222 if (initialMode == AnalyzeFile) {
1223 if (*appData.loadGameFile == NULLCHAR) {
1224 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1228 if (*appData.loadGameFile != NULLCHAR) {
1229 (void) LoadGameFromFile(appData.loadGameFile,
1230 appData.loadGameIndex,
1231 appData.loadGameFile, TRUE);
1232 } else if (*appData.loadPositionFile != NULLCHAR) {
1233 (void) LoadPositionFromFile(appData.loadPositionFile,
1234 appData.loadPositionIndex,
1235 appData.loadPositionFile);
1236 /* [HGM] try to make self-starting even after FEN load */
1237 /* to allow automatic setup of fairy variants with wtm */
1238 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1239 gameMode = BeginningOfGame;
1240 setboardSpoiledMachineBlack = 1;
1242 /* [HGM] loadPos: make that every new game uses the setup */
1243 /* from file as long as we do not switch variant */
1244 if(!blackPlaysFirst) {
1245 startedFromPositionFile = TRUE;
1246 CopyBoard(filePosition, boards[0]);
1249 if (initialMode == AnalyzeMode) {
1250 if (appData.noChessProgram) {
1251 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1254 if (appData.icsActive) {
1255 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1259 } else if (initialMode == AnalyzeFile) {
1260 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1261 ShowThinkingEvent();
1263 AnalysisPeriodicEvent(1);
1264 } else if (initialMode == MachinePlaysWhite) {
1265 if (appData.noChessProgram) {
1266 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1270 if (appData.icsActive) {
1271 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1275 MachineWhiteEvent();
1276 } else if (initialMode == MachinePlaysBlack) {
1277 if (appData.noChessProgram) {
1278 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1282 if (appData.icsActive) {
1283 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1287 MachineBlackEvent();
1288 } else if (initialMode == TwoMachinesPlay) {
1289 if (appData.noChessProgram) {
1290 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1294 if (appData.icsActive) {
1295 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1300 } else if (initialMode == EditGame) {
1302 } else if (initialMode == EditPosition) {
1303 EditPositionEvent();
1304 } else if (initialMode == Training) {
1305 if (*appData.loadGameFile == NULLCHAR) {
1306 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1315 * Establish will establish a contact to a remote host.port.
1316 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1317 * used to talk to the host.
1318 * Returns 0 if okay, error code if not.
1325 if (*appData.icsCommPort != NULLCHAR) {
1326 /* Talk to the host through a serial comm port */
1327 return OpenCommPort(appData.icsCommPort, &icsPR);
1329 } else if (*appData.gateway != NULLCHAR) {
1330 if (*appData.remoteShell == NULLCHAR) {
1331 /* Use the rcmd protocol to run telnet program on a gateway host */
1332 snprintf(buf, sizeof(buf), "%s %s %s",
1333 appData.telnetProgram, appData.icsHost, appData.icsPort);
1334 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1337 /* Use the rsh program to run telnet program on a gateway host */
1338 if (*appData.remoteUser == NULLCHAR) {
1339 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1340 appData.gateway, appData.telnetProgram,
1341 appData.icsHost, appData.icsPort);
1343 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1344 appData.remoteShell, appData.gateway,
1345 appData.remoteUser, appData.telnetProgram,
1346 appData.icsHost, appData.icsPort);
1348 return StartChildProcess(buf, "", &icsPR);
1351 } else if (appData.useTelnet) {
1352 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1355 /* TCP socket interface differs somewhat between
1356 Unix and NT; handle details in the front end.
1358 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1362 void EscapeExpand(char *p, char *q)
1363 { // [HGM] initstring: routine to shape up string arguments
1364 while(*p++ = *q++) if(p[-1] == '\\')
1366 case 'n': p[-1] = '\n'; break;
1367 case 'r': p[-1] = '\r'; break;
1368 case 't': p[-1] = '\t'; break;
1369 case '\\': p[-1] = '\\'; break;
1370 case 0: *p = 0; return;
1371 default: p[-1] = q[-1]; break;
1376 show_bytes(fp, buf, count)
1382 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1383 fprintf(fp, "\\%03o", *buf & 0xff);
1392 /* Returns an errno value */
1394 OutputMaybeTelnet(pr, message, count, outError)
1400 char buf[8192], *p, *q, *buflim;
1401 int left, newcount, outcount;
1403 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1404 *appData.gateway != NULLCHAR) {
1405 if (appData.debugMode) {
1406 fprintf(debugFP, ">ICS: ");
1407 show_bytes(debugFP, message, count);
1408 fprintf(debugFP, "\n");
1410 return OutputToProcess(pr, message, count, outError);
1413 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1420 if (appData.debugMode) {
1421 fprintf(debugFP, ">ICS: ");
1422 show_bytes(debugFP, buf, newcount);
1423 fprintf(debugFP, "\n");
1425 outcount = OutputToProcess(pr, buf, newcount, outError);
1426 if (outcount < newcount) return -1; /* to be sure */
1433 } else if (((unsigned char) *p) == TN_IAC) {
1434 *q++ = (char) TN_IAC;
1441 if (appData.debugMode) {
1442 fprintf(debugFP, ">ICS: ");
1443 show_bytes(debugFP, buf, newcount);
1444 fprintf(debugFP, "\n");
1446 outcount = OutputToProcess(pr, buf, newcount, outError);
1447 if (outcount < newcount) return -1; /* to be sure */
1452 read_from_player(isr, closure, message, count, error)
1459 int outError, outCount;
1460 static int gotEof = 0;
1462 /* Pass data read from player on to ICS */
1465 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1466 if (outCount < count) {
1467 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1469 } else if (count < 0) {
1470 RemoveInputSource(isr);
1471 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1472 } else if (gotEof++ > 0) {
1473 RemoveInputSource(isr);
1474 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1480 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1481 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1482 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1483 SendToICS("date\n");
1484 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1487 /* added routine for printf style output to ics */
1488 void ics_printf(char *format, ...)
1490 char buffer[MSG_SIZ];
1493 va_start(args, format);
1494 vsnprintf(buffer, sizeof(buffer), format, args);
1495 buffer[sizeof(buffer)-1] = '\0';
1504 int count, outCount, outError;
1506 if (icsPR == NULL) return;
1509 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1510 if (outCount < count) {
1511 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1515 /* This is used for sending logon scripts to the ICS. Sending
1516 without a delay causes problems when using timestamp on ICC
1517 (at least on my machine). */
1519 SendToICSDelayed(s,msdelay)
1523 int count, outCount, outError;
1525 if (icsPR == NULL) return;
1528 if (appData.debugMode) {
1529 fprintf(debugFP, ">ICS: ");
1530 show_bytes(debugFP, s, count);
1531 fprintf(debugFP, "\n");
1533 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1535 if (outCount < count) {
1536 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1541 /* Remove all highlighting escape sequences in s
1542 Also deletes any suffix starting with '('
1545 StripHighlightAndTitle(s)
1548 static char retbuf[MSG_SIZ];
1551 while (*s != NULLCHAR) {
1552 while (*s == '\033') {
1553 while (*s != NULLCHAR && !isalpha(*s)) s++;
1554 if (*s != NULLCHAR) s++;
1556 while (*s != NULLCHAR && *s != '\033') {
1557 if (*s == '(' || *s == '[') {
1568 /* Remove all highlighting escape sequences in s */
1573 static char retbuf[MSG_SIZ];
1576 while (*s != NULLCHAR) {
1577 while (*s == '\033') {
1578 while (*s != NULLCHAR && !isalpha(*s)) s++;
1579 if (*s != NULLCHAR) s++;
1581 while (*s != NULLCHAR && *s != '\033') {
1589 char *variantNames[] = VARIANT_NAMES;
1594 return variantNames[v];
1598 /* Identify a variant from the strings the chess servers use or the
1599 PGN Variant tag names we use. */
1606 VariantClass v = VariantNormal;
1607 int i, found = FALSE;
1612 /* [HGM] skip over optional board-size prefixes */
1613 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1614 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1615 while( *e++ != '_');
1618 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1622 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1623 if (StrCaseStr(e, variantNames[i])) {
1624 v = (VariantClass) i;
1631 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1632 || StrCaseStr(e, "wild/fr")
1633 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1634 v = VariantFischeRandom;
1635 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1636 (i = 1, p = StrCaseStr(e, "w"))) {
1638 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1645 case 0: /* FICS only, actually */
1647 /* Castling legal even if K starts on d-file */
1648 v = VariantWildCastle;
1653 /* Castling illegal even if K & R happen to start in
1654 normal positions. */
1655 v = VariantNoCastle;
1668 /* Castling legal iff K & R start in normal positions */
1674 /* Special wilds for position setup; unclear what to do here */
1675 v = VariantLoadable;
1678 /* Bizarre ICC game */
1679 v = VariantTwoKings;
1682 v = VariantKriegspiel;
1688 v = VariantFischeRandom;
1691 v = VariantCrazyhouse;
1694 v = VariantBughouse;
1700 /* Not quite the same as FICS suicide! */
1701 v = VariantGiveaway;
1707 v = VariantShatranj;
1710 /* Temporary names for future ICC types. The name *will* change in
1711 the next xboard/WinBoard release after ICC defines it. */
1749 v = VariantCapablanca;
1752 v = VariantKnightmate;
1758 v = VariantCylinder;
1764 v = VariantCapaRandom;
1767 v = VariantBerolina;
1779 /* Found "wild" or "w" in the string but no number;
1780 must assume it's normal chess. */
1784 sprintf(buf, _("Unknown wild type %d"), wnum);
1785 DisplayError(buf, 0);
1791 if (appData.debugMode) {
1792 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1793 e, wnum, VariantName(v));
1798 static int leftover_start = 0, leftover_len = 0;
1799 char star_match[STAR_MATCH_N][MSG_SIZ];
1801 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1802 advance *index beyond it, and set leftover_start to the new value of
1803 *index; else return FALSE. If pattern contains the character '*', it
1804 matches any sequence of characters not containing '\r', '\n', or the
1805 character following the '*' (if any), and the matched sequence(s) are
1806 copied into star_match.
1809 looking_at(buf, index, pattern)
1814 char *bufp = &buf[*index], *patternp = pattern;
1816 char *matchp = star_match[0];
1819 if (*patternp == NULLCHAR) {
1820 *index = leftover_start = bufp - buf;
1824 if (*bufp == NULLCHAR) return FALSE;
1825 if (*patternp == '*') {
1826 if (*bufp == *(patternp + 1)) {
1828 matchp = star_match[++star_count];
1832 } else if (*bufp == '\n' || *bufp == '\r') {
1834 if (*patternp == NULLCHAR)
1839 *matchp++ = *bufp++;
1843 if (*patternp != *bufp) return FALSE;
1850 SendToPlayer(data, length)
1854 int error, outCount;
1855 outCount = OutputToProcess(NoProc, data, length, &error);
1856 if (outCount < length) {
1857 DisplayFatalError(_("Error writing to display"), error, 1);
1862 PackHolding(packed, holding)
1874 switch (runlength) {
1885 sprintf(q, "%d", runlength);
1897 /* Telnet protocol requests from the front end */
1899 TelnetRequest(ddww, option)
1900 unsigned char ddww, option;
1902 unsigned char msg[3];
1903 int outCount, outError;
1905 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1907 if (appData.debugMode) {
1908 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1924 sprintf(buf1, "%d", ddww);
1933 sprintf(buf2, "%d", option);
1936 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1941 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1943 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1950 if (!appData.icsActive) return;
1951 TelnetRequest(TN_DO, TN_ECHO);
1957 if (!appData.icsActive) return;
1958 TelnetRequest(TN_DONT, TN_ECHO);
1962 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1964 /* put the holdings sent to us by the server on the board holdings area */
1965 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1969 if(gameInfo.holdingsWidth < 2) return;
1970 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1971 return; // prevent overwriting by pre-board holdings
1973 if( (int)lowestPiece >= BlackPawn ) {
1976 holdingsStartRow = BOARD_HEIGHT-1;
1979 holdingsColumn = BOARD_WIDTH-1;
1980 countsColumn = BOARD_WIDTH-2;
1981 holdingsStartRow = 0;
1985 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1986 board[i][holdingsColumn] = EmptySquare;
1987 board[i][countsColumn] = (ChessSquare) 0;
1989 while( (p=*holdings++) != NULLCHAR ) {
1990 piece = CharToPiece( ToUpper(p) );
1991 if(piece == EmptySquare) continue;
1992 /*j = (int) piece - (int) WhitePawn;*/
1993 j = PieceToNumber(piece);
1994 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1995 if(j < 0) continue; /* should not happen */
1996 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1997 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1998 board[holdingsStartRow+j*direction][countsColumn]++;
2004 VariantSwitch(Board board, VariantClass newVariant)
2006 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2007 static Board oldBoard;
2009 startedFromPositionFile = FALSE;
2010 if(gameInfo.variant == newVariant) return;
2012 /* [HGM] This routine is called each time an assignment is made to
2013 * gameInfo.variant during a game, to make sure the board sizes
2014 * are set to match the new variant. If that means adding or deleting
2015 * holdings, we shift the playing board accordingly
2016 * This kludge is needed because in ICS observe mode, we get boards
2017 * of an ongoing game without knowing the variant, and learn about the
2018 * latter only later. This can be because of the move list we requested,
2019 * in which case the game history is refilled from the beginning anyway,
2020 * but also when receiving holdings of a crazyhouse game. In the latter
2021 * case we want to add those holdings to the already received position.
2025 if (appData.debugMode) {
2026 fprintf(debugFP, "Switch board from %s to %s\n",
2027 VariantName(gameInfo.variant), VariantName(newVariant));
2028 setbuf(debugFP, NULL);
2030 shuffleOpenings = 0; /* [HGM] shuffle */
2031 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2035 newWidth = 9; newHeight = 9;
2036 gameInfo.holdingsSize = 7;
2037 case VariantBughouse:
2038 case VariantCrazyhouse:
2039 newHoldingsWidth = 2; break;
2043 newHoldingsWidth = 2;
2044 gameInfo.holdingsSize = 8;
2047 case VariantCapablanca:
2048 case VariantCapaRandom:
2051 newHoldingsWidth = gameInfo.holdingsSize = 0;
2054 if(newWidth != gameInfo.boardWidth ||
2055 newHeight != gameInfo.boardHeight ||
2056 newHoldingsWidth != gameInfo.holdingsWidth ) {
2058 /* shift position to new playing area, if needed */
2059 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2060 for(i=0; i<BOARD_HEIGHT; i++)
2061 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2062 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2064 for(i=0; i<newHeight; i++) {
2065 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2066 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2068 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2069 for(i=0; i<BOARD_HEIGHT; i++)
2070 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2071 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2074 gameInfo.boardWidth = newWidth;
2075 gameInfo.boardHeight = newHeight;
2076 gameInfo.holdingsWidth = newHoldingsWidth;
2077 gameInfo.variant = newVariant;
2078 InitDrawingSizes(-2, 0);
2079 } else gameInfo.variant = newVariant;
2080 CopyBoard(oldBoard, board); // remember correctly formatted board
2081 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2082 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2085 static int loggedOn = FALSE;
2087 /*-- Game start info cache: --*/
2089 char gs_kind[MSG_SIZ];
2090 static char player1Name[128] = "";
2091 static char player2Name[128] = "";
2092 static char cont_seq[] = "\n\\ ";
2093 static int player1Rating = -1;
2094 static int player2Rating = -1;
2095 /*----------------------------*/
2097 ColorClass curColor = ColorNormal;
2098 int suppressKibitz = 0;
2101 Boolean soughtPending = FALSE;
2102 Boolean seekGraphUp;
2103 #define MAX_SEEK_ADS 200
2105 char *seekAdList[MAX_SEEK_ADS];
2106 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2107 float tcList[MAX_SEEK_ADS];
2108 char colorList[MAX_SEEK_ADS];
2109 int nrOfSeekAds = 0;
2110 int minRating = 1010, maxRating = 2800;
2111 int hMargin = 10, vMargin = 20, h, w;
2112 extern int squareSize, lineGap;
2117 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2118 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2119 if(r < minRating+100 && r >=0 ) r = minRating+100;
2120 if(r > maxRating) r = maxRating;
2121 if(tc < 1.) tc = 1.;
2122 if(tc > 95.) tc = 95.;
2123 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2124 y = ((double)r - minRating)/(maxRating - minRating)
2125 * (h-vMargin-squareSize/8-1) + vMargin;
2126 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2127 if(strstr(seekAdList[i], " u ")) color = 1;
2128 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2129 !strstr(seekAdList[i], "bullet") &&
2130 !strstr(seekAdList[i], "blitz") &&
2131 !strstr(seekAdList[i], "standard") ) color = 2;
2132 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2133 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2137 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2139 char buf[MSG_SIZ], *ext = "";
2140 VariantClass v = StringToVariant(type);
2141 if(strstr(type, "wild")) {
2142 ext = type + 4; // append wild number
2143 if(v == VariantFischeRandom) type = "chess960"; else
2144 if(v == VariantLoadable) type = "setup"; else
2145 type = VariantName(v);
2147 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2148 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2149 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2150 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2151 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2152 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2153 seekNrList[nrOfSeekAds] = nr;
2154 zList[nrOfSeekAds] = 0;
2155 seekAdList[nrOfSeekAds++] = StrSave(buf);
2156 if(plot) PlotSeekAd(nrOfSeekAds-1);
2163 int x = xList[i], y = yList[i], d=squareSize/4, k;
2164 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2165 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2166 // now replot every dot that overlapped
2167 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2168 int xx = xList[k], yy = yList[k];
2169 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2170 DrawSeekDot(xx, yy, colorList[k]);
2175 RemoveSeekAd(int nr)
2178 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2180 if(seekAdList[i]) free(seekAdList[i]);
2181 seekAdList[i] = seekAdList[--nrOfSeekAds];
2182 seekNrList[i] = seekNrList[nrOfSeekAds];
2183 ratingList[i] = ratingList[nrOfSeekAds];
2184 colorList[i] = colorList[nrOfSeekAds];
2185 tcList[i] = tcList[nrOfSeekAds];
2186 xList[i] = xList[nrOfSeekAds];
2187 yList[i] = yList[nrOfSeekAds];
2188 zList[i] = zList[nrOfSeekAds];
2189 seekAdList[nrOfSeekAds] = NULL;
2195 MatchSoughtLine(char *line)
2197 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2198 int nr, base, inc, u=0; char dummy;
2200 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2201 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2203 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2204 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2205 // match: compact and save the line
2206 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2216 if(!seekGraphUp) return FALSE;
2217 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2218 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2220 DrawSeekBackground(0, 0, w, h);
2221 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2222 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2223 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2224 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2226 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2229 sprintf(buf, "%d", i);
2230 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2233 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2234 for(i=1; i<100; i+=(i<10?1:5)) {
2235 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2236 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2237 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2239 sprintf(buf, "%d", i);
2240 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2243 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2247 int SeekGraphClick(ClickType click, int x, int y, int moving)
2249 static int lastDown = 0, displayed = 0, lastSecond;
2250 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2251 if(click == Release || moving) return FALSE;
2253 soughtPending = TRUE;
2254 SendToICS(ics_prefix);
2255 SendToICS("sought\n"); // should this be "sought all"?
2256 } else { // issue challenge based on clicked ad
2257 int dist = 10000; int i, closest = 0, second = 0;
2258 for(i=0; i<nrOfSeekAds; i++) {
2259 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2260 if(d < dist) { dist = d; closest = i; }
2261 second += (d - zList[i] < 120); // count in-range ads
2262 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2266 second = (second > 1);
2267 if(displayed != closest || second != lastSecond) {
2268 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2269 lastSecond = second; displayed = closest;
2271 if(click == Press) {
2272 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2275 } // on press 'hit', only show info
2276 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2277 sprintf(buf, "play %d\n", seekNrList[closest]);
2278 SendToICS(ics_prefix);
2280 return TRUE; // let incoming board of started game pop down the graph
2281 } else if(click == Release) { // release 'miss' is ignored
2282 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2283 if(moving == 2) { // right up-click
2284 nrOfSeekAds = 0; // refresh graph
2285 soughtPending = TRUE;
2286 SendToICS(ics_prefix);
2287 SendToICS("sought\n"); // should this be "sought all"?
2290 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2291 // press miss or release hit 'pop down' seek graph
2292 seekGraphUp = FALSE;
2293 DrawPosition(TRUE, NULL);
2299 read_from_ics(isr, closure, data, count, error)
2306 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2307 #define STARTED_NONE 0
2308 #define STARTED_MOVES 1
2309 #define STARTED_BOARD 2
2310 #define STARTED_OBSERVE 3
2311 #define STARTED_HOLDINGS 4
2312 #define STARTED_CHATTER 5
2313 #define STARTED_COMMENT 6
2314 #define STARTED_MOVES_NOHIDE 7
2316 static int started = STARTED_NONE;
2317 static char parse[20000];
2318 static int parse_pos = 0;
2319 static char buf[BUF_SIZE + 1];
2320 static int firstTime = TRUE, intfSet = FALSE;
2321 static ColorClass prevColor = ColorNormal;
2322 static int savingComment = FALSE;
2323 static int cmatch = 0; // continuation sequence match
2330 int backup; /* [DM] For zippy color lines */
2332 char talker[MSG_SIZ]; // [HGM] chat
2335 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2337 if (appData.debugMode) {
2339 fprintf(debugFP, "<ICS: ");
2340 show_bytes(debugFP, data, count);
2341 fprintf(debugFP, "\n");
2345 if (appData.debugMode) { int f = forwardMostMove;
2346 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2347 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2348 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2351 /* If last read ended with a partial line that we couldn't parse,
2352 prepend it to the new read and try again. */
2353 if (leftover_len > 0) {
2354 for (i=0; i<leftover_len; i++)
2355 buf[i] = buf[leftover_start + i];
2358 /* copy new characters into the buffer */
2359 bp = buf + leftover_len;
2360 buf_len=leftover_len;
2361 for (i=0; i<count; i++)
2364 if (data[i] == '\r')
2367 // join lines split by ICS?
2368 if (!appData.noJoin)
2371 Joining just consists of finding matches against the
2372 continuation sequence, and discarding that sequence
2373 if found instead of copying it. So, until a match
2374 fails, there's nothing to do since it might be the
2375 complete sequence, and thus, something we don't want
2378 if (data[i] == cont_seq[cmatch])
2381 if (cmatch == strlen(cont_seq))
2383 cmatch = 0; // complete match. just reset the counter
2386 it's possible for the ICS to not include the space
2387 at the end of the last word, making our [correct]
2388 join operation fuse two separate words. the server
2389 does this when the space occurs at the width setting.
2391 if (!buf_len || buf[buf_len-1] != ' ')
2402 match failed, so we have to copy what matched before
2403 falling through and copying this character. In reality,
2404 this will only ever be just the newline character, but
2405 it doesn't hurt to be precise.
2407 strncpy(bp, cont_seq, cmatch);
2419 buf[buf_len] = NULLCHAR;
2420 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2425 while (i < buf_len) {
2426 /* Deal with part of the TELNET option negotiation
2427 protocol. We refuse to do anything beyond the
2428 defaults, except that we allow the WILL ECHO option,
2429 which ICS uses to turn off password echoing when we are
2430 directly connected to it. We reject this option
2431 if localLineEditing mode is on (always on in xboard)
2432 and we are talking to port 23, which might be a real
2433 telnet server that will try to keep WILL ECHO on permanently.
2435 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2436 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2437 unsigned char option;
2439 switch ((unsigned char) buf[++i]) {
2441 if (appData.debugMode)
2442 fprintf(debugFP, "\n<WILL ");
2443 switch (option = (unsigned char) buf[++i]) {
2445 if (appData.debugMode)
2446 fprintf(debugFP, "ECHO ");
2447 /* Reply only if this is a change, according
2448 to the protocol rules. */
2449 if (remoteEchoOption) break;
2450 if (appData.localLineEditing &&
2451 atoi(appData.icsPort) == TN_PORT) {
2452 TelnetRequest(TN_DONT, TN_ECHO);
2455 TelnetRequest(TN_DO, TN_ECHO);
2456 remoteEchoOption = TRUE;
2460 if (appData.debugMode)
2461 fprintf(debugFP, "%d ", option);
2462 /* Whatever this is, we don't want it. */
2463 TelnetRequest(TN_DONT, option);
2468 if (appData.debugMode)
2469 fprintf(debugFP, "\n<WONT ");
2470 switch (option = (unsigned char) buf[++i]) {
2472 if (appData.debugMode)
2473 fprintf(debugFP, "ECHO ");
2474 /* Reply only if this is a change, according
2475 to the protocol rules. */
2476 if (!remoteEchoOption) break;
2478 TelnetRequest(TN_DONT, TN_ECHO);
2479 remoteEchoOption = FALSE;
2482 if (appData.debugMode)
2483 fprintf(debugFP, "%d ", (unsigned char) option);
2484 /* Whatever this is, it must already be turned
2485 off, because we never agree to turn on
2486 anything non-default, so according to the
2487 protocol rules, we don't reply. */
2492 if (appData.debugMode)
2493 fprintf(debugFP, "\n<DO ");
2494 switch (option = (unsigned char) buf[++i]) {
2496 /* Whatever this is, we refuse to do it. */
2497 if (appData.debugMode)
2498 fprintf(debugFP, "%d ", option);
2499 TelnetRequest(TN_WONT, option);
2504 if (appData.debugMode)
2505 fprintf(debugFP, "\n<DONT ");
2506 switch (option = (unsigned char) buf[++i]) {
2508 if (appData.debugMode)
2509 fprintf(debugFP, "%d ", option);
2510 /* Whatever this is, we are already not doing
2511 it, because we never agree to do anything
2512 non-default, so according to the protocol
2513 rules, we don't reply. */
2518 if (appData.debugMode)
2519 fprintf(debugFP, "\n<IAC ");
2520 /* Doubled IAC; pass it through */
2524 if (appData.debugMode)
2525 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2526 /* Drop all other telnet commands on the floor */
2529 if (oldi > next_out)
2530 SendToPlayer(&buf[next_out], oldi - next_out);
2536 /* OK, this at least will *usually* work */
2537 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2541 if (loggedOn && !intfSet) {
2542 if (ics_type == ICS_ICC) {
2544 "/set-quietly interface %s\n/set-quietly style 12\n",
2546 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2547 strcat(str, "/set-2 51 1\n/set seek 1\n");
2548 } else if (ics_type == ICS_CHESSNET) {
2549 sprintf(str, "/style 12\n");
2551 strcpy(str, "alias $ @\n$set interface ");
2552 strcat(str, programVersion);
2553 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2554 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2555 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2557 strcat(str, "$iset nohighlight 1\n");
2559 strcat(str, "$iset lock 1\n$style 12\n");
2562 NotifyFrontendLogin();
2566 if (started == STARTED_COMMENT) {
2567 /* Accumulate characters in comment */
2568 parse[parse_pos++] = buf[i];
2569 if (buf[i] == '\n') {
2570 parse[parse_pos] = NULLCHAR;
2571 if(chattingPartner>=0) {
2573 sprintf(mess, "%s%s", talker, parse);
2574 OutputChatMessage(chattingPartner, mess);
2575 chattingPartner = -1;
2576 next_out = i+1; // [HGM] suppress printing in ICS window
2578 if(!suppressKibitz) // [HGM] kibitz
2579 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2580 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2581 int nrDigit = 0, nrAlph = 0, j;
2582 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2583 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2584 parse[parse_pos] = NULLCHAR;
2585 // try to be smart: if it does not look like search info, it should go to
2586 // ICS interaction window after all, not to engine-output window.
2587 for(j=0; j<parse_pos; j++) { // count letters and digits
2588 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2589 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2590 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2592 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2593 int depth=0; float score;
2594 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2595 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2596 pvInfoList[forwardMostMove-1].depth = depth;
2597 pvInfoList[forwardMostMove-1].score = 100*score;
2599 OutputKibitz(suppressKibitz, parse);
2602 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2603 SendToPlayer(tmp, strlen(tmp));
2605 next_out = i+1; // [HGM] suppress printing in ICS window
2607 started = STARTED_NONE;
2609 /* Don't match patterns against characters in comment */
2614 if (started == STARTED_CHATTER) {
2615 if (buf[i] != '\n') {
2616 /* Don't match patterns against characters in chatter */
2620 started = STARTED_NONE;
2621 if(suppressKibitz) next_out = i+1;
2624 /* Kludge to deal with rcmd protocol */
2625 if (firstTime && looking_at(buf, &i, "\001*")) {
2626 DisplayFatalError(&buf[1], 0, 1);
2632 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2635 if (appData.debugMode)
2636 fprintf(debugFP, "ics_type %d\n", ics_type);
2639 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2640 ics_type = ICS_FICS;
2642 if (appData.debugMode)
2643 fprintf(debugFP, "ics_type %d\n", ics_type);
2646 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2647 ics_type = ICS_CHESSNET;
2649 if (appData.debugMode)
2650 fprintf(debugFP, "ics_type %d\n", ics_type);
2655 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2656 looking_at(buf, &i, "Logging you in as \"*\"") ||
2657 looking_at(buf, &i, "will be \"*\""))) {
2658 strcpy(ics_handle, star_match[0]);
2662 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2664 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2665 DisplayIcsInteractionTitle(buf);
2666 have_set_title = TRUE;
2669 /* skip finger notes */
2670 if (started == STARTED_NONE &&
2671 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2672 (buf[i] == '1' && buf[i+1] == '0')) &&
2673 buf[i+2] == ':' && buf[i+3] == ' ') {
2674 started = STARTED_CHATTER;
2680 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2681 if(appData.seekGraph) {
2682 if(soughtPending && MatchSoughtLine(buf+i)) {
2683 i = strstr(buf+i, "rated") - buf;
2684 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2685 next_out = leftover_start = i;
2686 started = STARTED_CHATTER;
2687 suppressKibitz = TRUE;
2690 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2691 && looking_at(buf, &i, "* ads displayed")) {
2692 soughtPending = FALSE;
2697 if(appData.autoRefresh) {
2698 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2699 int s = (ics_type == ICS_ICC); // ICC format differs
2701 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2702 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2703 looking_at(buf, &i, "*% "); // eat prompt
2704 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2705 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2706 next_out = i; // suppress
2709 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2710 char *p = star_match[0];
2712 if(seekGraphUp) RemoveSeekAd(atoi(p));
2713 while(*p && *p++ != ' '); // next
2715 looking_at(buf, &i, "*% "); // eat prompt
2716 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2723 /* skip formula vars */
2724 if (started == STARTED_NONE &&
2725 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2726 started = STARTED_CHATTER;
2731 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2732 if (appData.autoKibitz && started == STARTED_NONE &&
2733 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2734 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2735 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2736 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2737 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2738 suppressKibitz = TRUE;
2739 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2741 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2742 && (gameMode == IcsPlayingWhite)) ||
2743 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2744 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2745 started = STARTED_CHATTER; // own kibitz we simply discard
2747 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2748 parse_pos = 0; parse[0] = NULLCHAR;
2749 savingComment = TRUE;
2750 suppressKibitz = gameMode != IcsObserving ? 2 :
2751 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2755 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2756 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2757 && atoi(star_match[0])) {
2758 // suppress the acknowledgements of our own autoKibitz
2760 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2761 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2762 SendToPlayer(star_match[0], strlen(star_match[0]));
2763 if(looking_at(buf, &i, "*% ")) // eat prompt
2764 suppressKibitz = FALSE;
2768 } // [HGM] kibitz: end of patch
2770 // [HGM] chat: intercept tells by users for which we have an open chat window
2772 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2773 looking_at(buf, &i, "* whispers:") ||
2774 looking_at(buf, &i, "* kibitzes:") ||
2775 looking_at(buf, &i, "* shouts:") ||
2776 looking_at(buf, &i, "* c-shouts:") ||
2777 looking_at(buf, &i, "--> * ") ||
2778 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2779 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2780 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2781 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2783 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2784 chattingPartner = -1;
2786 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2787 for(p=0; p<MAX_CHAT; p++) {
2788 if(channel == atoi(chatPartner[p])) {
2789 talker[0] = '['; strcat(talker, "] ");
2790 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2791 chattingPartner = p; break;
2794 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2795 for(p=0; p<MAX_CHAT; p++) {
2796 if(!strcmp("kibitzes", chatPartner[p])) {
2797 talker[0] = '['; strcat(talker, "] ");
2798 chattingPartner = p; break;
2801 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2802 for(p=0; p<MAX_CHAT; p++) {
2803 if(!strcmp("whispers", chatPartner[p])) {
2804 talker[0] = '['; strcat(talker, "] ");
2805 chattingPartner = p; break;
2808 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2809 if(buf[i-8] == '-' && buf[i-3] == 't')
2810 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2811 if(!strcmp("c-shouts", chatPartner[p])) {
2812 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2813 chattingPartner = p; break;
2816 if(chattingPartner < 0)
2817 for(p=0; p<MAX_CHAT; p++) {
2818 if(!strcmp("shouts", chatPartner[p])) {
2819 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2820 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2821 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2822 chattingPartner = p; break;
2826 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2827 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2828 talker[0] = 0; Colorize(ColorTell, FALSE);
2829 chattingPartner = p; break;
2831 if(chattingPartner<0) i = oldi; else {
2832 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2833 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2834 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2835 started = STARTED_COMMENT;
2836 parse_pos = 0; parse[0] = NULLCHAR;
2837 savingComment = 3 + chattingPartner; // counts as TRUE
2838 suppressKibitz = TRUE;
2841 } // [HGM] chat: end of patch
2843 if (appData.zippyTalk || appData.zippyPlay) {
2844 /* [DM] Backup address for color zippy lines */
2847 if (loggedOn == TRUE)
2848 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2849 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2851 } // [DM] 'else { ' deleted
2853 /* Regular tells and says */
2854 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2855 looking_at(buf, &i, "* (your partner) tells you: ") ||
2856 looking_at(buf, &i, "* says: ") ||
2857 /* Don't color "message" or "messages" output */
2858 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2859 looking_at(buf, &i, "*. * at *:*: ") ||
2860 looking_at(buf, &i, "--* (*:*): ") ||
2861 /* Message notifications (same color as tells) */
2862 looking_at(buf, &i, "* has left a message ") ||
2863 looking_at(buf, &i, "* just sent you a message:\n") ||
2864 /* Whispers and kibitzes */
2865 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2866 looking_at(buf, &i, "* kibitzes: ") ||
2868 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2870 if (tkind == 1 && strchr(star_match[0], ':')) {
2871 /* Avoid "tells you:" spoofs in channels */
2874 if (star_match[0][0] == NULLCHAR ||
2875 strchr(star_match[0], ' ') ||
2876 (tkind == 3 && strchr(star_match[1], ' '))) {
2877 /* Reject bogus matches */
2880 if (appData.colorize) {
2881 if (oldi > next_out) {
2882 SendToPlayer(&buf[next_out], oldi - next_out);
2887 Colorize(ColorTell, FALSE);
2888 curColor = ColorTell;
2891 Colorize(ColorKibitz, FALSE);
2892 curColor = ColorKibitz;
2895 p = strrchr(star_match[1], '(');
2902 Colorize(ColorChannel1, FALSE);
2903 curColor = ColorChannel1;
2905 Colorize(ColorChannel, FALSE);
2906 curColor = ColorChannel;
2910 curColor = ColorNormal;
2914 if (started == STARTED_NONE && appData.autoComment &&
2915 (gameMode == IcsObserving ||
2916 gameMode == IcsPlayingWhite ||
2917 gameMode == IcsPlayingBlack)) {
2918 parse_pos = i - oldi;
2919 memcpy(parse, &buf[oldi], parse_pos);
2920 parse[parse_pos] = NULLCHAR;
2921 started = STARTED_COMMENT;
2922 savingComment = TRUE;
2924 started = STARTED_CHATTER;
2925 savingComment = FALSE;
2932 if (looking_at(buf, &i, "* s-shouts: ") ||
2933 looking_at(buf, &i, "* c-shouts: ")) {
2934 if (appData.colorize) {
2935 if (oldi > next_out) {
2936 SendToPlayer(&buf[next_out], oldi - next_out);
2939 Colorize(ColorSShout, FALSE);
2940 curColor = ColorSShout;
2943 started = STARTED_CHATTER;
2947 if (looking_at(buf, &i, "--->")) {
2952 if (looking_at(buf, &i, "* shouts: ") ||
2953 looking_at(buf, &i, "--> ")) {
2954 if (appData.colorize) {
2955 if (oldi > next_out) {
2956 SendToPlayer(&buf[next_out], oldi - next_out);
2959 Colorize(ColorShout, FALSE);
2960 curColor = ColorShout;
2963 started = STARTED_CHATTER;
2967 if (looking_at( buf, &i, "Challenge:")) {
2968 if (appData.colorize) {
2969 if (oldi > next_out) {
2970 SendToPlayer(&buf[next_out], oldi - next_out);
2973 Colorize(ColorChallenge, FALSE);
2974 curColor = ColorChallenge;
2980 if (looking_at(buf, &i, "* offers you") ||
2981 looking_at(buf, &i, "* offers to be") ||
2982 looking_at(buf, &i, "* would like to") ||
2983 looking_at(buf, &i, "* requests to") ||
2984 looking_at(buf, &i, "Your opponent offers") ||
2985 looking_at(buf, &i, "Your opponent requests")) {
2987 if (appData.colorize) {
2988 if (oldi > next_out) {
2989 SendToPlayer(&buf[next_out], oldi - next_out);
2992 Colorize(ColorRequest, FALSE);
2993 curColor = ColorRequest;
2998 if (looking_at(buf, &i, "* (*) seeking")) {
2999 if (appData.colorize) {
3000 if (oldi > next_out) {
3001 SendToPlayer(&buf[next_out], oldi - next_out);
3004 Colorize(ColorSeek, FALSE);
3005 curColor = ColorSeek;
3010 if (looking_at(buf, &i, "\\ ")) {
3011 if (prevColor != ColorNormal) {
3012 if (oldi > next_out) {
3013 SendToPlayer(&buf[next_out], oldi - next_out);
3016 Colorize(prevColor, TRUE);
3017 curColor = prevColor;
3019 if (savingComment) {
3020 parse_pos = i - oldi;
3021 memcpy(parse, &buf[oldi], parse_pos);
3022 parse[parse_pos] = NULLCHAR;
3023 started = STARTED_COMMENT;
3024 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3025 chattingPartner = savingComment - 3; // kludge to remember the box
3027 started = STARTED_CHATTER;
3032 if (looking_at(buf, &i, "Black Strength :") ||
3033 looking_at(buf, &i, "<<< style 10 board >>>") ||
3034 looking_at(buf, &i, "<10>") ||
3035 looking_at(buf, &i, "#@#")) {
3036 /* Wrong board style */
3038 SendToICS(ics_prefix);
3039 SendToICS("set style 12\n");
3040 SendToICS(ics_prefix);
3041 SendToICS("refresh\n");
3045 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3047 have_sent_ICS_logon = 1;
3051 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3052 (looking_at(buf, &i, "\n<12> ") ||
3053 looking_at(buf, &i, "<12> "))) {
3055 if (oldi > next_out) {
3056 SendToPlayer(&buf[next_out], oldi - next_out);
3059 started = STARTED_BOARD;
3064 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3065 looking_at(buf, &i, "<b1> ")) {
3066 if (oldi > next_out) {
3067 SendToPlayer(&buf[next_out], oldi - next_out);
3070 started = STARTED_HOLDINGS;
3075 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3077 /* Header for a move list -- first line */
3079 switch (ics_getting_history) {
3083 case BeginningOfGame:
3084 /* User typed "moves" or "oldmoves" while we
3085 were idle. Pretend we asked for these
3086 moves and soak them up so user can step
3087 through them and/or save them.
3090 gameMode = IcsObserving;
3093 ics_getting_history = H_GOT_UNREQ_HEADER;
3095 case EditGame: /*?*/
3096 case EditPosition: /*?*/
3097 /* Should above feature work in these modes too? */
3098 /* For now it doesn't */
3099 ics_getting_history = H_GOT_UNWANTED_HEADER;
3102 ics_getting_history = H_GOT_UNWANTED_HEADER;
3107 /* Is this the right one? */
3108 if (gameInfo.white && gameInfo.black &&
3109 strcmp(gameInfo.white, star_match[0]) == 0 &&
3110 strcmp(gameInfo.black, star_match[2]) == 0) {
3112 ics_getting_history = H_GOT_REQ_HEADER;
3115 case H_GOT_REQ_HEADER:
3116 case H_GOT_UNREQ_HEADER:
3117 case H_GOT_UNWANTED_HEADER:
3118 case H_GETTING_MOVES:
3119 /* Should not happen */
3120 DisplayError(_("Error gathering move list: two headers"), 0);
3121 ics_getting_history = H_FALSE;
3125 /* Save player ratings into gameInfo if needed */
3126 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3127 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3128 (gameInfo.whiteRating == -1 ||
3129 gameInfo.blackRating == -1)) {
3131 gameInfo.whiteRating = string_to_rating(star_match[1]);
3132 gameInfo.blackRating = string_to_rating(star_match[3]);
3133 if (appData.debugMode)
3134 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3135 gameInfo.whiteRating, gameInfo.blackRating);
3140 if (looking_at(buf, &i,
3141 "* * match, initial time: * minute*, increment: * second")) {
3142 /* Header for a move list -- second line */
3143 /* Initial board will follow if this is a wild game */
3144 if (gameInfo.event != NULL) free(gameInfo.event);
3145 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3146 gameInfo.event = StrSave(str);
3147 /* [HGM] we switched variant. Translate boards if needed. */
3148 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3152 if (looking_at(buf, &i, "Move ")) {
3153 /* Beginning of a move list */
3154 switch (ics_getting_history) {
3156 /* Normally should not happen */
3157 /* Maybe user hit reset while we were parsing */
3160 /* Happens if we are ignoring a move list that is not
3161 * the one we just requested. Common if the user
3162 * tries to observe two games without turning off
3165 case H_GETTING_MOVES:
3166 /* Should not happen */
3167 DisplayError(_("Error gathering move list: nested"), 0);
3168 ics_getting_history = H_FALSE;
3170 case H_GOT_REQ_HEADER:
3171 ics_getting_history = H_GETTING_MOVES;
3172 started = STARTED_MOVES;
3174 if (oldi > next_out) {
3175 SendToPlayer(&buf[next_out], oldi - next_out);
3178 case H_GOT_UNREQ_HEADER:
3179 ics_getting_history = H_GETTING_MOVES;
3180 started = STARTED_MOVES_NOHIDE;
3183 case H_GOT_UNWANTED_HEADER:
3184 ics_getting_history = H_FALSE;
3190 if (looking_at(buf, &i, "% ") ||
3191 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3192 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3193 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3194 soughtPending = FALSE;
3198 if(suppressKibitz) next_out = i;
3199 savingComment = FALSE;
3203 case STARTED_MOVES_NOHIDE:
3204 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3205 parse[parse_pos + i - oldi] = NULLCHAR;
3206 ParseGameHistory(parse);
3208 if (appData.zippyPlay && first.initDone) {
3209 FeedMovesToProgram(&first, forwardMostMove);
3210 if (gameMode == IcsPlayingWhite) {
3211 if (WhiteOnMove(forwardMostMove)) {
3212 if (first.sendTime) {
3213 if (first.useColors) {
3214 SendToProgram("black\n", &first);
3216 SendTimeRemaining(&first, TRUE);
3218 if (first.useColors) {
3219 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3221 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3222 first.maybeThinking = TRUE;
3224 if (first.usePlayother) {
3225 if (first.sendTime) {
3226 SendTimeRemaining(&first, TRUE);
3228 SendToProgram("playother\n", &first);
3234 } else if (gameMode == IcsPlayingBlack) {
3235 if (!WhiteOnMove(forwardMostMove)) {
3236 if (first.sendTime) {
3237 if (first.useColors) {
3238 SendToProgram("white\n", &first);
3240 SendTimeRemaining(&first, FALSE);
3242 if (first.useColors) {
3243 SendToProgram("black\n", &first);
3245 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3246 first.maybeThinking = TRUE;
3248 if (first.usePlayother) {
3249 if (first.sendTime) {
3250 SendTimeRemaining(&first, FALSE);
3252 SendToProgram("playother\n", &first);
3261 if (gameMode == IcsObserving && ics_gamenum == -1) {
3262 /* Moves came from oldmoves or moves command
3263 while we weren't doing anything else.
3265 currentMove = forwardMostMove;
3266 ClearHighlights();/*!!could figure this out*/
3267 flipView = appData.flipView;
3268 DrawPosition(TRUE, boards[currentMove]);
3269 DisplayBothClocks();
3270 sprintf(str, "%s vs. %s",
3271 gameInfo.white, gameInfo.black);
3275 /* Moves were history of an active game */
3276 if (gameInfo.resultDetails != NULL) {
3277 free(gameInfo.resultDetails);
3278 gameInfo.resultDetails = NULL;
3281 HistorySet(parseList, backwardMostMove,
3282 forwardMostMove, currentMove-1);
3283 DisplayMove(currentMove - 1);
3284 if (started == STARTED_MOVES) next_out = i;
3285 started = STARTED_NONE;
3286 ics_getting_history = H_FALSE;
3289 case STARTED_OBSERVE:
3290 started = STARTED_NONE;
3291 SendToICS(ics_prefix);
3292 SendToICS("refresh\n");
3298 if(bookHit) { // [HGM] book: simulate book reply
3299 static char bookMove[MSG_SIZ]; // a bit generous?
3301 programStats.nodes = programStats.depth = programStats.time =
3302 programStats.score = programStats.got_only_move = 0;
3303 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3305 strcpy(bookMove, "move ");
3306 strcat(bookMove, bookHit);
3307 HandleMachineMove(bookMove, &first);
3312 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3313 started == STARTED_HOLDINGS ||
3314 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3315 /* Accumulate characters in move list or board */
3316 parse[parse_pos++] = buf[i];
3319 /* Start of game messages. Mostly we detect start of game
3320 when the first board image arrives. On some versions
3321 of the ICS, though, we need to do a "refresh" after starting
3322 to observe in order to get the current board right away. */
3323 if (looking_at(buf, &i, "Adding game * to observation list")) {
3324 started = STARTED_OBSERVE;
3328 /* Handle auto-observe */
3329 if (appData.autoObserve &&
3330 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3331 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3333 /* Choose the player that was highlighted, if any. */
3334 if (star_match[0][0] == '\033' ||
3335 star_match[1][0] != '\033') {
3336 player = star_match[0];
3338 player = star_match[2];
3340 sprintf(str, "%sobserve %s\n",
3341 ics_prefix, StripHighlightAndTitle(player));
3344 /* Save ratings from notify string */
3345 strcpy(player1Name, star_match[0]);
3346 player1Rating = string_to_rating(star_match[1]);
3347 strcpy(player2Name, star_match[2]);
3348 player2Rating = string_to_rating(star_match[3]);
3350 if (appData.debugMode)
3352 "Ratings from 'Game notification:' %s %d, %s %d\n",
3353 player1Name, player1Rating,
3354 player2Name, player2Rating);
3359 /* Deal with automatic examine mode after a game,
3360 and with IcsObserving -> IcsExamining transition */
3361 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3362 looking_at(buf, &i, "has made you an examiner of game *")) {
3364 int gamenum = atoi(star_match[0]);
3365 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3366 gamenum == ics_gamenum) {
3367 /* We were already playing or observing this game;
3368 no need to refetch history */
3369 gameMode = IcsExamining;
3371 pauseExamForwardMostMove = forwardMostMove;
3372 } else if (currentMove < forwardMostMove) {
3373 ForwardInner(forwardMostMove);
3376 /* I don't think this case really can happen */
3377 SendToICS(ics_prefix);
3378 SendToICS("refresh\n");
3383 /* Error messages */
3384 // if (ics_user_moved) {
3385 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3386 if (looking_at(buf, &i, "Illegal move") ||
3387 looking_at(buf, &i, "Not a legal move") ||
3388 looking_at(buf, &i, "Your king is in check") ||
3389 looking_at(buf, &i, "It isn't your turn") ||
3390 looking_at(buf, &i, "It is not your move")) {
3392 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3393 currentMove = forwardMostMove-1;
3394 DisplayMove(currentMove - 1); /* before DMError */
3395 DrawPosition(FALSE, boards[currentMove]);
3396 SwitchClocks(forwardMostMove-1); // [HGM] race
3397 DisplayBothClocks();
3399 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3405 if (looking_at(buf, &i, "still have time") ||
3406 looking_at(buf, &i, "not out of time") ||
3407 looking_at(buf, &i, "either player is out of time") ||
3408 looking_at(buf, &i, "has timeseal; checking")) {
3409 /* We must have called his flag a little too soon */
3410 whiteFlag = blackFlag = FALSE;
3414 if (looking_at(buf, &i, "added * seconds to") ||
3415 looking_at(buf, &i, "seconds were added to")) {
3416 /* Update the clocks */
3417 SendToICS(ics_prefix);
3418 SendToICS("refresh\n");
3422 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3423 ics_clock_paused = TRUE;
3428 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3429 ics_clock_paused = FALSE;
3434 /* Grab player ratings from the Creating: message.
3435 Note we have to check for the special case when
3436 the ICS inserts things like [white] or [black]. */
3437 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3438 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3440 0 player 1 name (not necessarily white)
3442 2 empty, white, or black (IGNORED)
3443 3 player 2 name (not necessarily black)
3446 The names/ratings are sorted out when the game
3447 actually starts (below).
3449 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3450 player1Rating = string_to_rating(star_match[1]);
3451 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3452 player2Rating = string_to_rating(star_match[4]);
3454 if (appData.debugMode)
3456 "Ratings from 'Creating:' %s %d, %s %d\n",
3457 player1Name, player1Rating,
3458 player2Name, player2Rating);
3463 /* Improved generic start/end-of-game messages */
3464 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3465 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3466 /* If tkind == 0: */
3467 /* star_match[0] is the game number */
3468 /* [1] is the white player's name */
3469 /* [2] is the black player's name */
3470 /* For end-of-game: */
3471 /* [3] is the reason for the game end */
3472 /* [4] is a PGN end game-token, preceded by " " */
3473 /* For start-of-game: */
3474 /* [3] begins with "Creating" or "Continuing" */
3475 /* [4] is " *" or empty (don't care). */
3476 int gamenum = atoi(star_match[0]);
3477 char *whitename, *blackname, *why, *endtoken;
3478 ChessMove endtype = (ChessMove) 0;
3481 whitename = star_match[1];
3482 blackname = star_match[2];
3483 why = star_match[3];
3484 endtoken = star_match[4];
3486 whitename = star_match[1];
3487 blackname = star_match[3];
3488 why = star_match[5];
3489 endtoken = star_match[6];
3492 /* Game start messages */
3493 if (strncmp(why, "Creating ", 9) == 0 ||
3494 strncmp(why, "Continuing ", 11) == 0) {
3495 gs_gamenum = gamenum;
3496 strcpy(gs_kind, strchr(why, ' ') + 1);
3497 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3499 if (appData.zippyPlay) {
3500 ZippyGameStart(whitename, blackname);
3503 partnerBoardValid = FALSE; // [HGM] bughouse
3507 /* Game end messages */
3508 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3509 ics_gamenum != gamenum) {
3512 while (endtoken[0] == ' ') endtoken++;
3513 switch (endtoken[0]) {
3516 endtype = GameUnfinished;
3519 endtype = BlackWins;
3522 if (endtoken[1] == '/')
3523 endtype = GameIsDrawn;
3525 endtype = WhiteWins;
3528 GameEnds(endtype, why, GE_ICS);
3530 if (appData.zippyPlay && first.initDone) {
3531 ZippyGameEnd(endtype, why);
3532 if (first.pr == NULL) {
3533 /* Start the next process early so that we'll
3534 be ready for the next challenge */
3535 StartChessProgram(&first);
3537 /* Send "new" early, in case this command takes
3538 a long time to finish, so that we'll be ready
3539 for the next challenge. */
3540 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3544 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3548 if (looking_at(buf, &i, "Removing game * from observation") ||
3549 looking_at(buf, &i, "no longer observing game *") ||
3550 looking_at(buf, &i, "Game * (*) has no examiners")) {
3551 if (gameMode == IcsObserving &&
3552 atoi(star_match[0]) == ics_gamenum)
3554 /* icsEngineAnalyze */
3555 if (appData.icsEngineAnalyze) {
3562 ics_user_moved = FALSE;
3567 if (looking_at(buf, &i, "no longer examining game *")) {
3568 if (gameMode == IcsExamining &&
3569 atoi(star_match[0]) == ics_gamenum)
3573 ics_user_moved = FALSE;
3578 /* Advance leftover_start past any newlines we find,
3579 so only partial lines can get reparsed */
3580 if (looking_at(buf, &i, "\n")) {
3581 prevColor = curColor;
3582 if (curColor != ColorNormal) {
3583 if (oldi > next_out) {
3584 SendToPlayer(&buf[next_out], oldi - next_out);
3587 Colorize(ColorNormal, FALSE);
3588 curColor = ColorNormal;
3590 if (started == STARTED_BOARD) {
3591 started = STARTED_NONE;
3592 parse[parse_pos] = NULLCHAR;
3593 ParseBoard12(parse);
3596 /* Send premove here */
3597 if (appData.premove) {
3599 if (currentMove == 0 &&
3600 gameMode == IcsPlayingWhite &&
3601 appData.premoveWhite) {
3602 sprintf(str, "%s\n", appData.premoveWhiteText);
3603 if (appData.debugMode)
3604 fprintf(debugFP, "Sending premove:\n");
3606 } else if (currentMove == 1 &&
3607 gameMode == IcsPlayingBlack &&
3608 appData.premoveBlack) {
3609 sprintf(str, "%s\n", appData.premoveBlackText);
3610 if (appData.debugMode)
3611 fprintf(debugFP, "Sending premove:\n");
3613 } else if (gotPremove) {
3615 ClearPremoveHighlights();
3616 if (appData.debugMode)
3617 fprintf(debugFP, "Sending premove:\n");
3618 UserMoveEvent(premoveFromX, premoveFromY,
3619 premoveToX, premoveToY,
3624 /* Usually suppress following prompt */
3625 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3626 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3627 if (looking_at(buf, &i, "*% ")) {
3628 savingComment = FALSE;
3633 } else if (started == STARTED_HOLDINGS) {
3635 char new_piece[MSG_SIZ];
3636 started = STARTED_NONE;
3637 parse[parse_pos] = NULLCHAR;
3638 if (appData.debugMode)
3639 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3640 parse, currentMove);
3641 if (sscanf(parse, " game %d", &gamenum) == 1) {
3642 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3643 if (gameInfo.variant == VariantNormal) {
3644 /* [HGM] We seem to switch variant during a game!
3645 * Presumably no holdings were displayed, so we have
3646 * to move the position two files to the right to
3647 * create room for them!
3649 VariantClass newVariant;
3650 switch(gameInfo.boardWidth) { // base guess on board width
3651 case 9: newVariant = VariantShogi; break;
3652 case 10: newVariant = VariantGreat; break;
3653 default: newVariant = VariantCrazyhouse; break;
3655 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3656 /* Get a move list just to see the header, which
3657 will tell us whether this is really bug or zh */
3658 if (ics_getting_history == H_FALSE) {
3659 ics_getting_history = H_REQUESTED;
3660 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3664 new_piece[0] = NULLCHAR;
3665 sscanf(parse, "game %d white [%s black [%s <- %s",
3666 &gamenum, white_holding, black_holding,
3668 white_holding[strlen(white_holding)-1] = NULLCHAR;
3669 black_holding[strlen(black_holding)-1] = NULLCHAR;
3670 /* [HGM] copy holdings to board holdings area */
3671 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3672 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3673 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3675 if (appData.zippyPlay && first.initDone) {
3676 ZippyHoldings(white_holding, black_holding,
3680 if (tinyLayout || smallLayout) {
3681 char wh[16], bh[16];
3682 PackHolding(wh, white_holding);
3683 PackHolding(bh, black_holding);
3684 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3685 gameInfo.white, gameInfo.black);
3687 sprintf(str, "%s [%s] vs. %s [%s]",
3688 gameInfo.white, white_holding,
3689 gameInfo.black, black_holding);
3691 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3692 DrawPosition(FALSE, boards[currentMove]);
3694 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3695 sscanf(parse, "game %d white [%s black [%s <- %s",
3696 &gamenum, white_holding, black_holding,
3698 white_holding[strlen(white_holding)-1] = NULLCHAR;
3699 black_holding[strlen(black_holding)-1] = NULLCHAR;
3700 /* [HGM] copy holdings to partner-board holdings area */
3701 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3702 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3703 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3704 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3705 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3708 /* Suppress following prompt */
3709 if (looking_at(buf, &i, "*% ")) {
3710 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3711 savingComment = FALSE;
3719 i++; /* skip unparsed character and loop back */
3722 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3723 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3724 // SendToPlayer(&buf[next_out], i - next_out);
3725 started != STARTED_HOLDINGS && leftover_start > next_out) {
3726 SendToPlayer(&buf[next_out], leftover_start - next_out);
3730 leftover_len = buf_len - leftover_start;
3731 /* if buffer ends with something we couldn't parse,
3732 reparse it after appending the next read */
3734 } else if (count == 0) {
3735 RemoveInputSource(isr);
3736 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3738 DisplayFatalError(_("Error reading from ICS"), error, 1);
3743 /* Board style 12 looks like this:
3745 <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
3747 * The "<12> " is stripped before it gets to this routine. The two
3748 * trailing 0's (flip state and clock ticking) are later addition, and
3749 * some chess servers may not have them, or may have only the first.
3750 * Additional trailing fields may be added in the future.
3753 #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"
3755 #define RELATION_OBSERVING_PLAYED 0
3756 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3757 #define RELATION_PLAYING_MYMOVE 1
3758 #define RELATION_PLAYING_NOTMYMOVE -1
3759 #define RELATION_EXAMINING 2
3760 #define RELATION_ISOLATED_BOARD -3
3761 #define RELATION_STARTING_POSITION -4 /* FICS only */
3764 ParseBoard12(string)
3767 GameMode newGameMode;
3768 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3769 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3770 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3771 char to_play, board_chars[200];
3772 char move_str[500], str[500], elapsed_time[500];
3773 char black[32], white[32];
3775 int prevMove = currentMove;
3778 int fromX, fromY, toX, toY;
3780 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3781 char *bookHit = NULL; // [HGM] book
3782 Boolean weird = FALSE, reqFlag = FALSE;
3784 fromX = fromY = toX = toY = -1;
3788 if (appData.debugMode)
3789 fprintf(debugFP, _("Parsing board: %s\n"), string);
3791 move_str[0] = NULLCHAR;
3792 elapsed_time[0] = NULLCHAR;
3793 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3795 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3796 if(string[i] == ' ') { ranks++; files = 0; }
3798 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3801 for(j = 0; j <i; j++) board_chars[j] = string[j];
3802 board_chars[i] = '\0';
3805 n = sscanf(string, PATTERN, &to_play, &double_push,
3806 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3807 &gamenum, white, black, &relation, &basetime, &increment,
3808 &white_stren, &black_stren, &white_time, &black_time,
3809 &moveNum, str, elapsed_time, move_str, &ics_flip,
3813 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3814 DisplayError(str, 0);
3818 /* Convert the move number to internal form */
3819 moveNum = (moveNum - 1) * 2;
3820 if (to_play == 'B') moveNum++;
3821 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3822 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3828 case RELATION_OBSERVING_PLAYED:
3829 case RELATION_OBSERVING_STATIC:
3830 if (gamenum == -1) {
3831 /* Old ICC buglet */
3832 relation = RELATION_OBSERVING_STATIC;
3834 newGameMode = IcsObserving;
3836 case RELATION_PLAYING_MYMOVE:
3837 case RELATION_PLAYING_NOTMYMOVE:
3839 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3840 IcsPlayingWhite : IcsPlayingBlack;
3842 case RELATION_EXAMINING:
3843 newGameMode = IcsExamining;
3845 case RELATION_ISOLATED_BOARD:
3847 /* Just display this board. If user was doing something else,
3848 we will forget about it until the next board comes. */
3849 newGameMode = IcsIdle;
3851 case RELATION_STARTING_POSITION:
3852 newGameMode = gameMode;
3856 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3857 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3858 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3860 for (k = 0; k < ranks; k++) {
3861 for (j = 0; j < files; j++)
3862 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3863 if(gameInfo.holdingsWidth > 1) {
3864 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3865 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3868 CopyBoard(partnerBoard, board);
3869 if(toSqr = strchr(str, '/')) { // extract highlights from long move
3870 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3871 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3872 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3873 if(toSqr = strchr(str, '-')) {
3874 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3875 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3876 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3877 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3878 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3879 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3880 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3881 sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3882 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3883 DisplayMessage(partnerStatus, "");
3884 partnerBoardValid = TRUE;
3888 /* Modify behavior for initial board display on move listing
3891 switch (ics_getting_history) {
3895 case H_GOT_REQ_HEADER:
3896 case H_GOT_UNREQ_HEADER:
3897 /* This is the initial position of the current game */
3898 gamenum = ics_gamenum;
3899 moveNum = 0; /* old ICS bug workaround */
3900 if (to_play == 'B') {
3901 startedFromSetupPosition = TRUE;
3902 blackPlaysFirst = TRUE;
3904 if (forwardMostMove == 0) forwardMostMove = 1;
3905 if (backwardMostMove == 0) backwardMostMove = 1;
3906 if (currentMove == 0) currentMove = 1;
3908 newGameMode = gameMode;
3909 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3911 case H_GOT_UNWANTED_HEADER:
3912 /* This is an initial board that we don't want */
3914 case H_GETTING_MOVES:
3915 /* Should not happen */
3916 DisplayError(_("Error gathering move list: extra board"), 0);
3917 ics_getting_history = H_FALSE;
3921 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3922 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3923 /* [HGM] We seem to have switched variant unexpectedly
3924 * Try to guess new variant from board size
3926 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3927 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3928 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3929 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3930 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3931 if(!weird) newVariant = VariantNormal;
3932 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3933 /* Get a move list just to see the header, which
3934 will tell us whether this is really bug or zh */
3935 if (ics_getting_history == H_FALSE) {
3936 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3937 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3942 /* Take action if this is the first board of a new game, or of a
3943 different game than is currently being displayed. */
3944 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3945 relation == RELATION_ISOLATED_BOARD) {
3947 /* Forget the old game and get the history (if any) of the new one */
3948 if (gameMode != BeginningOfGame) {
3952 if (appData.autoRaiseBoard) BoardToTop();
3954 if (gamenum == -1) {
3955 newGameMode = IcsIdle;
3956 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3957 appData.getMoveList && !reqFlag) {
3958 /* Need to get game history */
3959 ics_getting_history = H_REQUESTED;
3960 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3964 /* Initially flip the board to have black on the bottom if playing
3965 black or if the ICS flip flag is set, but let the user change
3966 it with the Flip View button. */
3967 flipView = appData.autoFlipView ?
3968 (newGameMode == IcsPlayingBlack) || ics_flip :
3971 /* Done with values from previous mode; copy in new ones */
3972 gameMode = newGameMode;
3974 ics_gamenum = gamenum;
3975 if (gamenum == gs_gamenum) {
3976 int klen = strlen(gs_kind);
3977 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3978 sprintf(str, "ICS %s", gs_kind);
3979 gameInfo.event = StrSave(str);
3981 gameInfo.event = StrSave("ICS game");
3983 gameInfo.site = StrSave(appData.icsHost);
3984 gameInfo.date = PGNDate();
3985 gameInfo.round = StrSave("-");
3986 gameInfo.white = StrSave(white);
3987 gameInfo.black = StrSave(black);
3988 timeControl = basetime * 60 * 1000;
3990 timeIncrement = increment * 1000;
3991 movesPerSession = 0;
3992 gameInfo.timeControl = TimeControlTagValue();
3993 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3994 if (appData.debugMode) {
3995 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3996 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3997 setbuf(debugFP, NULL);
4000 gameInfo.outOfBook = NULL;
4002 /* Do we have the ratings? */
4003 if (strcmp(player1Name, white) == 0 &&
4004 strcmp(player2Name, black) == 0) {
4005 if (appData.debugMode)
4006 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4007 player1Rating, player2Rating);
4008 gameInfo.whiteRating = player1Rating;
4009 gameInfo.blackRating = player2Rating;
4010 } else if (strcmp(player2Name, white) == 0 &&
4011 strcmp(player1Name, black) == 0) {
4012 if (appData.debugMode)
4013 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4014 player2Rating, player1Rating);
4015 gameInfo.whiteRating = player2Rating;
4016 gameInfo.blackRating = player1Rating;
4018 player1Name[0] = player2Name[0] = NULLCHAR;
4020 /* Silence shouts if requested */
4021 if (appData.quietPlay &&
4022 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4023 SendToICS(ics_prefix);
4024 SendToICS("set shout 0\n");
4028 /* Deal with midgame name changes */
4030 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4031 if (gameInfo.white) free(gameInfo.white);
4032 gameInfo.white = StrSave(white);
4034 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4035 if (gameInfo.black) free(gameInfo.black);
4036 gameInfo.black = StrSave(black);
4040 /* Throw away game result if anything actually changes in examine mode */
4041 if (gameMode == IcsExamining && !newGame) {
4042 gameInfo.result = GameUnfinished;
4043 if (gameInfo.resultDetails != NULL) {
4044 free(gameInfo.resultDetails);
4045 gameInfo.resultDetails = NULL;
4049 /* In pausing && IcsExamining mode, we ignore boards coming
4050 in if they are in a different variation than we are. */
4051 if (pauseExamInvalid) return;
4052 if (pausing && gameMode == IcsExamining) {
4053 if (moveNum <= pauseExamForwardMostMove) {
4054 pauseExamInvalid = TRUE;
4055 forwardMostMove = pauseExamForwardMostMove;
4060 if (appData.debugMode) {
4061 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4063 /* Parse the board */
4064 for (k = 0; k < ranks; k++) {
4065 for (j = 0; j < files; j++)
4066 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4067 if(gameInfo.holdingsWidth > 1) {
4068 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4069 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4072 CopyBoard(boards[moveNum], board);
4073 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4075 startedFromSetupPosition =
4076 !CompareBoards(board, initialPosition);
4077 if(startedFromSetupPosition)
4078 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4081 /* [HGM] Set castling rights. Take the outermost Rooks,
4082 to make it also work for FRC opening positions. Note that board12
4083 is really defective for later FRC positions, as it has no way to
4084 indicate which Rook can castle if they are on the same side of King.
4085 For the initial position we grant rights to the outermost Rooks,
4086 and remember thos rights, and we then copy them on positions
4087 later in an FRC game. This means WB might not recognize castlings with
4088 Rooks that have moved back to their original position as illegal,
4089 but in ICS mode that is not its job anyway.
4091 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4092 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4094 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4095 if(board[0][i] == WhiteRook) j = i;
4096 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4097 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4098 if(board[0][i] == WhiteRook) j = i;
4099 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4100 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4101 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4102 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4103 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4104 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4105 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4107 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4108 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4109 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4110 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4111 if(board[BOARD_HEIGHT-1][k] == bKing)
4112 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4113 if(gameInfo.variant == VariantTwoKings) {
4114 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4115 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4116 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4119 r = boards[moveNum][CASTLING][0] = initialRights[0];
4120 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4121 r = boards[moveNum][CASTLING][1] = initialRights[1];
4122 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4123 r = boards[moveNum][CASTLING][3] = initialRights[3];
4124 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4125 r = boards[moveNum][CASTLING][4] = initialRights[4];
4126 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4127 /* wildcastle kludge: always assume King has rights */
4128 r = boards[moveNum][CASTLING][2] = initialRights[2];
4129 r = boards[moveNum][CASTLING][5] = initialRights[5];
4131 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4132 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4135 if (ics_getting_history == H_GOT_REQ_HEADER ||
4136 ics_getting_history == H_GOT_UNREQ_HEADER) {
4137 /* This was an initial position from a move list, not
4138 the current position */
4142 /* Update currentMove and known move number limits */
4143 newMove = newGame || moveNum > forwardMostMove;
4146 forwardMostMove = backwardMostMove = currentMove = moveNum;
4147 if (gameMode == IcsExamining && moveNum == 0) {
4148 /* Workaround for ICS limitation: we are not told the wild
4149 type when starting to examine a game. But if we ask for
4150 the move list, the move list header will tell us */
4151 ics_getting_history = H_REQUESTED;
4152 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4155 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4156 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4158 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4159 /* [HGM] applied this also to an engine that is silently watching */
4160 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4161 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4162 gameInfo.variant == currentlyInitializedVariant) {
4163 takeback = forwardMostMove - moveNum;
4164 for (i = 0; i < takeback; i++) {
4165 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4166 SendToProgram("undo\n", &first);
4171 forwardMostMove = moveNum;
4172 if (!pausing || currentMove > forwardMostMove)
4173 currentMove = forwardMostMove;
4175 /* New part of history that is not contiguous with old part */
4176 if (pausing && gameMode == IcsExamining) {
4177 pauseExamInvalid = TRUE;
4178 forwardMostMove = pauseExamForwardMostMove;
4181 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4183 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4184 // [HGM] when we will receive the move list we now request, it will be
4185 // fed to the engine from the first move on. So if the engine is not
4186 // in the initial position now, bring it there.
4187 InitChessProgram(&first, 0);
4190 ics_getting_history = H_REQUESTED;
4191 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4194 forwardMostMove = backwardMostMove = currentMove = moveNum;
4197 /* Update the clocks */
4198 if (strchr(elapsed_time, '.')) {
4200 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4201 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4203 /* Time is in seconds */
4204 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4205 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4210 if (appData.zippyPlay && newGame &&
4211 gameMode != IcsObserving && gameMode != IcsIdle &&
4212 gameMode != IcsExamining)
4213 ZippyFirstBoard(moveNum, basetime, increment);
4216 /* Put the move on the move list, first converting
4217 to canonical algebraic form. */
4219 if (appData.debugMode) {
4220 if (appData.debugMode) { int f = forwardMostMove;
4221 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4222 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4223 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4225 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4226 fprintf(debugFP, "moveNum = %d\n", moveNum);
4227 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4228 setbuf(debugFP, NULL);
4230 if (moveNum <= backwardMostMove) {
4231 /* We don't know what the board looked like before
4233 strcpy(parseList[moveNum - 1], move_str);
4234 strcat(parseList[moveNum - 1], " ");
4235 strcat(parseList[moveNum - 1], elapsed_time);
4236 moveList[moveNum - 1][0] = NULLCHAR;
4237 } else if (strcmp(move_str, "none") == 0) {
4238 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4239 /* Again, we don't know what the board looked like;
4240 this is really the start of the game. */
4241 parseList[moveNum - 1][0] = NULLCHAR;
4242 moveList[moveNum - 1][0] = NULLCHAR;
4243 backwardMostMove = moveNum;
4244 startedFromSetupPosition = TRUE;
4245 fromX = fromY = toX = toY = -1;
4247 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4248 // So we parse the long-algebraic move string in stead of the SAN move
4249 int valid; char buf[MSG_SIZ], *prom;
4251 // str looks something like "Q/a1-a2"; kill the slash
4253 sprintf(buf, "%c%s", str[0], str+2);
4254 else strcpy(buf, str); // might be castling
4255 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4256 strcat(buf, prom); // long move lacks promo specification!
4257 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4258 if(appData.debugMode)
4259 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4260 strcpy(move_str, buf);
4262 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4263 &fromX, &fromY, &toX, &toY, &promoChar)
4264 || ParseOneMove(buf, moveNum - 1, &moveType,
4265 &fromX, &fromY, &toX, &toY, &promoChar);
4266 // end of long SAN patch
4268 (void) CoordsToAlgebraic(boards[moveNum - 1],
4269 PosFlags(moveNum - 1),
4270 fromY, fromX, toY, toX, promoChar,
4271 parseList[moveNum-1]);
4272 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4278 if(gameInfo.variant != VariantShogi)
4279 strcat(parseList[moveNum - 1], "+");
4282 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4283 strcat(parseList[moveNum - 1], "#");
4286 strcat(parseList[moveNum - 1], " ");
4287 strcat(parseList[moveNum - 1], elapsed_time);
4288 /* currentMoveString is set as a side-effect of ParseOneMove */
4289 strcpy(moveList[moveNum - 1], currentMoveString);
4290 strcat(moveList[moveNum - 1], "\n");
4292 /* Move from ICS was illegal!? Punt. */
4293 if (appData.debugMode) {
4294 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4295 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4297 strcpy(parseList[moveNum - 1], move_str);
4298 strcat(parseList[moveNum - 1], " ");
4299 strcat(parseList[moveNum - 1], elapsed_time);
4300 moveList[moveNum - 1][0] = NULLCHAR;
4301 fromX = fromY = toX = toY = -1;
4304 if (appData.debugMode) {
4305 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4306 setbuf(debugFP, NULL);
4310 /* Send move to chess program (BEFORE animating it). */
4311 if (appData.zippyPlay && !newGame && newMove &&
4312 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4314 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4315 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4316 if (moveList[moveNum - 1][0] == NULLCHAR) {
4317 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4319 DisplayError(str, 0);
4321 if (first.sendTime) {
4322 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4324 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4325 if (firstMove && !bookHit) {
4327 if (first.useColors) {
4328 SendToProgram(gameMode == IcsPlayingWhite ?
4330 "black\ngo\n", &first);
4332 SendToProgram("go\n", &first);
4334 first.maybeThinking = TRUE;
4337 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4338 if (moveList[moveNum - 1][0] == NULLCHAR) {
4339 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4340 DisplayError(str, 0);
4342 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4343 SendMoveToProgram(moveNum - 1, &first);
4350 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4351 /* If move comes from a remote source, animate it. If it
4352 isn't remote, it will have already been animated. */
4353 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4354 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4356 if (!pausing && appData.highlightLastMove) {
4357 SetHighlights(fromX, fromY, toX, toY);
4361 /* Start the clocks */
4362 whiteFlag = blackFlag = FALSE;
4363 appData.clockMode = !(basetime == 0 && increment == 0);
4365 ics_clock_paused = TRUE;
4367 } else if (ticking == 1) {
4368 ics_clock_paused = FALSE;
4370 if (gameMode == IcsIdle ||
4371 relation == RELATION_OBSERVING_STATIC ||
4372 relation == RELATION_EXAMINING ||
4374 DisplayBothClocks();
4378 /* Display opponents and material strengths */
4379 if (gameInfo.variant != VariantBughouse &&
4380 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4381 if (tinyLayout || smallLayout) {
4382 if(gameInfo.variant == VariantNormal)
4383 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4384 gameInfo.white, white_stren, gameInfo.black, black_stren,
4385 basetime, increment);
4387 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4388 gameInfo.white, white_stren, gameInfo.black, black_stren,
4389 basetime, increment, (int) gameInfo.variant);
4391 if(gameInfo.variant == VariantNormal)
4392 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4393 gameInfo.white, white_stren, gameInfo.black, black_stren,
4394 basetime, increment);
4396 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4397 gameInfo.white, white_stren, gameInfo.black, black_stren,
4398 basetime, increment, VariantName(gameInfo.variant));
4401 if (appData.debugMode) {
4402 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4407 /* Display the board */
4408 if (!pausing && !appData.noGUI) {
4410 if (appData.premove)
4412 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4413 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4414 ClearPremoveHighlights();
4416 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4417 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4418 DrawPosition(j, boards[currentMove]);
4420 DisplayMove(moveNum - 1);
4421 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4422 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4423 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4424 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4428 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4430 if(bookHit) { // [HGM] book: simulate book reply
4431 static char bookMove[MSG_SIZ]; // a bit generous?
4433 programStats.nodes = programStats.depth = programStats.time =
4434 programStats.score = programStats.got_only_move = 0;
4435 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4437 strcpy(bookMove, "move ");
4438 strcat(bookMove, bookHit);
4439 HandleMachineMove(bookMove, &first);
4448 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4449 ics_getting_history = H_REQUESTED;
4450 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4456 AnalysisPeriodicEvent(force)
4459 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4460 && !force) || !appData.periodicUpdates)
4463 /* Send . command to Crafty to collect stats */
4464 SendToProgram(".\n", &first);
4466 /* Don't send another until we get a response (this makes
4467 us stop sending to old Crafty's which don't understand
4468 the "." command (sending illegal cmds resets node count & time,
4469 which looks bad)) */
4470 programStats.ok_to_send = 0;
4473 void ics_update_width(new_width)
4476 ics_printf("set width %d\n", new_width);
4480 SendMoveToProgram(moveNum, cps)
4482 ChessProgramState *cps;
4486 if (cps->useUsermove) {
4487 SendToProgram("usermove ", cps);
4491 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4492 int len = space - parseList[moveNum];
4493 memcpy(buf, parseList[moveNum], len);
4495 buf[len] = NULLCHAR;
4497 sprintf(buf, "%s\n", parseList[moveNum]);
4499 SendToProgram(buf, cps);
4501 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4502 AlphaRank(moveList[moveNum], 4);
4503 SendToProgram(moveList[moveNum], cps);
4504 AlphaRank(moveList[moveNum], 4); // and back
4506 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4507 * the engine. It would be nice to have a better way to identify castle
4509 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4510 && cps->useOOCastle) {
4511 int fromX = moveList[moveNum][0] - AAA;
4512 int fromY = moveList[moveNum][1] - ONE;
4513 int toX = moveList[moveNum][2] - AAA;
4514 int toY = moveList[moveNum][3] - ONE;
4515 if((boards[moveNum][fromY][fromX] == WhiteKing
4516 && boards[moveNum][toY][toX] == WhiteRook)
4517 || (boards[moveNum][fromY][fromX] == BlackKing
4518 && boards[moveNum][toY][toX] == BlackRook)) {
4519 if(toX > fromX) SendToProgram("O-O\n", cps);
4520 else SendToProgram("O-O-O\n", cps);
4522 else SendToProgram(moveList[moveNum], cps);
4524 else SendToProgram(moveList[moveNum], cps);
4525 /* End of additions by Tord */
4528 /* [HGM] setting up the opening has brought engine in force mode! */
4529 /* Send 'go' if we are in a mode where machine should play. */
4530 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4531 (gameMode == TwoMachinesPlay ||
4533 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4535 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4536 SendToProgram("go\n", cps);
4537 if (appData.debugMode) {
4538 fprintf(debugFP, "(extra)\n");
4541 setboardSpoiledMachineBlack = 0;
4545 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4547 int fromX, fromY, toX, toY;
4550 char user_move[MSG_SIZ];
4554 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4555 (int)moveType, fromX, fromY, toX, toY);
4556 DisplayError(user_move + strlen("say "), 0);
4558 case WhiteKingSideCastle:
4559 case BlackKingSideCastle:
4560 case WhiteQueenSideCastleWild:
4561 case BlackQueenSideCastleWild:
4563 case WhiteHSideCastleFR:
4564 case BlackHSideCastleFR:
4566 sprintf(user_move, "o-o\n");
4568 case WhiteQueenSideCastle:
4569 case BlackQueenSideCastle:
4570 case WhiteKingSideCastleWild:
4571 case BlackKingSideCastleWild:
4573 case WhiteASideCastleFR:
4574 case BlackASideCastleFR:
4576 sprintf(user_move, "o-o-o\n");
4578 case WhiteNonPromotion:
4579 case BlackNonPromotion:
4580 sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4582 case WhitePromotion:
4583 case BlackPromotion:
4584 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4585 sprintf(user_move, "%c%c%c%c=%c\n",
4586 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4587 PieceToChar(WhiteFerz));
4588 else if(gameInfo.variant == VariantGreat)
4589 sprintf(user_move, "%c%c%c%c=%c\n",
4590 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4591 PieceToChar(WhiteMan));
4593 sprintf(user_move, "%c%c%c%c=%c\n",
4594 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4599 sprintf(user_move, "%c@%c%c\n",
4600 ToUpper(PieceToChar((ChessSquare) fromX)),
4601 AAA + toX, ONE + toY);
4604 case WhiteCapturesEnPassant:
4605 case BlackCapturesEnPassant:
4606 case IllegalMove: /* could be a variant we don't quite understand */
4607 sprintf(user_move, "%c%c%c%c\n",
4608 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4611 SendToICS(user_move);
4612 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4613 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4618 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4619 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4620 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4621 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4622 DisplayError("You cannot do this while you are playing or observing", 0);
4625 if(gameMode != IcsExamining) { // is this ever not the case?
4626 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4628 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4629 sprintf(command, "match %s", ics_handle);
4630 } else { // on FICS we must first go to general examine mode
4631 strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4633 if(gameInfo.variant != VariantNormal) {
4634 // try figure out wild number, as xboard names are not always valid on ICS
4635 for(i=1; i<=36; i++) {
4636 sprintf(buf, "wild/%d", i);
4637 if(StringToVariant(buf) == gameInfo.variant) break;
4639 if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4640 else if(i == 22) sprintf(buf, "%s fr\n", command);
4641 else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4642 } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4643 SendToICS(ics_prefix);
4645 if(startedFromSetupPosition || backwardMostMove != 0) {
4646 fen = PositionToFEN(backwardMostMove, NULL);
4647 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4648 sprintf(buf, "loadfen %s\n", fen);
4650 } else { // FICS: everything has to set by separate bsetup commands
4651 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4652 sprintf(buf, "bsetup fen %s\n", fen);
4654 if(!WhiteOnMove(backwardMostMove)) {
4655 SendToICS("bsetup tomove black\n");
4657 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4658 sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4660 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4661 sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4663 i = boards[backwardMostMove][EP_STATUS];
4664 if(i >= 0) { // set e.p.
4665 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4671 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4672 SendToICS("bsetup done\n"); // switch to normal examining.
4674 for(i = backwardMostMove; i<last; i++) {
4676 sprintf(buf, "%s\n", parseList[i]);
4679 SendToICS(ics_prefix);
4680 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4684 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4689 if (rf == DROP_RANK) {
4690 sprintf(move, "%c@%c%c\n",
4691 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4693 if (promoChar == 'x' || promoChar == NULLCHAR) {
4694 sprintf(move, "%c%c%c%c\n",
4695 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4697 sprintf(move, "%c%c%c%c%c\n",
4698 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4704 ProcessICSInitScript(f)
4709 while (fgets(buf, MSG_SIZ, f)) {
4710 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4717 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4719 AlphaRank(char *move, int n)
4721 // char *p = move, c; int x, y;
4723 if (appData.debugMode) {
4724 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4728 move[2]>='0' && move[2]<='9' &&
4729 move[3]>='a' && move[3]<='x' ) {
4731 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4732 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4734 if(move[0]>='0' && move[0]<='9' &&
4735 move[1]>='a' && move[1]<='x' &&
4736 move[2]>='0' && move[2]<='9' &&
4737 move[3]>='a' && move[3]<='x' ) {
4738 /* input move, Shogi -> normal */
4739 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4740 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4741 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4742 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4745 move[3]>='0' && move[3]<='9' &&
4746 move[2]>='a' && move[2]<='x' ) {
4748 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4749 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4752 move[0]>='a' && move[0]<='x' &&
4753 move[3]>='0' && move[3]<='9' &&
4754 move[2]>='a' && move[2]<='x' ) {
4755 /* output move, normal -> Shogi */
4756 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4757 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4758 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4759 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4760 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4762 if (appData.debugMode) {
4763 fprintf(debugFP, " out = '%s'\n", move);
4767 char yy_textstr[8000];
4769 /* Parser for moves from gnuchess, ICS, or user typein box */
4771 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4774 ChessMove *moveType;
4775 int *fromX, *fromY, *toX, *toY;
4778 if (appData.debugMode) {
4779 fprintf(debugFP, "move to parse: %s\n", move);
4781 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4783 switch (*moveType) {
4784 case WhitePromotion:
4785 case BlackPromotion:
4786 case WhiteNonPromotion:
4787 case BlackNonPromotion:
4789 case WhiteCapturesEnPassant:
4790 case BlackCapturesEnPassant:
4791 case WhiteKingSideCastle:
4792 case WhiteQueenSideCastle:
4793 case BlackKingSideCastle:
4794 case BlackQueenSideCastle:
4795 case WhiteKingSideCastleWild:
4796 case WhiteQueenSideCastleWild:
4797 case BlackKingSideCastleWild:
4798 case BlackQueenSideCastleWild:
4799 /* Code added by Tord: */
4800 case WhiteHSideCastleFR:
4801 case WhiteASideCastleFR:
4802 case BlackHSideCastleFR:
4803 case BlackASideCastleFR:
4804 /* End of code added by Tord */
4805 case IllegalMove: /* bug or odd chess variant */
4806 *fromX = currentMoveString[0] - AAA;
4807 *fromY = currentMoveString[1] - ONE;
4808 *toX = currentMoveString[2] - AAA;
4809 *toY = currentMoveString[3] - ONE;
4810 *promoChar = currentMoveString[4];
4811 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4812 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4813 if (appData.debugMode) {
4814 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4816 *fromX = *fromY = *toX = *toY = 0;
4819 if (appData.testLegality) {
4820 return (*moveType != IllegalMove);
4822 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4823 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4828 *fromX = *moveType == WhiteDrop ?
4829 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4830 (int) CharToPiece(ToLower(currentMoveString[0]));
4832 *toX = currentMoveString[2] - AAA;
4833 *toY = currentMoveString[3] - ONE;
4834 *promoChar = NULLCHAR;
4838 case ImpossibleMove:
4839 case (ChessMove) 0: /* end of file */
4848 if (appData.debugMode) {
4849 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4852 *fromX = *fromY = *toX = *toY = 0;
4853 *promoChar = NULLCHAR;
4860 ParsePV(char *pv, Boolean storeComments)
4861 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4862 int fromX, fromY, toX, toY; char promoChar;
4867 endPV = forwardMostMove;
4869 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4870 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4871 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4872 if(appData.debugMode){
4873 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);
4875 if(!valid && nr == 0 &&
4876 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4877 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4878 // Hande case where played move is different from leading PV move
4879 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4880 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4881 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4882 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4883 endPV += 2; // if position different, keep this
4884 moveList[endPV-1][0] = fromX + AAA;
4885 moveList[endPV-1][1] = fromY + ONE;
4886 moveList[endPV-1][2] = toX + AAA;
4887 moveList[endPV-1][3] = toY + ONE;
4888 parseList[endPV-1][0] = NULLCHAR;
4889 strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4892 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4893 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4894 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4895 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4896 valid++; // allow comments in PV
4900 if(endPV+1 > framePtr) break; // no space, truncate
4903 CopyBoard(boards[endPV], boards[endPV-1]);
4904 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4905 moveList[endPV-1][0] = fromX + AAA;
4906 moveList[endPV-1][1] = fromY + ONE;
4907 moveList[endPV-1][2] = toX + AAA;
4908 moveList[endPV-1][3] = toY + ONE;
4910 CoordsToAlgebraic(boards[endPV - 1],
4911 PosFlags(endPV - 1),
4912 fromY, fromX, toY, toX, promoChar,
4913 parseList[endPV - 1]);
4915 parseList[endPV-1][0] = NULLCHAR;
4917 currentMove = endPV;
4918 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4919 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4920 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4921 DrawPosition(TRUE, boards[currentMove]);
4924 static int lastX, lastY;
4927 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4932 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4933 lastX = x; lastY = y;
4934 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4936 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4937 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4939 do{ while(buf[index] && buf[index] != '\n') index++;
4940 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4942 ParsePV(buf+startPV, FALSE);
4943 *start = startPV; *end = index-1;
4948 LoadPV(int x, int y)
4949 { // called on right mouse click to load PV
4950 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4951 lastX = x; lastY = y;
4952 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4959 if(endPV < 0) return;
4961 currentMove = forwardMostMove;
4962 ClearPremoveHighlights();
4963 DrawPosition(TRUE, boards[currentMove]);
4967 MovePV(int x, int y, int h)
4968 { // step through PV based on mouse coordinates (called on mouse move)
4969 int margin = h>>3, step = 0;
4971 if(endPV < 0) return;
4972 // we must somehow check if right button is still down (might be released off board!)
4973 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4974 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4975 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4977 lastX = x; lastY = y;
4978 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4979 currentMove += step;
4980 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4981 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4982 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4983 DrawPosition(FALSE, boards[currentMove]);
4987 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4988 // All positions will have equal probability, but the current method will not provide a unique
4989 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4995 int piecesLeft[(int)BlackPawn];
4996 int seed, nrOfShuffles;
4998 void GetPositionNumber()
4999 { // sets global variable seed
5002 seed = appData.defaultFrcPosition;
5003 if(seed < 0) { // randomize based on time for negative FRC position numbers
5004 for(i=0; i<50; i++) seed += random();
5005 seed = random() ^ random() >> 8 ^ random() << 8;
5006 if(seed<0) seed = -seed;
5010 int put(Board board, int pieceType, int rank, int n, int shade)
5011 // put the piece on the (n-1)-th empty squares of the given shade
5015 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5016 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5017 board[rank][i] = (ChessSquare) pieceType;
5018 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5020 piecesLeft[pieceType]--;
5028 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5029 // calculate where the next piece goes, (any empty square), and put it there
5033 i = seed % squaresLeft[shade];
5034 nrOfShuffles *= squaresLeft[shade];
5035 seed /= squaresLeft[shade];
5036 put(board, pieceType, rank, i, shade);
5039 void AddTwoPieces(Board board, int pieceType, int rank)
5040 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5042 int i, n=squaresLeft[ANY], j=n-1, k;
5044 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5045 i = seed % k; // pick one
5048 while(i >= j) i -= j--;
5049 j = n - 1 - j; i += j;
5050 put(board, pieceType, rank, j, ANY);
5051 put(board, pieceType, rank, i, ANY);
5054 void SetUpShuffle(Board board, int number)
5058 GetPositionNumber(); nrOfShuffles = 1;
5060 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5061 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5062 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5064 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5066 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5067 p = (int) board[0][i];
5068 if(p < (int) BlackPawn) piecesLeft[p] ++;
5069 board[0][i] = EmptySquare;
5072 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5073 // shuffles restricted to allow normal castling put KRR first
5074 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5075 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5076 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5077 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5078 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5079 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5080 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5081 put(board, WhiteRook, 0, 0, ANY);
5082 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5085 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5086 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5087 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5088 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5089 while(piecesLeft[p] >= 2) {
5090 AddOnePiece(board, p, 0, LITE);
5091 AddOnePiece(board, p, 0, DARK);
5093 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5096 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5097 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5098 // but we leave King and Rooks for last, to possibly obey FRC restriction
5099 if(p == (int)WhiteRook) continue;
5100 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5101 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5104 // now everything is placed, except perhaps King (Unicorn) and Rooks
5106 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5107 // Last King gets castling rights
5108 while(piecesLeft[(int)WhiteUnicorn]) {
5109 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5110 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5113 while(piecesLeft[(int)WhiteKing]) {
5114 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5115 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5120 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5121 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5124 // Only Rooks can be left; simply place them all
5125 while(piecesLeft[(int)WhiteRook]) {
5126 i = put(board, WhiteRook, 0, 0, ANY);
5127 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5130 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5132 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5135 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5136 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5139 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5142 int SetCharTable( char *table, const char * map )
5143 /* [HGM] moved here from winboard.c because of its general usefulness */
5144 /* Basically a safe strcpy that uses the last character as King */
5146 int result = FALSE; int NrPieces;
5148 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5149 && NrPieces >= 12 && !(NrPieces&1)) {
5150 int i; /* [HGM] Accept even length from 12 to 34 */
5152 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5153 for( i=0; i<NrPieces/2-1; i++ ) {
5155 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5157 table[(int) WhiteKing] = map[NrPieces/2-1];
5158 table[(int) BlackKing] = map[NrPieces-1];
5166 void Prelude(Board board)
5167 { // [HGM] superchess: random selection of exo-pieces
5168 int i, j, k; ChessSquare p;
5169 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5171 GetPositionNumber(); // use FRC position number
5173 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5174 SetCharTable(pieceToChar, appData.pieceToCharTable);
5175 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5176 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5179 j = seed%4; seed /= 4;
5180 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5181 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5182 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5183 j = seed%3 + (seed%3 >= j); seed /= 3;
5184 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5185 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5186 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5187 j = seed%3; seed /= 3;
5188 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5189 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5190 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5191 j = seed%2 + (seed%2 >= j); seed /= 2;
5192 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5193 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5194 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5195 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5196 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5197 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5198 put(board, exoPieces[0], 0, 0, ANY);
5199 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5203 InitPosition(redraw)
5206 ChessSquare (* pieces)[BOARD_FILES];
5207 int i, j, pawnRow, overrule,
5208 oldx = gameInfo.boardWidth,
5209 oldy = gameInfo.boardHeight,
5210 oldh = gameInfo.holdingsWidth,
5211 oldv = gameInfo.variant;
5213 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5215 /* [AS] Initialize pv info list [HGM] and game status */
5217 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5218 pvInfoList[i].depth = 0;
5219 boards[i][EP_STATUS] = EP_NONE;
5220 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5223 initialRulePlies = 0; /* 50-move counter start */
5225 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5226 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5230 /* [HGM] logic here is completely changed. In stead of full positions */
5231 /* the initialized data only consist of the two backranks. The switch */
5232 /* selects which one we will use, which is than copied to the Board */
5233 /* initialPosition, which for the rest is initialized by Pawns and */
5234 /* empty squares. This initial position is then copied to boards[0], */
5235 /* possibly after shuffling, so that it remains available. */
5237 gameInfo.holdingsWidth = 0; /* default board sizes */
5238 gameInfo.boardWidth = 8;
5239 gameInfo.boardHeight = 8;
5240 gameInfo.holdingsSize = 0;
5241 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5242 for(i=0; i<BOARD_FILES-2; i++)
5243 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5244 initialPosition[EP_STATUS] = EP_NONE;
5245 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5246 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5247 SetCharTable(pieceNickName, appData.pieceNickNames);
5248 else SetCharTable(pieceNickName, "............");
5250 switch (gameInfo.variant) {
5251 case VariantFischeRandom:
5252 shuffleOpenings = TRUE;
5256 case VariantShatranj:
5257 pieces = ShatranjArray;
5258 nrCastlingRights = 0;
5259 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5262 pieces = makrukArray;
5263 nrCastlingRights = 0;
5264 startedFromSetupPosition = TRUE;
5265 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5267 case VariantTwoKings:
5268 pieces = twoKingsArray;
5270 case VariantCapaRandom:
5271 shuffleOpenings = TRUE;
5272 case VariantCapablanca:
5273 pieces = CapablancaArray;
5274 gameInfo.boardWidth = 10;
5275 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5278 pieces = GothicArray;
5279 gameInfo.boardWidth = 10;
5280 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5283 pieces = JanusArray;
5284 gameInfo.boardWidth = 10;
5285 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5286 nrCastlingRights = 6;
5287 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5288 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5289 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5290 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5291 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5292 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5295 pieces = FalconArray;
5296 gameInfo.boardWidth = 10;
5297 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5299 case VariantXiangqi:
5300 pieces = XiangqiArray;
5301 gameInfo.boardWidth = 9;
5302 gameInfo.boardHeight = 10;
5303 nrCastlingRights = 0;
5304 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5307 pieces = ShogiArray;
5308 gameInfo.boardWidth = 9;
5309 gameInfo.boardHeight = 9;
5310 gameInfo.holdingsSize = 7;
5311 nrCastlingRights = 0;
5312 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5314 case VariantCourier:
5315 pieces = CourierArray;
5316 gameInfo.boardWidth = 12;
5317 nrCastlingRights = 0;
5318 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5320 case VariantKnightmate:
5321 pieces = KnightmateArray;
5322 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5325 pieces = fairyArray;
5326 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5329 pieces = GreatArray;
5330 gameInfo.boardWidth = 10;
5331 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5332 gameInfo.holdingsSize = 8;
5336 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5337 gameInfo.holdingsSize = 8;
5338 startedFromSetupPosition = TRUE;
5340 case VariantCrazyhouse:
5341 case VariantBughouse:
5343 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5344 gameInfo.holdingsSize = 5;
5346 case VariantWildCastle:
5348 /* !!?shuffle with kings guaranteed to be on d or e file */
5349 shuffleOpenings = 1;
5351 case VariantNoCastle:
5353 nrCastlingRights = 0;
5354 /* !!?unconstrained back-rank shuffle */
5355 shuffleOpenings = 1;
5360 if(appData.NrFiles >= 0) {
5361 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5362 gameInfo.boardWidth = appData.NrFiles;
5364 if(appData.NrRanks >= 0) {
5365 gameInfo.boardHeight = appData.NrRanks;
5367 if(appData.holdingsSize >= 0) {
5368 i = appData.holdingsSize;
5369 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5370 gameInfo.holdingsSize = i;
5372 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5373 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5374 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5376 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5377 if(pawnRow < 1) pawnRow = 1;
5378 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5380 /* User pieceToChar list overrules defaults */
5381 if(appData.pieceToCharTable != NULL)
5382 SetCharTable(pieceToChar, appData.pieceToCharTable);
5384 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5386 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5387 s = (ChessSquare) 0; /* account holding counts in guard band */
5388 for( i=0; i<BOARD_HEIGHT; i++ )
5389 initialPosition[i][j] = s;
5391 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5392 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5393 initialPosition[pawnRow][j] = WhitePawn;
5394 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5395 if(gameInfo.variant == VariantXiangqi) {
5397 initialPosition[pawnRow][j] =
5398 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5399 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5400 initialPosition[2][j] = WhiteCannon;
5401 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5405 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5407 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5410 initialPosition[1][j] = WhiteBishop;
5411 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5413 initialPosition[1][j] = WhiteRook;
5414 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5417 if( nrCastlingRights == -1) {
5418 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5419 /* This sets default castling rights from none to normal corners */
5420 /* Variants with other castling rights must set them themselves above */
5421 nrCastlingRights = 6;
5423 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5424 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5425 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5426 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5427 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5428 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5431 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5432 if(gameInfo.variant == VariantGreat) { // promotion commoners
5433 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5434 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5435 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5436 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5438 if (appData.debugMode) {
5439 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5441 if(shuffleOpenings) {
5442 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5443 startedFromSetupPosition = TRUE;
5445 if(startedFromPositionFile) {
5446 /* [HGM] loadPos: use PositionFile for every new game */
5447 CopyBoard(initialPosition, filePosition);
5448 for(i=0; i<nrCastlingRights; i++)
5449 initialRights[i] = filePosition[CASTLING][i];
5450 startedFromSetupPosition = TRUE;
5453 CopyBoard(boards[0], initialPosition);
5455 if(oldx != gameInfo.boardWidth ||
5456 oldy != gameInfo.boardHeight ||
5457 oldh != gameInfo.holdingsWidth
5459 || oldv == VariantGothic || // For licensing popups
5460 gameInfo.variant == VariantGothic
5463 || oldv == VariantFalcon ||
5464 gameInfo.variant == VariantFalcon
5467 InitDrawingSizes(-2 ,0);
5470 DrawPosition(TRUE, boards[currentMove]);
5474 SendBoard(cps, moveNum)
5475 ChessProgramState *cps;
5478 char message[MSG_SIZ];
5480 if (cps->useSetboard) {
5481 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5482 sprintf(message, "setboard %s\n", fen);
5483 SendToProgram(message, cps);
5489 /* Kludge to set black to move, avoiding the troublesome and now
5490 * deprecated "black" command.
5492 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5494 SendToProgram("edit\n", cps);
5495 SendToProgram("#\n", cps);
5496 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5497 bp = &boards[moveNum][i][BOARD_LEFT];
5498 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5499 if ((int) *bp < (int) BlackPawn) {
5500 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5502 if(message[0] == '+' || message[0] == '~') {
5503 sprintf(message, "%c%c%c+\n",
5504 PieceToChar((ChessSquare)(DEMOTED *bp)),
5507 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5508 message[1] = BOARD_RGHT - 1 - j + '1';
5509 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5511 SendToProgram(message, cps);
5516 SendToProgram("c\n", cps);
5517 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5518 bp = &boards[moveNum][i][BOARD_LEFT];
5519 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5520 if (((int) *bp != (int) EmptySquare)
5521 && ((int) *bp >= (int) BlackPawn)) {
5522 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5524 if(message[0] == '+' || message[0] == '~') {
5525 sprintf(message, "%c%c%c+\n",
5526 PieceToChar((ChessSquare)(DEMOTED *bp)),
5529 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5530 message[1] = BOARD_RGHT - 1 - j + '1';
5531 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5533 SendToProgram(message, cps);
5538 SendToProgram(".\n", cps);
5540 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5543 static int autoQueen; // [HGM] oneclick
5546 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5548 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5549 /* [HGM] add Shogi promotions */
5550 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5555 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5556 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5558 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5559 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5562 piece = boards[currentMove][fromY][fromX];
5563 if(gameInfo.variant == VariantShogi) {
5564 promotionZoneSize = BOARD_HEIGHT/3;
5565 highestPromotingPiece = (int)WhiteFerz;
5566 } else if(gameInfo.variant == VariantMakruk) {
5567 promotionZoneSize = 3;
5570 // next weed out all moves that do not touch the promotion zone at all
5571 if((int)piece >= BlackPawn) {
5572 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5574 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5576 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5577 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5580 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5582 // weed out mandatory Shogi promotions
5583 if(gameInfo.variant == VariantShogi) {
5584 if(piece >= BlackPawn) {
5585 if(toY == 0 && piece == BlackPawn ||
5586 toY == 0 && piece == BlackQueen ||
5587 toY <= 1 && piece == BlackKnight) {
5592 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5593 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5594 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5601 // weed out obviously illegal Pawn moves
5602 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5603 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5604 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5605 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5606 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5607 // note we are not allowed to test for valid (non-)capture, due to premove
5610 // we either have a choice what to promote to, or (in Shogi) whether to promote
5611 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5612 *promoChoice = PieceToChar(BlackFerz); // no choice
5615 if(autoQueen) { // predetermined
5616 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5617 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5618 else *promoChoice = PieceToChar(BlackQueen);
5622 // suppress promotion popup on illegal moves that are not premoves
5623 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5624 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5625 if(appData.testLegality && !premove) {
5626 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5627 fromY, fromX, toY, toX, NULLCHAR);
5628 if(moveType != WhitePromotion && moveType != BlackPromotion)
5636 InPalace(row, column)
5638 { /* [HGM] for Xiangqi */
5639 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5640 column < (BOARD_WIDTH + 4)/2 &&
5641 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5646 PieceForSquare (x, y)
5650 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5653 return boards[currentMove][y][x];
5657 OKToStartUserMove(x, y)
5660 ChessSquare from_piece;
5663 if (matchMode) return FALSE;
5664 if (gameMode == EditPosition) return TRUE;
5666 if (x >= 0 && y >= 0)
5667 from_piece = boards[currentMove][y][x];
5669 from_piece = EmptySquare;
5671 if (from_piece == EmptySquare) return FALSE;
5673 white_piece = (int)from_piece >= (int)WhitePawn &&
5674 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5677 case PlayFromGameFile:
5679 case TwoMachinesPlay:
5687 case MachinePlaysWhite:
5688 case IcsPlayingBlack:
5689 if (appData.zippyPlay) return FALSE;
5691 DisplayMoveError(_("You are playing Black"));
5696 case MachinePlaysBlack:
5697 case IcsPlayingWhite:
5698 if (appData.zippyPlay) return FALSE;
5700 DisplayMoveError(_("You are playing White"));
5706 if (!white_piece && WhiteOnMove(currentMove)) {
5707 DisplayMoveError(_("It is White's turn"));
5710 if (white_piece && !WhiteOnMove(currentMove)) {
5711 DisplayMoveError(_("It is Black's turn"));
5714 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5715 /* Editing correspondence game history */
5716 /* Could disallow this or prompt for confirmation */
5721 case BeginningOfGame:
5722 if (appData.icsActive) return FALSE;
5723 if (!appData.noChessProgram) {
5725 DisplayMoveError(_("You are playing White"));
5732 if (!white_piece && WhiteOnMove(currentMove)) {
5733 DisplayMoveError(_("It is White's turn"));
5736 if (white_piece && !WhiteOnMove(currentMove)) {
5737 DisplayMoveError(_("It is Black's turn"));
5746 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5747 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5748 && gameMode != AnalyzeFile && gameMode != Training) {
5749 DisplayMoveError(_("Displayed position is not current"));
5756 OnlyMove(int *x, int *y, Boolean captures) {
5757 DisambiguateClosure cl;
5758 if (appData.zippyPlay) return FALSE;
5760 case MachinePlaysBlack:
5761 case IcsPlayingWhite:
5762 case BeginningOfGame:
5763 if(!WhiteOnMove(currentMove)) return FALSE;
5765 case MachinePlaysWhite:
5766 case IcsPlayingBlack:
5767 if(WhiteOnMove(currentMove)) return FALSE;
5772 cl.pieceIn = EmptySquare;
5777 cl.promoCharIn = NULLCHAR;
5778 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5779 if( cl.kind == NormalMove ||
5780 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5781 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5782 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5789 if(cl.kind != ImpossibleMove) return FALSE;
5790 cl.pieceIn = EmptySquare;
5795 cl.promoCharIn = NULLCHAR;
5796 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5797 if( cl.kind == NormalMove ||
5798 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5799 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5800 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5805 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5811 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5812 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5813 int lastLoadGameUseList = FALSE;
5814 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5815 ChessMove lastLoadGameStart = (ChessMove) 0;
5818 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5819 int fromX, fromY, toX, toY;
5823 ChessSquare pdown, pup;
5825 /* Check if the user is playing in turn. This is complicated because we
5826 let the user "pick up" a piece before it is his turn. So the piece he
5827 tried to pick up may have been captured by the time he puts it down!
5828 Therefore we use the color the user is supposed to be playing in this
5829 test, not the color of the piece that is currently on the starting
5830 square---except in EditGame mode, where the user is playing both
5831 sides; fortunately there the capture race can't happen. (It can
5832 now happen in IcsExamining mode, but that's just too bad. The user
5833 will get a somewhat confusing message in that case.)
5837 case PlayFromGameFile:
5839 case TwoMachinesPlay:
5843 /* We switched into a game mode where moves are not accepted,
5844 perhaps while the mouse button was down. */
5847 case MachinePlaysWhite:
5848 /* User is moving for Black */
5849 if (WhiteOnMove(currentMove)) {
5850 DisplayMoveError(_("It is White's turn"));
5855 case MachinePlaysBlack:
5856 /* User is moving for White */
5857 if (!WhiteOnMove(currentMove)) {
5858 DisplayMoveError(_("It is Black's turn"));
5865 case BeginningOfGame:
5868 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5869 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5870 /* User is moving for Black */
5871 if (WhiteOnMove(currentMove)) {
5872 DisplayMoveError(_("It is White's turn"));
5876 /* User is moving for White */
5877 if (!WhiteOnMove(currentMove)) {
5878 DisplayMoveError(_("It is Black's turn"));
5884 case IcsPlayingBlack:
5885 /* User is moving for Black */
5886 if (WhiteOnMove(currentMove)) {
5887 if (!appData.premove) {
5888 DisplayMoveError(_("It is White's turn"));
5889 } else if (toX >= 0 && toY >= 0) {
5892 premoveFromX = fromX;
5893 premoveFromY = fromY;
5894 premovePromoChar = promoChar;
5896 if (appData.debugMode)
5897 fprintf(debugFP, "Got premove: fromX %d,"
5898 "fromY %d, toX %d, toY %d\n",
5899 fromX, fromY, toX, toY);
5905 case IcsPlayingWhite:
5906 /* User is moving for White */
5907 if (!WhiteOnMove(currentMove)) {
5908 if (!appData.premove) {
5909 DisplayMoveError(_("It is Black's turn"));
5910 } else if (toX >= 0 && toY >= 0) {
5913 premoveFromX = fromX;
5914 premoveFromY = fromY;
5915 premovePromoChar = promoChar;
5917 if (appData.debugMode)
5918 fprintf(debugFP, "Got premove: fromX %d,"
5919 "fromY %d, toX %d, toY %d\n",
5920 fromX, fromY, toX, toY);
5930 /* EditPosition, empty square, or different color piece;
5931 click-click move is possible */
5932 if (toX == -2 || toY == -2) {
5933 boards[0][fromY][fromX] = EmptySquare;
5934 DrawPosition(FALSE, boards[currentMove]);
5936 } else if (toX >= 0 && toY >= 0) {
5937 boards[0][toY][toX] = boards[0][fromY][fromX];
5938 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5939 if(boards[0][fromY][0] != EmptySquare) {
5940 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5941 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5944 if(fromX == BOARD_RGHT+1) {
5945 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5946 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5947 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5950 boards[0][fromY][fromX] = EmptySquare;
5951 DrawPosition(FALSE, boards[currentMove]);
5957 if(toX < 0 || toY < 0) return;
5958 pdown = boards[currentMove][fromY][fromX];
5959 pup = boards[currentMove][toY][toX];
5961 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
5962 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5963 if( pup != EmptySquare ) return;
5964 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5965 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5966 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5967 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5968 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5969 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5970 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5974 /* [HGM] always test for legality, to get promotion info */
5975 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5976 fromY, fromX, toY, toX, promoChar);
5977 /* [HGM] but possibly ignore an IllegalMove result */
5978 if (appData.testLegality) {
5979 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5980 DisplayMoveError(_("Illegal move"));
5985 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5988 /* Common tail of UserMoveEvent and DropMenuEvent */
5990 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5992 int fromX, fromY, toX, toY;
5993 /*char*/int promoChar;
5997 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5998 // [HGM] superchess: suppress promotions to non-available piece
5999 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6000 if(WhiteOnMove(currentMove)) {
6001 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6003 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6007 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6008 move type in caller when we know the move is a legal promotion */
6009 if(moveType == NormalMove && promoChar)
6010 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6012 /* [HGM] <popupFix> The following if has been moved here from
6013 UserMoveEvent(). Because it seemed to belong here (why not allow
6014 piece drops in training games?), and because it can only be
6015 performed after it is known to what we promote. */
6016 if (gameMode == Training) {
6017 /* compare the move played on the board to the next move in the
6018 * game. If they match, display the move and the opponent's response.
6019 * If they don't match, display an error message.
6023 CopyBoard(testBoard, boards[currentMove]);
6024 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6026 if (CompareBoards(testBoard, boards[currentMove+1])) {
6027 ForwardInner(currentMove+1);
6029 /* Autoplay the opponent's response.
6030 * if appData.animate was TRUE when Training mode was entered,
6031 * the response will be animated.
6033 saveAnimate = appData.animate;
6034 appData.animate = animateTraining;
6035 ForwardInner(currentMove+1);
6036 appData.animate = saveAnimate;
6038 /* check for the end of the game */
6039 if (currentMove >= forwardMostMove) {
6040 gameMode = PlayFromGameFile;
6042 SetTrainingModeOff();
6043 DisplayInformation(_("End of game"));
6046 DisplayError(_("Incorrect move"), 0);
6051 /* Ok, now we know that the move is good, so we can kill
6052 the previous line in Analysis Mode */
6053 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6054 && currentMove < forwardMostMove) {
6055 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6058 /* If we need the chess program but it's dead, restart it */
6059 ResurrectChessProgram();
6061 /* A user move restarts a paused game*/
6065 thinkOutput[0] = NULLCHAR;
6067 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6069 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6071 if (gameMode == BeginningOfGame) {
6072 if (appData.noChessProgram) {
6073 gameMode = EditGame;
6077 gameMode = MachinePlaysBlack;
6080 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6082 if (first.sendName) {
6083 sprintf(buf, "name %s\n", gameInfo.white);
6084 SendToProgram(buf, &first);
6091 /* Relay move to ICS or chess engine */
6092 if (appData.icsActive) {
6093 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6094 gameMode == IcsExamining) {
6095 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6096 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6098 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6100 // also send plain move, in case ICS does not understand atomic claims
6101 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6105 if (first.sendTime && (gameMode == BeginningOfGame ||
6106 gameMode == MachinePlaysWhite ||
6107 gameMode == MachinePlaysBlack)) {
6108 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6110 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6111 // [HGM] book: if program might be playing, let it use book
6112 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6113 first.maybeThinking = TRUE;
6114 } else SendMoveToProgram(forwardMostMove-1, &first);
6115 if (currentMove == cmailOldMove + 1) {
6116 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6120 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6124 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6130 if (WhiteOnMove(currentMove)) {
6131 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6133 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6137 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6142 case MachinePlaysBlack:
6143 case MachinePlaysWhite:
6144 /* disable certain menu options while machine is thinking */
6145 SetMachineThinkingEnables();
6152 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6154 if(bookHit) { // [HGM] book: simulate book reply
6155 static char bookMove[MSG_SIZ]; // a bit generous?
6157 programStats.nodes = programStats.depth = programStats.time =
6158 programStats.score = programStats.got_only_move = 0;
6159 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6161 strcpy(bookMove, "move ");
6162 strcat(bookMove, bookHit);
6163 HandleMachineMove(bookMove, &first);
6169 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6176 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6177 Markers *m = (Markers *) closure;
6178 if(rf == fromY && ff == fromX)
6179 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6180 || kind == WhiteCapturesEnPassant
6181 || kind == BlackCapturesEnPassant);
6182 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6186 MarkTargetSquares(int clear)
6189 if(!appData.markers || !appData.highlightDragging ||
6190 !appData.testLegality || gameMode == EditPosition) return;
6192 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6195 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6196 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6197 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6199 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6202 DrawPosition(TRUE, NULL);
6205 void LeftClick(ClickType clickType, int xPix, int yPix)
6208 Boolean saveAnimate;
6209 static int second = 0, promotionChoice = 0, dragging = 0;
6210 char promoChoice = NULLCHAR;
6212 if(appData.seekGraph && appData.icsActive && loggedOn &&
6213 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6214 SeekGraphClick(clickType, xPix, yPix, 0);
6218 if (clickType == Press) ErrorPopDown();
6219 MarkTargetSquares(1);
6221 x = EventToSquare(xPix, BOARD_WIDTH);
6222 y = EventToSquare(yPix, BOARD_HEIGHT);
6223 if (!flipView && y >= 0) {
6224 y = BOARD_HEIGHT - 1 - y;
6226 if (flipView && x >= 0) {
6227 x = BOARD_WIDTH - 1 - x;
6230 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6231 if(clickType == Release) return; // ignore upclick of click-click destination
6232 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6233 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6234 if(gameInfo.holdingsWidth &&
6235 (WhiteOnMove(currentMove)
6236 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6237 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6238 // click in right holdings, for determining promotion piece
6239 ChessSquare p = boards[currentMove][y][x];
6240 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6241 if(p != EmptySquare) {
6242 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6247 DrawPosition(FALSE, boards[currentMove]);
6251 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6252 if(clickType == Press
6253 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6254 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6255 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6258 autoQueen = appData.alwaysPromoteToQueen;
6261 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6262 if (clickType == Press) {
6264 if (OKToStartUserMove(x, y)) {
6268 MarkTargetSquares(0);
6269 DragPieceBegin(xPix, yPix); dragging = 1;
6270 if (appData.highlightDragging) {
6271 SetHighlights(x, y, -1, -1);
6274 } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6275 DragPieceEnd(xPix, yPix); dragging = 0;
6276 DrawPosition(FALSE, NULL);
6283 if (clickType == Press && gameMode != EditPosition) {
6288 // ignore off-board to clicks
6289 if(y < 0 || x < 0) return;
6291 /* Check if clicking again on the same color piece */
6292 fromP = boards[currentMove][fromY][fromX];
6293 toP = boards[currentMove][y][x];
6294 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6295 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6296 WhitePawn <= toP && toP <= WhiteKing &&
6297 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6298 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6299 (BlackPawn <= fromP && fromP <= BlackKing &&
6300 BlackPawn <= toP && toP <= BlackKing &&
6301 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6302 !(fromP == BlackKing && toP == BlackRook && frc))) {
6303 /* Clicked again on same color piece -- changed his mind */
6304 second = (x == fromX && y == fromY);
6305 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6306 if (appData.highlightDragging) {
6307 SetHighlights(x, y, -1, -1);
6311 if (OKToStartUserMove(x, y)) {
6313 fromY = y; dragging = 1;
6314 MarkTargetSquares(0);
6315 DragPieceBegin(xPix, yPix);
6320 // ignore clicks on holdings
6321 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6324 if (clickType == Release && x == fromX && y == fromY) {
6325 DragPieceEnd(xPix, yPix); dragging = 0;
6326 if (appData.animateDragging) {
6327 /* Undo animation damage if any */
6328 DrawPosition(FALSE, NULL);
6331 /* Second up/down in same square; just abort move */
6336 ClearPremoveHighlights();
6338 /* First upclick in same square; start click-click mode */
6339 SetHighlights(x, y, -1, -1);
6344 /* we now have a different from- and (possibly off-board) to-square */
6345 /* Completed move */
6348 saveAnimate = appData.animate;
6349 if (clickType == Press) {
6350 /* Finish clickclick move */
6351 if (appData.animate || appData.highlightLastMove) {
6352 SetHighlights(fromX, fromY, toX, toY);
6357 /* Finish drag move */
6358 if (appData.highlightLastMove) {
6359 SetHighlights(fromX, fromY, toX, toY);
6363 DragPieceEnd(xPix, yPix); dragging = 0;
6364 /* Don't animate move and drag both */
6365 appData.animate = FALSE;
6368 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6369 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6370 ChessSquare piece = boards[currentMove][fromY][fromX];
6371 if(gameMode == EditPosition && piece != EmptySquare &&
6372 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6375 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6376 n = PieceToNumber(piece - (int)BlackPawn);
6377 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6378 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6379 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6381 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6382 n = PieceToNumber(piece);
6383 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6384 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6385 boards[currentMove][n][BOARD_WIDTH-2]++;
6387 boards[currentMove][fromY][fromX] = EmptySquare;
6391 DrawPosition(TRUE, boards[currentMove]);
6395 // off-board moves should not be highlighted
6396 if(x < 0 || x < 0) ClearHighlights();
6398 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6399 SetHighlights(fromX, fromY, toX, toY);
6400 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6401 // [HGM] super: promotion to captured piece selected from holdings
6402 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6403 promotionChoice = TRUE;
6404 // kludge follows to temporarily execute move on display, without promoting yet
6405 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6406 boards[currentMove][toY][toX] = p;
6407 DrawPosition(FALSE, boards[currentMove]);
6408 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6409 boards[currentMove][toY][toX] = q;
6410 DisplayMessage("Click in holdings to choose piece", "");
6415 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6416 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6417 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6420 appData.animate = saveAnimate;
6421 if (appData.animate || appData.animateDragging) {
6422 /* Undo animation damage if needed */
6423 DrawPosition(FALSE, NULL);
6427 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6428 { // front-end-free part taken out of PieceMenuPopup
6429 int whichMenu; int xSqr, ySqr;
6431 if(seekGraphUp) { // [HGM] seekgraph
6432 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6433 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6437 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6438 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6439 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6440 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6441 if(action == Press) {
6442 originalFlip = flipView;
6443 flipView = !flipView; // temporarily flip board to see game from partners perspective
6444 DrawPosition(TRUE, partnerBoard);
6445 DisplayMessage(partnerStatus, "");
6447 } else if(action == Release) {
6448 flipView = originalFlip;
6449 DrawPosition(TRUE, boards[currentMove]);
6455 xSqr = EventToSquare(x, BOARD_WIDTH);
6456 ySqr = EventToSquare(y, BOARD_HEIGHT);
6457 if (action == Release) UnLoadPV(); // [HGM] pv
6458 if (action != Press) return -2; // return code to be ignored
6461 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6463 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6464 if (xSqr < 0 || ySqr < 0) return -1;
\r
6465 whichMenu = 0; // edit-position menu
6468 if(!appData.icsEngineAnalyze) return -1;
6469 case IcsPlayingWhite:
6470 case IcsPlayingBlack:
6471 if(!appData.zippyPlay) goto noZip;
6474 case MachinePlaysWhite:
6475 case MachinePlaysBlack:
6476 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6477 if (!appData.dropMenu) {
6479 return 2; // flag front-end to grab mouse events
6481 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6482 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6485 if (xSqr < 0 || ySqr < 0) return -1;
6486 if (!appData.dropMenu || appData.testLegality &&
6487 gameInfo.variant != VariantBughouse &&
6488 gameInfo.variant != VariantCrazyhouse) return -1;
6489 whichMenu = 1; // drop menu
6495 if (((*fromX = xSqr) < 0) ||
6496 ((*fromY = ySqr) < 0)) {
6497 *fromX = *fromY = -1;
6501 *fromX = BOARD_WIDTH - 1 - *fromX;
6503 *fromY = BOARD_HEIGHT - 1 - *fromY;
6508 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6510 // char * hint = lastHint;
6511 FrontEndProgramStats stats;
6513 stats.which = cps == &first ? 0 : 1;
6514 stats.depth = cpstats->depth;
6515 stats.nodes = cpstats->nodes;
6516 stats.score = cpstats->score;
6517 stats.time = cpstats->time;
6518 stats.pv = cpstats->movelist;
6519 stats.hint = lastHint;
6520 stats.an_move_index = 0;
6521 stats.an_move_count = 0;
6523 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6524 stats.hint = cpstats->move_name;
6525 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6526 stats.an_move_count = cpstats->nr_moves;
6529 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6531 SetProgramStats( &stats );
6535 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6536 { // count all piece types
6538 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6539 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6540 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6543 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6544 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6545 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6546 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6547 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6548 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6553 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6555 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6556 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6558 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6559 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6560 if(myPawns == 2 && nMine == 3) // KPP
6561 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6562 if(myPawns == 1 && nMine == 2) // KP
6563 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6564 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6565 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6566 if(myPawns) return FALSE;
6567 if(pCnt[WhiteRook+side])
6568 return pCnt[BlackRook-side] ||
6569 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6570 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6571 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6572 if(pCnt[WhiteCannon+side]) {
6573 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6574 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6576 if(pCnt[WhiteKnight+side])
6577 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6582 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6584 VariantClass v = gameInfo.variant;
6586 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6587 if(v == VariantShatranj) return TRUE; // always winnable through baring
6588 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6589 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6591 if(v == VariantXiangqi) {
6592 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6594 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6595 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6596 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6597 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6598 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6599 if(stale) // we have at least one last-rank P plus perhaps C
6600 return majors // KPKX
6601 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6603 return pCnt[WhiteFerz+side] // KCAK
6604 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6605 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6606 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6608 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6609 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6611 if(nMine == 1) return FALSE; // bare King
6612 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
6613 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6614 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6615 // by now we have King + 1 piece (or multiple Bishops on the same color)
6616 if(pCnt[WhiteKnight+side])
6617 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6618 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6619 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6621 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6622 if(pCnt[WhiteAlfil+side])
6623 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6624 if(pCnt[WhiteWazir+side])
6625 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6632 Adjudicate(ChessProgramState *cps)
6633 { // [HGM] some adjudications useful with buggy engines
6634 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6635 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6636 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6637 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6638 int k, count = 0; static int bare = 1;
6639 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6640 Boolean canAdjudicate = !appData.icsActive;
6642 // most tests only when we understand the game, i.e. legality-checking on
6643 if( appData.testLegality )
6644 { /* [HGM] Some more adjudications for obstinate engines */
6645 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6646 static int moveCount = 6;
6648 char *reason = NULL;
6650 /* Count what is on board. */
6651 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6653 /* Some material-based adjudications that have to be made before stalemate test */
6654 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6655 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6656 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6657 if(canAdjudicate && appData.checkMates) {
6659 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6660 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6661 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6662 "Xboard adjudication: King destroyed", GE_XBOARD );
6667 /* Bare King in Shatranj (loses) or Losers (wins) */
6668 if( nrW == 1 || nrB == 1) {
6669 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6670 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6671 if(canAdjudicate && appData.checkMates) {
6673 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6674 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6675 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6676 "Xboard adjudication: Bare king", GE_XBOARD );
6680 if( gameInfo.variant == VariantShatranj && --bare < 0)
6682 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6683 if(canAdjudicate && appData.checkMates) {
6684 /* but only adjudicate if adjudication enabled */
6686 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6687 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6688 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6689 "Xboard adjudication: Bare king", GE_XBOARD );
6696 // don't wait for engine to announce game end if we can judge ourselves
6697 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6699 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6700 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6701 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6702 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6705 reason = "Xboard adjudication: 3rd check";
6706 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6716 reason = "Xboard adjudication: Stalemate";
6717 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6718 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6719 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6720 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6721 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6722 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6723 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6724 EP_CHECKMATE : EP_WINS);
6725 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6726 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6730 reason = "Xboard adjudication: Checkmate";
6731 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6735 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6737 result = GameIsDrawn; break;
6739 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6741 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6743 result = (ChessMove) 0;
6745 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6747 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6748 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6749 GameEnds( result, reason, GE_XBOARD );
6753 /* Next absolutely insufficient mating material. */
6754 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6755 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6756 { /* includes KBK, KNK, KK of KBKB with like Bishops */
6758 /* always flag draws, for judging claims */
6759 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6761 if(canAdjudicate && appData.materialDraws) {
6762 /* but only adjudicate them if adjudication enabled */
6763 if(engineOpponent) {
6764 SendToProgram("force\n", engineOpponent); // suppress reply
6765 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6767 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6768 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6773 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6774 if(gameInfo.variant == VariantXiangqi ?
6775 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6777 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6778 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
6779 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
6780 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6782 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6783 { /* if the first 3 moves do not show a tactical win, declare draw */
6784 if(engineOpponent) {
6785 SendToProgram("force\n", engineOpponent); // suppress reply
6786 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6788 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6789 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6792 } else moveCount = 6;
6795 if (appData.debugMode) { int i;
6796 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6797 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6798 appData.drawRepeats);
6799 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6800 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6804 // Repetition draws and 50-move rule can be applied independently of legality testing
6806 /* Check for rep-draws */
6808 for(k = forwardMostMove-2;
6809 k>=backwardMostMove && k>=forwardMostMove-100 &&
6810 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6811 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6814 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6815 /* compare castling rights */
6816 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6817 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6818 rights++; /* King lost rights, while rook still had them */
6819 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6820 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6821 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6822 rights++; /* but at least one rook lost them */
6824 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6825 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6827 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6828 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6829 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6832 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6833 && appData.drawRepeats > 1) {
6834 /* adjudicate after user-specified nr of repeats */
6835 int result = GameIsDrawn;
6836 char *details = "XBoard adjudication: repetition draw";
6837 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6838 // [HGM] xiangqi: check for forbidden perpetuals
6839 int m, ourPerpetual = 1, hisPerpetual = 1;
6840 for(m=forwardMostMove; m>k; m-=2) {
6841 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6842 ourPerpetual = 0; // the current mover did not always check
6843 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6844 hisPerpetual = 0; // the opponent did not always check
6846 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6847 ourPerpetual, hisPerpetual);
6848 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6849 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6850 details = "Xboard adjudication: perpetual checking";
6852 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6853 break; // (or we would have caught him before). Abort repetition-checking loop.
6855 // Now check for perpetual chases
6856 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6857 hisPerpetual = PerpetualChase(k, forwardMostMove);
6858 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6859 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6860 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6861 details = "Xboard adjudication: perpetual chasing";
6863 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6864 break; // Abort repetition-checking loop.
6866 // if neither of us is checking or chasing all the time, or both are, it is draw
6868 if(engineOpponent) {
6869 SendToProgram("force\n", engineOpponent); // suppress reply
6870 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6872 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6873 GameEnds( result, details, GE_XBOARD );
6876 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6877 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6881 /* Now we test for 50-move draws. Determine ply count */
6882 count = forwardMostMove;
6883 /* look for last irreversble move */
6884 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6886 /* if we hit starting position, add initial plies */
6887 if( count == backwardMostMove )
6888 count -= initialRulePlies;
6889 count = forwardMostMove - count;
6890 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6891 // adjust reversible move counter for checks in Xiangqi
6892 int i = forwardMostMove - count, inCheck = 0, lastCheck;
6893 if(i < backwardMostMove) i = backwardMostMove;
6894 while(i <= forwardMostMove) {
6895 lastCheck = inCheck; // check evasion does not count
6896 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6897 if(inCheck || lastCheck) count--; // check does not count
6902 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6903 /* this is used to judge if draw claims are legal */
6904 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6905 if(engineOpponent) {
6906 SendToProgram("force\n", engineOpponent); // suppress reply
6907 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6909 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6910 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6914 /* if draw offer is pending, treat it as a draw claim
6915 * when draw condition present, to allow engines a way to
6916 * claim draws before making their move to avoid a race
6917 * condition occurring after their move
6919 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6921 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6922 p = "Draw claim: 50-move rule";
6923 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6924 p = "Draw claim: 3-fold repetition";
6925 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6926 p = "Draw claim: insufficient mating material";
6927 if( p != NULL && canAdjudicate) {
6928 if(engineOpponent) {
6929 SendToProgram("force\n", engineOpponent); // suppress reply
6930 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6932 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6933 GameEnds( GameIsDrawn, p, GE_XBOARD );
6938 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6939 if(engineOpponent) {
6940 SendToProgram("force\n", engineOpponent); // suppress reply
6941 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6943 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6944 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6950 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6951 { // [HGM] book: this routine intercepts moves to simulate book replies
6952 char *bookHit = NULL;
6954 //first determine if the incoming move brings opponent into his book
6955 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6956 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6957 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6958 if(bookHit != NULL && !cps->bookSuspend) {
6959 // make sure opponent is not going to reply after receiving move to book position
6960 SendToProgram("force\n", cps);
6961 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6963 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6964 // now arrange restart after book miss
6966 // after a book hit we never send 'go', and the code after the call to this routine
6967 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6969 sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6970 SendToProgram(buf, cps);
6971 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6972 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6973 SendToProgram("go\n", cps);
6974 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6975 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6976 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6977 SendToProgram("go\n", cps);
6978 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6980 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6984 ChessProgramState *savedState;
6985 void DeferredBookMove(void)
6987 if(savedState->lastPing != savedState->lastPong)
6988 ScheduleDelayedEvent(DeferredBookMove, 10);
6990 HandleMachineMove(savedMessage, savedState);
6994 HandleMachineMove(message, cps)
6996 ChessProgramState *cps;
6998 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6999 char realname[MSG_SIZ];
7000 int fromX, fromY, toX, toY;
7009 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7011 * Kludge to ignore BEL characters
7013 while (*message == '\007') message++;
7016 * [HGM] engine debug message: ignore lines starting with '#' character
7018 if(cps->debug && *message == '#') return;
7021 * Look for book output
7023 if (cps == &first && bookRequested) {
7024 if (message[0] == '\t' || message[0] == ' ') {
7025 /* Part of the book output is here; append it */
7026 strcat(bookOutput, message);
7027 strcat(bookOutput, " \n");
7029 } else if (bookOutput[0] != NULLCHAR) {
7030 /* All of book output has arrived; display it */
7031 char *p = bookOutput;
7032 while (*p != NULLCHAR) {
7033 if (*p == '\t') *p = ' ';
7036 DisplayInformation(bookOutput);
7037 bookRequested = FALSE;
7038 /* Fall through to parse the current output */
7043 * Look for machine move.
7045 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7046 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7048 /* This method is only useful on engines that support ping */
7049 if (cps->lastPing != cps->lastPong) {
7050 if (gameMode == BeginningOfGame) {
7051 /* Extra move from before last new; ignore */
7052 if (appData.debugMode) {
7053 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7056 if (appData.debugMode) {
7057 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7058 cps->which, gameMode);
7061 SendToProgram("undo\n", cps);
7067 case BeginningOfGame:
7068 /* Extra move from before last reset; ignore */
7069 if (appData.debugMode) {
7070 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7077 /* Extra move after we tried to stop. The mode test is
7078 not a reliable way of detecting this problem, but it's
7079 the best we can do on engines that don't support ping.
7081 if (appData.debugMode) {
7082 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7083 cps->which, gameMode);
7085 SendToProgram("undo\n", cps);
7088 case MachinePlaysWhite:
7089 case IcsPlayingWhite:
7090 machineWhite = TRUE;
7093 case MachinePlaysBlack:
7094 case IcsPlayingBlack:
7095 machineWhite = FALSE;
7098 case TwoMachinesPlay:
7099 machineWhite = (cps->twoMachinesColor[0] == 'w');
7102 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7103 if (appData.debugMode) {
7105 "Ignoring move out of turn by %s, gameMode %d"
7106 ", forwardMost %d\n",
7107 cps->which, gameMode, forwardMostMove);
7112 if (appData.debugMode) { int f = forwardMostMove;
7113 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7114 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7115 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7117 if(cps->alphaRank) AlphaRank(machineMove, 4);
7118 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7119 &fromX, &fromY, &toX, &toY, &promoChar)) {
7120 /* Machine move could not be parsed; ignore it. */
7121 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7122 machineMove, cps->which);
7123 DisplayError(buf1, 0);
7124 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7125 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7126 if (gameMode == TwoMachinesPlay) {
7127 GameEnds(machineWhite ? BlackWins : WhiteWins,
7133 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7134 /* So we have to redo legality test with true e.p. status here, */
7135 /* to make sure an illegal e.p. capture does not slip through, */
7136 /* to cause a forfeit on a justified illegal-move complaint */
7137 /* of the opponent. */
7138 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7140 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7141 fromY, fromX, toY, toX, promoChar);
7142 if (appData.debugMode) {
7144 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7145 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7146 fprintf(debugFP, "castling rights\n");
7148 if(moveType == IllegalMove) {
7149 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7150 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7151 GameEnds(machineWhite ? BlackWins : WhiteWins,
7154 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7155 /* [HGM] Kludge to handle engines that send FRC-style castling
7156 when they shouldn't (like TSCP-Gothic) */
7158 case WhiteASideCastleFR:
7159 case BlackASideCastleFR:
7161 currentMoveString[2]++;
7163 case WhiteHSideCastleFR:
7164 case BlackHSideCastleFR:
7166 currentMoveString[2]--;
7168 default: ; // nothing to do, but suppresses warning of pedantic compilers
7171 hintRequested = FALSE;
7172 lastHint[0] = NULLCHAR;
7173 bookRequested = FALSE;
7174 /* Program may be pondering now */
7175 cps->maybeThinking = TRUE;
7176 if (cps->sendTime == 2) cps->sendTime = 1;
7177 if (cps->offeredDraw) cps->offeredDraw--;
7179 /* currentMoveString is set as a side-effect of ParseOneMove */
7180 strcpy(machineMove, currentMoveString);
7181 strcat(machineMove, "\n");
7182 strcpy(moveList[forwardMostMove], machineMove);
7184 /* [AS] Save move info*/
7185 pvInfoList[ forwardMostMove ].score = programStats.score;
7186 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7187 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7189 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7191 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7192 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7195 while( count < adjudicateLossPlies ) {
7196 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7199 score = -score; /* Flip score for winning side */
7202 if( score > adjudicateLossThreshold ) {
7209 if( count >= adjudicateLossPlies ) {
7210 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7212 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7213 "Xboard adjudication",
7220 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7223 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7225 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7226 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7228 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7230 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7232 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7233 char buf[3*MSG_SIZ];
7235 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7236 programStats.score / 100.,
7238 programStats.time / 100.,
7239 (unsigned int)programStats.nodes,
7240 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7241 programStats.movelist);
7243 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7248 /* [AS] Clear stats for next move */
7249 ClearProgramStats();
7250 thinkOutput[0] = NULLCHAR;
7251 hiddenThinkOutputState = 0;
7254 if (gameMode == TwoMachinesPlay) {
7255 /* [HGM] relaying draw offers moved to after reception of move */
7256 /* and interpreting offer as claim if it brings draw condition */
7257 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7258 SendToProgram("draw\n", cps->other);
7260 if (cps->other->sendTime) {
7261 SendTimeRemaining(cps->other,
7262 cps->other->twoMachinesColor[0] == 'w');
7264 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7265 if (firstMove && !bookHit) {
7267 if (cps->other->useColors) {
7268 SendToProgram(cps->other->twoMachinesColor, cps->other);
7270 SendToProgram("go\n", cps->other);
7272 cps->other->maybeThinking = TRUE;
7275 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7277 if (!pausing && appData.ringBellAfterMoves) {
7282 * Reenable menu items that were disabled while
7283 * machine was thinking
7285 if (gameMode != TwoMachinesPlay)
7286 SetUserThinkingEnables();
7288 // [HGM] book: after book hit opponent has received move and is now in force mode
7289 // force the book reply into it, and then fake that it outputted this move by jumping
7290 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7292 static char bookMove[MSG_SIZ]; // a bit generous?
7294 strcpy(bookMove, "move ");
7295 strcat(bookMove, bookHit);
7298 programStats.nodes = programStats.depth = programStats.time =
7299 programStats.score = programStats.got_only_move = 0;
7300 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7302 if(cps->lastPing != cps->lastPong) {
7303 savedMessage = message; // args for deferred call
7305 ScheduleDelayedEvent(DeferredBookMove, 10);
7314 /* Set special modes for chess engines. Later something general
7315 * could be added here; for now there is just one kludge feature,
7316 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7317 * when "xboard" is given as an interactive command.
7319 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7320 cps->useSigint = FALSE;
7321 cps->useSigterm = FALSE;
7323 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7324 ParseFeatures(message+8, cps);
7325 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7328 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7329 * want this, I was asked to put it in, and obliged.
7331 if (!strncmp(message, "setboard ", 9)) {
7332 Board initial_position;
7334 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7336 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7337 DisplayError(_("Bad FEN received from engine"), 0);
7341 CopyBoard(boards[0], initial_position);
7342 initialRulePlies = FENrulePlies;
7343 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7344 else gameMode = MachinePlaysBlack;
7345 DrawPosition(FALSE, boards[currentMove]);
7351 * Look for communication commands
7353 if (!strncmp(message, "telluser ", 9)) {
7354 EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7355 DisplayNote(message + 9);
7358 if (!strncmp(message, "tellusererror ", 14)) {
7360 EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7361 DisplayError(message + 14, 0);
7364 if (!strncmp(message, "tellopponent ", 13)) {
7365 if (appData.icsActive) {
7367 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7371 DisplayNote(message + 13);
7375 if (!strncmp(message, "tellothers ", 11)) {
7376 if (appData.icsActive) {
7378 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7384 if (!strncmp(message, "tellall ", 8)) {
7385 if (appData.icsActive) {
7387 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7391 DisplayNote(message + 8);
7395 if (strncmp(message, "warning", 7) == 0) {
7396 /* Undocumented feature, use tellusererror in new code */
7397 DisplayError(message, 0);
7400 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7401 strcpy(realname, cps->tidy);
7402 strcat(realname, " query");
7403 AskQuestion(realname, buf2, buf1, cps->pr);
7406 /* Commands from the engine directly to ICS. We don't allow these to be
7407 * sent until we are logged on. Crafty kibitzes have been known to
7408 * interfere with the login process.
7411 if (!strncmp(message, "tellics ", 8)) {
7412 SendToICS(message + 8);
7416 if (!strncmp(message, "tellicsnoalias ", 15)) {
7417 SendToICS(ics_prefix);
7418 SendToICS(message + 15);
7422 /* The following are for backward compatibility only */
7423 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7424 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7425 SendToICS(ics_prefix);
7431 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7435 * If the move is illegal, cancel it and redraw the board.
7436 * Also deal with other error cases. Matching is rather loose
7437 * here to accommodate engines written before the spec.
7439 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7440 strncmp(message, "Error", 5) == 0) {
7441 if (StrStr(message, "name") ||
7442 StrStr(message, "rating") || StrStr(message, "?") ||
7443 StrStr(message, "result") || StrStr(message, "board") ||
7444 StrStr(message, "bk") || StrStr(message, "computer") ||
7445 StrStr(message, "variant") || StrStr(message, "hint") ||
7446 StrStr(message, "random") || StrStr(message, "depth") ||
7447 StrStr(message, "accepted")) {
7450 if (StrStr(message, "protover")) {
7451 /* Program is responding to input, so it's apparently done
7452 initializing, and this error message indicates it is
7453 protocol version 1. So we don't need to wait any longer
7454 for it to initialize and send feature commands. */
7455 FeatureDone(cps, 1);
7456 cps->protocolVersion = 1;
7459 cps->maybeThinking = FALSE;
7461 if (StrStr(message, "draw")) {
7462 /* Program doesn't have "draw" command */
7463 cps->sendDrawOffers = 0;
7466 if (cps->sendTime != 1 &&
7467 (StrStr(message, "time") || StrStr(message, "otim"))) {
7468 /* Program apparently doesn't have "time" or "otim" command */
7472 if (StrStr(message, "analyze")) {
7473 cps->analysisSupport = FALSE;
7474 cps->analyzing = FALSE;
7476 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7477 DisplayError(buf2, 0);
7480 if (StrStr(message, "(no matching move)st")) {
7481 /* Special kludge for GNU Chess 4 only */
7482 cps->stKludge = TRUE;
7483 SendTimeControl(cps, movesPerSession, timeControl,
7484 timeIncrement, appData.searchDepth,
7488 if (StrStr(message, "(no matching move)sd")) {
7489 /* Special kludge for GNU Chess 4 only */
7490 cps->sdKludge = TRUE;
7491 SendTimeControl(cps, movesPerSession, timeControl,
7492 timeIncrement, appData.searchDepth,
7496 if (!StrStr(message, "llegal")) {
7499 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7500 gameMode == IcsIdle) return;
7501 if (forwardMostMove <= backwardMostMove) return;
7502 if (pausing) PauseEvent();
7503 if(appData.forceIllegal) {
7504 // [HGM] illegal: machine refused move; force position after move into it
7505 SendToProgram("force\n", cps);
7506 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7507 // we have a real problem now, as SendBoard will use the a2a3 kludge
7508 // when black is to move, while there might be nothing on a2 or black
7509 // might already have the move. So send the board as if white has the move.
7510 // But first we must change the stm of the engine, as it refused the last move
7511 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7512 if(WhiteOnMove(forwardMostMove)) {
7513 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7514 SendBoard(cps, forwardMostMove); // kludgeless board
7516 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7517 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7518 SendBoard(cps, forwardMostMove+1); // kludgeless board
7520 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7521 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7522 gameMode == TwoMachinesPlay)
7523 SendToProgram("go\n", cps);
7526 if (gameMode == PlayFromGameFile) {
7527 /* Stop reading this game file */
7528 gameMode = EditGame;
7531 currentMove = forwardMostMove-1;
7532 DisplayMove(currentMove-1); /* before DisplayMoveError */
7533 SwitchClocks(forwardMostMove-1); // [HGM] race
7534 DisplayBothClocks();
7535 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7536 parseList[currentMove], cps->which);
7537 DisplayMoveError(buf1);
7538 DrawPosition(FALSE, boards[currentMove]);
7540 /* [HGM] illegal-move claim should forfeit game when Xboard */
7541 /* only passes fully legal moves */
7542 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7543 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7544 "False illegal-move claim", GE_XBOARD );
7548 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7549 /* Program has a broken "time" command that
7550 outputs a string not ending in newline.
7556 * If chess program startup fails, exit with an error message.
7557 * Attempts to recover here are futile.
7559 if ((StrStr(message, "unknown host") != NULL)
7560 || (StrStr(message, "No remote directory") != NULL)
7561 || (StrStr(message, "not found") != NULL)
7562 || (StrStr(message, "No such file") != NULL)
7563 || (StrStr(message, "can't alloc") != NULL)
7564 || (StrStr(message, "Permission denied") != NULL)) {
7566 cps->maybeThinking = FALSE;
7567 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7568 cps->which, cps->program, cps->host, message);
7569 RemoveInputSource(cps->isr);
7570 DisplayFatalError(buf1, 0, 1);
7575 * Look for hint output
7577 if (sscanf(message, "Hint: %s", buf1) == 1) {
7578 if (cps == &first && hintRequested) {
7579 hintRequested = FALSE;
7580 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7581 &fromX, &fromY, &toX, &toY, &promoChar)) {
7582 (void) CoordsToAlgebraic(boards[forwardMostMove],
7583 PosFlags(forwardMostMove),
7584 fromY, fromX, toY, toX, promoChar, buf1);
7585 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7586 DisplayInformation(buf2);
7588 /* Hint move could not be parsed!? */
7589 snprintf(buf2, sizeof(buf2),
7590 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7592 DisplayError(buf2, 0);
7595 strcpy(lastHint, buf1);
7601 * Ignore other messages if game is not in progress
7603 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7604 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7607 * look for win, lose, draw, or draw offer
7609 if (strncmp(message, "1-0", 3) == 0) {
7610 char *p, *q, *r = "";
7611 p = strchr(message, '{');
7619 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7621 } else if (strncmp(message, "0-1", 3) == 0) {
7622 char *p, *q, *r = "";
7623 p = strchr(message, '{');
7631 /* Kludge for Arasan 4.1 bug */
7632 if (strcmp(r, "Black resigns") == 0) {
7633 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7636 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7638 } else if (strncmp(message, "1/2", 3) == 0) {
7639 char *p, *q, *r = "";
7640 p = strchr(message, '{');
7649 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7652 } else if (strncmp(message, "White resign", 12) == 0) {
7653 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7655 } else if (strncmp(message, "Black resign", 12) == 0) {
7656 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7658 } else if (strncmp(message, "White matches", 13) == 0 ||
7659 strncmp(message, "Black matches", 13) == 0 ) {
7660 /* [HGM] ignore GNUShogi noises */
7662 } else if (strncmp(message, "White", 5) == 0 &&
7663 message[5] != '(' &&
7664 StrStr(message, "Black") == NULL) {
7665 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7667 } else if (strncmp(message, "Black", 5) == 0 &&
7668 message[5] != '(') {
7669 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7671 } else if (strcmp(message, "resign") == 0 ||
7672 strcmp(message, "computer resigns") == 0) {
7674 case MachinePlaysBlack:
7675 case IcsPlayingBlack:
7676 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7678 case MachinePlaysWhite:
7679 case IcsPlayingWhite:
7680 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7682 case TwoMachinesPlay:
7683 if (cps->twoMachinesColor[0] == 'w')
7684 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7686 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7693 } else if (strncmp(message, "opponent mates", 14) == 0) {
7695 case MachinePlaysBlack:
7696 case IcsPlayingBlack:
7697 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7699 case MachinePlaysWhite:
7700 case IcsPlayingWhite:
7701 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7703 case TwoMachinesPlay:
7704 if (cps->twoMachinesColor[0] == 'w')
7705 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7707 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7714 } else if (strncmp(message, "computer mates", 14) == 0) {
7716 case MachinePlaysBlack:
7717 case IcsPlayingBlack:
7718 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7720 case MachinePlaysWhite:
7721 case IcsPlayingWhite:
7722 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7724 case TwoMachinesPlay:
7725 if (cps->twoMachinesColor[0] == 'w')
7726 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7728 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7735 } else if (strncmp(message, "checkmate", 9) == 0) {
7736 if (WhiteOnMove(forwardMostMove)) {
7737 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7739 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7742 } else if (strstr(message, "Draw") != NULL ||
7743 strstr(message, "game is a draw") != NULL) {
7744 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7746 } else if (strstr(message, "offer") != NULL &&
7747 strstr(message, "draw") != NULL) {
7749 if (appData.zippyPlay && first.initDone) {
7750 /* Relay offer to ICS */
7751 SendToICS(ics_prefix);
7752 SendToICS("draw\n");
7755 cps->offeredDraw = 2; /* valid until this engine moves twice */
7756 if (gameMode == TwoMachinesPlay) {
7757 if (cps->other->offeredDraw) {
7758 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7759 /* [HGM] in two-machine mode we delay relaying draw offer */
7760 /* until after we also have move, to see if it is really claim */
7762 } else if (gameMode == MachinePlaysWhite ||
7763 gameMode == MachinePlaysBlack) {
7764 if (userOfferedDraw) {
7765 DisplayInformation(_("Machine accepts your draw offer"));
7766 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7768 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7775 * Look for thinking output
7777 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7778 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7780 int plylev, mvleft, mvtot, curscore, time;
7781 char mvname[MOVE_LEN];
7785 int prefixHint = FALSE;
7786 mvname[0] = NULLCHAR;
7789 case MachinePlaysBlack:
7790 case IcsPlayingBlack:
7791 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7793 case MachinePlaysWhite:
7794 case IcsPlayingWhite:
7795 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7800 case IcsObserving: /* [DM] icsEngineAnalyze */
7801 if (!appData.icsEngineAnalyze) ignore = TRUE;
7803 case TwoMachinesPlay:
7804 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7814 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7816 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7817 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7819 if (plyext != ' ' && plyext != '\t') {
7823 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7824 if( cps->scoreIsAbsolute &&
7825 ( gameMode == MachinePlaysBlack ||
7826 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7827 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7828 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7829 !WhiteOnMove(currentMove)
7832 curscore = -curscore;
7836 tempStats.depth = plylev;
7837 tempStats.nodes = nodes;
7838 tempStats.time = time;
7839 tempStats.score = curscore;
7840 tempStats.got_only_move = 0;
7842 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7845 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7846 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7847 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7848 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7849 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7850 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7851 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7852 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7855 /* Buffer overflow protection */
7856 if (buf1[0] != NULLCHAR) {
7857 if (strlen(buf1) >= sizeof(tempStats.movelist)
7858 && appData.debugMode) {
7860 "PV is too long; using the first %u bytes.\n",
7861 (unsigned) sizeof(tempStats.movelist) - 1);
7864 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7866 sprintf(tempStats.movelist, " no PV\n");
7869 if (tempStats.seen_stat) {
7870 tempStats.ok_to_send = 1;
7873 if (strchr(tempStats.movelist, '(') != NULL) {
7874 tempStats.line_is_book = 1;
7875 tempStats.nr_moves = 0;
7876 tempStats.moves_left = 0;
7878 tempStats.line_is_book = 0;
7881 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7882 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7884 SendProgramStatsToFrontend( cps, &tempStats );
7887 [AS] Protect the thinkOutput buffer from overflow... this
7888 is only useful if buf1 hasn't overflowed first!
7890 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7892 (gameMode == TwoMachinesPlay ?
7893 ToUpper(cps->twoMachinesColor[0]) : ' '),
7894 ((double) curscore) / 100.0,
7895 prefixHint ? lastHint : "",
7896 prefixHint ? " " : "" );
7898 if( buf1[0] != NULLCHAR ) {
7899 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7901 if( strlen(buf1) > max_len ) {
7902 if( appData.debugMode) {
7903 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7905 buf1[max_len+1] = '\0';
7908 strcat( thinkOutput, buf1 );
7911 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7912 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7913 DisplayMove(currentMove - 1);
7917 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7918 /* crafty (9.25+) says "(only move) <move>"
7919 * if there is only 1 legal move
7921 sscanf(p, "(only move) %s", buf1);
7922 sprintf(thinkOutput, "%s (only move)", buf1);
7923 sprintf(programStats.movelist, "%s (only move)", buf1);
7924 programStats.depth = 1;
7925 programStats.nr_moves = 1;
7926 programStats.moves_left = 1;
7927 programStats.nodes = 1;
7928 programStats.time = 1;
7929 programStats.got_only_move = 1;
7931 /* Not really, but we also use this member to
7932 mean "line isn't going to change" (Crafty
7933 isn't searching, so stats won't change) */
7934 programStats.line_is_book = 1;
7936 SendProgramStatsToFrontend( cps, &programStats );
7938 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7939 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7940 DisplayMove(currentMove - 1);
7943 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7944 &time, &nodes, &plylev, &mvleft,
7945 &mvtot, mvname) >= 5) {
7946 /* The stat01: line is from Crafty (9.29+) in response
7947 to the "." command */
7948 programStats.seen_stat = 1;
7949 cps->maybeThinking = TRUE;
7951 if (programStats.got_only_move || !appData.periodicUpdates)
7954 programStats.depth = plylev;
7955 programStats.time = time;
7956 programStats.nodes = nodes;
7957 programStats.moves_left = mvleft;
7958 programStats.nr_moves = mvtot;
7959 strcpy(programStats.move_name, mvname);
7960 programStats.ok_to_send = 1;
7961 programStats.movelist[0] = '\0';
7963 SendProgramStatsToFrontend( cps, &programStats );
7967 } else if (strncmp(message,"++",2) == 0) {
7968 /* Crafty 9.29+ outputs this */
7969 programStats.got_fail = 2;
7972 } else if (strncmp(message,"--",2) == 0) {
7973 /* Crafty 9.29+ outputs this */
7974 programStats.got_fail = 1;
7977 } else if (thinkOutput[0] != NULLCHAR &&
7978 strncmp(message, " ", 4) == 0) {
7979 unsigned message_len;
7982 while (*p && *p == ' ') p++;
7984 message_len = strlen( p );
7986 /* [AS] Avoid buffer overflow */
7987 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7988 strcat(thinkOutput, " ");
7989 strcat(thinkOutput, p);
7992 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7993 strcat(programStats.movelist, " ");
7994 strcat(programStats.movelist, p);
7997 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7998 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7999 DisplayMove(currentMove - 1);
8007 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8008 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8010 ChessProgramStats cpstats;
8012 if (plyext != ' ' && plyext != '\t') {
8016 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8017 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8018 curscore = -curscore;
8021 cpstats.depth = plylev;
8022 cpstats.nodes = nodes;
8023 cpstats.time = time;
8024 cpstats.score = curscore;
8025 cpstats.got_only_move = 0;
8026 cpstats.movelist[0] = '\0';
8028 if (buf1[0] != NULLCHAR) {
8029 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8032 cpstats.ok_to_send = 0;
8033 cpstats.line_is_book = 0;
8034 cpstats.nr_moves = 0;
8035 cpstats.moves_left = 0;
8037 SendProgramStatsToFrontend( cps, &cpstats );
8044 /* Parse a game score from the character string "game", and
8045 record it as the history of the current game. The game
8046 score is NOT assumed to start from the standard position.
8047 The display is not updated in any way.
8050 ParseGameHistory(game)
8054 int fromX, fromY, toX, toY, boardIndex;
8059 if (appData.debugMode)
8060 fprintf(debugFP, "Parsing game history: %s\n", game);
8062 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8063 gameInfo.site = StrSave(appData.icsHost);
8064 gameInfo.date = PGNDate();
8065 gameInfo.round = StrSave("-");
8067 /* Parse out names of players */
8068 while (*game == ' ') game++;
8070 while (*game != ' ') *p++ = *game++;
8072 gameInfo.white = StrSave(buf);
8073 while (*game == ' ') game++;
8075 while (*game != ' ' && *game != '\n') *p++ = *game++;
8077 gameInfo.black = StrSave(buf);
8080 boardIndex = blackPlaysFirst ? 1 : 0;
8083 yyboardindex = boardIndex;
8084 moveType = (ChessMove) yylex();
8086 case IllegalMove: /* maybe suicide chess, etc. */
8087 if (appData.debugMode) {
8088 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8089 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8090 setbuf(debugFP, NULL);
8092 case WhitePromotion:
8093 case BlackPromotion:
8094 case WhiteNonPromotion:
8095 case BlackNonPromotion:
8097 case WhiteCapturesEnPassant:
8098 case BlackCapturesEnPassant:
8099 case WhiteKingSideCastle:
8100 case WhiteQueenSideCastle:
8101 case BlackKingSideCastle:
8102 case BlackQueenSideCastle:
8103 case WhiteKingSideCastleWild:
8104 case WhiteQueenSideCastleWild:
8105 case BlackKingSideCastleWild:
8106 case BlackQueenSideCastleWild:
8108 case WhiteHSideCastleFR:
8109 case WhiteASideCastleFR:
8110 case BlackHSideCastleFR:
8111 case BlackASideCastleFR:
8113 fromX = currentMoveString[0] - AAA;
8114 fromY = currentMoveString[1] - ONE;
8115 toX = currentMoveString[2] - AAA;
8116 toY = currentMoveString[3] - ONE;
8117 promoChar = currentMoveString[4];
8121 fromX = moveType == WhiteDrop ?
8122 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8123 (int) CharToPiece(ToLower(currentMoveString[0]));
8125 toX = currentMoveString[2] - AAA;
8126 toY = currentMoveString[3] - ONE;
8127 promoChar = NULLCHAR;
8131 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8132 if (appData.debugMode) {
8133 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8134 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8135 setbuf(debugFP, NULL);
8137 DisplayError(buf, 0);
8139 case ImpossibleMove:
8141 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8142 if (appData.debugMode) {
8143 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8144 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8145 setbuf(debugFP, NULL);
8147 DisplayError(buf, 0);
8149 case (ChessMove) 0: /* end of file */
8150 if (boardIndex < backwardMostMove) {
8151 /* Oops, gap. How did that happen? */
8152 DisplayError(_("Gap in move list"), 0);
8155 backwardMostMove = blackPlaysFirst ? 1 : 0;
8156 if (boardIndex > forwardMostMove) {
8157 forwardMostMove = boardIndex;
8161 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8162 strcat(parseList[boardIndex-1], " ");
8163 strcat(parseList[boardIndex-1], yy_text);
8175 case GameUnfinished:
8176 if (gameMode == IcsExamining) {
8177 if (boardIndex < backwardMostMove) {
8178 /* Oops, gap. How did that happen? */
8181 backwardMostMove = blackPlaysFirst ? 1 : 0;
8184 gameInfo.result = moveType;
8185 p = strchr(yy_text, '{');
8186 if (p == NULL) p = strchr(yy_text, '(');
8189 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8191 q = strchr(p, *p == '{' ? '}' : ')');
8192 if (q != NULL) *q = NULLCHAR;
8195 gameInfo.resultDetails = StrSave(p);
8198 if (boardIndex >= forwardMostMove &&
8199 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8200 backwardMostMove = blackPlaysFirst ? 1 : 0;
8203 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8204 fromY, fromX, toY, toX, promoChar,
8205 parseList[boardIndex]);
8206 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8207 /* currentMoveString is set as a side-effect of yylex */
8208 strcpy(moveList[boardIndex], currentMoveString);
8209 strcat(moveList[boardIndex], "\n");
8211 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8212 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8218 if(gameInfo.variant != VariantShogi)
8219 strcat(parseList[boardIndex - 1], "+");
8223 strcat(parseList[boardIndex - 1], "#");
8230 /* Apply a move to the given board */
8232 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8233 int fromX, fromY, toX, toY;
8237 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8238 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8240 /* [HGM] compute & store e.p. status and castling rights for new position */
8241 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8243 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8244 oldEP = (signed char)board[EP_STATUS];
8245 board[EP_STATUS] = EP_NONE;
8247 if( board[toY][toX] != EmptySquare )
8248 board[EP_STATUS] = EP_CAPTURE;
8250 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8251 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8252 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8254 if (fromY == DROP_RANK) {
8256 piece = board[toY][toX] = (ChessSquare) fromX;
8260 if( board[fromY][fromX] == WhitePawn ) {
8261 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8262 board[EP_STATUS] = EP_PAWN_MOVE;
8264 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8265 gameInfo.variant != VariantBerolina || toX < fromX)
8266 board[EP_STATUS] = toX | berolina;
8267 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8268 gameInfo.variant != VariantBerolina || toX > fromX)
8269 board[EP_STATUS] = toX;
8272 if( board[fromY][fromX] == BlackPawn ) {
8273 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8274 board[EP_STATUS] = EP_PAWN_MOVE;
8275 if( toY-fromY== -2) {
8276 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8277 gameInfo.variant != VariantBerolina || toX < fromX)
8278 board[EP_STATUS] = toX | berolina;
8279 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8280 gameInfo.variant != VariantBerolina || toX > fromX)
8281 board[EP_STATUS] = toX;
8285 for(i=0; i<nrCastlingRights; i++) {
8286 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8287 board[CASTLING][i] == toX && castlingRank[i] == toY
8288 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8291 if (fromX == toX && fromY == toY) return;
8293 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8294 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8295 if(gameInfo.variant == VariantKnightmate)
8296 king += (int) WhiteUnicorn - (int) WhiteKing;
8298 /* Code added by Tord: */
8299 /* FRC castling assumed when king captures friendly rook. */
8300 if (board[fromY][fromX] == WhiteKing &&
8301 board[toY][toX] == WhiteRook) {
8302 board[fromY][fromX] = EmptySquare;
8303 board[toY][toX] = EmptySquare;
8305 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8307 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8309 } else if (board[fromY][fromX] == BlackKing &&
8310 board[toY][toX] == BlackRook) {
8311 board[fromY][fromX] = EmptySquare;
8312 board[toY][toX] = EmptySquare;
8314 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8316 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8318 /* End of code added by Tord */
8320 } else if (board[fromY][fromX] == king
8321 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8322 && toY == fromY && toX > fromX+1) {
8323 board[fromY][fromX] = EmptySquare;
8324 board[toY][toX] = king;
8325 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8326 board[fromY][BOARD_RGHT-1] = EmptySquare;
8327 } else if (board[fromY][fromX] == king
8328 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8329 && toY == fromY && toX < fromX-1) {
8330 board[fromY][fromX] = EmptySquare;
8331 board[toY][toX] = king;
8332 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8333 board[fromY][BOARD_LEFT] = EmptySquare;
8334 } else if (board[fromY][fromX] == WhitePawn
8335 && toY >= BOARD_HEIGHT-promoRank
8336 && gameInfo.variant != VariantXiangqi
8338 /* white pawn promotion */
8339 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8340 if (board[toY][toX] == EmptySquare) {
8341 board[toY][toX] = WhiteQueen;
8343 if(gameInfo.variant==VariantBughouse ||
8344 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8345 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8346 board[fromY][fromX] = EmptySquare;
8347 } else if ((fromY == BOARD_HEIGHT-4)
8349 && gameInfo.variant != VariantXiangqi
8350 && gameInfo.variant != VariantBerolina
8351 && (board[fromY][fromX] == WhitePawn)
8352 && (board[toY][toX] == EmptySquare)) {
8353 board[fromY][fromX] = EmptySquare;
8354 board[toY][toX] = WhitePawn;
8355 captured = board[toY - 1][toX];
8356 board[toY - 1][toX] = EmptySquare;
8357 } else if ((fromY == BOARD_HEIGHT-4)
8359 && gameInfo.variant == VariantBerolina
8360 && (board[fromY][fromX] == WhitePawn)
8361 && (board[toY][toX] == EmptySquare)) {
8362 board[fromY][fromX] = EmptySquare;
8363 board[toY][toX] = WhitePawn;
8364 if(oldEP & EP_BEROLIN_A) {
8365 captured = board[fromY][fromX-1];
8366 board[fromY][fromX-1] = EmptySquare;
8367 }else{ captured = board[fromY][fromX+1];
8368 board[fromY][fromX+1] = EmptySquare;
8370 } else if (board[fromY][fromX] == king
8371 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8372 && toY == fromY && toX > fromX+1) {
8373 board[fromY][fromX] = EmptySquare;
8374 board[toY][toX] = king;
8375 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8376 board[fromY][BOARD_RGHT-1] = EmptySquare;
8377 } else if (board[fromY][fromX] == king
8378 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8379 && toY == fromY && toX < fromX-1) {
8380 board[fromY][fromX] = EmptySquare;
8381 board[toY][toX] = king;
8382 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8383 board[fromY][BOARD_LEFT] = EmptySquare;
8384 } else if (fromY == 7 && fromX == 3
8385 && board[fromY][fromX] == BlackKing
8386 && toY == 7 && toX == 5) {
8387 board[fromY][fromX] = EmptySquare;
8388 board[toY][toX] = BlackKing;
8389 board[fromY][7] = EmptySquare;
8390 board[toY][4] = BlackRook;
8391 } else if (fromY == 7 && fromX == 3
8392 && board[fromY][fromX] == BlackKing
8393 && toY == 7 && toX == 1) {
8394 board[fromY][fromX] = EmptySquare;
8395 board[toY][toX] = BlackKing;
8396 board[fromY][0] = EmptySquare;
8397 board[toY][2] = BlackRook;
8398 } else if (board[fromY][fromX] == BlackPawn
8400 && gameInfo.variant != VariantXiangqi
8402 /* black pawn promotion */
8403 board[toY][toX] = CharToPiece(ToLower(promoChar));
8404 if (board[toY][toX] == EmptySquare) {
8405 board[toY][toX] = BlackQueen;
8407 if(gameInfo.variant==VariantBughouse ||
8408 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8409 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8410 board[fromY][fromX] = EmptySquare;
8411 } else if ((fromY == 3)
8413 && gameInfo.variant != VariantXiangqi
8414 && gameInfo.variant != VariantBerolina
8415 && (board[fromY][fromX] == BlackPawn)
8416 && (board[toY][toX] == EmptySquare)) {
8417 board[fromY][fromX] = EmptySquare;
8418 board[toY][toX] = BlackPawn;
8419 captured = board[toY + 1][toX];
8420 board[toY + 1][toX] = EmptySquare;
8421 } else if ((fromY == 3)
8423 && gameInfo.variant == VariantBerolina
8424 && (board[fromY][fromX] == BlackPawn)
8425 && (board[toY][toX] == EmptySquare)) {
8426 board[fromY][fromX] = EmptySquare;
8427 board[toY][toX] = BlackPawn;
8428 if(oldEP & EP_BEROLIN_A) {
8429 captured = board[fromY][fromX-1];
8430 board[fromY][fromX-1] = EmptySquare;
8431 }else{ captured = board[fromY][fromX+1];
8432 board[fromY][fromX+1] = EmptySquare;
8435 board[toY][toX] = board[fromY][fromX];
8436 board[fromY][fromX] = EmptySquare;
8440 if (gameInfo.holdingsWidth != 0) {
8442 /* !!A lot more code needs to be written to support holdings */
8443 /* [HGM] OK, so I have written it. Holdings are stored in the */
8444 /* penultimate board files, so they are automaticlly stored */
8445 /* in the game history. */
8446 if (fromY == DROP_RANK) {
8447 /* Delete from holdings, by decreasing count */
8448 /* and erasing image if necessary */
8450 if(p < (int) BlackPawn) { /* white drop */
8451 p -= (int)WhitePawn;
8452 p = PieceToNumber((ChessSquare)p);
8453 if(p >= gameInfo.holdingsSize) p = 0;
8454 if(--board[p][BOARD_WIDTH-2] <= 0)
8455 board[p][BOARD_WIDTH-1] = EmptySquare;
8456 if((int)board[p][BOARD_WIDTH-2] < 0)
8457 board[p][BOARD_WIDTH-2] = 0;
8458 } else { /* black drop */
8459 p -= (int)BlackPawn;
8460 p = PieceToNumber((ChessSquare)p);
8461 if(p >= gameInfo.holdingsSize) p = 0;
8462 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8463 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8464 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8465 board[BOARD_HEIGHT-1-p][1] = 0;
8468 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8469 && gameInfo.variant != VariantBughouse ) {
8470 /* [HGM] holdings: Add to holdings, if holdings exist */
8471 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8472 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8473 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8476 if (p >= (int) BlackPawn) {
8477 p -= (int)BlackPawn;
8478 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8479 /* in Shogi restore piece to its original first */
8480 captured = (ChessSquare) (DEMOTED captured);
8483 p = PieceToNumber((ChessSquare)p);
8484 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8485 board[p][BOARD_WIDTH-2]++;
8486 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8488 p -= (int)WhitePawn;
8489 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8490 captured = (ChessSquare) (DEMOTED captured);
8493 p = PieceToNumber((ChessSquare)p);
8494 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8495 board[BOARD_HEIGHT-1-p][1]++;
8496 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8499 } else if (gameInfo.variant == VariantAtomic) {
8500 if (captured != EmptySquare) {
8502 for (y = toY-1; y <= toY+1; y++) {
8503 for (x = toX-1; x <= toX+1; x++) {
8504 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8505 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8506 board[y][x] = EmptySquare;
8510 board[toY][toX] = EmptySquare;
8513 if(promoChar == '+') {
8514 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8515 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8516 } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8517 board[toY][toX] = CharToPiece(promoChar);
8519 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8520 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8521 // [HGM] superchess: take promotion piece out of holdings
8522 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8523 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8524 if(!--board[k][BOARD_WIDTH-2])
8525 board[k][BOARD_WIDTH-1] = EmptySquare;
8527 if(!--board[BOARD_HEIGHT-1-k][1])
8528 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8534 /* Updates forwardMostMove */
8536 MakeMove(fromX, fromY, toX, toY, promoChar)
8537 int fromX, fromY, toX, toY;
8540 // forwardMostMove++; // [HGM] bare: moved downstream
8542 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8543 int timeLeft; static int lastLoadFlag=0; int king, piece;
8544 piece = boards[forwardMostMove][fromY][fromX];
8545 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8546 if(gameInfo.variant == VariantKnightmate)
8547 king += (int) WhiteUnicorn - (int) WhiteKing;
8548 if(forwardMostMove == 0) {
8550 fprintf(serverMoves, "%s;", second.tidy);
8551 fprintf(serverMoves, "%s;", first.tidy);
8552 if(!blackPlaysFirst)
8553 fprintf(serverMoves, "%s;", second.tidy);
8554 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8555 lastLoadFlag = loadFlag;
8557 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8558 // print castling suffix
8559 if( toY == fromY && piece == king ) {
8561 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8563 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8566 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8567 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8568 boards[forwardMostMove][toY][toX] == EmptySquare
8569 && fromX != toX && fromY != toY)
8570 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8572 if(promoChar != NULLCHAR)
8573 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8575 fprintf(serverMoves, "/%d/%d",
8576 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8577 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8578 else timeLeft = blackTimeRemaining/1000;
8579 fprintf(serverMoves, "/%d", timeLeft);
8581 fflush(serverMoves);
8584 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8585 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8589 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8590 if (commentList[forwardMostMove+1] != NULL) {
8591 free(commentList[forwardMostMove+1]);
8592 commentList[forwardMostMove+1] = NULL;
8594 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8595 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8596 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8597 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8598 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8599 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8600 gameInfo.result = GameUnfinished;
8601 if (gameInfo.resultDetails != NULL) {
8602 free(gameInfo.resultDetails);
8603 gameInfo.resultDetails = NULL;
8605 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8606 moveList[forwardMostMove - 1]);
8607 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8608 PosFlags(forwardMostMove - 1),
8609 fromY, fromX, toY, toX, promoChar,
8610 parseList[forwardMostMove - 1]);
8611 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8617 if(gameInfo.variant != VariantShogi)
8618 strcat(parseList[forwardMostMove - 1], "+");
8622 strcat(parseList[forwardMostMove - 1], "#");
8625 if (appData.debugMode) {
8626 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8631 /* Updates currentMove if not pausing */
8633 ShowMove(fromX, fromY, toX, toY)
8635 int instant = (gameMode == PlayFromGameFile) ?
8636 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8637 if(appData.noGUI) return;
8638 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8640 if (forwardMostMove == currentMove + 1) {
8641 AnimateMove(boards[forwardMostMove - 1],
8642 fromX, fromY, toX, toY);
8644 if (appData.highlightLastMove) {
8645 SetHighlights(fromX, fromY, toX, toY);
8648 currentMove = forwardMostMove;
8651 if (instant) return;
8653 DisplayMove(currentMove - 1);
8654 DrawPosition(FALSE, boards[currentMove]);
8655 DisplayBothClocks();
8656 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8659 void SendEgtPath(ChessProgramState *cps)
8660 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8661 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8663 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8666 char c, *q = name+1, *r, *s;
8668 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8669 while(*p && *p != ',') *q++ = *p++;
8671 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8672 strcmp(name, ",nalimov:") == 0 ) {
8673 // take nalimov path from the menu-changeable option first, if it is defined
8674 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8675 SendToProgram(buf,cps); // send egtbpath command for nalimov
8677 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8678 (s = StrStr(appData.egtFormats, name)) != NULL) {
8679 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8680 s = r = StrStr(s, ":") + 1; // beginning of path info
8681 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8682 c = *r; *r = 0; // temporarily null-terminate path info
8683 *--q = 0; // strip of trailig ':' from name
8684 sprintf(buf, "egtpath %s %s\n", name+1, s);
8686 SendToProgram(buf,cps); // send egtbpath command for this format
8688 if(*p == ',') p++; // read away comma to position for next format name
8693 InitChessProgram(cps, setup)
8694 ChessProgramState *cps;
8695 int setup; /* [HGM] needed to setup FRC opening position */
8697 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8698 if (appData.noChessProgram) return;
8699 hintRequested = FALSE;
8700 bookRequested = FALSE;
8702 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8703 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8704 if(cps->memSize) { /* [HGM] memory */
8705 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8706 SendToProgram(buf, cps);
8708 SendEgtPath(cps); /* [HGM] EGT */
8709 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8710 sprintf(buf, "cores %d\n", appData.smpCores);
8711 SendToProgram(buf, cps);
8714 SendToProgram(cps->initString, cps);
8715 if (gameInfo.variant != VariantNormal &&
8716 gameInfo.variant != VariantLoadable
8717 /* [HGM] also send variant if board size non-standard */
8718 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8720 char *v = VariantName(gameInfo.variant);
8721 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8722 /* [HGM] in protocol 1 we have to assume all variants valid */
8723 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8724 DisplayFatalError(buf, 0, 1);
8728 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8729 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8730 if( gameInfo.variant == VariantXiangqi )
8731 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8732 if( gameInfo.variant == VariantShogi )
8733 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8734 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8735 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8736 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8737 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8738 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8739 if( gameInfo.variant == VariantCourier )
8740 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8741 if( gameInfo.variant == VariantSuper )
8742 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8743 if( gameInfo.variant == VariantGreat )
8744 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8747 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8748 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8749 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8750 if(StrStr(cps->variants, b) == NULL) {
8751 // specific sized variant not known, check if general sizing allowed
8752 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8753 if(StrStr(cps->variants, "boardsize") == NULL) {
8754 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8755 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8756 DisplayFatalError(buf, 0, 1);
8759 /* [HGM] here we really should compare with the maximum supported board size */
8762 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8763 sprintf(buf, "variant %s\n", b);
8764 SendToProgram(buf, cps);
8766 currentlyInitializedVariant = gameInfo.variant;
8768 /* [HGM] send opening position in FRC to first engine */
8770 SendToProgram("force\n", cps);
8772 /* engine is now in force mode! Set flag to wake it up after first move. */
8773 setboardSpoiledMachineBlack = 1;
8777 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8778 SendToProgram(buf, cps);
8780 cps->maybeThinking = FALSE;
8781 cps->offeredDraw = 0;
8782 if (!appData.icsActive) {
8783 SendTimeControl(cps, movesPerSession, timeControl,
8784 timeIncrement, appData.searchDepth,
8787 if (appData.showThinking
8788 // [HGM] thinking: four options require thinking output to be sent
8789 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8791 SendToProgram("post\n", cps);
8793 SendToProgram("hard\n", cps);
8794 if (!appData.ponderNextMove) {
8795 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8796 it without being sure what state we are in first. "hard"
8797 is not a toggle, so that one is OK.
8799 SendToProgram("easy\n", cps);
8802 sprintf(buf, "ping %d\n", ++cps->lastPing);
8803 SendToProgram(buf, cps);
8805 cps->initDone = TRUE;
8810 StartChessProgram(cps)
8811 ChessProgramState *cps;
8816 if (appData.noChessProgram) return;
8817 cps->initDone = FALSE;
8819 if (strcmp(cps->host, "localhost") == 0) {
8820 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8821 } else if (*appData.remoteShell == NULLCHAR) {
8822 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8824 if (*appData.remoteUser == NULLCHAR) {
8825 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8828 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8829 cps->host, appData.remoteUser, cps->program);
8831 err = StartChildProcess(buf, "", &cps->pr);
8835 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8836 DisplayFatalError(buf, err, 1);
8842 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8843 if (cps->protocolVersion > 1) {
8844 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8845 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8846 cps->comboCnt = 0; // and values of combo boxes
8847 SendToProgram(buf, cps);
8849 SendToProgram("xboard\n", cps);
8855 TwoMachinesEventIfReady P((void))
8857 if (first.lastPing != first.lastPong) {
8858 DisplayMessage("", _("Waiting for first chess program"));
8859 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8862 if (second.lastPing != second.lastPong) {
8863 DisplayMessage("", _("Waiting for second chess program"));
8864 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8872 NextMatchGame P((void))
8874 int index; /* [HGM] autoinc: step load index during match */
8876 if (*appData.loadGameFile != NULLCHAR) {
8877 index = appData.loadGameIndex;
8878 if(index < 0) { // [HGM] autoinc
8879 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8880 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8882 LoadGameFromFile(appData.loadGameFile,
8884 appData.loadGameFile, FALSE);
8885 } else if (*appData.loadPositionFile != NULLCHAR) {
8886 index = appData.loadPositionIndex;
8887 if(index < 0) { // [HGM] autoinc
8888 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8889 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8891 LoadPositionFromFile(appData.loadPositionFile,
8893 appData.loadPositionFile);
8895 TwoMachinesEventIfReady();
8898 void UserAdjudicationEvent( int result )
8900 ChessMove gameResult = GameIsDrawn;
8903 gameResult = WhiteWins;
8905 else if( result < 0 ) {
8906 gameResult = BlackWins;
8909 if( gameMode == TwoMachinesPlay ) {
8910 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8915 // [HGM] save: calculate checksum of game to make games easily identifiable
8916 int StringCheckSum(char *s)
8919 if(s==NULL) return 0;
8920 while(*s) i = i*259 + *s++;
8927 for(i=backwardMostMove; i<forwardMostMove; i++) {
8928 sum += pvInfoList[i].depth;
8929 sum += StringCheckSum(parseList[i]);
8930 sum += StringCheckSum(commentList[i]);
8933 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8934 return sum + StringCheckSum(commentList[i]);
8935 } // end of save patch
8938 GameEnds(result, resultDetails, whosays)
8940 char *resultDetails;
8943 GameMode nextGameMode;
8945 char buf[MSG_SIZ], popupRequested = 0;
8947 if(endingGame) return; /* [HGM] crash: forbid recursion */
8949 if(twoBoards) { // [HGM] dual: switch back to one board
8950 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8951 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8953 if (appData.debugMode) {
8954 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8955 result, resultDetails ? resultDetails : "(null)", whosays);
8958 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8960 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8961 /* If we are playing on ICS, the server decides when the
8962 game is over, but the engine can offer to draw, claim
8966 if (appData.zippyPlay && first.initDone) {
8967 if (result == GameIsDrawn) {
8968 /* In case draw still needs to be claimed */
8969 SendToICS(ics_prefix);
8970 SendToICS("draw\n");
8971 } else if (StrCaseStr(resultDetails, "resign")) {
8972 SendToICS(ics_prefix);
8973 SendToICS("resign\n");
8977 endingGame = 0; /* [HGM] crash */
8981 /* If we're loading the game from a file, stop */
8982 if (whosays == GE_FILE) {
8983 (void) StopLoadGameTimer();
8987 /* Cancel draw offers */
8988 first.offeredDraw = second.offeredDraw = 0;
8990 /* If this is an ICS game, only ICS can really say it's done;
8991 if not, anyone can. */
8992 isIcsGame = (gameMode == IcsPlayingWhite ||
8993 gameMode == IcsPlayingBlack ||
8994 gameMode == IcsObserving ||
8995 gameMode == IcsExamining);
8997 if (!isIcsGame || whosays == GE_ICS) {
8998 /* OK -- not an ICS game, or ICS said it was done */
9000 if (!isIcsGame && !appData.noChessProgram)
9001 SetUserThinkingEnables();
9003 /* [HGM] if a machine claims the game end we verify this claim */
9004 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9005 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9007 ChessMove trueResult = (ChessMove) -1;
9009 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9010 first.twoMachinesColor[0] :
9011 second.twoMachinesColor[0] ;
9013 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9014 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9015 /* [HGM] verify: engine mate claims accepted if they were flagged */
9016 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9018 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9019 /* [HGM] verify: engine mate claims accepted if they were flagged */
9020 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9022 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9023 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9026 // now verify win claims, but not in drop games, as we don't understand those yet
9027 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9028 || gameInfo.variant == VariantGreat) &&
9029 (result == WhiteWins && claimer == 'w' ||
9030 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9031 if (appData.debugMode) {
9032 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9033 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9035 if(result != trueResult) {
9036 sprintf(buf, "False win claim: '%s'", resultDetails);
9037 result = claimer == 'w' ? BlackWins : WhiteWins;
9038 resultDetails = buf;
9041 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9042 && (forwardMostMove <= backwardMostMove ||
9043 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9044 (claimer=='b')==(forwardMostMove&1))
9046 /* [HGM] verify: draws that were not flagged are false claims */
9047 sprintf(buf, "False draw claim: '%s'", resultDetails);
9048 result = claimer == 'w' ? BlackWins : WhiteWins;
9049 resultDetails = buf;
9051 /* (Claiming a loss is accepted no questions asked!) */
9053 /* [HGM] bare: don't allow bare King to win */
9054 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9055 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9056 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9057 && result != GameIsDrawn)
9058 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9059 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9060 int p = (signed char)boards[forwardMostMove][i][j] - color;
9061 if(p >= 0 && p <= (int)WhiteKing) k++;
9063 if (appData.debugMode) {
9064 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9065 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9068 result = GameIsDrawn;
9069 sprintf(buf, "%s but bare king", resultDetails);
9070 resultDetails = buf;
9076 if(serverMoves != NULL && !loadFlag) { char c = '=';
9077 if(result==WhiteWins) c = '+';
9078 if(result==BlackWins) c = '-';
9079 if(resultDetails != NULL)
9080 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9082 if (resultDetails != NULL) {
9083 gameInfo.result = result;
9084 gameInfo.resultDetails = StrSave(resultDetails);
9086 /* display last move only if game was not loaded from file */
9087 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9088 DisplayMove(currentMove - 1);
9090 if (forwardMostMove != 0) {
9091 if (gameMode != PlayFromGameFile && gameMode != EditGame
9092 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9094 if (*appData.saveGameFile != NULLCHAR) {
9095 SaveGameToFile(appData.saveGameFile, TRUE);
9096 } else if (appData.autoSaveGames) {
9099 if (*appData.savePositionFile != NULLCHAR) {
9100 SavePositionToFile(appData.savePositionFile);
9105 /* Tell program how game ended in case it is learning */
9106 /* [HGM] Moved this to after saving the PGN, just in case */
9107 /* engine died and we got here through time loss. In that */
9108 /* case we will get a fatal error writing the pipe, which */
9109 /* would otherwise lose us the PGN. */
9110 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9111 /* output during GameEnds should never be fatal anymore */
9112 if (gameMode == MachinePlaysWhite ||
9113 gameMode == MachinePlaysBlack ||
9114 gameMode == TwoMachinesPlay ||
9115 gameMode == IcsPlayingWhite ||
9116 gameMode == IcsPlayingBlack ||
9117 gameMode == BeginningOfGame) {
9119 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9121 if (first.pr != NoProc) {
9122 SendToProgram(buf, &first);
9124 if (second.pr != NoProc &&
9125 gameMode == TwoMachinesPlay) {
9126 SendToProgram(buf, &second);
9131 if (appData.icsActive) {
9132 if (appData.quietPlay &&
9133 (gameMode == IcsPlayingWhite ||
9134 gameMode == IcsPlayingBlack)) {
9135 SendToICS(ics_prefix);
9136 SendToICS("set shout 1\n");
9138 nextGameMode = IcsIdle;
9139 ics_user_moved = FALSE;
9140 /* clean up premove. It's ugly when the game has ended and the
9141 * premove highlights are still on the board.
9145 ClearPremoveHighlights();
9146 DrawPosition(FALSE, boards[currentMove]);
9148 if (whosays == GE_ICS) {
9151 if (gameMode == IcsPlayingWhite)
9153 else if(gameMode == IcsPlayingBlack)
9157 if (gameMode == IcsPlayingBlack)
9159 else if(gameMode == IcsPlayingWhite)
9166 PlayIcsUnfinishedSound();
9169 } else if (gameMode == EditGame ||
9170 gameMode == PlayFromGameFile ||
9171 gameMode == AnalyzeMode ||
9172 gameMode == AnalyzeFile) {
9173 nextGameMode = gameMode;
9175 nextGameMode = EndOfGame;
9180 nextGameMode = gameMode;
9183 if (appData.noChessProgram) {
9184 gameMode = nextGameMode;
9186 endingGame = 0; /* [HGM] crash */
9191 /* Put first chess program into idle state */
9192 if (first.pr != NoProc &&
9193 (gameMode == MachinePlaysWhite ||
9194 gameMode == MachinePlaysBlack ||
9195 gameMode == TwoMachinesPlay ||
9196 gameMode == IcsPlayingWhite ||
9197 gameMode == IcsPlayingBlack ||
9198 gameMode == BeginningOfGame)) {
9199 SendToProgram("force\n", &first);
9200 if (first.usePing) {
9202 sprintf(buf, "ping %d\n", ++first.lastPing);
9203 SendToProgram(buf, &first);
9206 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9207 /* Kill off first chess program */
9208 if (first.isr != NULL)
9209 RemoveInputSource(first.isr);
9212 if (first.pr != NoProc) {
9214 DoSleep( appData.delayBeforeQuit );
9215 SendToProgram("quit\n", &first);
9216 DoSleep( appData.delayAfterQuit );
9217 DestroyChildProcess(first.pr, first.useSigterm);
9222 /* Put second chess program into idle state */
9223 if (second.pr != NoProc &&
9224 gameMode == TwoMachinesPlay) {
9225 SendToProgram("force\n", &second);
9226 if (second.usePing) {
9228 sprintf(buf, "ping %d\n", ++second.lastPing);
9229 SendToProgram(buf, &second);
9232 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9233 /* Kill off second chess program */
9234 if (second.isr != NULL)
9235 RemoveInputSource(second.isr);
9238 if (second.pr != NoProc) {
9239 DoSleep( appData.delayBeforeQuit );
9240 SendToProgram("quit\n", &second);
9241 DoSleep( appData.delayAfterQuit );
9242 DestroyChildProcess(second.pr, second.useSigterm);
9247 if (matchMode && gameMode == TwoMachinesPlay) {
9250 if (first.twoMachinesColor[0] == 'w') {
9257 if (first.twoMachinesColor[0] == 'b') {
9266 if (matchGame < appData.matchGames) {
9268 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9269 tmp = first.twoMachinesColor;
9270 first.twoMachinesColor = second.twoMachinesColor;
9271 second.twoMachinesColor = tmp;
9273 gameMode = nextGameMode;
9275 if(appData.matchPause>10000 || appData.matchPause<10)
9276 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9277 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9278 endingGame = 0; /* [HGM] crash */
9281 gameMode = nextGameMode;
9282 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9283 first.tidy, second.tidy,
9284 first.matchWins, second.matchWins,
9285 appData.matchGames - (first.matchWins + second.matchWins));
9286 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9289 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9290 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9292 gameMode = nextGameMode;
9294 endingGame = 0; /* [HGM] crash */
9295 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9296 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9297 matchMode = FALSE; appData.matchGames = matchGame = 0;
9303 /* Assumes program was just initialized (initString sent).
9304 Leaves program in force mode. */
9306 FeedMovesToProgram(cps, upto)
9307 ChessProgramState *cps;
9312 if (appData.debugMode)
9313 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9314 startedFromSetupPosition ? "position and " : "",
9315 backwardMostMove, upto, cps->which);
9316 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9317 // [HGM] variantswitch: make engine aware of new variant
9318 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9319 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9320 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9321 SendToProgram(buf, cps);
9322 currentlyInitializedVariant = gameInfo.variant;
9324 SendToProgram("force\n", cps);
9325 if (startedFromSetupPosition) {
9326 SendBoard(cps, backwardMostMove);
9327 if (appData.debugMode) {
9328 fprintf(debugFP, "feedMoves\n");
9331 for (i = backwardMostMove; i < upto; i++) {
9332 SendMoveToProgram(i, cps);
9338 ResurrectChessProgram()
9340 /* The chess program may have exited.
9341 If so, restart it and feed it all the moves made so far. */
9343 if (appData.noChessProgram || first.pr != NoProc) return;
9345 StartChessProgram(&first);
9346 InitChessProgram(&first, FALSE);
9347 FeedMovesToProgram(&first, currentMove);
9349 if (!first.sendTime) {
9350 /* can't tell gnuchess what its clock should read,
9351 so we bow to its notion. */
9353 timeRemaining[0][currentMove] = whiteTimeRemaining;
9354 timeRemaining[1][currentMove] = blackTimeRemaining;
9357 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9358 appData.icsEngineAnalyze) && first.analysisSupport) {
9359 SendToProgram("analyze\n", &first);
9360 first.analyzing = TRUE;
9373 if (appData.debugMode) {
9374 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9375 redraw, init, gameMode);
9377 CleanupTail(); // [HGM] vari: delete any stored variations
9378 pausing = pauseExamInvalid = FALSE;
9379 startedFromSetupPosition = blackPlaysFirst = FALSE;
9381 whiteFlag = blackFlag = FALSE;
9382 userOfferedDraw = FALSE;
9383 hintRequested = bookRequested = FALSE;
9384 first.maybeThinking = FALSE;
9385 second.maybeThinking = FALSE;
9386 first.bookSuspend = FALSE; // [HGM] book
9387 second.bookSuspend = FALSE;
9388 thinkOutput[0] = NULLCHAR;
9389 lastHint[0] = NULLCHAR;
9390 ClearGameInfo(&gameInfo);
9391 gameInfo.variant = StringToVariant(appData.variant);
9392 ics_user_moved = ics_clock_paused = FALSE;
9393 ics_getting_history = H_FALSE;
9395 white_holding[0] = black_holding[0] = NULLCHAR;
9396 ClearProgramStats();
9397 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9401 flipView = appData.flipView;
9402 ClearPremoveHighlights();
9404 alarmSounded = FALSE;
9406 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9407 if(appData.serverMovesName != NULL) {
9408 /* [HGM] prepare to make moves file for broadcasting */
9409 clock_t t = clock();
9410 if(serverMoves != NULL) fclose(serverMoves);
9411 serverMoves = fopen(appData.serverMovesName, "r");
9412 if(serverMoves != NULL) {
9413 fclose(serverMoves);
9414 /* delay 15 sec before overwriting, so all clients can see end */
9415 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9417 serverMoves = fopen(appData.serverMovesName, "w");
9421 gameMode = BeginningOfGame;
9423 if(appData.icsActive) gameInfo.variant = VariantNormal;
9424 currentMove = forwardMostMove = backwardMostMove = 0;
9425 InitPosition(redraw);
9426 for (i = 0; i < MAX_MOVES; i++) {
9427 if (commentList[i] != NULL) {
9428 free(commentList[i]);
9429 commentList[i] = NULL;
9433 timeRemaining[0][0] = whiteTimeRemaining;
9434 timeRemaining[1][0] = blackTimeRemaining;
9435 if (first.pr == NULL) {
9436 StartChessProgram(&first);
9439 InitChessProgram(&first, startedFromSetupPosition);
9442 DisplayMessage("", "");
9443 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9444 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9451 if (!AutoPlayOneMove())
9453 if (matchMode || appData.timeDelay == 0)
9455 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9457 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9466 int fromX, fromY, toX, toY;
9468 if (appData.debugMode) {
9469 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9472 if (gameMode != PlayFromGameFile)
9475 if (currentMove >= forwardMostMove) {
9476 gameMode = EditGame;
9479 /* [AS] Clear current move marker at the end of a game */
9480 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9485 toX = moveList[currentMove][2] - AAA;
9486 toY = moveList[currentMove][3] - ONE;
9488 if (moveList[currentMove][1] == '@') {
9489 if (appData.highlightLastMove) {
9490 SetHighlights(-1, -1, toX, toY);
9493 fromX = moveList[currentMove][0] - AAA;
9494 fromY = moveList[currentMove][1] - ONE;
9496 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9498 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9500 if (appData.highlightLastMove) {
9501 SetHighlights(fromX, fromY, toX, toY);
9504 DisplayMove(currentMove);
9505 SendMoveToProgram(currentMove++, &first);
9506 DisplayBothClocks();
9507 DrawPosition(FALSE, boards[currentMove]);
9508 // [HGM] PV info: always display, routine tests if empty
9509 DisplayComment(currentMove - 1, commentList[currentMove]);
9515 LoadGameOneMove(readAhead)
9516 ChessMove readAhead;
9518 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9519 char promoChar = NULLCHAR;
9524 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9525 gameMode != AnalyzeMode && gameMode != Training) {
9530 yyboardindex = forwardMostMove;
9531 if (readAhead != (ChessMove)0) {
9532 moveType = readAhead;
9534 if (gameFileFP == NULL)
9536 moveType = (ChessMove) yylex();
9542 if (appData.debugMode)
9543 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9546 /* append the comment but don't display it */
9547 AppendComment(currentMove, p, FALSE);
9550 case WhiteCapturesEnPassant:
9551 case BlackCapturesEnPassant:
9552 case WhitePromotion:
9553 case BlackPromotion:
9554 case WhiteNonPromotion:
9555 case BlackNonPromotion:
9557 case WhiteKingSideCastle:
9558 case WhiteQueenSideCastle:
9559 case BlackKingSideCastle:
9560 case BlackQueenSideCastle:
9561 case WhiteKingSideCastleWild:
9562 case WhiteQueenSideCastleWild:
9563 case BlackKingSideCastleWild:
9564 case BlackQueenSideCastleWild:
9566 case WhiteHSideCastleFR:
9567 case WhiteASideCastleFR:
9568 case BlackHSideCastleFR:
9569 case BlackASideCastleFR:
9571 if (appData.debugMode)
9572 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9573 fromX = currentMoveString[0] - AAA;
9574 fromY = currentMoveString[1] - ONE;
9575 toX = currentMoveString[2] - AAA;
9576 toY = currentMoveString[3] - ONE;
9577 promoChar = currentMoveString[4];
9582 if (appData.debugMode)
9583 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9584 fromX = moveType == WhiteDrop ?
9585 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9586 (int) CharToPiece(ToLower(currentMoveString[0]));
9588 toX = currentMoveString[2] - AAA;
9589 toY = currentMoveString[3] - ONE;
9595 case GameUnfinished:
9596 if (appData.debugMode)
9597 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9598 p = strchr(yy_text, '{');
9599 if (p == NULL) p = strchr(yy_text, '(');
9602 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9604 q = strchr(p, *p == '{' ? '}' : ')');
9605 if (q != NULL) *q = NULLCHAR;
9608 GameEnds(moveType, p, GE_FILE);
9610 if (cmailMsgLoaded) {
9612 flipView = WhiteOnMove(currentMove);
9613 if (moveType == GameUnfinished) flipView = !flipView;
9614 if (appData.debugMode)
9615 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9619 case (ChessMove) 0: /* end of file */
9620 if (appData.debugMode)
9621 fprintf(debugFP, "Parser hit end of file\n");
9622 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9628 if (WhiteOnMove(currentMove)) {
9629 GameEnds(BlackWins, "Black mates", GE_FILE);
9631 GameEnds(WhiteWins, "White mates", GE_FILE);
9635 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9642 if (lastLoadGameStart == GNUChessGame) {
9643 /* GNUChessGames have numbers, but they aren't move numbers */
9644 if (appData.debugMode)
9645 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9646 yy_text, (int) moveType);
9647 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9649 /* else fall thru */
9654 /* Reached start of next game in file */
9655 if (appData.debugMode)
9656 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9657 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9663 if (WhiteOnMove(currentMove)) {
9664 GameEnds(BlackWins, "Black mates", GE_FILE);
9666 GameEnds(WhiteWins, "White mates", GE_FILE);
9670 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9676 case PositionDiagram: /* should not happen; ignore */
9677 case ElapsedTime: /* ignore */
9678 case NAG: /* ignore */
9679 if (appData.debugMode)
9680 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9681 yy_text, (int) moveType);
9682 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9685 if (appData.testLegality) {
9686 if (appData.debugMode)
9687 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9688 sprintf(move, _("Illegal move: %d.%s%s"),
9689 (forwardMostMove / 2) + 1,
9690 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9691 DisplayError(move, 0);
9694 if (appData.debugMode)
9695 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9696 yy_text, currentMoveString);
9697 fromX = currentMoveString[0] - AAA;
9698 fromY = currentMoveString[1] - ONE;
9699 toX = currentMoveString[2] - AAA;
9700 toY = currentMoveString[3] - ONE;
9701 promoChar = currentMoveString[4];
9706 if (appData.debugMode)
9707 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9708 sprintf(move, _("Ambiguous move: %d.%s%s"),
9709 (forwardMostMove / 2) + 1,
9710 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9711 DisplayError(move, 0);
9716 case ImpossibleMove:
9717 if (appData.debugMode)
9718 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9719 sprintf(move, _("Illegal move: %d.%s%s"),
9720 (forwardMostMove / 2) + 1,
9721 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9722 DisplayError(move, 0);
9728 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9729 DrawPosition(FALSE, boards[currentMove]);
9730 DisplayBothClocks();
9731 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9732 DisplayComment(currentMove - 1, commentList[currentMove]);
9734 (void) StopLoadGameTimer();
9736 cmailOldMove = forwardMostMove;
9739 /* currentMoveString is set as a side-effect of yylex */
9740 strcat(currentMoveString, "\n");
9741 strcpy(moveList[forwardMostMove], currentMoveString);
9743 thinkOutput[0] = NULLCHAR;
9744 MakeMove(fromX, fromY, toX, toY, promoChar);
9745 currentMove = forwardMostMove;
9750 /* Load the nth game from the given file */
9752 LoadGameFromFile(filename, n, title, useList)
9756 /*Boolean*/ int useList;
9761 if (strcmp(filename, "-") == 0) {
9765 f = fopen(filename, "rb");
9767 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9768 DisplayError(buf, errno);
9772 if (fseek(f, 0, 0) == -1) {
9773 /* f is not seekable; probably a pipe */
9776 if (useList && n == 0) {
9777 int error = GameListBuild(f);
9779 DisplayError(_("Cannot build game list"), error);
9780 } else if (!ListEmpty(&gameList) &&
9781 ((ListGame *) gameList.tailPred)->number > 1) {
9782 GameListPopUp(f, title);
9789 return LoadGame(f, n, title, FALSE);
9794 MakeRegisteredMove()
9796 int fromX, fromY, toX, toY;
9798 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9799 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9802 if (appData.debugMode)
9803 fprintf(debugFP, "Restoring %s for game %d\n",
9804 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9806 thinkOutput[0] = NULLCHAR;
9807 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9808 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9809 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9810 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9811 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9812 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9813 MakeMove(fromX, fromY, toX, toY, promoChar);
9814 ShowMove(fromX, fromY, toX, toY);
9816 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9823 if (WhiteOnMove(currentMove)) {
9824 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9826 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9831 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9838 if (WhiteOnMove(currentMove)) {
9839 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9841 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9846 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9857 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9859 CmailLoadGame(f, gameNumber, title, useList)
9867 if (gameNumber > nCmailGames) {
9868 DisplayError(_("No more games in this message"), 0);
9871 if (f == lastLoadGameFP) {
9872 int offset = gameNumber - lastLoadGameNumber;
9874 cmailMsg[0] = NULLCHAR;
9875 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9876 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9877 nCmailMovesRegistered--;
9879 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9880 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9881 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9884 if (! RegisterMove()) return FALSE;
9888 retVal = LoadGame(f, gameNumber, title, useList);
9890 /* Make move registered during previous look at this game, if any */
9891 MakeRegisteredMove();
9893 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9894 commentList[currentMove]
9895 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9896 DisplayComment(currentMove - 1, commentList[currentMove]);
9902 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9907 int gameNumber = lastLoadGameNumber + offset;
9908 if (lastLoadGameFP == NULL) {
9909 DisplayError(_("No game has been loaded yet"), 0);
9912 if (gameNumber <= 0) {
9913 DisplayError(_("Can't back up any further"), 0);
9916 if (cmailMsgLoaded) {
9917 return CmailLoadGame(lastLoadGameFP, gameNumber,
9918 lastLoadGameTitle, lastLoadGameUseList);
9920 return LoadGame(lastLoadGameFP, gameNumber,
9921 lastLoadGameTitle, lastLoadGameUseList);
9927 /* Load the nth game from open file f */
9929 LoadGame(f, gameNumber, title, useList)
9937 int gn = gameNumber;
9938 ListGame *lg = NULL;
9941 GameMode oldGameMode;
9942 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9944 if (appData.debugMode)
9945 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9947 if (gameMode == Training )
9948 SetTrainingModeOff();
9950 oldGameMode = gameMode;
9951 if (gameMode != BeginningOfGame) {
9956 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9957 fclose(lastLoadGameFP);
9961 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9964 fseek(f, lg->offset, 0);
9965 GameListHighlight(gameNumber);
9969 DisplayError(_("Game number out of range"), 0);
9974 if (fseek(f, 0, 0) == -1) {
9975 if (f == lastLoadGameFP ?
9976 gameNumber == lastLoadGameNumber + 1 :
9980 DisplayError(_("Can't seek on game file"), 0);
9986 lastLoadGameNumber = gameNumber;
9987 strcpy(lastLoadGameTitle, title);
9988 lastLoadGameUseList = useList;
9992 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9993 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9994 lg->gameInfo.black);
9996 } else if (*title != NULLCHAR) {
9997 if (gameNumber > 1) {
9998 sprintf(buf, "%s %d", title, gameNumber);
10001 DisplayTitle(title);
10005 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10006 gameMode = PlayFromGameFile;
10010 currentMove = forwardMostMove = backwardMostMove = 0;
10011 CopyBoard(boards[0], initialPosition);
10015 * Skip the first gn-1 games in the file.
10016 * Also skip over anything that precedes an identifiable
10017 * start of game marker, to avoid being confused by
10018 * garbage at the start of the file. Currently
10019 * recognized start of game markers are the move number "1",
10020 * the pattern "gnuchess .* game", the pattern
10021 * "^[#;%] [^ ]* game file", and a PGN tag block.
10022 * A game that starts with one of the latter two patterns
10023 * will also have a move number 1, possibly
10024 * following a position diagram.
10025 * 5-4-02: Let's try being more lenient and allowing a game to
10026 * start with an unnumbered move. Does that break anything?
10028 cm = lastLoadGameStart = (ChessMove) 0;
10030 yyboardindex = forwardMostMove;
10031 cm = (ChessMove) yylex();
10033 case (ChessMove) 0:
10034 if (cmailMsgLoaded) {
10035 nCmailGames = CMAIL_MAX_GAMES - gn;
10038 DisplayError(_("Game not found in file"), 0);
10045 lastLoadGameStart = cm;
10048 case MoveNumberOne:
10049 switch (lastLoadGameStart) {
10054 case MoveNumberOne:
10055 case (ChessMove) 0:
10056 gn--; /* count this game */
10057 lastLoadGameStart = cm;
10066 switch (lastLoadGameStart) {
10069 case MoveNumberOne:
10070 case (ChessMove) 0:
10071 gn--; /* count this game */
10072 lastLoadGameStart = cm;
10075 lastLoadGameStart = cm; /* game counted already */
10083 yyboardindex = forwardMostMove;
10084 cm = (ChessMove) yylex();
10085 } while (cm == PGNTag || cm == Comment);
10092 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10093 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10094 != CMAIL_OLD_RESULT) {
10096 cmailResult[ CMAIL_MAX_GAMES
10097 - gn - 1] = CMAIL_OLD_RESULT;
10103 /* Only a NormalMove can be at the start of a game
10104 * without a position diagram. */
10105 if (lastLoadGameStart == (ChessMove) 0) {
10107 lastLoadGameStart = MoveNumberOne;
10116 if (appData.debugMode)
10117 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10119 if (cm == XBoardGame) {
10120 /* Skip any header junk before position diagram and/or move 1 */
10122 yyboardindex = forwardMostMove;
10123 cm = (ChessMove) yylex();
10125 if (cm == (ChessMove) 0 ||
10126 cm == GNUChessGame || cm == XBoardGame) {
10127 /* Empty game; pretend end-of-file and handle later */
10128 cm = (ChessMove) 0;
10132 if (cm == MoveNumberOne || cm == PositionDiagram ||
10133 cm == PGNTag || cm == Comment)
10136 } else if (cm == GNUChessGame) {
10137 if (gameInfo.event != NULL) {
10138 free(gameInfo.event);
10140 gameInfo.event = StrSave(yy_text);
10143 startedFromSetupPosition = FALSE;
10144 while (cm == PGNTag) {
10145 if (appData.debugMode)
10146 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10147 err = ParsePGNTag(yy_text, &gameInfo);
10148 if (!err) numPGNTags++;
10150 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10151 if(gameInfo.variant != oldVariant) {
10152 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10153 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10154 InitPosition(TRUE);
10155 oldVariant = gameInfo.variant;
10156 if (appData.debugMode)
10157 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10161 if (gameInfo.fen != NULL) {
10162 Board initial_position;
10163 startedFromSetupPosition = TRUE;
10164 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10166 DisplayError(_("Bad FEN position in file"), 0);
10169 CopyBoard(boards[0], initial_position);
10170 if (blackPlaysFirst) {
10171 currentMove = forwardMostMove = backwardMostMove = 1;
10172 CopyBoard(boards[1], initial_position);
10173 strcpy(moveList[0], "");
10174 strcpy(parseList[0], "");
10175 timeRemaining[0][1] = whiteTimeRemaining;
10176 timeRemaining[1][1] = blackTimeRemaining;
10177 if (commentList[0] != NULL) {
10178 commentList[1] = commentList[0];
10179 commentList[0] = NULL;
10182 currentMove = forwardMostMove = backwardMostMove = 0;
10184 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10186 initialRulePlies = FENrulePlies;
10187 for( i=0; i< nrCastlingRights; i++ )
10188 initialRights[i] = initial_position[CASTLING][i];
10190 yyboardindex = forwardMostMove;
10191 free(gameInfo.fen);
10192 gameInfo.fen = NULL;
10195 yyboardindex = forwardMostMove;
10196 cm = (ChessMove) yylex();
10198 /* Handle comments interspersed among the tags */
10199 while (cm == Comment) {
10201 if (appData.debugMode)
10202 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10204 AppendComment(currentMove, p, FALSE);
10205 yyboardindex = forwardMostMove;
10206 cm = (ChessMove) yylex();
10210 /* don't rely on existence of Event tag since if game was
10211 * pasted from clipboard the Event tag may not exist
10213 if (numPGNTags > 0){
10215 if (gameInfo.variant == VariantNormal) {
10216 VariantClass v = StringToVariant(gameInfo.event);
10217 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10218 if(v < VariantShogi) gameInfo.variant = v;
10221 if( appData.autoDisplayTags ) {
10222 tags = PGNTags(&gameInfo);
10223 TagsPopUp(tags, CmailMsg());
10228 /* Make something up, but don't display it now */
10233 if (cm == PositionDiagram) {
10236 Board initial_position;
10238 if (appData.debugMode)
10239 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10241 if (!startedFromSetupPosition) {
10243 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10244 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10254 initial_position[i][j++] = CharToPiece(*p);
10257 while (*p == ' ' || *p == '\t' ||
10258 *p == '\n' || *p == '\r') p++;
10260 if (strncmp(p, "black", strlen("black"))==0)
10261 blackPlaysFirst = TRUE;
10263 blackPlaysFirst = FALSE;
10264 startedFromSetupPosition = TRUE;
10266 CopyBoard(boards[0], initial_position);
10267 if (blackPlaysFirst) {
10268 currentMove = forwardMostMove = backwardMostMove = 1;
10269 CopyBoard(boards[1], initial_position);
10270 strcpy(moveList[0], "");
10271 strcpy(parseList[0], "");
10272 timeRemaining[0][1] = whiteTimeRemaining;
10273 timeRemaining[1][1] = blackTimeRemaining;
10274 if (commentList[0] != NULL) {
10275 commentList[1] = commentList[0];
10276 commentList[0] = NULL;
10279 currentMove = forwardMostMove = backwardMostMove = 0;
10282 yyboardindex = forwardMostMove;
10283 cm = (ChessMove) yylex();
10286 if (first.pr == NoProc) {
10287 StartChessProgram(&first);
10289 InitChessProgram(&first, FALSE);
10290 SendToProgram("force\n", &first);
10291 if (startedFromSetupPosition) {
10292 SendBoard(&first, forwardMostMove);
10293 if (appData.debugMode) {
10294 fprintf(debugFP, "Load Game\n");
10296 DisplayBothClocks();
10299 /* [HGM] server: flag to write setup moves in broadcast file as one */
10300 loadFlag = appData.suppressLoadMoves;
10302 while (cm == Comment) {
10304 if (appData.debugMode)
10305 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10307 AppendComment(currentMove, p, FALSE);
10308 yyboardindex = forwardMostMove;
10309 cm = (ChessMove) yylex();
10312 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10313 cm == WhiteWins || cm == BlackWins ||
10314 cm == GameIsDrawn || cm == GameUnfinished) {
10315 DisplayMessage("", _("No moves in game"));
10316 if (cmailMsgLoaded) {
10317 if (appData.debugMode)
10318 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10322 DrawPosition(FALSE, boards[currentMove]);
10323 DisplayBothClocks();
10324 gameMode = EditGame;
10331 // [HGM] PV info: routine tests if comment empty
10332 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10333 DisplayComment(currentMove - 1, commentList[currentMove]);
10335 if (!matchMode && appData.timeDelay != 0)
10336 DrawPosition(FALSE, boards[currentMove]);
10338 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10339 programStats.ok_to_send = 1;
10342 /* if the first token after the PGN tags is a move
10343 * and not move number 1, retrieve it from the parser
10345 if (cm != MoveNumberOne)
10346 LoadGameOneMove(cm);
10348 /* load the remaining moves from the file */
10349 while (LoadGameOneMove((ChessMove)0)) {
10350 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10351 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10354 /* rewind to the start of the game */
10355 currentMove = backwardMostMove;
10357 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10359 if (oldGameMode == AnalyzeFile ||
10360 oldGameMode == AnalyzeMode) {
10361 AnalyzeFileEvent();
10364 if (matchMode || appData.timeDelay == 0) {
10366 gameMode = EditGame;
10368 } else if (appData.timeDelay > 0) {
10369 AutoPlayGameLoop();
10372 if (appData.debugMode)
10373 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10375 loadFlag = 0; /* [HGM] true game starts */
10379 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10381 ReloadPosition(offset)
10384 int positionNumber = lastLoadPositionNumber + offset;
10385 if (lastLoadPositionFP == NULL) {
10386 DisplayError(_("No position has been loaded yet"), 0);
10389 if (positionNumber <= 0) {
10390 DisplayError(_("Can't back up any further"), 0);
10393 return LoadPosition(lastLoadPositionFP, positionNumber,
10394 lastLoadPositionTitle);
10397 /* Load the nth position from the given file */
10399 LoadPositionFromFile(filename, n, title)
10407 if (strcmp(filename, "-") == 0) {
10408 return LoadPosition(stdin, n, "stdin");
10410 f = fopen(filename, "rb");
10412 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10413 DisplayError(buf, errno);
10416 return LoadPosition(f, n, title);
10421 /* Load the nth position from the given open file, and close it */
10423 LoadPosition(f, positionNumber, title)
10425 int positionNumber;
10428 char *p, line[MSG_SIZ];
10429 Board initial_position;
10430 int i, j, fenMode, pn;
10432 if (gameMode == Training )
10433 SetTrainingModeOff();
10435 if (gameMode != BeginningOfGame) {
10436 Reset(FALSE, TRUE);
10438 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10439 fclose(lastLoadPositionFP);
10441 if (positionNumber == 0) positionNumber = 1;
10442 lastLoadPositionFP = f;
10443 lastLoadPositionNumber = positionNumber;
10444 strcpy(lastLoadPositionTitle, title);
10445 if (first.pr == NoProc) {
10446 StartChessProgram(&first);
10447 InitChessProgram(&first, FALSE);
10449 pn = positionNumber;
10450 if (positionNumber < 0) {
10451 /* Negative position number means to seek to that byte offset */
10452 if (fseek(f, -positionNumber, 0) == -1) {
10453 DisplayError(_("Can't seek on position file"), 0);
10458 if (fseek(f, 0, 0) == -1) {
10459 if (f == lastLoadPositionFP ?
10460 positionNumber == lastLoadPositionNumber + 1 :
10461 positionNumber == 1) {
10464 DisplayError(_("Can't seek on position file"), 0);
10469 /* See if this file is FEN or old-style xboard */
10470 if (fgets(line, MSG_SIZ, f) == NULL) {
10471 DisplayError(_("Position not found in file"), 0);
10474 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10475 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10478 if (fenMode || line[0] == '#') pn--;
10480 /* skip positions before number pn */
10481 if (fgets(line, MSG_SIZ, f) == NULL) {
10483 DisplayError(_("Position not found in file"), 0);
10486 if (fenMode || line[0] == '#') pn--;
10491 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10492 DisplayError(_("Bad FEN position in file"), 0);
10496 (void) fgets(line, MSG_SIZ, f);
10497 (void) fgets(line, MSG_SIZ, f);
10499 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10500 (void) fgets(line, MSG_SIZ, f);
10501 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10504 initial_position[i][j++] = CharToPiece(*p);
10508 blackPlaysFirst = FALSE;
10510 (void) fgets(line, MSG_SIZ, f);
10511 if (strncmp(line, "black", strlen("black"))==0)
10512 blackPlaysFirst = TRUE;
10515 startedFromSetupPosition = TRUE;
10517 SendToProgram("force\n", &first);
10518 CopyBoard(boards[0], initial_position);
10519 if (blackPlaysFirst) {
10520 currentMove = forwardMostMove = backwardMostMove = 1;
10521 strcpy(moveList[0], "");
10522 strcpy(parseList[0], "");
10523 CopyBoard(boards[1], initial_position);
10524 DisplayMessage("", _("Black to play"));
10526 currentMove = forwardMostMove = backwardMostMove = 0;
10527 DisplayMessage("", _("White to play"));
10529 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10530 SendBoard(&first, forwardMostMove);
10531 if (appData.debugMode) {
10533 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10534 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10535 fprintf(debugFP, "Load Position\n");
10538 if (positionNumber > 1) {
10539 sprintf(line, "%s %d", title, positionNumber);
10540 DisplayTitle(line);
10542 DisplayTitle(title);
10544 gameMode = EditGame;
10547 timeRemaining[0][1] = whiteTimeRemaining;
10548 timeRemaining[1][1] = blackTimeRemaining;
10549 DrawPosition(FALSE, boards[currentMove]);
10556 CopyPlayerNameIntoFileName(dest, src)
10559 while (*src != NULLCHAR && *src != ',') {
10564 *(*dest)++ = *src++;
10569 char *DefaultFileName(ext)
10572 static char def[MSG_SIZ];
10575 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10577 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10579 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10588 /* Save the current game to the given file */
10590 SaveGameToFile(filename, append)
10597 if (strcmp(filename, "-") == 0) {
10598 return SaveGame(stdout, 0, NULL);
10600 f = fopen(filename, append ? "a" : "w");
10602 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10603 DisplayError(buf, errno);
10606 return SaveGame(f, 0, NULL);
10615 static char buf[MSG_SIZ];
10618 p = strchr(str, ' ');
10619 if (p == NULL) return str;
10620 strncpy(buf, str, p - str);
10621 buf[p - str] = NULLCHAR;
10625 #define PGN_MAX_LINE 75
10627 #define PGN_SIDE_WHITE 0
10628 #define PGN_SIDE_BLACK 1
10631 static int FindFirstMoveOutOfBook( int side )
10635 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10636 int index = backwardMostMove;
10637 int has_book_hit = 0;
10639 if( (index % 2) != side ) {
10643 while( index < forwardMostMove ) {
10644 /* Check to see if engine is in book */
10645 int depth = pvInfoList[index].depth;
10646 int score = pvInfoList[index].score;
10652 else if( score == 0 && depth == 63 ) {
10653 in_book = 1; /* Zappa */
10655 else if( score == 2 && depth == 99 ) {
10656 in_book = 1; /* Abrok */
10659 has_book_hit += in_book;
10675 void GetOutOfBookInfo( char * buf )
10679 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10681 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10682 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10686 if( oob[0] >= 0 || oob[1] >= 0 ) {
10687 for( i=0; i<2; i++ ) {
10691 if( i > 0 && oob[0] >= 0 ) {
10692 strcat( buf, " " );
10695 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10696 sprintf( buf+strlen(buf), "%s%.2f",
10697 pvInfoList[idx].score >= 0 ? "+" : "",
10698 pvInfoList[idx].score / 100.0 );
10704 /* Save game in PGN style and close the file */
10709 int i, offset, linelen, newblock;
10713 int movelen, numlen, blank;
10714 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10716 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10718 tm = time((time_t *) NULL);
10720 PrintPGNTags(f, &gameInfo);
10722 if (backwardMostMove > 0 || startedFromSetupPosition) {
10723 char *fen = PositionToFEN(backwardMostMove, NULL);
10724 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10725 fprintf(f, "\n{--------------\n");
10726 PrintPosition(f, backwardMostMove);
10727 fprintf(f, "--------------}\n");
10731 /* [AS] Out of book annotation */
10732 if( appData.saveOutOfBookInfo ) {
10735 GetOutOfBookInfo( buf );
10737 if( buf[0] != '\0' ) {
10738 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10745 i = backwardMostMove;
10749 while (i < forwardMostMove) {
10750 /* Print comments preceding this move */
10751 if (commentList[i] != NULL) {
10752 if (linelen > 0) fprintf(f, "\n");
10753 fprintf(f, "%s", commentList[i]);
10758 /* Format move number */
10759 if ((i % 2) == 0) {
10760 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10763 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10765 numtext[0] = NULLCHAR;
10768 numlen = strlen(numtext);
10771 /* Print move number */
10772 blank = linelen > 0 && numlen > 0;
10773 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10782 fprintf(f, "%s", numtext);
10786 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10787 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10790 blank = linelen > 0 && movelen > 0;
10791 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10800 fprintf(f, "%s", move_buffer);
10801 linelen += movelen;
10803 /* [AS] Add PV info if present */
10804 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10805 /* [HGM] add time */
10806 char buf[MSG_SIZ]; int seconds;
10808 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10810 if( seconds <= 0) buf[0] = 0; else
10811 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10812 seconds = (seconds + 4)/10; // round to full seconds
10813 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10814 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10817 sprintf( move_buffer, "{%s%.2f/%d%s}",
10818 pvInfoList[i].score >= 0 ? "+" : "",
10819 pvInfoList[i].score / 100.0,
10820 pvInfoList[i].depth,
10823 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10825 /* Print score/depth */
10826 blank = linelen > 0 && movelen > 0;
10827 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10836 fprintf(f, "%s", move_buffer);
10837 linelen += movelen;
10843 /* Start a new line */
10844 if (linelen > 0) fprintf(f, "\n");
10846 /* Print comments after last move */
10847 if (commentList[i] != NULL) {
10848 fprintf(f, "%s\n", commentList[i]);
10852 if (gameInfo.resultDetails != NULL &&
10853 gameInfo.resultDetails[0] != NULLCHAR) {
10854 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10855 PGNResult(gameInfo.result));
10857 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10861 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10865 /* Save game in old style and close the file */
10867 SaveGameOldStyle(f)
10873 tm = time((time_t *) NULL);
10875 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10878 if (backwardMostMove > 0 || startedFromSetupPosition) {
10879 fprintf(f, "\n[--------------\n");
10880 PrintPosition(f, backwardMostMove);
10881 fprintf(f, "--------------]\n");
10886 i = backwardMostMove;
10887 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10889 while (i < forwardMostMove) {
10890 if (commentList[i] != NULL) {
10891 fprintf(f, "[%s]\n", commentList[i]);
10894 if ((i % 2) == 1) {
10895 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10898 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10900 if (commentList[i] != NULL) {
10904 if (i >= forwardMostMove) {
10908 fprintf(f, "%s\n", parseList[i]);
10913 if (commentList[i] != NULL) {
10914 fprintf(f, "[%s]\n", commentList[i]);
10917 /* This isn't really the old style, but it's close enough */
10918 if (gameInfo.resultDetails != NULL &&
10919 gameInfo.resultDetails[0] != NULLCHAR) {
10920 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10921 gameInfo.resultDetails);
10923 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10930 /* Save the current game to open file f and close the file */
10932 SaveGame(f, dummy, dummy2)
10937 if (gameMode == EditPosition) EditPositionDone(TRUE);
10938 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10939 if (appData.oldSaveStyle)
10940 return SaveGameOldStyle(f);
10942 return SaveGamePGN(f);
10945 /* Save the current position to the given file */
10947 SavePositionToFile(filename)
10953 if (strcmp(filename, "-") == 0) {
10954 return SavePosition(stdout, 0, NULL);
10956 f = fopen(filename, "a");
10958 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10959 DisplayError(buf, errno);
10962 SavePosition(f, 0, NULL);
10968 /* Save the current position to the given open file and close the file */
10970 SavePosition(f, dummy, dummy2)
10978 if (gameMode == EditPosition) EditPositionDone(TRUE);
10979 if (appData.oldSaveStyle) {
10980 tm = time((time_t *) NULL);
10982 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10984 fprintf(f, "[--------------\n");
10985 PrintPosition(f, currentMove);
10986 fprintf(f, "--------------]\n");
10988 fen = PositionToFEN(currentMove, NULL);
10989 fprintf(f, "%s\n", fen);
10997 ReloadCmailMsgEvent(unregister)
11001 static char *inFilename = NULL;
11002 static char *outFilename;
11004 struct stat inbuf, outbuf;
11007 /* Any registered moves are unregistered if unregister is set, */
11008 /* i.e. invoked by the signal handler */
11010 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11011 cmailMoveRegistered[i] = FALSE;
11012 if (cmailCommentList[i] != NULL) {
11013 free(cmailCommentList[i]);
11014 cmailCommentList[i] = NULL;
11017 nCmailMovesRegistered = 0;
11020 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11021 cmailResult[i] = CMAIL_NOT_RESULT;
11025 if (inFilename == NULL) {
11026 /* Because the filenames are static they only get malloced once */
11027 /* and they never get freed */
11028 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11029 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11031 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11032 sprintf(outFilename, "%s.out", appData.cmailGameName);
11035 status = stat(outFilename, &outbuf);
11037 cmailMailedMove = FALSE;
11039 status = stat(inFilename, &inbuf);
11040 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11043 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11044 counts the games, notes how each one terminated, etc.
11046 It would be nice to remove this kludge and instead gather all
11047 the information while building the game list. (And to keep it
11048 in the game list nodes instead of having a bunch of fixed-size
11049 parallel arrays.) Note this will require getting each game's
11050 termination from the PGN tags, as the game list builder does
11051 not process the game moves. --mann
11053 cmailMsgLoaded = TRUE;
11054 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11056 /* Load first game in the file or popup game menu */
11057 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11059 #endif /* !WIN32 */
11067 char string[MSG_SIZ];
11069 if ( cmailMailedMove
11070 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11071 return TRUE; /* Allow free viewing */
11074 /* Unregister move to ensure that we don't leave RegisterMove */
11075 /* with the move registered when the conditions for registering no */
11077 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11078 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11079 nCmailMovesRegistered --;
11081 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11083 free(cmailCommentList[lastLoadGameNumber - 1]);
11084 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11088 if (cmailOldMove == -1) {
11089 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11093 if (currentMove > cmailOldMove + 1) {
11094 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11098 if (currentMove < cmailOldMove) {
11099 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11103 if (forwardMostMove > currentMove) {
11104 /* Silently truncate extra moves */
11108 if ( (currentMove == cmailOldMove + 1)
11109 || ( (currentMove == cmailOldMove)
11110 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11111 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11112 if (gameInfo.result != GameUnfinished) {
11113 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11116 if (commentList[currentMove] != NULL) {
11117 cmailCommentList[lastLoadGameNumber - 1]
11118 = StrSave(commentList[currentMove]);
11120 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11122 if (appData.debugMode)
11123 fprintf(debugFP, "Saving %s for game %d\n",
11124 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11127 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11129 f = fopen(string, "w");
11130 if (appData.oldSaveStyle) {
11131 SaveGameOldStyle(f); /* also closes the file */
11133 sprintf(string, "%s.pos.out", appData.cmailGameName);
11134 f = fopen(string, "w");
11135 SavePosition(f, 0, NULL); /* also closes the file */
11137 fprintf(f, "{--------------\n");
11138 PrintPosition(f, currentMove);
11139 fprintf(f, "--------------}\n\n");
11141 SaveGame(f, 0, NULL); /* also closes the file*/
11144 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11145 nCmailMovesRegistered ++;
11146 } else if (nCmailGames == 1) {
11147 DisplayError(_("You have not made a move yet"), 0);
11158 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11159 FILE *commandOutput;
11160 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11161 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11167 if (! cmailMsgLoaded) {
11168 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11172 if (nCmailGames == nCmailResults) {
11173 DisplayError(_("No unfinished games"), 0);
11177 #if CMAIL_PROHIBIT_REMAIL
11178 if (cmailMailedMove) {
11179 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);
11180 DisplayError(msg, 0);
11185 if (! (cmailMailedMove || RegisterMove())) return;
11187 if ( cmailMailedMove
11188 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11189 sprintf(string, partCommandString,
11190 appData.debugMode ? " -v" : "", appData.cmailGameName);
11191 commandOutput = popen(string, "r");
11193 if (commandOutput == NULL) {
11194 DisplayError(_("Failed to invoke cmail"), 0);
11196 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11197 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11199 if (nBuffers > 1) {
11200 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11201 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11202 nBytes = MSG_SIZ - 1;
11204 (void) memcpy(msg, buffer, nBytes);
11206 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11208 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11209 cmailMailedMove = TRUE; /* Prevent >1 moves */
11212 for (i = 0; i < nCmailGames; i ++) {
11213 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11218 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11220 sprintf(buffer, "%s/%s.%s.archive",
11222 appData.cmailGameName,
11224 LoadGameFromFile(buffer, 1, buffer, FALSE);
11225 cmailMsgLoaded = FALSE;
11229 DisplayInformation(msg);
11230 pclose(commandOutput);
11233 if ((*cmailMsg) != '\0') {
11234 DisplayInformation(cmailMsg);
11239 #endif /* !WIN32 */
11248 int prependComma = 0;
11250 char string[MSG_SIZ]; /* Space for game-list */
11253 if (!cmailMsgLoaded) return "";
11255 if (cmailMailedMove) {
11256 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11258 /* Create a list of games left */
11259 sprintf(string, "[");
11260 for (i = 0; i < nCmailGames; i ++) {
11261 if (! ( cmailMoveRegistered[i]
11262 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11263 if (prependComma) {
11264 sprintf(number, ",%d", i + 1);
11266 sprintf(number, "%d", i + 1);
11270 strcat(string, number);
11273 strcat(string, "]");
11275 if (nCmailMovesRegistered + nCmailResults == 0) {
11276 switch (nCmailGames) {
11279 _("Still need to make move for game\n"));
11284 _("Still need to make moves for both games\n"));
11289 _("Still need to make moves for all %d games\n"),
11294 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11297 _("Still need to make a move for game %s\n"),
11302 if (nCmailResults == nCmailGames) {
11303 sprintf(cmailMsg, _("No unfinished games\n"));
11305 sprintf(cmailMsg, _("Ready to send mail\n"));
11311 _("Still need to make moves for games %s\n"),
11323 if (gameMode == Training)
11324 SetTrainingModeOff();
11327 cmailMsgLoaded = FALSE;
11328 if (appData.icsActive) {
11329 SendToICS(ics_prefix);
11330 SendToICS("refresh\n");
11340 /* Give up on clean exit */
11344 /* Keep trying for clean exit */
11348 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11350 if (telnetISR != NULL) {
11351 RemoveInputSource(telnetISR);
11353 if (icsPR != NoProc) {
11354 DestroyChildProcess(icsPR, TRUE);
11357 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11358 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11360 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11361 /* make sure this other one finishes before killing it! */
11362 if(endingGame) { int count = 0;
11363 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11364 while(endingGame && count++ < 10) DoSleep(1);
11365 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11368 /* Kill off chess programs */
11369 if (first.pr != NoProc) {
11372 DoSleep( appData.delayBeforeQuit );
11373 SendToProgram("quit\n", &first);
11374 DoSleep( appData.delayAfterQuit );
11375 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11377 if (second.pr != NoProc) {
11378 DoSleep( appData.delayBeforeQuit );
11379 SendToProgram("quit\n", &second);
11380 DoSleep( appData.delayAfterQuit );
11381 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11383 if (first.isr != NULL) {
11384 RemoveInputSource(first.isr);
11386 if (second.isr != NULL) {
11387 RemoveInputSource(second.isr);
11390 ShutDownFrontEnd();
11397 if (appData.debugMode)
11398 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11402 if (gameMode == MachinePlaysWhite ||
11403 gameMode == MachinePlaysBlack) {
11406 DisplayBothClocks();
11408 if (gameMode == PlayFromGameFile) {
11409 if (appData.timeDelay >= 0)
11410 AutoPlayGameLoop();
11411 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11412 Reset(FALSE, TRUE);
11413 SendToICS(ics_prefix);
11414 SendToICS("refresh\n");
11415 } else if (currentMove < forwardMostMove) {
11416 ForwardInner(forwardMostMove);
11418 pauseExamInvalid = FALSE;
11420 switch (gameMode) {
11424 pauseExamForwardMostMove = forwardMostMove;
11425 pauseExamInvalid = FALSE;
11428 case IcsPlayingWhite:
11429 case IcsPlayingBlack:
11433 case PlayFromGameFile:
11434 (void) StopLoadGameTimer();
11438 case BeginningOfGame:
11439 if (appData.icsActive) return;
11440 /* else fall through */
11441 case MachinePlaysWhite:
11442 case MachinePlaysBlack:
11443 case TwoMachinesPlay:
11444 if (forwardMostMove == 0)
11445 return; /* don't pause if no one has moved */
11446 if ((gameMode == MachinePlaysWhite &&
11447 !WhiteOnMove(forwardMostMove)) ||
11448 (gameMode == MachinePlaysBlack &&
11449 WhiteOnMove(forwardMostMove))) {
11462 char title[MSG_SIZ];
11464 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11465 strcpy(title, _("Edit comment"));
11467 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11468 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11469 parseList[currentMove - 1]);
11472 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11479 char *tags = PGNTags(&gameInfo);
11480 EditTagsPopUp(tags);
11487 if (appData.noChessProgram || gameMode == AnalyzeMode)
11490 if (gameMode != AnalyzeFile) {
11491 if (!appData.icsEngineAnalyze) {
11493 if (gameMode != EditGame) return;
11495 ResurrectChessProgram();
11496 SendToProgram("analyze\n", &first);
11497 first.analyzing = TRUE;
11498 /*first.maybeThinking = TRUE;*/
11499 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11500 EngineOutputPopUp();
11502 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11507 StartAnalysisClock();
11508 GetTimeMark(&lastNodeCountTime);
11515 if (appData.noChessProgram || gameMode == AnalyzeFile)
11518 if (gameMode != AnalyzeMode) {
11520 if (gameMode != EditGame) return;
11521 ResurrectChessProgram();
11522 SendToProgram("analyze\n", &first);
11523 first.analyzing = TRUE;
11524 /*first.maybeThinking = TRUE;*/
11525 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11526 EngineOutputPopUp();
11528 gameMode = AnalyzeFile;
11533 StartAnalysisClock();
11534 GetTimeMark(&lastNodeCountTime);
11539 MachineWhiteEvent()
11542 char *bookHit = NULL;
11544 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11548 if (gameMode == PlayFromGameFile ||
11549 gameMode == TwoMachinesPlay ||
11550 gameMode == Training ||
11551 gameMode == AnalyzeMode ||
11552 gameMode == EndOfGame)
11555 if (gameMode == EditPosition)
11556 EditPositionDone(TRUE);
11558 if (!WhiteOnMove(currentMove)) {
11559 DisplayError(_("It is not White's turn"), 0);
11563 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11566 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11567 gameMode == AnalyzeFile)
11570 ResurrectChessProgram(); /* in case it isn't running */
11571 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11572 gameMode = MachinePlaysWhite;
11575 gameMode = MachinePlaysWhite;
11579 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11581 if (first.sendName) {
11582 sprintf(buf, "name %s\n", gameInfo.black);
11583 SendToProgram(buf, &first);
11585 if (first.sendTime) {
11586 if (first.useColors) {
11587 SendToProgram("black\n", &first); /*gnu kludge*/
11589 SendTimeRemaining(&first, TRUE);
11591 if (first.useColors) {
11592 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11594 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11595 SetMachineThinkingEnables();
11596 first.maybeThinking = TRUE;
11600 if (appData.autoFlipView && !flipView) {
11601 flipView = !flipView;
11602 DrawPosition(FALSE, NULL);
11603 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11606 if(bookHit) { // [HGM] book: simulate book reply
11607 static char bookMove[MSG_SIZ]; // a bit generous?
11609 programStats.nodes = programStats.depth = programStats.time =
11610 programStats.score = programStats.got_only_move = 0;
11611 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11613 strcpy(bookMove, "move ");
11614 strcat(bookMove, bookHit);
11615 HandleMachineMove(bookMove, &first);
11620 MachineBlackEvent()
11623 char *bookHit = NULL;
11625 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11629 if (gameMode == PlayFromGameFile ||
11630 gameMode == TwoMachinesPlay ||
11631 gameMode == Training ||
11632 gameMode == AnalyzeMode ||
11633 gameMode == EndOfGame)
11636 if (gameMode == EditPosition)
11637 EditPositionDone(TRUE);
11639 if (WhiteOnMove(currentMove)) {
11640 DisplayError(_("It is not Black's turn"), 0);
11644 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11647 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11648 gameMode == AnalyzeFile)
11651 ResurrectChessProgram(); /* in case it isn't running */
11652 gameMode = MachinePlaysBlack;
11656 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11658 if (first.sendName) {
11659 sprintf(buf, "name %s\n", gameInfo.white);
11660 SendToProgram(buf, &first);
11662 if (first.sendTime) {
11663 if (first.useColors) {
11664 SendToProgram("white\n", &first); /*gnu kludge*/
11666 SendTimeRemaining(&first, FALSE);
11668 if (first.useColors) {
11669 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11671 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11672 SetMachineThinkingEnables();
11673 first.maybeThinking = TRUE;
11676 if (appData.autoFlipView && flipView) {
11677 flipView = !flipView;
11678 DrawPosition(FALSE, NULL);
11679 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11681 if(bookHit) { // [HGM] book: simulate book reply
11682 static char bookMove[MSG_SIZ]; // a bit generous?
11684 programStats.nodes = programStats.depth = programStats.time =
11685 programStats.score = programStats.got_only_move = 0;
11686 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11688 strcpy(bookMove, "move ");
11689 strcat(bookMove, bookHit);
11690 HandleMachineMove(bookMove, &first);
11696 DisplayTwoMachinesTitle()
11699 if (appData.matchGames > 0) {
11700 if (first.twoMachinesColor[0] == 'w') {
11701 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11702 gameInfo.white, gameInfo.black,
11703 first.matchWins, second.matchWins,
11704 matchGame - 1 - (first.matchWins + second.matchWins));
11706 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11707 gameInfo.white, gameInfo.black,
11708 second.matchWins, first.matchWins,
11709 matchGame - 1 - (first.matchWins + second.matchWins));
11712 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11718 TwoMachinesEvent P((void))
11722 ChessProgramState *onmove;
11723 char *bookHit = NULL;
11725 if (appData.noChessProgram) return;
11727 switch (gameMode) {
11728 case TwoMachinesPlay:
11730 case MachinePlaysWhite:
11731 case MachinePlaysBlack:
11732 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11733 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11737 case BeginningOfGame:
11738 case PlayFromGameFile:
11741 if (gameMode != EditGame) return;
11744 EditPositionDone(TRUE);
11755 // forwardMostMove = currentMove;
11756 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11757 ResurrectChessProgram(); /* in case first program isn't running */
11759 if (second.pr == NULL) {
11760 StartChessProgram(&second);
11761 if (second.protocolVersion == 1) {
11762 TwoMachinesEventIfReady();
11764 /* kludge: allow timeout for initial "feature" command */
11766 DisplayMessage("", _("Starting second chess program"));
11767 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11771 DisplayMessage("", "");
11772 InitChessProgram(&second, FALSE);
11773 SendToProgram("force\n", &second);
11774 if (startedFromSetupPosition) {
11775 SendBoard(&second, backwardMostMove);
11776 if (appData.debugMode) {
11777 fprintf(debugFP, "Two Machines\n");
11780 for (i = backwardMostMove; i < forwardMostMove; i++) {
11781 SendMoveToProgram(i, &second);
11784 gameMode = TwoMachinesPlay;
11788 DisplayTwoMachinesTitle();
11790 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11796 SendToProgram(first.computerString, &first);
11797 if (first.sendName) {
11798 sprintf(buf, "name %s\n", second.tidy);
11799 SendToProgram(buf, &first);
11801 SendToProgram(second.computerString, &second);
11802 if (second.sendName) {
11803 sprintf(buf, "name %s\n", first.tidy);
11804 SendToProgram(buf, &second);
11808 if (!first.sendTime || !second.sendTime) {
11809 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11810 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11812 if (onmove->sendTime) {
11813 if (onmove->useColors) {
11814 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11816 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11818 if (onmove->useColors) {
11819 SendToProgram(onmove->twoMachinesColor, onmove);
11821 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11822 // SendToProgram("go\n", onmove);
11823 onmove->maybeThinking = TRUE;
11824 SetMachineThinkingEnables();
11828 if(bookHit) { // [HGM] book: simulate book reply
11829 static char bookMove[MSG_SIZ]; // a bit generous?
11831 programStats.nodes = programStats.depth = programStats.time =
11832 programStats.score = programStats.got_only_move = 0;
11833 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11835 strcpy(bookMove, "move ");
11836 strcat(bookMove, bookHit);
11837 savedMessage = bookMove; // args for deferred call
11838 savedState = onmove;
11839 ScheduleDelayedEvent(DeferredBookMove, 1);
11846 if (gameMode == Training) {
11847 SetTrainingModeOff();
11848 gameMode = PlayFromGameFile;
11849 DisplayMessage("", _("Training mode off"));
11851 gameMode = Training;
11852 animateTraining = appData.animate;
11854 /* make sure we are not already at the end of the game */
11855 if (currentMove < forwardMostMove) {
11856 SetTrainingModeOn();
11857 DisplayMessage("", _("Training mode on"));
11859 gameMode = PlayFromGameFile;
11860 DisplayError(_("Already at end of game"), 0);
11869 if (!appData.icsActive) return;
11870 switch (gameMode) {
11871 case IcsPlayingWhite:
11872 case IcsPlayingBlack:
11875 case BeginningOfGame:
11883 EditPositionDone(TRUE);
11896 gameMode = IcsIdle;
11907 switch (gameMode) {
11909 SetTrainingModeOff();
11911 case MachinePlaysWhite:
11912 case MachinePlaysBlack:
11913 case BeginningOfGame:
11914 SendToProgram("force\n", &first);
11915 SetUserThinkingEnables();
11917 case PlayFromGameFile:
11918 (void) StopLoadGameTimer();
11919 if (gameFileFP != NULL) {
11924 EditPositionDone(TRUE);
11929 SendToProgram("force\n", &first);
11931 case TwoMachinesPlay:
11932 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11933 ResurrectChessProgram();
11934 SetUserThinkingEnables();
11937 ResurrectChessProgram();
11939 case IcsPlayingBlack:
11940 case IcsPlayingWhite:
11941 DisplayError(_("Warning: You are still playing a game"), 0);
11944 DisplayError(_("Warning: You are still observing a game"), 0);
11947 DisplayError(_("Warning: You are still examining a game"), 0);
11958 first.offeredDraw = second.offeredDraw = 0;
11960 if (gameMode == PlayFromGameFile) {
11961 whiteTimeRemaining = timeRemaining[0][currentMove];
11962 blackTimeRemaining = timeRemaining[1][currentMove];
11966 if (gameMode == MachinePlaysWhite ||
11967 gameMode == MachinePlaysBlack ||
11968 gameMode == TwoMachinesPlay ||
11969 gameMode == EndOfGame) {
11970 i = forwardMostMove;
11971 while (i > currentMove) {
11972 SendToProgram("undo\n", &first);
11975 whiteTimeRemaining = timeRemaining[0][currentMove];
11976 blackTimeRemaining = timeRemaining[1][currentMove];
11977 DisplayBothClocks();
11978 if (whiteFlag || blackFlag) {
11979 whiteFlag = blackFlag = 0;
11984 gameMode = EditGame;
11991 EditPositionEvent()
11993 if (gameMode == EditPosition) {
11999 if (gameMode != EditGame) return;
12001 gameMode = EditPosition;
12004 if (currentMove > 0)
12005 CopyBoard(boards[0], boards[currentMove]);
12007 blackPlaysFirst = !WhiteOnMove(currentMove);
12009 currentMove = forwardMostMove = backwardMostMove = 0;
12010 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12017 /* [DM] icsEngineAnalyze - possible call from other functions */
12018 if (appData.icsEngineAnalyze) {
12019 appData.icsEngineAnalyze = FALSE;
12021 DisplayMessage("",_("Close ICS engine analyze..."));
12023 if (first.analysisSupport && first.analyzing) {
12024 SendToProgram("exit\n", &first);
12025 first.analyzing = FALSE;
12027 thinkOutput[0] = NULLCHAR;
12031 EditPositionDone(Boolean fakeRights)
12033 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12035 startedFromSetupPosition = TRUE;
12036 InitChessProgram(&first, FALSE);
12037 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12038 boards[0][EP_STATUS] = EP_NONE;
12039 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12040 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12041 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12042 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12043 } else boards[0][CASTLING][2] = NoRights;
12044 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12045 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12046 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12047 } else boards[0][CASTLING][5] = NoRights;
12049 SendToProgram("force\n", &first);
12050 if (blackPlaysFirst) {
12051 strcpy(moveList[0], "");
12052 strcpy(parseList[0], "");
12053 currentMove = forwardMostMove = backwardMostMove = 1;
12054 CopyBoard(boards[1], boards[0]);
12056 currentMove = forwardMostMove = backwardMostMove = 0;
12058 SendBoard(&first, forwardMostMove);
12059 if (appData.debugMode) {
12060 fprintf(debugFP, "EditPosDone\n");
12063 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12064 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12065 gameMode = EditGame;
12067 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12068 ClearHighlights(); /* [AS] */
12071 /* Pause for `ms' milliseconds */
12072 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12082 } while (SubtractTimeMarks(&m2, &m1) < ms);
12085 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12087 SendMultiLineToICS(buf)
12090 char temp[MSG_SIZ+1], *p;
12097 strncpy(temp, buf, len);
12102 if (*p == '\n' || *p == '\r')
12107 strcat(temp, "\n");
12109 SendToPlayer(temp, strlen(temp));
12113 SetWhiteToPlayEvent()
12115 if (gameMode == EditPosition) {
12116 blackPlaysFirst = FALSE;
12117 DisplayBothClocks(); /* works because currentMove is 0 */
12118 } else if (gameMode == IcsExamining) {
12119 SendToICS(ics_prefix);
12120 SendToICS("tomove white\n");
12125 SetBlackToPlayEvent()
12127 if (gameMode == EditPosition) {
12128 blackPlaysFirst = TRUE;
12129 currentMove = 1; /* kludge */
12130 DisplayBothClocks();
12132 } else if (gameMode == IcsExamining) {
12133 SendToICS(ics_prefix);
12134 SendToICS("tomove black\n");
12139 EditPositionMenuEvent(selection, x, y)
12140 ChessSquare selection;
12144 ChessSquare piece = boards[0][y][x];
12146 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12148 switch (selection) {
12150 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12151 SendToICS(ics_prefix);
12152 SendToICS("bsetup clear\n");
12153 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12154 SendToICS(ics_prefix);
12155 SendToICS("clearboard\n");
12157 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12158 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12159 for (y = 0; y < BOARD_HEIGHT; y++) {
12160 if (gameMode == IcsExamining) {
12161 if (boards[currentMove][y][x] != EmptySquare) {
12162 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12167 boards[0][y][x] = p;
12172 if (gameMode == EditPosition) {
12173 DrawPosition(FALSE, boards[0]);
12178 SetWhiteToPlayEvent();
12182 SetBlackToPlayEvent();
12186 if (gameMode == IcsExamining) {
12187 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12188 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12191 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12192 if(x == BOARD_LEFT-2) {
12193 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12194 boards[0][y][1] = 0;
12196 if(x == BOARD_RGHT+1) {
12197 if(y >= gameInfo.holdingsSize) break;
12198 boards[0][y][BOARD_WIDTH-2] = 0;
12201 boards[0][y][x] = EmptySquare;
12202 DrawPosition(FALSE, boards[0]);
12207 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12208 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12209 selection = (ChessSquare) (PROMOTED piece);
12210 } else if(piece == EmptySquare) selection = WhiteSilver;
12211 else selection = (ChessSquare)((int)piece - 1);
12215 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12216 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12217 selection = (ChessSquare) (DEMOTED piece);
12218 } else if(piece == EmptySquare) selection = BlackSilver;
12219 else selection = (ChessSquare)((int)piece + 1);
12224 if(gameInfo.variant == VariantShatranj ||
12225 gameInfo.variant == VariantXiangqi ||
12226 gameInfo.variant == VariantCourier ||
12227 gameInfo.variant == VariantMakruk )
12228 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12233 if(gameInfo.variant == VariantXiangqi)
12234 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12235 if(gameInfo.variant == VariantKnightmate)
12236 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12239 if (gameMode == IcsExamining) {
12240 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12241 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12242 PieceToChar(selection), AAA + x, ONE + y);
12245 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12247 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12248 n = PieceToNumber(selection - BlackPawn);
12249 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12250 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12251 boards[0][BOARD_HEIGHT-1-n][1]++;
12253 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12254 n = PieceToNumber(selection);
12255 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12256 boards[0][n][BOARD_WIDTH-1] = selection;
12257 boards[0][n][BOARD_WIDTH-2]++;
12260 boards[0][y][x] = selection;
12261 DrawPosition(TRUE, boards[0]);
12269 DropMenuEvent(selection, x, y)
12270 ChessSquare selection;
12273 ChessMove moveType;
12275 switch (gameMode) {
12276 case IcsPlayingWhite:
12277 case MachinePlaysBlack:
12278 if (!WhiteOnMove(currentMove)) {
12279 DisplayMoveError(_("It is Black's turn"));
12282 moveType = WhiteDrop;
12284 case IcsPlayingBlack:
12285 case MachinePlaysWhite:
12286 if (WhiteOnMove(currentMove)) {
12287 DisplayMoveError(_("It is White's turn"));
12290 moveType = BlackDrop;
12293 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12299 if (moveType == BlackDrop && selection < BlackPawn) {
12300 selection = (ChessSquare) ((int) selection
12301 + (int) BlackPawn - (int) WhitePawn);
12303 if (boards[currentMove][y][x] != EmptySquare) {
12304 DisplayMoveError(_("That square is occupied"));
12308 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12314 /* Accept a pending offer of any kind from opponent */
12316 if (appData.icsActive) {
12317 SendToICS(ics_prefix);
12318 SendToICS("accept\n");
12319 } else if (cmailMsgLoaded) {
12320 if (currentMove == cmailOldMove &&
12321 commentList[cmailOldMove] != NULL &&
12322 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12323 "Black offers a draw" : "White offers a draw")) {
12325 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12326 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12328 DisplayError(_("There is no pending offer on this move"), 0);
12329 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12332 /* Not used for offers from chess program */
12339 /* Decline a pending offer of any kind from opponent */
12341 if (appData.icsActive) {
12342 SendToICS(ics_prefix);
12343 SendToICS("decline\n");
12344 } else if (cmailMsgLoaded) {
12345 if (currentMove == cmailOldMove &&
12346 commentList[cmailOldMove] != NULL &&
12347 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12348 "Black offers a draw" : "White offers a draw")) {
12350 AppendComment(cmailOldMove, "Draw declined", TRUE);
12351 DisplayComment(cmailOldMove - 1, "Draw declined");
12354 DisplayError(_("There is no pending offer on this move"), 0);
12357 /* Not used for offers from chess program */
12364 /* Issue ICS rematch command */
12365 if (appData.icsActive) {
12366 SendToICS(ics_prefix);
12367 SendToICS("rematch\n");
12374 /* Call your opponent's flag (claim a win on time) */
12375 if (appData.icsActive) {
12376 SendToICS(ics_prefix);
12377 SendToICS("flag\n");
12379 switch (gameMode) {
12382 case MachinePlaysWhite:
12385 GameEnds(GameIsDrawn, "Both players ran out of time",
12388 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12390 DisplayError(_("Your opponent is not out of time"), 0);
12393 case MachinePlaysBlack:
12396 GameEnds(GameIsDrawn, "Both players ran out of time",
12399 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12401 DisplayError(_("Your opponent is not out of time"), 0);
12411 /* Offer draw or accept pending draw offer from opponent */
12413 if (appData.icsActive) {
12414 /* Note: tournament rules require draw offers to be
12415 made after you make your move but before you punch
12416 your clock. Currently ICS doesn't let you do that;
12417 instead, you immediately punch your clock after making
12418 a move, but you can offer a draw at any time. */
12420 SendToICS(ics_prefix);
12421 SendToICS("draw\n");
12422 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12423 } else if (cmailMsgLoaded) {
12424 if (currentMove == cmailOldMove &&
12425 commentList[cmailOldMove] != NULL &&
12426 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12427 "Black offers a draw" : "White offers a draw")) {
12428 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12429 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12430 } else if (currentMove == cmailOldMove + 1) {
12431 char *offer = WhiteOnMove(cmailOldMove) ?
12432 "White offers a draw" : "Black offers a draw";
12433 AppendComment(currentMove, offer, TRUE);
12434 DisplayComment(currentMove - 1, offer);
12435 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12437 DisplayError(_("You must make your move before offering a draw"), 0);
12438 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12440 } else if (first.offeredDraw) {
12441 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12443 if (first.sendDrawOffers) {
12444 SendToProgram("draw\n", &first);
12445 userOfferedDraw = TRUE;
12453 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12455 if (appData.icsActive) {
12456 SendToICS(ics_prefix);
12457 SendToICS("adjourn\n");
12459 /* Currently GNU Chess doesn't offer or accept Adjourns */
12467 /* Offer Abort or accept pending Abort offer from opponent */
12469 if (appData.icsActive) {
12470 SendToICS(ics_prefix);
12471 SendToICS("abort\n");
12473 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12480 /* Resign. You can do this even if it's not your turn. */
12482 if (appData.icsActive) {
12483 SendToICS(ics_prefix);
12484 SendToICS("resign\n");
12486 switch (gameMode) {
12487 case MachinePlaysWhite:
12488 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12490 case MachinePlaysBlack:
12491 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12494 if (cmailMsgLoaded) {
12496 if (WhiteOnMove(cmailOldMove)) {
12497 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12499 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12501 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12512 StopObservingEvent()
12514 /* Stop observing current games */
12515 SendToICS(ics_prefix);
12516 SendToICS("unobserve\n");
12520 StopExaminingEvent()
12522 /* Stop observing current game */
12523 SendToICS(ics_prefix);
12524 SendToICS("unexamine\n");
12528 ForwardInner(target)
12533 if (appData.debugMode)
12534 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12535 target, currentMove, forwardMostMove);
12537 if (gameMode == EditPosition)
12540 if (gameMode == PlayFromGameFile && !pausing)
12543 if (gameMode == IcsExamining && pausing)
12544 limit = pauseExamForwardMostMove;
12546 limit = forwardMostMove;
12548 if (target > limit) target = limit;
12550 if (target > 0 && moveList[target - 1][0]) {
12551 int fromX, fromY, toX, toY;
12552 toX = moveList[target - 1][2] - AAA;
12553 toY = moveList[target - 1][3] - ONE;
12554 if (moveList[target - 1][1] == '@') {
12555 if (appData.highlightLastMove) {
12556 SetHighlights(-1, -1, toX, toY);
12559 fromX = moveList[target - 1][0] - AAA;
12560 fromY = moveList[target - 1][1] - ONE;
12561 if (target == currentMove + 1) {
12562 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12564 if (appData.highlightLastMove) {
12565 SetHighlights(fromX, fromY, toX, toY);
12569 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12570 gameMode == Training || gameMode == PlayFromGameFile ||
12571 gameMode == AnalyzeFile) {
12572 while (currentMove < target) {
12573 SendMoveToProgram(currentMove++, &first);
12576 currentMove = target;
12579 if (gameMode == EditGame || gameMode == EndOfGame) {
12580 whiteTimeRemaining = timeRemaining[0][currentMove];
12581 blackTimeRemaining = timeRemaining[1][currentMove];
12583 DisplayBothClocks();
12584 DisplayMove(currentMove - 1);
12585 DrawPosition(FALSE, boards[currentMove]);
12586 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12587 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12588 DisplayComment(currentMove - 1, commentList[currentMove]);
12596 if (gameMode == IcsExamining && !pausing) {
12597 SendToICS(ics_prefix);
12598 SendToICS("forward\n");
12600 ForwardInner(currentMove + 1);
12607 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12608 /* to optimze, we temporarily turn off analysis mode while we feed
12609 * the remaining moves to the engine. Otherwise we get analysis output
12612 if (first.analysisSupport) {
12613 SendToProgram("exit\nforce\n", &first);
12614 first.analyzing = FALSE;
12618 if (gameMode == IcsExamining && !pausing) {
12619 SendToICS(ics_prefix);
12620 SendToICS("forward 999999\n");
12622 ForwardInner(forwardMostMove);
12625 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12626 /* we have fed all the moves, so reactivate analysis mode */
12627 SendToProgram("analyze\n", &first);
12628 first.analyzing = TRUE;
12629 /*first.maybeThinking = TRUE;*/
12630 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12635 BackwardInner(target)
12638 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12640 if (appData.debugMode)
12641 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12642 target, currentMove, forwardMostMove);
12644 if (gameMode == EditPosition) return;
12645 if (currentMove <= backwardMostMove) {
12647 DrawPosition(full_redraw, boards[currentMove]);
12650 if (gameMode == PlayFromGameFile && !pausing)
12653 if (moveList[target][0]) {
12654 int fromX, fromY, toX, toY;
12655 toX = moveList[target][2] - AAA;
12656 toY = moveList[target][3] - ONE;
12657 if (moveList[target][1] == '@') {
12658 if (appData.highlightLastMove) {
12659 SetHighlights(-1, -1, toX, toY);
12662 fromX = moveList[target][0] - AAA;
12663 fromY = moveList[target][1] - ONE;
12664 if (target == currentMove - 1) {
12665 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12667 if (appData.highlightLastMove) {
12668 SetHighlights(fromX, fromY, toX, toY);
12672 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12673 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12674 while (currentMove > target) {
12675 SendToProgram("undo\n", &first);
12679 currentMove = target;
12682 if (gameMode == EditGame || gameMode == EndOfGame) {
12683 whiteTimeRemaining = timeRemaining[0][currentMove];
12684 blackTimeRemaining = timeRemaining[1][currentMove];
12686 DisplayBothClocks();
12687 DisplayMove(currentMove - 1);
12688 DrawPosition(full_redraw, boards[currentMove]);
12689 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12690 // [HGM] PV info: routine tests if comment empty
12691 DisplayComment(currentMove - 1, commentList[currentMove]);
12697 if (gameMode == IcsExamining && !pausing) {
12698 SendToICS(ics_prefix);
12699 SendToICS("backward\n");
12701 BackwardInner(currentMove - 1);
12708 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12709 /* to optimize, we temporarily turn off analysis mode while we undo
12710 * all the moves. Otherwise we get analysis output after each undo.
12712 if (first.analysisSupport) {
12713 SendToProgram("exit\nforce\n", &first);
12714 first.analyzing = FALSE;
12718 if (gameMode == IcsExamining && !pausing) {
12719 SendToICS(ics_prefix);
12720 SendToICS("backward 999999\n");
12722 BackwardInner(backwardMostMove);
12725 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12726 /* we have fed all the moves, so reactivate analysis mode */
12727 SendToProgram("analyze\n", &first);
12728 first.analyzing = TRUE;
12729 /*first.maybeThinking = TRUE;*/
12730 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12737 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12738 if (to >= forwardMostMove) to = forwardMostMove;
12739 if (to <= backwardMostMove) to = backwardMostMove;
12740 if (to < currentMove) {
12748 RevertEvent(Boolean annotate)
12750 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12753 if (gameMode != IcsExamining) {
12754 DisplayError(_("You are not examining a game"), 0);
12758 DisplayError(_("You can't revert while pausing"), 0);
12761 SendToICS(ics_prefix);
12762 SendToICS("revert\n");
12768 switch (gameMode) {
12769 case MachinePlaysWhite:
12770 case MachinePlaysBlack:
12771 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12772 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12775 if (forwardMostMove < 2) return;
12776 currentMove = forwardMostMove = forwardMostMove - 2;
12777 whiteTimeRemaining = timeRemaining[0][currentMove];
12778 blackTimeRemaining = timeRemaining[1][currentMove];
12779 DisplayBothClocks();
12780 DisplayMove(currentMove - 1);
12781 ClearHighlights();/*!! could figure this out*/
12782 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12783 SendToProgram("remove\n", &first);
12784 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12787 case BeginningOfGame:
12791 case IcsPlayingWhite:
12792 case IcsPlayingBlack:
12793 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12794 SendToICS(ics_prefix);
12795 SendToICS("takeback 2\n");
12797 SendToICS(ics_prefix);
12798 SendToICS("takeback 1\n");
12807 ChessProgramState *cps;
12809 switch (gameMode) {
12810 case MachinePlaysWhite:
12811 if (!WhiteOnMove(forwardMostMove)) {
12812 DisplayError(_("It is your turn"), 0);
12817 case MachinePlaysBlack:
12818 if (WhiteOnMove(forwardMostMove)) {
12819 DisplayError(_("It is your turn"), 0);
12824 case TwoMachinesPlay:
12825 if (WhiteOnMove(forwardMostMove) ==
12826 (first.twoMachinesColor[0] == 'w')) {
12832 case BeginningOfGame:
12836 SendToProgram("?\n", cps);
12840 TruncateGameEvent()
12843 if (gameMode != EditGame) return;
12850 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12851 if (forwardMostMove > currentMove) {
12852 if (gameInfo.resultDetails != NULL) {
12853 free(gameInfo.resultDetails);
12854 gameInfo.resultDetails = NULL;
12855 gameInfo.result = GameUnfinished;
12857 forwardMostMove = currentMove;
12858 HistorySet(parseList, backwardMostMove, forwardMostMove,
12866 if (appData.noChessProgram) return;
12867 switch (gameMode) {
12868 case MachinePlaysWhite:
12869 if (WhiteOnMove(forwardMostMove)) {
12870 DisplayError(_("Wait until your turn"), 0);
12874 case BeginningOfGame:
12875 case MachinePlaysBlack:
12876 if (!WhiteOnMove(forwardMostMove)) {
12877 DisplayError(_("Wait until your turn"), 0);
12882 DisplayError(_("No hint available"), 0);
12885 SendToProgram("hint\n", &first);
12886 hintRequested = TRUE;
12892 if (appData.noChessProgram) return;
12893 switch (gameMode) {
12894 case MachinePlaysWhite:
12895 if (WhiteOnMove(forwardMostMove)) {
12896 DisplayError(_("Wait until your turn"), 0);
12900 case BeginningOfGame:
12901 case MachinePlaysBlack:
12902 if (!WhiteOnMove(forwardMostMove)) {
12903 DisplayError(_("Wait until your turn"), 0);
12908 EditPositionDone(TRUE);
12910 case TwoMachinesPlay:
12915 SendToProgram("bk\n", &first);
12916 bookOutput[0] = NULLCHAR;
12917 bookRequested = TRUE;
12923 char *tags = PGNTags(&gameInfo);
12924 TagsPopUp(tags, CmailMsg());
12928 /* end button procedures */
12931 PrintPosition(fp, move)
12937 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12938 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12939 char c = PieceToChar(boards[move][i][j]);
12940 fputc(c == 'x' ? '.' : c, fp);
12941 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12944 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12945 fprintf(fp, "white to play\n");
12947 fprintf(fp, "black to play\n");
12954 if (gameInfo.white != NULL) {
12955 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12961 /* Find last component of program's own name, using some heuristics */
12963 TidyProgramName(prog, host, buf)
12964 char *prog, *host, buf[MSG_SIZ];
12967 int local = (strcmp(host, "localhost") == 0);
12968 while (!local && (p = strchr(prog, ';')) != NULL) {
12970 while (*p == ' ') p++;
12973 if (*prog == '"' || *prog == '\'') {
12974 q = strchr(prog + 1, *prog);
12976 q = strchr(prog, ' ');
12978 if (q == NULL) q = prog + strlen(prog);
12980 while (p >= prog && *p != '/' && *p != '\\') p--;
12982 if(p == prog && *p == '"') p++;
12983 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12984 memcpy(buf, p, q - p);
12985 buf[q - p] = NULLCHAR;
12993 TimeControlTagValue()
12996 if (!appData.clockMode) {
12998 } else if (movesPerSession > 0) {
12999 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13000 } else if (timeIncrement == 0) {
13001 sprintf(buf, "%ld", timeControl/1000);
13003 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13005 return StrSave(buf);
13011 /* This routine is used only for certain modes */
13012 VariantClass v = gameInfo.variant;
13013 ChessMove r = GameUnfinished;
13016 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13017 r = gameInfo.result;
13018 p = gameInfo.resultDetails;
13019 gameInfo.resultDetails = NULL;
13021 ClearGameInfo(&gameInfo);
13022 gameInfo.variant = v;
13024 switch (gameMode) {
13025 case MachinePlaysWhite:
13026 gameInfo.event = StrSave( appData.pgnEventHeader );
13027 gameInfo.site = StrSave(HostName());
13028 gameInfo.date = PGNDate();
13029 gameInfo.round = StrSave("-");
13030 gameInfo.white = StrSave(first.tidy);
13031 gameInfo.black = StrSave(UserName());
13032 gameInfo.timeControl = TimeControlTagValue();
13035 case MachinePlaysBlack:
13036 gameInfo.event = StrSave( appData.pgnEventHeader );
13037 gameInfo.site = StrSave(HostName());
13038 gameInfo.date = PGNDate();
13039 gameInfo.round = StrSave("-");
13040 gameInfo.white = StrSave(UserName());
13041 gameInfo.black = StrSave(first.tidy);
13042 gameInfo.timeControl = TimeControlTagValue();
13045 case TwoMachinesPlay:
13046 gameInfo.event = StrSave( appData.pgnEventHeader );
13047 gameInfo.site = StrSave(HostName());
13048 gameInfo.date = PGNDate();
13049 if (matchGame > 0) {
13051 sprintf(buf, "%d", matchGame);
13052 gameInfo.round = StrSave(buf);
13054 gameInfo.round = StrSave("-");
13056 if (first.twoMachinesColor[0] == 'w') {
13057 gameInfo.white = StrSave(first.tidy);
13058 gameInfo.black = StrSave(second.tidy);
13060 gameInfo.white = StrSave(second.tidy);
13061 gameInfo.black = StrSave(first.tidy);
13063 gameInfo.timeControl = TimeControlTagValue();
13067 gameInfo.event = StrSave("Edited game");
13068 gameInfo.site = StrSave(HostName());
13069 gameInfo.date = PGNDate();
13070 gameInfo.round = StrSave("-");
13071 gameInfo.white = StrSave("-");
13072 gameInfo.black = StrSave("-");
13073 gameInfo.result = r;
13074 gameInfo.resultDetails = p;
13078 gameInfo.event = StrSave("Edited position");
13079 gameInfo.site = StrSave(HostName());
13080 gameInfo.date = PGNDate();
13081 gameInfo.round = StrSave("-");
13082 gameInfo.white = StrSave("-");
13083 gameInfo.black = StrSave("-");
13086 case IcsPlayingWhite:
13087 case IcsPlayingBlack:
13092 case PlayFromGameFile:
13093 gameInfo.event = StrSave("Game from non-PGN file");
13094 gameInfo.site = StrSave(HostName());
13095 gameInfo.date = PGNDate();
13096 gameInfo.round = StrSave("-");
13097 gameInfo.white = StrSave("?");
13098 gameInfo.black = StrSave("?");
13107 ReplaceComment(index, text)
13113 while (*text == '\n') text++;
13114 len = strlen(text);
13115 while (len > 0 && text[len - 1] == '\n') len--;
13117 if (commentList[index] != NULL)
13118 free(commentList[index]);
13121 commentList[index] = NULL;
13124 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13125 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13126 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13127 commentList[index] = (char *) malloc(len + 2);
13128 strncpy(commentList[index], text, len);
13129 commentList[index][len] = '\n';
13130 commentList[index][len + 1] = NULLCHAR;
13132 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13134 commentList[index] = (char *) malloc(len + 6);
13135 strcpy(commentList[index], "{\n");
13136 strncpy(commentList[index]+2, text, len);
13137 commentList[index][len+2] = NULLCHAR;
13138 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13139 strcat(commentList[index], "\n}\n");
13153 if (ch == '\r') continue;
13155 } while (ch != '\0');
13159 AppendComment(index, text, addBraces)
13162 Boolean addBraces; // [HGM] braces: tells if we should add {}
13167 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13168 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13171 while (*text == '\n') text++;
13172 len = strlen(text);
13173 while (len > 0 && text[len - 1] == '\n') len--;
13175 if (len == 0) return;
13177 if (commentList[index] != NULL) {
13178 old = commentList[index];
13179 oldlen = strlen(old);
13180 while(commentList[index][oldlen-1] == '\n')
13181 commentList[index][--oldlen] = NULLCHAR;
13182 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13183 strcpy(commentList[index], old);
13185 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13186 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13187 if(addBraces) addBraces = FALSE; else { text++; len--; }
13188 while (*text == '\n') { text++; len--; }
13189 commentList[index][--oldlen] = NULLCHAR;
13191 if(addBraces) strcat(commentList[index], "\n{\n");
13192 else strcat(commentList[index], "\n");
13193 strcat(commentList[index], text);
13194 if(addBraces) strcat(commentList[index], "\n}\n");
13195 else strcat(commentList[index], "\n");
13197 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13199 strcpy(commentList[index], "{\n");
13200 else commentList[index][0] = NULLCHAR;
13201 strcat(commentList[index], text);
13202 strcat(commentList[index], "\n");
13203 if(addBraces) strcat(commentList[index], "}\n");
13207 static char * FindStr( char * text, char * sub_text )
13209 char * result = strstr( text, sub_text );
13211 if( result != NULL ) {
13212 result += strlen( sub_text );
13218 /* [AS] Try to extract PV info from PGN comment */
13219 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13220 char *GetInfoFromComment( int index, char * text )
13224 if( text != NULL && index > 0 ) {
13227 int time = -1, sec = 0, deci;
13228 char * s_eval = FindStr( text, "[%eval " );
13229 char * s_emt = FindStr( text, "[%emt " );
13231 if( s_eval != NULL || s_emt != NULL ) {
13235 if( s_eval != NULL ) {
13236 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13240 if( delim != ']' ) {
13245 if( s_emt != NULL ) {
13250 /* We expect something like: [+|-]nnn.nn/dd */
13253 if(*text != '{') return text; // [HGM] braces: must be normal comment
13255 sep = strchr( text, '/' );
13256 if( sep == NULL || sep < (text+4) ) {
13260 time = -1; sec = -1; deci = -1;
13261 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13262 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13263 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13264 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13268 if( score_lo < 0 || score_lo >= 100 ) {
13272 if(sec >= 0) time = 600*time + 10*sec; else
13273 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13275 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13277 /* [HGM] PV time: now locate end of PV info */
13278 while( *++sep >= '0' && *sep <= '9'); // strip depth
13280 while( *++sep >= '0' && *sep <= '9'); // strip time
13282 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13284 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13285 while(*sep == ' ') sep++;
13296 pvInfoList[index-1].depth = depth;
13297 pvInfoList[index-1].score = score;
13298 pvInfoList[index-1].time = 10*time; // centi-sec
13299 if(*sep == '}') *sep = 0; else *--sep = '{';
13305 SendToProgram(message, cps)
13307 ChessProgramState *cps;
13309 int count, outCount, error;
13312 if (cps->pr == NULL) return;
13315 if (appData.debugMode) {
13318 fprintf(debugFP, "%ld >%-6s: %s",
13319 SubtractTimeMarks(&now, &programStartTime),
13320 cps->which, message);
13323 count = strlen(message);
13324 outCount = OutputToProcess(cps->pr, message, count, &error);
13325 if (outCount < count && !exiting
13326 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13327 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13328 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13329 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13330 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13331 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13333 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13335 gameInfo.resultDetails = StrSave(buf);
13337 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13342 ReceiveFromProgram(isr, closure, message, count, error)
13343 InputSourceRef isr;
13351 ChessProgramState *cps = (ChessProgramState *)closure;
13353 if (isr != cps->isr) return; /* Killed intentionally */
13357 _("Error: %s chess program (%s) exited unexpectedly"),
13358 cps->which, cps->program);
13359 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13360 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13361 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13362 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13364 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13366 gameInfo.resultDetails = StrSave(buf);
13368 RemoveInputSource(cps->isr);
13369 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13372 _("Error reading from %s chess program (%s)"),
13373 cps->which, cps->program);
13374 RemoveInputSource(cps->isr);
13376 /* [AS] Program is misbehaving badly... kill it */
13377 if( count == -2 ) {
13378 DestroyChildProcess( cps->pr, 9 );
13382 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13387 if ((end_str = strchr(message, '\r')) != NULL)
13388 *end_str = NULLCHAR;
13389 if ((end_str = strchr(message, '\n')) != NULL)
13390 *end_str = NULLCHAR;
13392 if (appData.debugMode) {
13393 TimeMark now; int print = 1;
13394 char *quote = ""; char c; int i;
13396 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13397 char start = message[0];
13398 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13399 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13400 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13401 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13402 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13403 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13404 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13405 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13406 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13407 print = (appData.engineComments >= 2);
13409 message[0] = start; // restore original message
13413 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13414 SubtractTimeMarks(&now, &programStartTime), cps->which,
13420 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13421 if (appData.icsEngineAnalyze) {
13422 if (strstr(message, "whisper") != NULL ||
13423 strstr(message, "kibitz") != NULL ||
13424 strstr(message, "tellics") != NULL) return;
13427 HandleMachineMove(message, cps);
13432 SendTimeControl(cps, mps, tc, inc, sd, st)
13433 ChessProgramState *cps;
13434 int mps, inc, sd, st;
13440 if( timeControl_2 > 0 ) {
13441 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13442 tc = timeControl_2;
13445 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13446 inc /= cps->timeOdds;
13447 st /= cps->timeOdds;
13449 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13452 /* Set exact time per move, normally using st command */
13453 if (cps->stKludge) {
13454 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13456 if (seconds == 0) {
13457 sprintf(buf, "level 1 %d\n", st/60);
13459 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13462 sprintf(buf, "st %d\n", st);
13465 /* Set conventional or incremental time control, using level command */
13466 if (seconds == 0) {
13467 /* Note old gnuchess bug -- minutes:seconds used to not work.
13468 Fixed in later versions, but still avoid :seconds
13469 when seconds is 0. */
13470 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13472 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13473 seconds, inc/1000);
13476 SendToProgram(buf, cps);
13478 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13479 /* Orthogonally, limit search to given depth */
13481 if (cps->sdKludge) {
13482 sprintf(buf, "depth\n%d\n", sd);
13484 sprintf(buf, "sd %d\n", sd);
13486 SendToProgram(buf, cps);
13489 if(cps->nps > 0) { /* [HGM] nps */
13490 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13492 sprintf(buf, "nps %d\n", cps->nps);
13493 SendToProgram(buf, cps);
13498 ChessProgramState *WhitePlayer()
13499 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13501 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13502 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13508 SendTimeRemaining(cps, machineWhite)
13509 ChessProgramState *cps;
13510 int /*boolean*/ machineWhite;
13512 char message[MSG_SIZ];
13515 /* Note: this routine must be called when the clocks are stopped
13516 or when they have *just* been set or switched; otherwise
13517 it will be off by the time since the current tick started.
13519 if (machineWhite) {
13520 time = whiteTimeRemaining / 10;
13521 otime = blackTimeRemaining / 10;
13523 time = blackTimeRemaining / 10;
13524 otime = whiteTimeRemaining / 10;
13526 /* [HGM] translate opponent's time by time-odds factor */
13527 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13528 if (appData.debugMode) {
13529 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13532 if (time <= 0) time = 1;
13533 if (otime <= 0) otime = 1;
13535 sprintf(message, "time %ld\n", time);
13536 SendToProgram(message, cps);
13538 sprintf(message, "otim %ld\n", otime);
13539 SendToProgram(message, cps);
13543 BoolFeature(p, name, loc, cps)
13547 ChessProgramState *cps;
13550 int len = strlen(name);
13552 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13554 sscanf(*p, "%d", &val);
13556 while (**p && **p != ' ') (*p)++;
13557 sprintf(buf, "accepted %s\n", name);
13558 SendToProgram(buf, cps);
13565 IntFeature(p, name, loc, cps)
13569 ChessProgramState *cps;
13572 int len = strlen(name);
13573 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13575 sscanf(*p, "%d", loc);
13576 while (**p && **p != ' ') (*p)++;
13577 sprintf(buf, "accepted %s\n", name);
13578 SendToProgram(buf, cps);
13585 StringFeature(p, name, loc, cps)
13589 ChessProgramState *cps;
13592 int len = strlen(name);
13593 if (strncmp((*p), name, len) == 0
13594 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13596 sscanf(*p, "%[^\"]", loc);
13597 while (**p && **p != '\"') (*p)++;
13598 if (**p == '\"') (*p)++;
13599 sprintf(buf, "accepted %s\n", name);
13600 SendToProgram(buf, cps);
13607 ParseOption(Option *opt, ChessProgramState *cps)
13608 // [HGM] options: process the string that defines an engine option, and determine
13609 // name, type, default value, and allowed value range
13611 char *p, *q, buf[MSG_SIZ];
13612 int n, min = (-1)<<31, max = 1<<31, def;
13614 if(p = strstr(opt->name, " -spin ")) {
13615 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13616 if(max < min) max = min; // enforce consistency
13617 if(def < min) def = min;
13618 if(def > max) def = max;
13623 } else if((p = strstr(opt->name, " -slider "))) {
13624 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13625 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13626 if(max < min) max = min; // enforce consistency
13627 if(def < min) def = min;
13628 if(def > max) def = max;
13632 opt->type = Spin; // Slider;
13633 } else if((p = strstr(opt->name, " -string "))) {
13634 opt->textValue = p+9;
13635 opt->type = TextBox;
13636 } else if((p = strstr(opt->name, " -file "))) {
13637 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13638 opt->textValue = p+7;
13639 opt->type = TextBox; // FileName;
13640 } else if((p = strstr(opt->name, " -path "))) {
13641 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13642 opt->textValue = p+7;
13643 opt->type = TextBox; // PathName;
13644 } else if(p = strstr(opt->name, " -check ")) {
13645 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13646 opt->value = (def != 0);
13647 opt->type = CheckBox;
13648 } else if(p = strstr(opt->name, " -combo ")) {
13649 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13650 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13651 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13652 opt->value = n = 0;
13653 while(q = StrStr(q, " /// ")) {
13654 n++; *q = 0; // count choices, and null-terminate each of them
13656 if(*q == '*') { // remember default, which is marked with * prefix
13660 cps->comboList[cps->comboCnt++] = q;
13662 cps->comboList[cps->comboCnt++] = NULL;
13664 opt->type = ComboBox;
13665 } else if(p = strstr(opt->name, " -button")) {
13666 opt->type = Button;
13667 } else if(p = strstr(opt->name, " -save")) {
13668 opt->type = SaveButton;
13669 } else return FALSE;
13670 *p = 0; // terminate option name
13671 // now look if the command-line options define a setting for this engine option.
13672 if(cps->optionSettings && cps->optionSettings[0])
13673 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13674 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13675 sprintf(buf, "option %s", p);
13676 if(p = strstr(buf, ",")) *p = 0;
13678 SendToProgram(buf, cps);
13684 FeatureDone(cps, val)
13685 ChessProgramState* cps;
13688 DelayedEventCallback cb = GetDelayedEvent();
13689 if ((cb == InitBackEnd3 && cps == &first) ||
13690 (cb == TwoMachinesEventIfReady && cps == &second)) {
13691 CancelDelayedEvent();
13692 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13694 cps->initDone = val;
13697 /* Parse feature command from engine */
13699 ParseFeatures(args, cps)
13701 ChessProgramState *cps;
13709 while (*p == ' ') p++;
13710 if (*p == NULLCHAR) return;
13712 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13713 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13714 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13715 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13716 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13717 if (BoolFeature(&p, "reuse", &val, cps)) {
13718 /* Engine can disable reuse, but can't enable it if user said no */
13719 if (!val) cps->reuse = FALSE;
13722 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13723 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13724 if (gameMode == TwoMachinesPlay) {
13725 DisplayTwoMachinesTitle();
13731 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13732 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13733 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13734 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13735 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13736 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13737 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13738 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13739 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13740 if (IntFeature(&p, "done", &val, cps)) {
13741 FeatureDone(cps, val);
13744 /* Added by Tord: */
13745 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13746 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13747 /* End of additions by Tord */
13749 /* [HGM] added features: */
13750 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13751 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13752 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13753 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13754 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13755 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13756 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13757 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13758 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13759 SendToProgram(buf, cps);
13762 if(cps->nrOptions >= MAX_OPTIONS) {
13764 sprintf(buf, "%s engine has too many options\n", cps->which);
13765 DisplayError(buf, 0);
13769 /* End of additions by HGM */
13771 /* unknown feature: complain and skip */
13773 while (*q && *q != '=') q++;
13774 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13775 SendToProgram(buf, cps);
13781 while (*p && *p != '\"') p++;
13782 if (*p == '\"') p++;
13784 while (*p && *p != ' ') p++;
13792 PeriodicUpdatesEvent(newState)
13795 if (newState == appData.periodicUpdates)
13798 appData.periodicUpdates=newState;
13800 /* Display type changes, so update it now */
13801 // DisplayAnalysis();
13803 /* Get the ball rolling again... */
13805 AnalysisPeriodicEvent(1);
13806 StartAnalysisClock();
13811 PonderNextMoveEvent(newState)
13814 if (newState == appData.ponderNextMove) return;
13815 if (gameMode == EditPosition) EditPositionDone(TRUE);
13817 SendToProgram("hard\n", &first);
13818 if (gameMode == TwoMachinesPlay) {
13819 SendToProgram("hard\n", &second);
13822 SendToProgram("easy\n", &first);
13823 thinkOutput[0] = NULLCHAR;
13824 if (gameMode == TwoMachinesPlay) {
13825 SendToProgram("easy\n", &second);
13828 appData.ponderNextMove = newState;
13832 NewSettingEvent(option, feature, command, value)
13834 int option, value, *feature;
13838 if (gameMode == EditPosition) EditPositionDone(TRUE);
13839 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13840 if(feature == NULL || *feature) SendToProgram(buf, &first);
13841 if (gameMode == TwoMachinesPlay) {
13842 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13847 ShowThinkingEvent()
13848 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13850 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13851 int newState = appData.showThinking
13852 // [HGM] thinking: other features now need thinking output as well
13853 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13855 if (oldState == newState) return;
13856 oldState = newState;
13857 if (gameMode == EditPosition) EditPositionDone(TRUE);
13859 SendToProgram("post\n", &first);
13860 if (gameMode == TwoMachinesPlay) {
13861 SendToProgram("post\n", &second);
13864 SendToProgram("nopost\n", &first);
13865 thinkOutput[0] = NULLCHAR;
13866 if (gameMode == TwoMachinesPlay) {
13867 SendToProgram("nopost\n", &second);
13870 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13874 AskQuestionEvent(title, question, replyPrefix, which)
13875 char *title; char *question; char *replyPrefix; char *which;
13877 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13878 if (pr == NoProc) return;
13879 AskQuestion(title, question, replyPrefix, pr);
13883 DisplayMove(moveNumber)
13886 char message[MSG_SIZ];
13888 char cpThinkOutput[MSG_SIZ];
13890 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13892 if (moveNumber == forwardMostMove - 1 ||
13893 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13895 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13897 if (strchr(cpThinkOutput, '\n')) {
13898 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13901 *cpThinkOutput = NULLCHAR;
13904 /* [AS] Hide thinking from human user */
13905 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13906 *cpThinkOutput = NULLCHAR;
13907 if( thinkOutput[0] != NULLCHAR ) {
13910 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13911 cpThinkOutput[i] = '.';
13913 cpThinkOutput[i] = NULLCHAR;
13914 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13918 if (moveNumber == forwardMostMove - 1 &&
13919 gameInfo.resultDetails != NULL) {
13920 if (gameInfo.resultDetails[0] == NULLCHAR) {
13921 sprintf(res, " %s", PGNResult(gameInfo.result));
13923 sprintf(res, " {%s} %s",
13924 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
13930 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13931 DisplayMessage(res, cpThinkOutput);
13933 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13934 WhiteOnMove(moveNumber) ? " " : ".. ",
13935 parseList[moveNumber], res);
13936 DisplayMessage(message, cpThinkOutput);
13941 DisplayComment(moveNumber, text)
13945 char title[MSG_SIZ];
13946 char buf[8000]; // comment can be long!
13949 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13950 strcpy(title, "Comment");
13952 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13953 WhiteOnMove(moveNumber) ? " " : ".. ",
13954 parseList[moveNumber]);
13956 // [HGM] PV info: display PV info together with (or as) comment
13957 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13958 if(text == NULL) text = "";
13959 score = pvInfoList[moveNumber].score;
13960 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13961 depth, (pvInfoList[moveNumber].time+50)/100, text);
13964 if (text != NULL && (appData.autoDisplayComment || commentUp))
13965 CommentPopUp(title, text);
13968 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13969 * might be busy thinking or pondering. It can be omitted if your
13970 * gnuchess is configured to stop thinking immediately on any user
13971 * input. However, that gnuchess feature depends on the FIONREAD
13972 * ioctl, which does not work properly on some flavors of Unix.
13976 ChessProgramState *cps;
13979 if (!cps->useSigint) return;
13980 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13981 switch (gameMode) {
13982 case MachinePlaysWhite:
13983 case MachinePlaysBlack:
13984 case TwoMachinesPlay:
13985 case IcsPlayingWhite:
13986 case IcsPlayingBlack:
13989 /* Skip if we know it isn't thinking */
13990 if (!cps->maybeThinking) return;
13991 if (appData.debugMode)
13992 fprintf(debugFP, "Interrupting %s\n", cps->which);
13993 InterruptChildProcess(cps->pr);
13994 cps->maybeThinking = FALSE;
13999 #endif /*ATTENTION*/
14005 if (whiteTimeRemaining <= 0) {
14008 if (appData.icsActive) {
14009 if (appData.autoCallFlag &&
14010 gameMode == IcsPlayingBlack && !blackFlag) {
14011 SendToICS(ics_prefix);
14012 SendToICS("flag\n");
14016 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14018 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14019 if (appData.autoCallFlag) {
14020 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14027 if (blackTimeRemaining <= 0) {
14030 if (appData.icsActive) {
14031 if (appData.autoCallFlag &&
14032 gameMode == IcsPlayingWhite && !whiteFlag) {
14033 SendToICS(ics_prefix);
14034 SendToICS("flag\n");
14038 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14040 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14041 if (appData.autoCallFlag) {
14042 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14055 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14056 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14059 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14061 if ( !WhiteOnMove(forwardMostMove) )
14062 /* White made time control */
14063 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14064 /* [HGM] time odds: correct new time quota for time odds! */
14065 / WhitePlayer()->timeOdds;
14067 /* Black made time control */
14068 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14069 / WhitePlayer()->other->timeOdds;
14073 DisplayBothClocks()
14075 int wom = gameMode == EditPosition ?
14076 !blackPlaysFirst : WhiteOnMove(currentMove);
14077 DisplayWhiteClock(whiteTimeRemaining, wom);
14078 DisplayBlackClock(blackTimeRemaining, !wom);
14082 /* Timekeeping seems to be a portability nightmare. I think everyone
14083 has ftime(), but I'm really not sure, so I'm including some ifdefs
14084 to use other calls if you don't. Clocks will be less accurate if
14085 you have neither ftime nor gettimeofday.
14088 /* VS 2008 requires the #include outside of the function */
14089 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14090 #include <sys/timeb.h>
14093 /* Get the current time as a TimeMark */
14098 #if HAVE_GETTIMEOFDAY
14100 struct timeval timeVal;
14101 struct timezone timeZone;
14103 gettimeofday(&timeVal, &timeZone);
14104 tm->sec = (long) timeVal.tv_sec;
14105 tm->ms = (int) (timeVal.tv_usec / 1000L);
14107 #else /*!HAVE_GETTIMEOFDAY*/
14110 // include <sys/timeb.h> / moved to just above start of function
14111 struct timeb timeB;
14114 tm->sec = (long) timeB.time;
14115 tm->ms = (int) timeB.millitm;
14117 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14118 tm->sec = (long) time(NULL);
14124 /* Return the difference in milliseconds between two
14125 time marks. We assume the difference will fit in a long!
14128 SubtractTimeMarks(tm2, tm1)
14129 TimeMark *tm2, *tm1;
14131 return 1000L*(tm2->sec - tm1->sec) +
14132 (long) (tm2->ms - tm1->ms);
14137 * Code to manage the game clocks.
14139 * In tournament play, black starts the clock and then white makes a move.
14140 * We give the human user a slight advantage if he is playing white---the
14141 * clocks don't run until he makes his first move, so it takes zero time.
14142 * Also, we don't account for network lag, so we could get out of sync
14143 * with GNU Chess's clock -- but then, referees are always right.
14146 static TimeMark tickStartTM;
14147 static long intendedTickLength;
14150 NextTickLength(timeRemaining)
14151 long timeRemaining;
14153 long nominalTickLength, nextTickLength;
14155 if (timeRemaining > 0L && timeRemaining <= 10000L)
14156 nominalTickLength = 100L;
14158 nominalTickLength = 1000L;
14159 nextTickLength = timeRemaining % nominalTickLength;
14160 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14162 return nextTickLength;
14165 /* Adjust clock one minute up or down */
14167 AdjustClock(Boolean which, int dir)
14169 if(which) blackTimeRemaining += 60000*dir;
14170 else whiteTimeRemaining += 60000*dir;
14171 DisplayBothClocks();
14174 /* Stop clocks and reset to a fresh time control */
14178 (void) StopClockTimer();
14179 if (appData.icsActive) {
14180 whiteTimeRemaining = blackTimeRemaining = 0;
14181 } else if (searchTime) {
14182 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14183 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14184 } else { /* [HGM] correct new time quote for time odds */
14185 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14186 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14188 if (whiteFlag || blackFlag) {
14190 whiteFlag = blackFlag = FALSE;
14192 DisplayBothClocks();
14195 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14197 /* Decrement running clock by amount of time that has passed */
14201 long timeRemaining;
14202 long lastTickLength, fudge;
14205 if (!appData.clockMode) return;
14206 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14210 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14212 /* Fudge if we woke up a little too soon */
14213 fudge = intendedTickLength - lastTickLength;
14214 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14216 if (WhiteOnMove(forwardMostMove)) {
14217 if(whiteNPS >= 0) lastTickLength = 0;
14218 timeRemaining = whiteTimeRemaining -= lastTickLength;
14219 DisplayWhiteClock(whiteTimeRemaining - fudge,
14220 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14222 if(blackNPS >= 0) lastTickLength = 0;
14223 timeRemaining = blackTimeRemaining -= lastTickLength;
14224 DisplayBlackClock(blackTimeRemaining - fudge,
14225 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14228 if (CheckFlags()) return;
14231 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14232 StartClockTimer(intendedTickLength);
14234 /* if the time remaining has fallen below the alarm threshold, sound the
14235 * alarm. if the alarm has sounded and (due to a takeback or time control
14236 * with increment) the time remaining has increased to a level above the
14237 * threshold, reset the alarm so it can sound again.
14240 if (appData.icsActive && appData.icsAlarm) {
14242 /* make sure we are dealing with the user's clock */
14243 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14244 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14247 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14248 alarmSounded = FALSE;
14249 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14251 alarmSounded = TRUE;
14257 /* A player has just moved, so stop the previously running
14258 clock and (if in clock mode) start the other one.
14259 We redisplay both clocks in case we're in ICS mode, because
14260 ICS gives us an update to both clocks after every move.
14261 Note that this routine is called *after* forwardMostMove
14262 is updated, so the last fractional tick must be subtracted
14263 from the color that is *not* on move now.
14266 SwitchClocks(int newMoveNr)
14268 long lastTickLength;
14270 int flagged = FALSE;
14274 if (StopClockTimer() && appData.clockMode) {
14275 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14276 if (!WhiteOnMove(forwardMostMove)) {
14277 if(blackNPS >= 0) lastTickLength = 0;
14278 blackTimeRemaining -= lastTickLength;
14279 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14280 // if(pvInfoList[forwardMostMove-1].time == -1)
14281 pvInfoList[forwardMostMove-1].time = // use GUI time
14282 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14284 if(whiteNPS >= 0) lastTickLength = 0;
14285 whiteTimeRemaining -= lastTickLength;
14286 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14287 // if(pvInfoList[forwardMostMove-1].time == -1)
14288 pvInfoList[forwardMostMove-1].time =
14289 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14291 flagged = CheckFlags();
14293 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14294 CheckTimeControl();
14296 if (flagged || !appData.clockMode) return;
14298 switch (gameMode) {
14299 case MachinePlaysBlack:
14300 case MachinePlaysWhite:
14301 case BeginningOfGame:
14302 if (pausing) return;
14306 case PlayFromGameFile:
14314 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14315 if(WhiteOnMove(forwardMostMove))
14316 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14317 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14321 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14322 whiteTimeRemaining : blackTimeRemaining);
14323 StartClockTimer(intendedTickLength);
14327 /* Stop both clocks */
14331 long lastTickLength;
14334 if (!StopClockTimer()) return;
14335 if (!appData.clockMode) return;
14339 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14340 if (WhiteOnMove(forwardMostMove)) {
14341 if(whiteNPS >= 0) lastTickLength = 0;
14342 whiteTimeRemaining -= lastTickLength;
14343 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14345 if(blackNPS >= 0) lastTickLength = 0;
14346 blackTimeRemaining -= lastTickLength;
14347 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14352 /* Start clock of player on move. Time may have been reset, so
14353 if clock is already running, stop and restart it. */
14357 (void) StopClockTimer(); /* in case it was running already */
14358 DisplayBothClocks();
14359 if (CheckFlags()) return;
14361 if (!appData.clockMode) return;
14362 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14364 GetTimeMark(&tickStartTM);
14365 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14366 whiteTimeRemaining : blackTimeRemaining);
14368 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14369 whiteNPS = blackNPS = -1;
14370 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14371 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14372 whiteNPS = first.nps;
14373 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14374 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14375 blackNPS = first.nps;
14376 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14377 whiteNPS = second.nps;
14378 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14379 blackNPS = second.nps;
14380 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14382 StartClockTimer(intendedTickLength);
14389 long second, minute, hour, day;
14391 static char buf[32];
14393 if (ms > 0 && ms <= 9900) {
14394 /* convert milliseconds to tenths, rounding up */
14395 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14397 sprintf(buf, " %03.1f ", tenths/10.0);
14401 /* convert milliseconds to seconds, rounding up */
14402 /* use floating point to avoid strangeness of integer division
14403 with negative dividends on many machines */
14404 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14411 day = second / (60 * 60 * 24);
14412 second = second % (60 * 60 * 24);
14413 hour = second / (60 * 60);
14414 second = second % (60 * 60);
14415 minute = second / 60;
14416 second = second % 60;
14419 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14420 sign, day, hour, minute, second);
14422 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14424 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14431 * This is necessary because some C libraries aren't ANSI C compliant yet.
14434 StrStr(string, match)
14435 char *string, *match;
14439 length = strlen(match);
14441 for (i = strlen(string) - length; i >= 0; i--, string++)
14442 if (!strncmp(match, string, length))
14449 StrCaseStr(string, match)
14450 char *string, *match;
14454 length = strlen(match);
14456 for (i = strlen(string) - length; i >= 0; i--, string++) {
14457 for (j = 0; j < length; j++) {
14458 if (ToLower(match[j]) != ToLower(string[j]))
14461 if (j == length) return string;
14475 c1 = ToLower(*s1++);
14476 c2 = ToLower(*s2++);
14477 if (c1 > c2) return 1;
14478 if (c1 < c2) return -1;
14479 if (c1 == NULLCHAR) return 0;
14488 return isupper(c) ? tolower(c) : c;
14496 return islower(c) ? toupper(c) : c;
14498 #endif /* !_amigados */
14506 if ((ret = (char *) malloc(strlen(s) + 1))) {
14513 StrSavePtr(s, savePtr)
14514 char *s, **savePtr;
14519 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14520 strcpy(*savePtr, s);
14532 clock = time((time_t *)NULL);
14533 tm = localtime(&clock);
14534 sprintf(buf, "%04d.%02d.%02d",
14535 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14536 return StrSave(buf);
14541 PositionToFEN(move, overrideCastling)
14543 char *overrideCastling;
14545 int i, j, fromX, fromY, toX, toY;
14552 whiteToPlay = (gameMode == EditPosition) ?
14553 !blackPlaysFirst : (move % 2 == 0);
14556 /* Piece placement data */
14557 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14559 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14560 if (boards[move][i][j] == EmptySquare) {
14562 } else { ChessSquare piece = boards[move][i][j];
14563 if (emptycount > 0) {
14564 if(emptycount<10) /* [HGM] can be >= 10 */
14565 *p++ = '0' + emptycount;
14566 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14569 if(PieceToChar(piece) == '+') {
14570 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14572 piece = (ChessSquare)(DEMOTED piece);
14574 *p++ = PieceToChar(piece);
14576 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14577 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14582 if (emptycount > 0) {
14583 if(emptycount<10) /* [HGM] can be >= 10 */
14584 *p++ = '0' + emptycount;
14585 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14592 /* [HGM] print Crazyhouse or Shogi holdings */
14593 if( gameInfo.holdingsWidth ) {
14594 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14596 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14597 piece = boards[move][i][BOARD_WIDTH-1];
14598 if( piece != EmptySquare )
14599 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14600 *p++ = PieceToChar(piece);
14602 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14603 piece = boards[move][BOARD_HEIGHT-i-1][0];
14604 if( piece != EmptySquare )
14605 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14606 *p++ = PieceToChar(piece);
14609 if( q == p ) *p++ = '-';
14615 *p++ = whiteToPlay ? 'w' : 'b';
14618 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14619 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14621 if(nrCastlingRights) {
14623 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14624 /* [HGM] write directly from rights */
14625 if(boards[move][CASTLING][2] != NoRights &&
14626 boards[move][CASTLING][0] != NoRights )
14627 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14628 if(boards[move][CASTLING][2] != NoRights &&
14629 boards[move][CASTLING][1] != NoRights )
14630 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14631 if(boards[move][CASTLING][5] != NoRights &&
14632 boards[move][CASTLING][3] != NoRights )
14633 *p++ = boards[move][CASTLING][3] + AAA;
14634 if(boards[move][CASTLING][5] != NoRights &&
14635 boards[move][CASTLING][4] != NoRights )
14636 *p++ = boards[move][CASTLING][4] + AAA;
14639 /* [HGM] write true castling rights */
14640 if( nrCastlingRights == 6 ) {
14641 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14642 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14643 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14644 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14645 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14646 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14647 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14648 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14651 if (q == p) *p++ = '-'; /* No castling rights */
14655 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14656 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14657 /* En passant target square */
14658 if (move > backwardMostMove) {
14659 fromX = moveList[move - 1][0] - AAA;
14660 fromY = moveList[move - 1][1] - ONE;
14661 toX = moveList[move - 1][2] - AAA;
14662 toY = moveList[move - 1][3] - ONE;
14663 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14664 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14665 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14667 /* 2-square pawn move just happened */
14669 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14673 } else if(move == backwardMostMove) {
14674 // [HGM] perhaps we should always do it like this, and forget the above?
14675 if((signed char)boards[move][EP_STATUS] >= 0) {
14676 *p++ = boards[move][EP_STATUS] + AAA;
14677 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14688 /* [HGM] find reversible plies */
14689 { int i = 0, j=move;
14691 if (appData.debugMode) { int k;
14692 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14693 for(k=backwardMostMove; k<=forwardMostMove; k++)
14694 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14698 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14699 if( j == backwardMostMove ) i += initialRulePlies;
14700 sprintf(p, "%d ", i);
14701 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14703 /* Fullmove number */
14704 sprintf(p, "%d", (move / 2) + 1);
14706 return StrSave(buf);
14710 ParseFEN(board, blackPlaysFirst, fen)
14712 int *blackPlaysFirst;
14722 /* [HGM] by default clear Crazyhouse holdings, if present */
14723 if(gameInfo.holdingsWidth) {
14724 for(i=0; i<BOARD_HEIGHT; i++) {
14725 board[i][0] = EmptySquare; /* black holdings */
14726 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14727 board[i][1] = (ChessSquare) 0; /* black counts */
14728 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14732 /* Piece placement data */
14733 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14736 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14737 if (*p == '/') p++;
14738 emptycount = gameInfo.boardWidth - j;
14739 while (emptycount--)
14740 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14742 #if(BOARD_FILES >= 10)
14743 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14744 p++; emptycount=10;
14745 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14746 while (emptycount--)
14747 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14749 } else if (isdigit(*p)) {
14750 emptycount = *p++ - '0';
14751 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14752 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14753 while (emptycount--)
14754 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14755 } else if (*p == '+' || isalpha(*p)) {
14756 if (j >= gameInfo.boardWidth) return FALSE;
14758 piece = CharToPiece(*++p);
14759 if(piece == EmptySquare) return FALSE; /* unknown piece */
14760 piece = (ChessSquare) (PROMOTED piece ); p++;
14761 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14762 } else piece = CharToPiece(*p++);
14764 if(piece==EmptySquare) return FALSE; /* unknown piece */
14765 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14766 piece = (ChessSquare) (PROMOTED piece);
14767 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14770 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14776 while (*p == '/' || *p == ' ') p++;
14778 /* [HGM] look for Crazyhouse holdings here */
14779 while(*p==' ') p++;
14780 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14782 if(*p == '-' ) *p++; /* empty holdings */ else {
14783 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14784 /* if we would allow FEN reading to set board size, we would */
14785 /* have to add holdings and shift the board read so far here */
14786 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14788 if((int) piece >= (int) BlackPawn ) {
14789 i = (int)piece - (int)BlackPawn;
14790 i = PieceToNumber((ChessSquare)i);
14791 if( i >= gameInfo.holdingsSize ) return FALSE;
14792 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14793 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14795 i = (int)piece - (int)WhitePawn;
14796 i = PieceToNumber((ChessSquare)i);
14797 if( i >= gameInfo.holdingsSize ) return FALSE;
14798 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14799 board[i][BOARD_WIDTH-2]++; /* black holdings */
14803 if(*p == ']') *p++;
14806 while(*p == ' ') p++;
14810 if(appData.colorNickNames) {
14811 if( c == appData.colorNickNames[0] ) c = 'w'; else
14812 if( c == appData.colorNickNames[1] ) c = 'b';
14816 *blackPlaysFirst = FALSE;
14819 *blackPlaysFirst = TRUE;
14825 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14826 /* return the extra info in global variiables */
14828 /* set defaults in case FEN is incomplete */
14829 board[EP_STATUS] = EP_UNKNOWN;
14830 for(i=0; i<nrCastlingRights; i++ ) {
14831 board[CASTLING][i] =
14832 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14833 } /* assume possible unless obviously impossible */
14834 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14835 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14836 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14837 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14838 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14839 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14840 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14841 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14844 while(*p==' ') p++;
14845 if(nrCastlingRights) {
14846 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14847 /* castling indicator present, so default becomes no castlings */
14848 for(i=0; i<nrCastlingRights; i++ ) {
14849 board[CASTLING][i] = NoRights;
14852 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14853 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14854 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14855 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14856 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14858 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14859 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14860 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14862 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14863 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14864 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14865 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14866 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14867 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14870 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14871 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14872 board[CASTLING][2] = whiteKingFile;
14875 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14876 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14877 board[CASTLING][2] = whiteKingFile;
14880 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14881 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14882 board[CASTLING][5] = blackKingFile;
14885 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14886 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14887 board[CASTLING][5] = blackKingFile;
14890 default: /* FRC castlings */
14891 if(c >= 'a') { /* black rights */
14892 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14893 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14894 if(i == BOARD_RGHT) break;
14895 board[CASTLING][5] = i;
14897 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14898 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14900 board[CASTLING][3] = c;
14902 board[CASTLING][4] = c;
14903 } else { /* white rights */
14904 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14905 if(board[0][i] == WhiteKing) break;
14906 if(i == BOARD_RGHT) break;
14907 board[CASTLING][2] = i;
14908 c -= AAA - 'a' + 'A';
14909 if(board[0][c] >= WhiteKing) break;
14911 board[CASTLING][0] = c;
14913 board[CASTLING][1] = c;
14917 for(i=0; i<nrCastlingRights; i++)
14918 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14919 if (appData.debugMode) {
14920 fprintf(debugFP, "FEN castling rights:");
14921 for(i=0; i<nrCastlingRights; i++)
14922 fprintf(debugFP, " %d", board[CASTLING][i]);
14923 fprintf(debugFP, "\n");
14926 while(*p==' ') p++;
14929 /* read e.p. field in games that know e.p. capture */
14930 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14931 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14933 p++; board[EP_STATUS] = EP_NONE;
14935 char c = *p++ - AAA;
14937 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14938 if(*p >= '0' && *p <='9') *p++;
14939 board[EP_STATUS] = c;
14944 if(sscanf(p, "%d", &i) == 1) {
14945 FENrulePlies = i; /* 50-move ply counter */
14946 /* (The move number is still ignored) */
14953 EditPositionPasteFEN(char *fen)
14956 Board initial_position;
14958 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14959 DisplayError(_("Bad FEN position in clipboard"), 0);
14962 int savedBlackPlaysFirst = blackPlaysFirst;
14963 EditPositionEvent();
14964 blackPlaysFirst = savedBlackPlaysFirst;
14965 CopyBoard(boards[0], initial_position);
14966 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14967 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14968 DisplayBothClocks();
14969 DrawPosition(FALSE, boards[currentMove]);
14974 static char cseq[12] = "\\ ";
14976 Boolean set_cont_sequence(char *new_seq)
14981 // handle bad attempts to set the sequence
14983 return 0; // acceptable error - no debug
14985 len = strlen(new_seq);
14986 ret = (len > 0) && (len < sizeof(cseq));
14988 strcpy(cseq, new_seq);
14989 else if (appData.debugMode)
14990 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14995 reformat a source message so words don't cross the width boundary. internal
14996 newlines are not removed. returns the wrapped size (no null character unless
14997 included in source message). If dest is NULL, only calculate the size required
14998 for the dest buffer. lp argument indicats line position upon entry, and it's
14999 passed back upon exit.
15001 int wrap(char *dest, char *src, int count, int width, int *lp)
15003 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15005 cseq_len = strlen(cseq);
15006 old_line = line = *lp;
15007 ansi = len = clen = 0;
15009 for (i=0; i < count; i++)
15011 if (src[i] == '\033')
15014 // if we hit the width, back up
15015 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15017 // store i & len in case the word is too long
15018 old_i = i, old_len = len;
15020 // find the end of the last word
15021 while (i && src[i] != ' ' && src[i] != '\n')
15027 // word too long? restore i & len before splitting it
15028 if ((old_i-i+clen) >= width)
15035 if (i && src[i-1] == ' ')
15038 if (src[i] != ' ' && src[i] != '\n')
15045 // now append the newline and continuation sequence
15050 strncpy(dest+len, cseq, cseq_len);
15058 dest[len] = src[i];
15062 if (src[i] == '\n')
15067 if (dest && appData.debugMode)
15069 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15070 count, width, line, len, *lp);
15071 show_bytes(debugFP, src, count);
15072 fprintf(debugFP, "\ndest: ");
15073 show_bytes(debugFP, dest, len);
15074 fprintf(debugFP, "\n");
15076 *lp = dest ? line : old_line;
15081 // [HGM] vari: routines for shelving variations
15084 PushTail(int firstMove, int lastMove)
15086 int i, j, nrMoves = lastMove - firstMove;
15088 if(appData.icsActive) { // only in local mode
15089 forwardMostMove = currentMove; // mimic old ICS behavior
15092 if(storedGames >= MAX_VARIATIONS-1) return;
15094 // push current tail of game on stack
15095 savedResult[storedGames] = gameInfo.result;
15096 savedDetails[storedGames] = gameInfo.resultDetails;
15097 gameInfo.resultDetails = NULL;
15098 savedFirst[storedGames] = firstMove;
15099 savedLast [storedGames] = lastMove;
15100 savedFramePtr[storedGames] = framePtr;
15101 framePtr -= nrMoves; // reserve space for the boards
15102 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15103 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15104 for(j=0; j<MOVE_LEN; j++)
15105 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15106 for(j=0; j<2*MOVE_LEN; j++)
15107 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15108 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15109 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15110 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15111 pvInfoList[firstMove+i-1].depth = 0;
15112 commentList[framePtr+i] = commentList[firstMove+i];
15113 commentList[firstMove+i] = NULL;
15117 forwardMostMove = firstMove; // truncate game so we can start variation
15118 if(storedGames == 1) GreyRevert(FALSE);
15122 PopTail(Boolean annotate)
15125 char buf[8000], moveBuf[20];
15127 if(appData.icsActive) return FALSE; // only in local mode
15128 if(!storedGames) return FALSE; // sanity
15129 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15132 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15133 nrMoves = savedLast[storedGames] - currentMove;
15136 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15137 else strcpy(buf, "(");
15138 for(i=currentMove; i<forwardMostMove; i++) {
15140 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15141 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15142 strcat(buf, moveBuf);
15143 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15144 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15148 for(i=1; i<=nrMoves; i++) { // copy last variation back
15149 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15150 for(j=0; j<MOVE_LEN; j++)
15151 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15152 for(j=0; j<2*MOVE_LEN; j++)
15153 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15154 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15155 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15156 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15157 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15158 commentList[currentMove+i] = commentList[framePtr+i];
15159 commentList[framePtr+i] = NULL;
15161 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15162 framePtr = savedFramePtr[storedGames];
15163 gameInfo.result = savedResult[storedGames];
15164 if(gameInfo.resultDetails != NULL) {
15165 free(gameInfo.resultDetails);
15167 gameInfo.resultDetails = savedDetails[storedGames];
15168 forwardMostMove = currentMove + nrMoves;
15169 if(storedGames == 0) GreyRevert(TRUE);
15175 { // remove all shelved variations
15177 for(i=0; i<storedGames; i++) {
15178 if(savedDetails[i])
15179 free(savedDetails[i]);
15180 savedDetails[i] = NULL;
15182 for(i=framePtr; i<MAX_MOVES; i++) {
15183 if(commentList[i]) free(commentList[i]);
15184 commentList[i] = NULL;
15186 framePtr = MAX_MOVES-1;
15191 LoadVariation(int index, char *text)
15192 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15193 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15194 int level = 0, move;
15196 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15197 // first find outermost bracketing variation
15198 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15199 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15200 if(*p == '{') wait = '}'; else
15201 if(*p == '[') wait = ']'; else
15202 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15203 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15205 if(*p == wait) wait = NULLCHAR; // closing ]} found
15208 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15209 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15210 end[1] = NULLCHAR; // clip off comment beyond variation
15211 ToNrEvent(currentMove-1);
15212 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15213 // kludge: use ParsePV() to append variation to game
15214 move = currentMove;
15215 ParsePV(start, TRUE);
15216 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15217 ClearPremoveHighlights();
15219 ToNrEvent(currentMove+1);