2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
243 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
253 /* States for ics_getting_history */
255 #define H_REQUESTED 1
256 #define H_GOT_REQ_HEADER 2
257 #define H_GOT_UNREQ_HEADER 3
258 #define H_GETTING_MOVES 4
259 #define H_GOT_UNWANTED_HEADER 5
261 /* whosays values for GameEnds */
270 /* Maximum number of games in a cmail message */
271 #define CMAIL_MAX_GAMES 20
273 /* Different types of move when calling RegisterMove */
275 #define CMAIL_RESIGN 1
277 #define CMAIL_ACCEPT 3
279 /* Different types of result to remember for each game */
280 #define CMAIL_NOT_RESULT 0
281 #define CMAIL_OLD_RESULT 1
282 #define CMAIL_NEW_RESULT 2
284 /* Telnet protocol constants */
295 static char * safeStrCpy( char * dst, const char * src, size_t count )
297 assert( dst != NULL );
298 assert( src != NULL );
301 strncpy( dst, src, count );
302 dst[ count-1 ] = '\0';
306 /* Some compiler can't cast u64 to double
307 * This function do the job for us:
309 * We use the highest bit for cast, this only
310 * works if the highest bit is not
311 * in use (This should not happen)
313 * We used this for all compiler
316 u64ToDouble(u64 value)
319 u64 tmp = value & u64Const(0x7fffffffffffffff);
320 r = (double)(s64)tmp;
321 if (value & u64Const(0x8000000000000000))
322 r += 9.2233720368547758080e18; /* 2^63 */
326 /* Fake up flags for now, as we aren't keeping track of castling
327 availability yet. [HGM] Change of logic: the flag now only
328 indicates the type of castlings allowed by the rule of the game.
329 The actual rights themselves are maintained in the array
330 castlingRights, as part of the game history, and are not probed
336 int flags = F_ALL_CASTLE_OK;
337 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
338 switch (gameInfo.variant) {
340 flags &= ~F_ALL_CASTLE_OK;
341 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
342 flags |= F_IGNORE_CHECK;
344 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
347 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
349 case VariantKriegspiel:
350 flags |= F_KRIEGSPIEL_CAPTURE;
352 case VariantCapaRandom:
353 case VariantFischeRandom:
354 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
355 case VariantNoCastle:
356 case VariantShatranj:
358 flags &= ~F_ALL_CASTLE_OK;
366 FILE *gameFileFP, *debugFP;
369 [AS] Note: sometimes, the sscanf() function is used to parse the input
370 into a fixed-size buffer. Because of this, we must be prepared to
371 receive strings as long as the size of the input buffer, which is currently
372 set to 4K for Windows and 8K for the rest.
373 So, we must either allocate sufficiently large buffers here, or
374 reduce the size of the input buffer in the input reading part.
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
379 char thinkOutput1[MSG_SIZ*10];
381 ChessProgramState first, second;
383 /* premove variables */
386 int premoveFromX = 0;
387 int premoveFromY = 0;
388 int premovePromoChar = 0;
390 Boolean alarmSounded;
391 /* end premove variables */
393 char *ics_prefix = "$";
394 int ics_type = ICS_GENERIC;
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
397 int pauseExamForwardMostMove = 0;
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
404 int whiteFlag = FALSE, blackFlag = FALSE;
405 int userOfferedDraw = FALSE;
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
408 int cmailMoveType[CMAIL_MAX_GAMES];
409 long ics_clock_paused = 0;
410 ProcRef icsPR = NoProc, cmailPR = NoProc;
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
412 GameMode gameMode = BeginningOfGame;
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
416 int hiddenThinkOutputState = 0; /* [AS] */
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
418 int adjudicateLossPlies = 6;
419 char white_holding[64], black_holding[64];
420 TimeMark lastNodeCountTime;
421 long lastNodeCount=0;
422 int have_sent_ICS_logon = 0;
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
425 long timeControl_2; /* [AS] Allow separate time controls */
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
427 long timeRemaining[2][MAX_MOVES];
429 TimeMark programStartTime;
430 char ics_handle[MSG_SIZ];
431 int have_set_title = 0;
433 /* animateTraining preserves the state of appData.animate
434 * when Training mode is activated. This allows the
435 * response to be animated when appData.animate == TRUE and
436 * appData.animateDragging == TRUE.
438 Boolean animateTraining;
444 Board boards[MAX_MOVES];
445 /* [HGM] Following 7 needed for accurate legality tests: */
446 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
447 signed char initialRights[BOARD_FILES];
448 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int initialRulePlies, FENrulePlies;
450 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
453 int mute; // mute all sounds
455 // [HGM] vari: next 12 to save and restore variations
456 #define MAX_VARIATIONS 10
457 int framePtr = MAX_MOVES-1; // points to free stack entry
459 int savedFirst[MAX_VARIATIONS];
460 int savedLast[MAX_VARIATIONS];
461 int savedFramePtr[MAX_VARIATIONS];
462 char *savedDetails[MAX_VARIATIONS];
463 ChessMove savedResult[MAX_VARIATIONS];
465 void PushTail P((int firstMove, int lastMove));
466 Boolean PopTail P((Boolean annotate));
467 void CleanupTail P((void));
469 ChessSquare FIDEArray[2][BOARD_FILES] = {
470 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
471 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
472 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
473 BlackKing, BlackBishop, BlackKnight, BlackRook }
476 ChessSquare twoKingsArray[2][BOARD_FILES] = {
477 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
479 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480 BlackKing, BlackKing, BlackKnight, BlackRook }
483 ChessSquare KnightmateArray[2][BOARD_FILES] = {
484 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
485 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
486 { BlackRook, BlackMan, BlackBishop, BlackQueen,
487 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
490 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
491 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
492 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
493 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
494 BlackKing, BlackBishop, BlackKnight, BlackRook }
497 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
498 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
499 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
500 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
501 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 #if (BOARD_FILES>=10)
506 ChessSquare ShogiArray[2][BOARD_FILES] = {
507 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
508 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
509 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
510 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
513 ChessSquare XiangqiArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
515 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
517 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
520 ChessSquare CapablancaArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
524 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
527 ChessSquare GreatArray[2][BOARD_FILES] = {
528 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
529 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
530 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
531 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
534 ChessSquare JanusArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
536 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
537 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
538 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
542 ChessSquare GothicArray[2][BOARD_FILES] = {
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
544 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
546 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
549 #define GothicArray CapablancaArray
553 ChessSquare FalconArray[2][BOARD_FILES] = {
554 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
555 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
556 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
557 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
560 #define FalconArray CapablancaArray
563 #else // !(BOARD_FILES>=10)
564 #define XiangqiPosition FIDEArray
565 #define CapablancaArray FIDEArray
566 #define GothicArray FIDEArray
567 #define GreatArray FIDEArray
568 #endif // !(BOARD_FILES>=10)
570 #if (BOARD_FILES>=12)
571 ChessSquare CourierArray[2][BOARD_FILES] = {
572 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
573 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
574 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
575 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 #else // !(BOARD_FILES>=12)
578 #define CourierArray CapablancaArray
579 #endif // !(BOARD_FILES>=12)
582 Board initialPosition;
585 /* Convert str to a rating. Checks for special cases of "----",
587 "++++", etc. Also strips ()'s */
589 string_to_rating(str)
592 while(*str && !isdigit(*str)) ++str;
594 return 0; /* One of the special "no rating" cases */
602 /* Init programStats */
603 programStats.movelist[0] = 0;
604 programStats.depth = 0;
605 programStats.nr_moves = 0;
606 programStats.moves_left = 0;
607 programStats.nodes = 0;
608 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
609 programStats.score = 0;
610 programStats.got_only_move = 0;
611 programStats.got_fail = 0;
612 programStats.line_is_book = 0;
618 int matched, min, sec;
620 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622 GetTimeMark(&programStartTime);
623 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
626 programStats.ok_to_send = 1;
627 programStats.seen_stat = 0;
630 * Initialize game list
636 * Internet chess server status
638 if (appData.icsActive) {
639 appData.matchMode = FALSE;
640 appData.matchGames = 0;
642 appData.noChessProgram = !appData.zippyPlay;
644 appData.zippyPlay = FALSE;
645 appData.zippyTalk = FALSE;
646 appData.noChessProgram = TRUE;
648 if (*appData.icsHelper != NULLCHAR) {
649 appData.useTelnet = TRUE;
650 appData.telnetProgram = appData.icsHelper;
653 appData.zippyTalk = appData.zippyPlay = FALSE;
656 /* [AS] Initialize pv info list [HGM] and game state */
660 for( i=0; i<=framePtr; i++ ) {
661 pvInfoList[i].depth = -1;
662 boards[i][EP_STATUS] = EP_NONE;
663 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
668 * Parse timeControl resource
670 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
671 appData.movesPerSession)) {
673 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
674 DisplayFatalError(buf, 0, 2);
678 * Parse searchTime resource
680 if (*appData.searchTime != NULLCHAR) {
681 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683 searchTime = min * 60;
684 } else if (matched == 2) {
685 searchTime = min * 60 + sec;
688 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
689 DisplayFatalError(buf, 0, 2);
693 /* [AS] Adjudication threshold */
694 adjudicateLossThreshold = appData.adjudicateLossThreshold;
696 first.which = "first";
697 second.which = "second";
698 first.maybeThinking = second.maybeThinking = FALSE;
699 first.pr = second.pr = NoProc;
700 first.isr = second.isr = NULL;
701 first.sendTime = second.sendTime = 2;
702 first.sendDrawOffers = 1;
703 if (appData.firstPlaysBlack) {
704 first.twoMachinesColor = "black\n";
705 second.twoMachinesColor = "white\n";
707 first.twoMachinesColor = "white\n";
708 second.twoMachinesColor = "black\n";
710 first.program = appData.firstChessProgram;
711 second.program = appData.secondChessProgram;
712 first.host = appData.firstHost;
713 second.host = appData.secondHost;
714 first.dir = appData.firstDirectory;
715 second.dir = appData.secondDirectory;
716 first.other = &second;
717 second.other = &first;
718 first.initString = appData.initString;
719 second.initString = appData.secondInitString;
720 first.computerString = appData.firstComputerString;
721 second.computerString = appData.secondComputerString;
722 first.useSigint = second.useSigint = TRUE;
723 first.useSigterm = second.useSigterm = TRUE;
724 first.reuse = appData.reuseFirst;
725 second.reuse = appData.reuseSecond;
726 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
727 second.nps = appData.secondNPS;
728 first.useSetboard = second.useSetboard = FALSE;
729 first.useSAN = second.useSAN = FALSE;
730 first.usePing = second.usePing = FALSE;
731 first.lastPing = second.lastPing = 0;
732 first.lastPong = second.lastPong = 0;
733 first.usePlayother = second.usePlayother = FALSE;
734 first.useColors = second.useColors = TRUE;
735 first.useUsermove = second.useUsermove = FALSE;
736 first.sendICS = second.sendICS = FALSE;
737 first.sendName = second.sendName = appData.icsActive;
738 first.sdKludge = second.sdKludge = FALSE;
739 first.stKludge = second.stKludge = FALSE;
740 TidyProgramName(first.program, first.host, first.tidy);
741 TidyProgramName(second.program, second.host, second.tidy);
742 first.matchWins = second.matchWins = 0;
743 strcpy(first.variants, appData.variant);
744 strcpy(second.variants, appData.variant);
745 first.analysisSupport = second.analysisSupport = 2; /* detect */
746 first.analyzing = second.analyzing = FALSE;
747 first.initDone = second.initDone = FALSE;
749 /* New features added by Tord: */
750 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
751 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
752 /* End of new features added by Tord. */
753 first.fenOverride = appData.fenOverride1;
754 second.fenOverride = appData.fenOverride2;
756 /* [HGM] time odds: set factor for each machine */
757 first.timeOdds = appData.firstTimeOdds;
758 second.timeOdds = appData.secondTimeOdds;
760 if(appData.timeOddsMode) {
761 norm = first.timeOdds;
762 if(norm > second.timeOdds) norm = second.timeOdds;
764 first.timeOdds /= norm;
765 second.timeOdds /= norm;
768 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
769 first.accumulateTC = appData.firstAccumulateTC;
770 second.accumulateTC = appData.secondAccumulateTC;
771 first.maxNrOfSessions = second.maxNrOfSessions = 1;
774 first.debug = second.debug = FALSE;
775 first.supportsNPS = second.supportsNPS = UNKNOWN;
778 first.optionSettings = appData.firstOptions;
779 second.optionSettings = appData.secondOptions;
781 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
782 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
783 first.isUCI = appData.firstIsUCI; /* [AS] */
784 second.isUCI = appData.secondIsUCI; /* [AS] */
785 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
786 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788 if (appData.firstProtocolVersion > PROTOVER ||
789 appData.firstProtocolVersion < 1) {
791 sprintf(buf, _("protocol version %d not supported"),
792 appData.firstProtocolVersion);
793 DisplayFatalError(buf, 0, 2);
795 first.protocolVersion = appData.firstProtocolVersion;
798 if (appData.secondProtocolVersion > PROTOVER ||
799 appData.secondProtocolVersion < 1) {
801 sprintf(buf, _("protocol version %d not supported"),
802 appData.secondProtocolVersion);
803 DisplayFatalError(buf, 0, 2);
805 second.protocolVersion = appData.secondProtocolVersion;
808 if (appData.icsActive) {
809 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
810 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
811 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
812 appData.clockMode = FALSE;
813 first.sendTime = second.sendTime = 0;
817 /* Override some settings from environment variables, for backward
818 compatibility. Unfortunately it's not feasible to have the env
819 vars just set defaults, at least in xboard. Ugh.
821 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
826 if (appData.noChessProgram) {
827 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828 sprintf(programVersion, "%s", PACKAGE_STRING);
830 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
831 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
832 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
835 if (!appData.icsActive) {
837 /* Check for variants that are supported only in ICS mode,
838 or not at all. Some that are accepted here nevertheless
839 have bugs; see comments below.
841 VariantClass variant = StringToVariant(appData.variant);
843 case VariantBughouse: /* need four players and two boards */
844 case VariantKriegspiel: /* need to hide pieces and move details */
845 /* case VariantFischeRandom: (Fabien: moved below) */
846 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
847 DisplayFatalError(buf, 0, 2);
851 case VariantLoadable:
861 sprintf(buf, _("Unknown variant name %s"), appData.variant);
862 DisplayFatalError(buf, 0, 2);
865 case VariantXiangqi: /* [HGM] repetition rules not implemented */
866 case VariantFairy: /* [HGM] TestLegality definitely off! */
867 case VariantGothic: /* [HGM] should work */
868 case VariantCapablanca: /* [HGM] should work */
869 case VariantCourier: /* [HGM] initial forced moves not implemented */
870 case VariantShogi: /* [HGM] drops not tested for legality */
871 case VariantKnightmate: /* [HGM] should work */
872 case VariantCylinder: /* [HGM] untested */
873 case VariantFalcon: /* [HGM] untested */
874 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
875 offboard interposition not understood */
876 case VariantNormal: /* definitely works! */
877 case VariantWildCastle: /* pieces not automatically shuffled */
878 case VariantNoCastle: /* pieces not automatically shuffled */
879 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
880 case VariantLosers: /* should work except for win condition,
881 and doesn't know captures are mandatory */
882 case VariantSuicide: /* should work except for win condition,
883 and doesn't know captures are mandatory */
884 case VariantGiveaway: /* should work except for win condition,
885 and doesn't know captures are mandatory */
886 case VariantTwoKings: /* should work */
887 case VariantAtomic: /* should work except for win condition */
888 case Variant3Check: /* should work except for win condition */
889 case VariantShatranj: /* should work except for all win conditions */
890 case VariantBerolina: /* might work if TestLegality is off */
891 case VariantCapaRandom: /* should work */
892 case VariantJanus: /* should work */
893 case VariantSuper: /* experimental */
894 case VariantGreat: /* experimental, requires legality testing to be off */
899 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
900 InitEngineUCI( installDir, &second );
903 int NextIntegerFromString( char ** str, long * value )
908 while( *s == ' ' || *s == '\t' ) {
914 if( *s >= '0' && *s <= '9' ) {
915 while( *s >= '0' && *s <= '9' ) {
916 *value = *value * 10 + (*s - '0');
928 int NextTimeControlFromString( char ** str, long * value )
931 int result = NextIntegerFromString( str, &temp );
934 *value = temp * 60; /* Minutes */
937 result = NextIntegerFromString( str, &temp );
938 *value += temp; /* Seconds */
945 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
946 { /* [HGM] routine added to read '+moves/time' for secondary time control */
947 int result = -1; long temp, temp2;
949 if(**str != '+') return -1; // old params remain in force!
951 if( NextTimeControlFromString( str, &temp ) ) return -1;
954 /* time only: incremental or sudden-death time control */
955 if(**str == '+') { /* increment follows; read it */
957 if(result = NextIntegerFromString( str, &temp2)) return -1;
960 *moves = 0; *tc = temp * 1000;
962 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
964 (*str)++; /* classical time control */
965 result = NextTimeControlFromString( str, &temp2);
974 int GetTimeQuota(int movenr)
975 { /* [HGM] get time to add from the multi-session time-control string */
976 int moves=1; /* kludge to force reading of first session */
977 long time, increment;
978 char *s = fullTimeControlString;
980 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
982 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
983 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
984 if(movenr == -1) return time; /* last move before new session */
985 if(!moves) return increment; /* current session is incremental */
986 if(movenr >= 0) movenr -= moves; /* we already finished this session */
987 } while(movenr >= -1); /* try again for next session */
989 return 0; // no new time quota on this move
993 ParseTimeControl(tc, ti, mps)
1002 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1005 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1006 else sprintf(buf, "+%s+%d", tc, ti);
1009 sprintf(buf, "+%d/%s", mps, tc);
1010 else sprintf(buf, "+%s", tc);
1012 fullTimeControlString = StrSave(buf);
1014 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1019 /* Parse second time control */
1022 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1030 timeControl_2 = tc2 * 1000;
1040 timeControl = tc1 * 1000;
1043 timeIncrement = ti * 1000; /* convert to ms */
1044 movesPerSession = 0;
1047 movesPerSession = mps;
1055 if (appData.debugMode) {
1056 fprintf(debugFP, "%s\n", programVersion);
1059 set_cont_sequence(appData.wrapContSeq);
1060 if (appData.matchGames > 0) {
1061 appData.matchMode = TRUE;
1062 } else if (appData.matchMode) {
1063 appData.matchGames = 1;
1065 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1066 appData.matchGames = appData.sameColorGames;
1067 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1068 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1069 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1072 if (appData.noChessProgram || first.protocolVersion == 1) {
1075 /* kludge: allow timeout for initial "feature" commands */
1077 DisplayMessage("", _("Starting chess program"));
1078 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1083 InitBackEnd3 P((void))
1085 GameMode initialMode;
1089 InitChessProgram(&first, startedFromSetupPosition);
1092 if (appData.icsActive) {
1094 /* [DM] Make a console window if needed [HGM] merged ifs */
1099 if (*appData.icsCommPort != NULLCHAR) {
1100 sprintf(buf, _("Could not open comm port %s"),
1101 appData.icsCommPort);
1103 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1104 appData.icsHost, appData.icsPort);
1106 DisplayFatalError(buf, err, 1);
1111 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1113 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1114 } else if (appData.noChessProgram) {
1120 if (*appData.cmailGameName != NULLCHAR) {
1122 OpenLoopback(&cmailPR);
1124 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1128 DisplayMessage("", "");
1129 if (StrCaseCmp(appData.initialMode, "") == 0) {
1130 initialMode = BeginningOfGame;
1131 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1132 initialMode = TwoMachinesPlay;
1133 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1134 initialMode = AnalyzeFile;
1135 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1136 initialMode = AnalyzeMode;
1137 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1138 initialMode = MachinePlaysWhite;
1139 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1140 initialMode = MachinePlaysBlack;
1141 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1142 initialMode = EditGame;
1143 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1144 initialMode = EditPosition;
1145 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1146 initialMode = Training;
1148 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1149 DisplayFatalError(buf, 0, 2);
1153 if (appData.matchMode) {
1154 /* Set up machine vs. machine match */
1155 if (appData.noChessProgram) {
1156 DisplayFatalError(_("Can't have a match with no chess programs"),
1162 if (*appData.loadGameFile != NULLCHAR) {
1163 int index = appData.loadGameIndex; // [HGM] autoinc
1164 if(index<0) lastIndex = index = 1;
1165 if (!LoadGameFromFile(appData.loadGameFile,
1167 appData.loadGameFile, FALSE)) {
1168 DisplayFatalError(_("Bad game file"), 0, 1);
1171 } else if (*appData.loadPositionFile != NULLCHAR) {
1172 int index = appData.loadPositionIndex; // [HGM] autoinc
1173 if(index<0) lastIndex = index = 1;
1174 if (!LoadPositionFromFile(appData.loadPositionFile,
1176 appData.loadPositionFile)) {
1177 DisplayFatalError(_("Bad position file"), 0, 1);
1182 } else if (*appData.cmailGameName != NULLCHAR) {
1183 /* Set up cmail mode */
1184 ReloadCmailMsgEvent(TRUE);
1186 /* Set up other modes */
1187 if (initialMode == AnalyzeFile) {
1188 if (*appData.loadGameFile == NULLCHAR) {
1189 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1193 if (*appData.loadGameFile != NULLCHAR) {
1194 (void) LoadGameFromFile(appData.loadGameFile,
1195 appData.loadGameIndex,
1196 appData.loadGameFile, TRUE);
1197 } else if (*appData.loadPositionFile != NULLCHAR) {
1198 (void) LoadPositionFromFile(appData.loadPositionFile,
1199 appData.loadPositionIndex,
1200 appData.loadPositionFile);
1201 /* [HGM] try to make self-starting even after FEN load */
1202 /* to allow automatic setup of fairy variants with wtm */
1203 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1204 gameMode = BeginningOfGame;
1205 setboardSpoiledMachineBlack = 1;
1207 /* [HGM] loadPos: make that every new game uses the setup */
1208 /* from file as long as we do not switch variant */
1209 if(!blackPlaysFirst) {
1210 startedFromPositionFile = TRUE;
1211 CopyBoard(filePosition, boards[0]);
1214 if (initialMode == AnalyzeMode) {
1215 if (appData.noChessProgram) {
1216 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1219 if (appData.icsActive) {
1220 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1224 } else if (initialMode == AnalyzeFile) {
1225 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1226 ShowThinkingEvent();
1228 AnalysisPeriodicEvent(1);
1229 } else if (initialMode == MachinePlaysWhite) {
1230 if (appData.noChessProgram) {
1231 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1235 if (appData.icsActive) {
1236 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1240 MachineWhiteEvent();
1241 } else if (initialMode == MachinePlaysBlack) {
1242 if (appData.noChessProgram) {
1243 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1247 if (appData.icsActive) {
1248 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1252 MachineBlackEvent();
1253 } else if (initialMode == TwoMachinesPlay) {
1254 if (appData.noChessProgram) {
1255 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1259 if (appData.icsActive) {
1260 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1265 } else if (initialMode == EditGame) {
1267 } else if (initialMode == EditPosition) {
1268 EditPositionEvent();
1269 } else if (initialMode == Training) {
1270 if (*appData.loadGameFile == NULLCHAR) {
1271 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1280 * Establish will establish a contact to a remote host.port.
1281 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1282 * used to talk to the host.
1283 * Returns 0 if okay, error code if not.
1290 if (*appData.icsCommPort != NULLCHAR) {
1291 /* Talk to the host through a serial comm port */
1292 return OpenCommPort(appData.icsCommPort, &icsPR);
1294 } else if (*appData.gateway != NULLCHAR) {
1295 if (*appData.remoteShell == NULLCHAR) {
1296 /* Use the rcmd protocol to run telnet program on a gateway host */
1297 snprintf(buf, sizeof(buf), "%s %s %s",
1298 appData.telnetProgram, appData.icsHost, appData.icsPort);
1299 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1302 /* Use the rsh program to run telnet program on a gateway host */
1303 if (*appData.remoteUser == NULLCHAR) {
1304 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1305 appData.gateway, appData.telnetProgram,
1306 appData.icsHost, appData.icsPort);
1308 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1309 appData.remoteShell, appData.gateway,
1310 appData.remoteUser, appData.telnetProgram,
1311 appData.icsHost, appData.icsPort);
1313 return StartChildProcess(buf, "", &icsPR);
1316 } else if (appData.useTelnet) {
1317 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1320 /* TCP socket interface differs somewhat between
1321 Unix and NT; handle details in the front end.
1323 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1328 show_bytes(fp, buf, count)
1334 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1335 fprintf(fp, "\\%03o", *buf & 0xff);
1344 /* Returns an errno value */
1346 OutputMaybeTelnet(pr, message, count, outError)
1352 char buf[8192], *p, *q, *buflim;
1353 int left, newcount, outcount;
1355 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1356 *appData.gateway != NULLCHAR) {
1357 if (appData.debugMode) {
1358 fprintf(debugFP, ">ICS: ");
1359 show_bytes(debugFP, message, count);
1360 fprintf(debugFP, "\n");
1362 return OutputToProcess(pr, message, count, outError);
1365 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1372 if (appData.debugMode) {
1373 fprintf(debugFP, ">ICS: ");
1374 show_bytes(debugFP, buf, newcount);
1375 fprintf(debugFP, "\n");
1377 outcount = OutputToProcess(pr, buf, newcount, outError);
1378 if (outcount < newcount) return -1; /* to be sure */
1385 } else if (((unsigned char) *p) == TN_IAC) {
1386 *q++ = (char) TN_IAC;
1393 if (appData.debugMode) {
1394 fprintf(debugFP, ">ICS: ");
1395 show_bytes(debugFP, buf, newcount);
1396 fprintf(debugFP, "\n");
1398 outcount = OutputToProcess(pr, buf, newcount, outError);
1399 if (outcount < newcount) return -1; /* to be sure */
1404 read_from_player(isr, closure, message, count, error)
1411 int outError, outCount;
1412 static int gotEof = 0;
1414 /* Pass data read from player on to ICS */
1417 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1418 if (outCount < count) {
1419 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1421 } else if (count < 0) {
1422 RemoveInputSource(isr);
1423 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1424 } else if (gotEof++ > 0) {
1425 RemoveInputSource(isr);
1426 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1432 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1433 SendToICS("date\n");
1434 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1437 /* added routine for printf style output to ics */
1438 void ics_printf(char *format, ...)
1440 char buffer[MSG_SIZ];
1443 va_start(args, format);
1444 vsnprintf(buffer, sizeof(buffer), format, args);
1445 buffer[sizeof(buffer)-1] = '\0';
1454 int count, outCount, outError;
1456 if (icsPR == NULL) return;
1459 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1460 if (outCount < count) {
1461 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1465 /* This is used for sending logon scripts to the ICS. Sending
1466 without a delay causes problems when using timestamp on ICC
1467 (at least on my machine). */
1469 SendToICSDelayed(s,msdelay)
1473 int count, outCount, outError;
1475 if (icsPR == NULL) return;
1478 if (appData.debugMode) {
1479 fprintf(debugFP, ">ICS: ");
1480 show_bytes(debugFP, s, count);
1481 fprintf(debugFP, "\n");
1483 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1485 if (outCount < count) {
1486 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1491 /* Remove all highlighting escape sequences in s
1492 Also deletes any suffix starting with '('
1495 StripHighlightAndTitle(s)
1498 static char retbuf[MSG_SIZ];
1501 while (*s != NULLCHAR) {
1502 while (*s == '\033') {
1503 while (*s != NULLCHAR && !isalpha(*s)) s++;
1504 if (*s != NULLCHAR) s++;
1506 while (*s != NULLCHAR && *s != '\033') {
1507 if (*s == '(' || *s == '[') {
1518 /* Remove all highlighting escape sequences in s */
1523 static char retbuf[MSG_SIZ];
1526 while (*s != NULLCHAR) {
1527 while (*s == '\033') {
1528 while (*s != NULLCHAR && !isalpha(*s)) s++;
1529 if (*s != NULLCHAR) s++;
1531 while (*s != NULLCHAR && *s != '\033') {
1539 char *variantNames[] = VARIANT_NAMES;
1544 return variantNames[v];
1548 /* Identify a variant from the strings the chess servers use or the
1549 PGN Variant tag names we use. */
1556 VariantClass v = VariantNormal;
1557 int i, found = FALSE;
1562 /* [HGM] skip over optional board-size prefixes */
1563 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1564 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1565 while( *e++ != '_');
1568 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1572 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573 if (StrCaseStr(e, variantNames[i])) {
1574 v = (VariantClass) i;
1581 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582 || StrCaseStr(e, "wild/fr")
1583 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584 v = VariantFischeRandom;
1585 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586 (i = 1, p = StrCaseStr(e, "w"))) {
1588 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1595 case 0: /* FICS only, actually */
1597 /* Castling legal even if K starts on d-file */
1598 v = VariantWildCastle;
1603 /* Castling illegal even if K & R happen to start in
1604 normal positions. */
1605 v = VariantNoCastle;
1618 /* Castling legal iff K & R start in normal positions */
1624 /* Special wilds for position setup; unclear what to do here */
1625 v = VariantLoadable;
1628 /* Bizarre ICC game */
1629 v = VariantTwoKings;
1632 v = VariantKriegspiel;
1638 v = VariantFischeRandom;
1641 v = VariantCrazyhouse;
1644 v = VariantBughouse;
1650 /* Not quite the same as FICS suicide! */
1651 v = VariantGiveaway;
1657 v = VariantShatranj;
1660 /* Temporary names for future ICC types. The name *will* change in
1661 the next xboard/WinBoard release after ICC defines it. */
1699 v = VariantCapablanca;
1702 v = VariantKnightmate;
1708 v = VariantCylinder;
1714 v = VariantCapaRandom;
1717 v = VariantBerolina;
1729 /* Found "wild" or "w" in the string but no number;
1730 must assume it's normal chess. */
1734 sprintf(buf, _("Unknown wild type %d"), wnum);
1735 DisplayError(buf, 0);
1741 if (appData.debugMode) {
1742 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743 e, wnum, VariantName(v));
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752 advance *index beyond it, and set leftover_start to the new value of
1753 *index; else return FALSE. If pattern contains the character '*', it
1754 matches any sequence of characters not containing '\r', '\n', or the
1755 character following the '*' (if any), and the matched sequence(s) are
1756 copied into star_match.
1759 looking_at(buf, index, pattern)
1764 char *bufp = &buf[*index], *patternp = pattern;
1766 char *matchp = star_match[0];
1769 if (*patternp == NULLCHAR) {
1770 *index = leftover_start = bufp - buf;
1774 if (*bufp == NULLCHAR) return FALSE;
1775 if (*patternp == '*') {
1776 if (*bufp == *(patternp + 1)) {
1778 matchp = star_match[++star_count];
1782 } else if (*bufp == '\n' || *bufp == '\r') {
1784 if (*patternp == NULLCHAR)
1789 *matchp++ = *bufp++;
1793 if (*patternp != *bufp) return FALSE;
1800 SendToPlayer(data, length)
1804 int error, outCount;
1805 outCount = OutputToProcess(NoProc, data, length, &error);
1806 if (outCount < length) {
1807 DisplayFatalError(_("Error writing to display"), error, 1);
1812 PackHolding(packed, holding)
1824 switch (runlength) {
1835 sprintf(q, "%d", runlength);
1847 /* Telnet protocol requests from the front end */
1849 TelnetRequest(ddww, option)
1850 unsigned char ddww, option;
1852 unsigned char msg[3];
1853 int outCount, outError;
1855 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857 if (appData.debugMode) {
1858 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1874 sprintf(buf1, "%d", ddww);
1883 sprintf(buf2, "%d", option);
1886 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1891 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 if (!appData.icsActive) return;
1901 TelnetRequest(TN_DO, TN_ECHO);
1907 if (!appData.icsActive) return;
1908 TelnetRequest(TN_DONT, TN_ECHO);
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 /* put the holdings sent to us by the server on the board holdings area */
1915 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1919 if(gameInfo.holdingsWidth < 2) return;
1920 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1921 return; // prevent overwriting by pre-board holdings
1923 if( (int)lowestPiece >= BlackPawn ) {
1926 holdingsStartRow = BOARD_HEIGHT-1;
1929 holdingsColumn = BOARD_WIDTH-1;
1930 countsColumn = BOARD_WIDTH-2;
1931 holdingsStartRow = 0;
1935 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1936 board[i][holdingsColumn] = EmptySquare;
1937 board[i][countsColumn] = (ChessSquare) 0;
1939 while( (p=*holdings++) != NULLCHAR ) {
1940 piece = CharToPiece( ToUpper(p) );
1941 if(piece == EmptySquare) continue;
1942 /*j = (int) piece - (int) WhitePawn;*/
1943 j = PieceToNumber(piece);
1944 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1945 if(j < 0) continue; /* should not happen */
1946 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1947 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1948 board[holdingsStartRow+j*direction][countsColumn]++;
1954 VariantSwitch(Board board, VariantClass newVariant)
1956 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1959 startedFromPositionFile = FALSE;
1960 if(gameInfo.variant == newVariant) return;
1962 /* [HGM] This routine is called each time an assignment is made to
1963 * gameInfo.variant during a game, to make sure the board sizes
1964 * are set to match the new variant. If that means adding or deleting
1965 * holdings, we shift the playing board accordingly
1966 * This kludge is needed because in ICS observe mode, we get boards
1967 * of an ongoing game without knowing the variant, and learn about the
1968 * latter only later. This can be because of the move list we requested,
1969 * in which case the game history is refilled from the beginning anyway,
1970 * but also when receiving holdings of a crazyhouse game. In the latter
1971 * case we want to add those holdings to the already received position.
1975 if (appData.debugMode) {
1976 fprintf(debugFP, "Switch board from %s to %s\n",
1977 VariantName(gameInfo.variant), VariantName(newVariant));
1978 setbuf(debugFP, NULL);
1980 shuffleOpenings = 0; /* [HGM] shuffle */
1981 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1985 newWidth = 9; newHeight = 9;
1986 gameInfo.holdingsSize = 7;
1987 case VariantBughouse:
1988 case VariantCrazyhouse:
1989 newHoldingsWidth = 2; break;
1993 newHoldingsWidth = 2;
1994 gameInfo.holdingsSize = 8;
1997 case VariantCapablanca:
1998 case VariantCapaRandom:
2001 newHoldingsWidth = gameInfo.holdingsSize = 0;
2004 if(newWidth != gameInfo.boardWidth ||
2005 newHeight != gameInfo.boardHeight ||
2006 newHoldingsWidth != gameInfo.holdingsWidth ) {
2008 /* shift position to new playing area, if needed */
2009 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010 for(i=0; i<BOARD_HEIGHT; i++)
2011 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 for(i=0; i<newHeight; i++) {
2015 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2018 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019 for(i=0; i<BOARD_HEIGHT; i++)
2020 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2024 gameInfo.boardWidth = newWidth;
2025 gameInfo.boardHeight = newHeight;
2026 gameInfo.holdingsWidth = newHoldingsWidth;
2027 gameInfo.variant = newVariant;
2028 InitDrawingSizes(-2, 0);
2029 } else gameInfo.variant = newVariant;
2030 CopyBoard(oldBoard, board); // remember correctly formatted board
2031 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2032 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2035 static int loggedOn = FALSE;
2037 /*-- Game start info cache: --*/
2039 char gs_kind[MSG_SIZ];
2040 static char player1Name[128] = "";
2041 static char player2Name[128] = "";
2042 static char cont_seq[] = "\n\\ ";
2043 static int player1Rating = -1;
2044 static int player2Rating = -1;
2045 /*----------------------------*/
2047 ColorClass curColor = ColorNormal;
2048 int suppressKibitz = 0;
2051 read_from_ics(isr, closure, data, count, error)
2058 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2059 #define STARTED_NONE 0
2060 #define STARTED_MOVES 1
2061 #define STARTED_BOARD 2
2062 #define STARTED_OBSERVE 3
2063 #define STARTED_HOLDINGS 4
2064 #define STARTED_CHATTER 5
2065 #define STARTED_COMMENT 6
2066 #define STARTED_MOVES_NOHIDE 7
2068 static int started = STARTED_NONE;
2069 static char parse[20000];
2070 static int parse_pos = 0;
2071 static char buf[BUF_SIZE + 1];
2072 static int firstTime = TRUE, intfSet = FALSE;
2073 static ColorClass prevColor = ColorNormal;
2074 static int savingComment = FALSE;
2075 static int cmatch = 0; // continuation sequence match
2082 int backup; /* [DM] For zippy color lines */
2084 char talker[MSG_SIZ]; // [HGM] chat
2087 if (appData.debugMode) {
2089 fprintf(debugFP, "<ICS: ");
2090 show_bytes(debugFP, data, count);
2091 fprintf(debugFP, "\n");
2095 if (appData.debugMode) { int f = forwardMostMove;
2096 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2098 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2101 /* If last read ended with a partial line that we couldn't parse,
2102 prepend it to the new read and try again. */
2103 if (leftover_len > 0) {
2104 for (i=0; i<leftover_len; i++)
2105 buf[i] = buf[leftover_start + i];
2108 /* copy new characters into the buffer */
2109 bp = buf + leftover_len;
2110 buf_len=leftover_len;
2111 for (i=0; i<count; i++)
2114 if (data[i] == '\r')
2117 // join lines split by ICS?
2118 if (!appData.noJoin)
2121 Joining just consists of finding matches against the
2122 continuation sequence, and discarding that sequence
2123 if found instead of copying it. So, until a match
2124 fails, there's nothing to do since it might be the
2125 complete sequence, and thus, something we don't want
2128 if (data[i] == cont_seq[cmatch])
2131 if (cmatch == strlen(cont_seq))
2133 cmatch = 0; // complete match. just reset the counter
2136 it's possible for the ICS to not include the space
2137 at the end of the last word, making our [correct]
2138 join operation fuse two separate words. the server
2139 does this when the space occurs at the width setting.
2141 if (!buf_len || buf[buf_len-1] != ' ')
2152 match failed, so we have to copy what matched before
2153 falling through and copying this character. In reality,
2154 this will only ever be just the newline character, but
2155 it doesn't hurt to be precise.
2157 strncpy(bp, cont_seq, cmatch);
2169 buf[buf_len] = NULLCHAR;
2170 next_out = leftover_len;
2174 while (i < buf_len) {
2175 /* Deal with part of the TELNET option negotiation
2176 protocol. We refuse to do anything beyond the
2177 defaults, except that we allow the WILL ECHO option,
2178 which ICS uses to turn off password echoing when we are
2179 directly connected to it. We reject this option
2180 if localLineEditing mode is on (always on in xboard)
2181 and we are talking to port 23, which might be a real
2182 telnet server that will try to keep WILL ECHO on permanently.
2184 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2185 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2186 unsigned char option;
2188 switch ((unsigned char) buf[++i]) {
2190 if (appData.debugMode)
2191 fprintf(debugFP, "\n<WILL ");
2192 switch (option = (unsigned char) buf[++i]) {
2194 if (appData.debugMode)
2195 fprintf(debugFP, "ECHO ");
2196 /* Reply only if this is a change, according
2197 to the protocol rules. */
2198 if (remoteEchoOption) break;
2199 if (appData.localLineEditing &&
2200 atoi(appData.icsPort) == TN_PORT) {
2201 TelnetRequest(TN_DONT, TN_ECHO);
2204 TelnetRequest(TN_DO, TN_ECHO);
2205 remoteEchoOption = TRUE;
2209 if (appData.debugMode)
2210 fprintf(debugFP, "%d ", option);
2211 /* Whatever this is, we don't want it. */
2212 TelnetRequest(TN_DONT, option);
2217 if (appData.debugMode)
2218 fprintf(debugFP, "\n<WONT ");
2219 switch (option = (unsigned char) buf[++i]) {
2221 if (appData.debugMode)
2222 fprintf(debugFP, "ECHO ");
2223 /* Reply only if this is a change, according
2224 to the protocol rules. */
2225 if (!remoteEchoOption) break;
2227 TelnetRequest(TN_DONT, TN_ECHO);
2228 remoteEchoOption = FALSE;
2231 if (appData.debugMode)
2232 fprintf(debugFP, "%d ", (unsigned char) option);
2233 /* Whatever this is, it must already be turned
2234 off, because we never agree to turn on
2235 anything non-default, so according to the
2236 protocol rules, we don't reply. */
2241 if (appData.debugMode)
2242 fprintf(debugFP, "\n<DO ");
2243 switch (option = (unsigned char) buf[++i]) {
2245 /* Whatever this is, we refuse to do it. */
2246 if (appData.debugMode)
2247 fprintf(debugFP, "%d ", option);
2248 TelnetRequest(TN_WONT, option);
2253 if (appData.debugMode)
2254 fprintf(debugFP, "\n<DONT ");
2255 switch (option = (unsigned char) buf[++i]) {
2257 if (appData.debugMode)
2258 fprintf(debugFP, "%d ", option);
2259 /* Whatever this is, we are already not doing
2260 it, because we never agree to do anything
2261 non-default, so according to the protocol
2262 rules, we don't reply. */
2267 if (appData.debugMode)
2268 fprintf(debugFP, "\n<IAC ");
2269 /* Doubled IAC; pass it through */
2273 if (appData.debugMode)
2274 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2275 /* Drop all other telnet commands on the floor */
2278 if (oldi > next_out)
2279 SendToPlayer(&buf[next_out], oldi - next_out);
2285 /* OK, this at least will *usually* work */
2286 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2290 if (loggedOn && !intfSet) {
2291 if (ics_type == ICS_ICC) {
2293 "/set-quietly interface %s\n/set-quietly style 12\n",
2295 } else if (ics_type == ICS_CHESSNET) {
2296 sprintf(str, "/style 12\n");
2298 strcpy(str, "alias $ @\n$set interface ");
2299 strcat(str, programVersion);
2300 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2302 strcat(str, "$iset nohighlight 1\n");
2304 strcat(str, "$iset lock 1\n$style 12\n");
2307 NotifyFrontendLogin();
2311 if (started == STARTED_COMMENT) {
2312 /* Accumulate characters in comment */
2313 parse[parse_pos++] = buf[i];
2314 if (buf[i] == '\n') {
2315 parse[parse_pos] = NULLCHAR;
2316 if(chattingPartner>=0) {
2318 sprintf(mess, "%s%s", talker, parse);
2319 OutputChatMessage(chattingPartner, mess);
2320 chattingPartner = -1;
2322 if(!suppressKibitz) // [HGM] kibitz
2323 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2324 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2325 int nrDigit = 0, nrAlph = 0, i;
2326 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2327 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2328 parse[parse_pos] = NULLCHAR;
2329 // try to be smart: if it does not look like search info, it should go to
2330 // ICS interaction window after all, not to engine-output window.
2331 for(i=0; i<parse_pos; i++) { // count letters and digits
2332 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2333 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2334 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2336 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2337 int depth=0; float score;
2338 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2339 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2340 pvInfoList[forwardMostMove-1].depth = depth;
2341 pvInfoList[forwardMostMove-1].score = 100*score;
2343 OutputKibitz(suppressKibitz, parse);
2346 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2347 SendToPlayer(tmp, strlen(tmp));
2350 started = STARTED_NONE;
2352 /* Don't match patterns against characters in chatter */
2357 if (started == STARTED_CHATTER) {
2358 if (buf[i] != '\n') {
2359 /* Don't match patterns against characters in chatter */
2363 started = STARTED_NONE;
2366 /* Kludge to deal with rcmd protocol */
2367 if (firstTime && looking_at(buf, &i, "\001*")) {
2368 DisplayFatalError(&buf[1], 0, 1);
2374 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2377 if (appData.debugMode)
2378 fprintf(debugFP, "ics_type %d\n", ics_type);
2381 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2382 ics_type = ICS_FICS;
2384 if (appData.debugMode)
2385 fprintf(debugFP, "ics_type %d\n", ics_type);
2388 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2389 ics_type = ICS_CHESSNET;
2391 if (appData.debugMode)
2392 fprintf(debugFP, "ics_type %d\n", ics_type);
2397 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2398 looking_at(buf, &i, "Logging you in as \"*\"") ||
2399 looking_at(buf, &i, "will be \"*\""))) {
2400 strcpy(ics_handle, star_match[0]);
2404 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2406 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2407 DisplayIcsInteractionTitle(buf);
2408 have_set_title = TRUE;
2411 /* skip finger notes */
2412 if (started == STARTED_NONE &&
2413 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2414 (buf[i] == '1' && buf[i+1] == '0')) &&
2415 buf[i+2] == ':' && buf[i+3] == ' ') {
2416 started = STARTED_CHATTER;
2421 /* skip formula vars */
2422 if (started == STARTED_NONE &&
2423 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2424 started = STARTED_CHATTER;
2430 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2431 if (appData.autoKibitz && started == STARTED_NONE &&
2432 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2433 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2434 if(looking_at(buf, &i, "* kibitzes: ") &&
2435 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2436 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2437 suppressKibitz = TRUE;
2438 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2439 && (gameMode == IcsPlayingWhite)) ||
2440 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2441 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2442 started = STARTED_CHATTER; // own kibitz we simply discard
2444 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2445 parse_pos = 0; parse[0] = NULLCHAR;
2446 savingComment = TRUE;
2447 suppressKibitz = gameMode != IcsObserving ? 2 :
2448 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2452 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2453 started = STARTED_CHATTER;
2454 suppressKibitz = TRUE;
2456 } // [HGM] kibitz: end of patch
2458 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2460 // [HGM] chat: intercept tells by users for which we have an open chat window
2462 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2463 looking_at(buf, &i, "* whispers:") ||
2464 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2465 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2467 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2468 chattingPartner = -1;
2470 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2471 for(p=0; p<MAX_CHAT; p++) {
2472 if(channel == atoi(chatPartner[p])) {
2473 talker[0] = '['; strcat(talker, "]");
2474 chattingPartner = p; break;
2477 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2478 for(p=0; p<MAX_CHAT; p++) {
2479 if(!strcmp("WHISPER", chatPartner[p])) {
2480 talker[0] = '['; strcat(talker, "]");
2481 chattingPartner = p; break;
2484 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2485 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2487 chattingPartner = p; break;
2489 if(chattingPartner<0) i = oldi; else {
2490 started = STARTED_COMMENT;
2491 parse_pos = 0; parse[0] = NULLCHAR;
2492 savingComment = TRUE;
2493 suppressKibitz = TRUE;
2495 } // [HGM] chat: end of patch
2497 if (appData.zippyTalk || appData.zippyPlay) {
2498 /* [DM] Backup address for color zippy lines */
2502 if (loggedOn == TRUE)
2503 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2504 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2506 if (ZippyControl(buf, &i) ||
2507 ZippyConverse(buf, &i) ||
2508 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2510 if (!appData.colorize) continue;
2514 } // [DM] 'else { ' deleted
2516 /* Regular tells and says */
2517 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2518 looking_at(buf, &i, "* (your partner) tells you: ") ||
2519 looking_at(buf, &i, "* says: ") ||
2520 /* Don't color "message" or "messages" output */
2521 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2522 looking_at(buf, &i, "*. * at *:*: ") ||
2523 looking_at(buf, &i, "--* (*:*): ") ||
2524 /* Message notifications (same color as tells) */
2525 looking_at(buf, &i, "* has left a message ") ||
2526 looking_at(buf, &i, "* just sent you a message:\n") ||
2527 /* Whispers and kibitzes */
2528 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2529 looking_at(buf, &i, "* kibitzes: ") ||
2531 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2533 if (tkind == 1 && strchr(star_match[0], ':')) {
2534 /* Avoid "tells you:" spoofs in channels */
2537 if (star_match[0][0] == NULLCHAR ||
2538 strchr(star_match[0], ' ') ||
2539 (tkind == 3 && strchr(star_match[1], ' '))) {
2540 /* Reject bogus matches */
2543 if (appData.colorize) {
2544 if (oldi > next_out) {
2545 SendToPlayer(&buf[next_out], oldi - next_out);
2550 Colorize(ColorTell, FALSE);
2551 curColor = ColorTell;
2554 Colorize(ColorKibitz, FALSE);
2555 curColor = ColorKibitz;
2558 p = strrchr(star_match[1], '(');
2565 Colorize(ColorChannel1, FALSE);
2566 curColor = ColorChannel1;
2568 Colorize(ColorChannel, FALSE);
2569 curColor = ColorChannel;
2573 curColor = ColorNormal;
2577 if (started == STARTED_NONE && appData.autoComment &&
2578 (gameMode == IcsObserving ||
2579 gameMode == IcsPlayingWhite ||
2580 gameMode == IcsPlayingBlack)) {
2581 parse_pos = i - oldi;
2582 memcpy(parse, &buf[oldi], parse_pos);
2583 parse[parse_pos] = NULLCHAR;
2584 started = STARTED_COMMENT;
2585 savingComment = TRUE;
2587 started = STARTED_CHATTER;
2588 savingComment = FALSE;
2595 if (looking_at(buf, &i, "* s-shouts: ") ||
2596 looking_at(buf, &i, "* c-shouts: ")) {
2597 if (appData.colorize) {
2598 if (oldi > next_out) {
2599 SendToPlayer(&buf[next_out], oldi - next_out);
2602 Colorize(ColorSShout, FALSE);
2603 curColor = ColorSShout;
2606 started = STARTED_CHATTER;
2610 if (looking_at(buf, &i, "--->")) {
2615 if (looking_at(buf, &i, "* shouts: ") ||
2616 looking_at(buf, &i, "--> ")) {
2617 if (appData.colorize) {
2618 if (oldi > next_out) {
2619 SendToPlayer(&buf[next_out], oldi - next_out);
2622 Colorize(ColorShout, FALSE);
2623 curColor = ColorShout;
2626 started = STARTED_CHATTER;
2630 if (looking_at( buf, &i, "Challenge:")) {
2631 if (appData.colorize) {
2632 if (oldi > next_out) {
2633 SendToPlayer(&buf[next_out], oldi - next_out);
2636 Colorize(ColorChallenge, FALSE);
2637 curColor = ColorChallenge;
2643 if (looking_at(buf, &i, "* offers you") ||
2644 looking_at(buf, &i, "* offers to be") ||
2645 looking_at(buf, &i, "* would like to") ||
2646 looking_at(buf, &i, "* requests to") ||
2647 looking_at(buf, &i, "Your opponent offers") ||
2648 looking_at(buf, &i, "Your opponent requests")) {
2650 if (appData.colorize) {
2651 if (oldi > next_out) {
2652 SendToPlayer(&buf[next_out], oldi - next_out);
2655 Colorize(ColorRequest, FALSE);
2656 curColor = ColorRequest;
2661 if (looking_at(buf, &i, "* (*) seeking")) {
2662 if (appData.colorize) {
2663 if (oldi > next_out) {
2664 SendToPlayer(&buf[next_out], oldi - next_out);
2667 Colorize(ColorSeek, FALSE);
2668 curColor = ColorSeek;
2673 if (looking_at(buf, &i, "\\ ")) {
2674 if (prevColor != ColorNormal) {
2675 if (oldi > next_out) {
2676 SendToPlayer(&buf[next_out], oldi - next_out);
2679 Colorize(prevColor, TRUE);
2680 curColor = prevColor;
2682 if (savingComment) {
2683 parse_pos = i - oldi;
2684 memcpy(parse, &buf[oldi], parse_pos);
2685 parse[parse_pos] = NULLCHAR;
2686 started = STARTED_COMMENT;
2688 started = STARTED_CHATTER;
2693 if (looking_at(buf, &i, "Black Strength :") ||
2694 looking_at(buf, &i, "<<< style 10 board >>>") ||
2695 looking_at(buf, &i, "<10>") ||
2696 looking_at(buf, &i, "#@#")) {
2697 /* Wrong board style */
2699 SendToICS(ics_prefix);
2700 SendToICS("set style 12\n");
2701 SendToICS(ics_prefix);
2702 SendToICS("refresh\n");
2706 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2708 have_sent_ICS_logon = 1;
2712 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2713 (looking_at(buf, &i, "\n<12> ") ||
2714 looking_at(buf, &i, "<12> "))) {
2716 if (oldi > next_out) {
2717 SendToPlayer(&buf[next_out], oldi - next_out);
2720 started = STARTED_BOARD;
2725 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2726 looking_at(buf, &i, "<b1> ")) {
2727 if (oldi > next_out) {
2728 SendToPlayer(&buf[next_out], oldi - next_out);
2731 started = STARTED_HOLDINGS;
2736 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2738 /* Header for a move list -- first line */
2740 switch (ics_getting_history) {
2744 case BeginningOfGame:
2745 /* User typed "moves" or "oldmoves" while we
2746 were idle. Pretend we asked for these
2747 moves and soak them up so user can step
2748 through them and/or save them.
2751 gameMode = IcsObserving;
2754 ics_getting_history = H_GOT_UNREQ_HEADER;
2756 case EditGame: /*?*/
2757 case EditPosition: /*?*/
2758 /* Should above feature work in these modes too? */
2759 /* For now it doesn't */
2760 ics_getting_history = H_GOT_UNWANTED_HEADER;
2763 ics_getting_history = H_GOT_UNWANTED_HEADER;
2768 /* Is this the right one? */
2769 if (gameInfo.white && gameInfo.black &&
2770 strcmp(gameInfo.white, star_match[0]) == 0 &&
2771 strcmp(gameInfo.black, star_match[2]) == 0) {
2773 ics_getting_history = H_GOT_REQ_HEADER;
2776 case H_GOT_REQ_HEADER:
2777 case H_GOT_UNREQ_HEADER:
2778 case H_GOT_UNWANTED_HEADER:
2779 case H_GETTING_MOVES:
2780 /* Should not happen */
2781 DisplayError(_("Error gathering move list: two headers"), 0);
2782 ics_getting_history = H_FALSE;
2786 /* Save player ratings into gameInfo if needed */
2787 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2788 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2789 (gameInfo.whiteRating == -1 ||
2790 gameInfo.blackRating == -1)) {
2792 gameInfo.whiteRating = string_to_rating(star_match[1]);
2793 gameInfo.blackRating = string_to_rating(star_match[3]);
2794 if (appData.debugMode)
2795 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2796 gameInfo.whiteRating, gameInfo.blackRating);
2801 if (looking_at(buf, &i,
2802 "* * match, initial time: * minute*, increment: * second")) {
2803 /* Header for a move list -- second line */
2804 /* Initial board will follow if this is a wild game */
2805 if (gameInfo.event != NULL) free(gameInfo.event);
2806 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2807 gameInfo.event = StrSave(str);
2808 /* [HGM] we switched variant. Translate boards if needed. */
2809 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2813 if (looking_at(buf, &i, "Move ")) {
2814 /* Beginning of a move list */
2815 switch (ics_getting_history) {
2817 /* Normally should not happen */
2818 /* Maybe user hit reset while we were parsing */
2821 /* Happens if we are ignoring a move list that is not
2822 * the one we just requested. Common if the user
2823 * tries to observe two games without turning off
2826 case H_GETTING_MOVES:
2827 /* Should not happen */
2828 DisplayError(_("Error gathering move list: nested"), 0);
2829 ics_getting_history = H_FALSE;
2831 case H_GOT_REQ_HEADER:
2832 ics_getting_history = H_GETTING_MOVES;
2833 started = STARTED_MOVES;
2835 if (oldi > next_out) {
2836 SendToPlayer(&buf[next_out], oldi - next_out);
2839 case H_GOT_UNREQ_HEADER:
2840 ics_getting_history = H_GETTING_MOVES;
2841 started = STARTED_MOVES_NOHIDE;
2844 case H_GOT_UNWANTED_HEADER:
2845 ics_getting_history = H_FALSE;
2851 if (looking_at(buf, &i, "% ") ||
2852 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2853 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2854 savingComment = FALSE;
2857 case STARTED_MOVES_NOHIDE:
2858 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2859 parse[parse_pos + i - oldi] = NULLCHAR;
2860 ParseGameHistory(parse);
2862 if (appData.zippyPlay && first.initDone) {
2863 FeedMovesToProgram(&first, forwardMostMove);
2864 if (gameMode == IcsPlayingWhite) {
2865 if (WhiteOnMove(forwardMostMove)) {
2866 if (first.sendTime) {
2867 if (first.useColors) {
2868 SendToProgram("black\n", &first);
2870 SendTimeRemaining(&first, TRUE);
2872 if (first.useColors) {
2873 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2875 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2876 first.maybeThinking = TRUE;
2878 if (first.usePlayother) {
2879 if (first.sendTime) {
2880 SendTimeRemaining(&first, TRUE);
2882 SendToProgram("playother\n", &first);
2888 } else if (gameMode == IcsPlayingBlack) {
2889 if (!WhiteOnMove(forwardMostMove)) {
2890 if (first.sendTime) {
2891 if (first.useColors) {
2892 SendToProgram("white\n", &first);
2894 SendTimeRemaining(&first, FALSE);
2896 if (first.useColors) {
2897 SendToProgram("black\n", &first);
2899 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2900 first.maybeThinking = TRUE;
2902 if (first.usePlayother) {
2903 if (first.sendTime) {
2904 SendTimeRemaining(&first, FALSE);
2906 SendToProgram("playother\n", &first);
2915 if (gameMode == IcsObserving && ics_gamenum == -1) {
2916 /* Moves came from oldmoves or moves command
2917 while we weren't doing anything else.
2919 currentMove = forwardMostMove;
2920 ClearHighlights();/*!!could figure this out*/
2921 flipView = appData.flipView;
2922 DrawPosition(TRUE, boards[currentMove]);
2923 DisplayBothClocks();
2924 sprintf(str, "%s vs. %s",
2925 gameInfo.white, gameInfo.black);
2929 /* Moves were history of an active game */
2930 if (gameInfo.resultDetails != NULL) {
2931 free(gameInfo.resultDetails);
2932 gameInfo.resultDetails = NULL;
2935 HistorySet(parseList, backwardMostMove,
2936 forwardMostMove, currentMove-1);
2937 DisplayMove(currentMove - 1);
2938 if (started == STARTED_MOVES) next_out = i;
2939 started = STARTED_NONE;
2940 ics_getting_history = H_FALSE;
2943 case STARTED_OBSERVE:
2944 started = STARTED_NONE;
2945 SendToICS(ics_prefix);
2946 SendToICS("refresh\n");
2952 if(bookHit) { // [HGM] book: simulate book reply
2953 static char bookMove[MSG_SIZ]; // a bit generous?
2955 programStats.nodes = programStats.depth = programStats.time =
2956 programStats.score = programStats.got_only_move = 0;
2957 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2959 strcpy(bookMove, "move ");
2960 strcat(bookMove, bookHit);
2961 HandleMachineMove(bookMove, &first);
2966 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2967 started == STARTED_HOLDINGS ||
2968 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2969 /* Accumulate characters in move list or board */
2970 parse[parse_pos++] = buf[i];
2973 /* Start of game messages. Mostly we detect start of game
2974 when the first board image arrives. On some versions
2975 of the ICS, though, we need to do a "refresh" after starting
2976 to observe in order to get the current board right away. */
2977 if (looking_at(buf, &i, "Adding game * to observation list")) {
2978 started = STARTED_OBSERVE;
2982 /* Handle auto-observe */
2983 if (appData.autoObserve &&
2984 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2985 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2987 /* Choose the player that was highlighted, if any. */
2988 if (star_match[0][0] == '\033' ||
2989 star_match[1][0] != '\033') {
2990 player = star_match[0];
2992 player = star_match[2];
2994 sprintf(str, "%sobserve %s\n",
2995 ics_prefix, StripHighlightAndTitle(player));
2998 /* Save ratings from notify string */
2999 strcpy(player1Name, star_match[0]);
3000 player1Rating = string_to_rating(star_match[1]);
3001 strcpy(player2Name, star_match[2]);
3002 player2Rating = string_to_rating(star_match[3]);
3004 if (appData.debugMode)
3006 "Ratings from 'Game notification:' %s %d, %s %d\n",
3007 player1Name, player1Rating,
3008 player2Name, player2Rating);
3013 /* Deal with automatic examine mode after a game,
3014 and with IcsObserving -> IcsExamining transition */
3015 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3016 looking_at(buf, &i, "has made you an examiner of game *")) {
3018 int gamenum = atoi(star_match[0]);
3019 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3020 gamenum == ics_gamenum) {
3021 /* We were already playing or observing this game;
3022 no need to refetch history */
3023 gameMode = IcsExamining;
3025 pauseExamForwardMostMove = forwardMostMove;
3026 } else if (currentMove < forwardMostMove) {
3027 ForwardInner(forwardMostMove);
3030 /* I don't think this case really can happen */
3031 SendToICS(ics_prefix);
3032 SendToICS("refresh\n");
3037 /* Error messages */
3038 // if (ics_user_moved) {
3039 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3040 if (looking_at(buf, &i, "Illegal move") ||
3041 looking_at(buf, &i, "Not a legal move") ||
3042 looking_at(buf, &i, "Your king is in check") ||
3043 looking_at(buf, &i, "It isn't your turn") ||
3044 looking_at(buf, &i, "It is not your move")) {
3046 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3047 currentMove = --forwardMostMove;
3048 DisplayMove(currentMove - 1); /* before DMError */
3049 DrawPosition(FALSE, boards[currentMove]);
3051 DisplayBothClocks();
3053 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3059 if (looking_at(buf, &i, "still have time") ||
3060 looking_at(buf, &i, "not out of time") ||
3061 looking_at(buf, &i, "either player is out of time") ||
3062 looking_at(buf, &i, "has timeseal; checking")) {
3063 /* We must have called his flag a little too soon */
3064 whiteFlag = blackFlag = FALSE;
3068 if (looking_at(buf, &i, "added * seconds to") ||
3069 looking_at(buf, &i, "seconds were added to")) {
3070 /* Update the clocks */
3071 SendToICS(ics_prefix);
3072 SendToICS("refresh\n");
3076 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3077 ics_clock_paused = TRUE;
3082 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3083 ics_clock_paused = FALSE;
3088 /* Grab player ratings from the Creating: message.
3089 Note we have to check for the special case when
3090 the ICS inserts things like [white] or [black]. */
3091 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3092 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3094 0 player 1 name (not necessarily white)
3096 2 empty, white, or black (IGNORED)
3097 3 player 2 name (not necessarily black)
3100 The names/ratings are sorted out when the game
3101 actually starts (below).
3103 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3104 player1Rating = string_to_rating(star_match[1]);
3105 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3106 player2Rating = string_to_rating(star_match[4]);
3108 if (appData.debugMode)
3110 "Ratings from 'Creating:' %s %d, %s %d\n",
3111 player1Name, player1Rating,
3112 player2Name, player2Rating);
3117 /* Improved generic start/end-of-game messages */
3118 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3119 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3120 /* If tkind == 0: */
3121 /* star_match[0] is the game number */
3122 /* [1] is the white player's name */
3123 /* [2] is the black player's name */
3124 /* For end-of-game: */
3125 /* [3] is the reason for the game end */
3126 /* [4] is a PGN end game-token, preceded by " " */
3127 /* For start-of-game: */
3128 /* [3] begins with "Creating" or "Continuing" */
3129 /* [4] is " *" or empty (don't care). */
3130 int gamenum = atoi(star_match[0]);
3131 char *whitename, *blackname, *why, *endtoken;
3132 ChessMove endtype = (ChessMove) 0;
3135 whitename = star_match[1];
3136 blackname = star_match[2];
3137 why = star_match[3];
3138 endtoken = star_match[4];
3140 whitename = star_match[1];
3141 blackname = star_match[3];
3142 why = star_match[5];
3143 endtoken = star_match[6];
3146 /* Game start messages */
3147 if (strncmp(why, "Creating ", 9) == 0 ||
3148 strncmp(why, "Continuing ", 11) == 0) {
3149 gs_gamenum = gamenum;
3150 strcpy(gs_kind, strchr(why, ' ') + 1);
3152 if (appData.zippyPlay) {
3153 ZippyGameStart(whitename, blackname);
3159 /* Game end messages */
3160 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3161 ics_gamenum != gamenum) {
3164 while (endtoken[0] == ' ') endtoken++;
3165 switch (endtoken[0]) {
3168 endtype = GameUnfinished;
3171 endtype = BlackWins;
3174 if (endtoken[1] == '/')
3175 endtype = GameIsDrawn;
3177 endtype = WhiteWins;
3180 GameEnds(endtype, why, GE_ICS);
3182 if (appData.zippyPlay && first.initDone) {
3183 ZippyGameEnd(endtype, why);
3184 if (first.pr == NULL) {
3185 /* Start the next process early so that we'll
3186 be ready for the next challenge */
3187 StartChessProgram(&first);
3189 /* Send "new" early, in case this command takes
3190 a long time to finish, so that we'll be ready
3191 for the next challenge. */
3192 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3199 if (looking_at(buf, &i, "Removing game * from observation") ||
3200 looking_at(buf, &i, "no longer observing game *") ||
3201 looking_at(buf, &i, "Game * (*) has no examiners")) {
3202 if (gameMode == IcsObserving &&
3203 atoi(star_match[0]) == ics_gamenum)
3205 /* icsEngineAnalyze */
3206 if (appData.icsEngineAnalyze) {
3213 ics_user_moved = FALSE;
3218 if (looking_at(buf, &i, "no longer examining game *")) {
3219 if (gameMode == IcsExamining &&
3220 atoi(star_match[0]) == ics_gamenum)
3224 ics_user_moved = FALSE;
3229 /* Advance leftover_start past any newlines we find,
3230 so only partial lines can get reparsed */
3231 if (looking_at(buf, &i, "\n")) {
3232 prevColor = curColor;
3233 if (curColor != ColorNormal) {
3234 if (oldi > next_out) {
3235 SendToPlayer(&buf[next_out], oldi - next_out);
3238 Colorize(ColorNormal, FALSE);
3239 curColor = ColorNormal;
3241 if (started == STARTED_BOARD) {
3242 started = STARTED_NONE;
3243 parse[parse_pos] = NULLCHAR;
3244 ParseBoard12(parse);
3247 /* Send premove here */
3248 if (appData.premove) {
3250 if (currentMove == 0 &&
3251 gameMode == IcsPlayingWhite &&
3252 appData.premoveWhite) {
3253 sprintf(str, "%s\n", appData.premoveWhiteText);
3254 if (appData.debugMode)
3255 fprintf(debugFP, "Sending premove:\n");
3257 } else if (currentMove == 1 &&
3258 gameMode == IcsPlayingBlack &&
3259 appData.premoveBlack) {
3260 sprintf(str, "%s\n", appData.premoveBlackText);
3261 if (appData.debugMode)
3262 fprintf(debugFP, "Sending premove:\n");
3264 } else if (gotPremove) {
3266 ClearPremoveHighlights();
3267 if (appData.debugMode)
3268 fprintf(debugFP, "Sending premove:\n");
3269 UserMoveEvent(premoveFromX, premoveFromY,
3270 premoveToX, premoveToY,
3275 /* Usually suppress following prompt */
3276 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3277 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3278 if (looking_at(buf, &i, "*% ")) {
3279 savingComment = FALSE;
3283 } else if (started == STARTED_HOLDINGS) {
3285 char new_piece[MSG_SIZ];
3286 started = STARTED_NONE;
3287 parse[parse_pos] = NULLCHAR;
3288 if (appData.debugMode)
3289 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3290 parse, currentMove);
3291 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3292 gamenum == ics_gamenum) {
3293 if (gameInfo.variant == VariantNormal) {
3294 /* [HGM] We seem to switch variant during a game!
3295 * Presumably no holdings were displayed, so we have
3296 * to move the position two files to the right to
3297 * create room for them!
3299 VariantClass newVariant;
3300 switch(gameInfo.boardWidth) { // base guess on board width
3301 case 9: newVariant = VariantShogi; break;
3302 case 10: newVariant = VariantGreat; break;
3303 default: newVariant = VariantCrazyhouse; break;
3305 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3306 /* Get a move list just to see the header, which
3307 will tell us whether this is really bug or zh */
3308 if (ics_getting_history == H_FALSE) {
3309 ics_getting_history = H_REQUESTED;
3310 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3314 new_piece[0] = NULLCHAR;
3315 sscanf(parse, "game %d white [%s black [%s <- %s",
3316 &gamenum, white_holding, black_holding,
3318 white_holding[strlen(white_holding)-1] = NULLCHAR;
3319 black_holding[strlen(black_holding)-1] = NULLCHAR;
3320 /* [HGM] copy holdings to board holdings area */
3321 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3322 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3323 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3325 if (appData.zippyPlay && first.initDone) {
3326 ZippyHoldings(white_holding, black_holding,
3330 if (tinyLayout || smallLayout) {
3331 char wh[16], bh[16];
3332 PackHolding(wh, white_holding);
3333 PackHolding(bh, black_holding);
3334 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3335 gameInfo.white, gameInfo.black);
3337 sprintf(str, "%s [%s] vs. %s [%s]",
3338 gameInfo.white, white_holding,
3339 gameInfo.black, black_holding);
3342 DrawPosition(FALSE, boards[currentMove]);
3345 /* Suppress following prompt */
3346 if (looking_at(buf, &i, "*% ")) {
3347 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3348 savingComment = FALSE;
3355 i++; /* skip unparsed character and loop back */
3358 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3359 started != STARTED_HOLDINGS && i > next_out) {
3360 SendToPlayer(&buf[next_out], i - next_out);
3363 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3365 leftover_len = buf_len - leftover_start;
3366 /* if buffer ends with something we couldn't parse,
3367 reparse it after appending the next read */
3369 } else if (count == 0) {
3370 RemoveInputSource(isr);
3371 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3373 DisplayFatalError(_("Error reading from ICS"), error, 1);
3378 /* Board style 12 looks like this:
3380 <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
3382 * The "<12> " is stripped before it gets to this routine. The two
3383 * trailing 0's (flip state and clock ticking) are later addition, and
3384 * some chess servers may not have them, or may have only the first.
3385 * Additional trailing fields may be added in the future.
3388 #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"
3390 #define RELATION_OBSERVING_PLAYED 0
3391 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3392 #define RELATION_PLAYING_MYMOVE 1
3393 #define RELATION_PLAYING_NOTMYMOVE -1
3394 #define RELATION_EXAMINING 2
3395 #define RELATION_ISOLATED_BOARD -3
3396 #define RELATION_STARTING_POSITION -4 /* FICS only */
3399 ParseBoard12(string)
3402 GameMode newGameMode;
3403 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3404 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3405 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3406 char to_play, board_chars[200];
3407 char move_str[500], str[500], elapsed_time[500];
3408 char black[32], white[32];
3410 int prevMove = currentMove;
3413 int fromX, fromY, toX, toY;
3415 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3416 char *bookHit = NULL; // [HGM] book
3417 Boolean weird = FALSE, reqFlag = FALSE;
3419 fromX = fromY = toX = toY = -1;
3423 if (appData.debugMode)
3424 fprintf(debugFP, _("Parsing board: %s\n"), string);
3426 move_str[0] = NULLCHAR;
3427 elapsed_time[0] = NULLCHAR;
3428 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3430 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3431 if(string[i] == ' ') { ranks++; files = 0; }
3433 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3436 for(j = 0; j <i; j++) board_chars[j] = string[j];
3437 board_chars[i] = '\0';
3440 n = sscanf(string, PATTERN, &to_play, &double_push,
3441 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3442 &gamenum, white, black, &relation, &basetime, &increment,
3443 &white_stren, &black_stren, &white_time, &black_time,
3444 &moveNum, str, elapsed_time, move_str, &ics_flip,
3448 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3449 DisplayError(str, 0);
3453 /* Convert the move number to internal form */
3454 moveNum = (moveNum - 1) * 2;
3455 if (to_play == 'B') moveNum++;
3456 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3457 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3463 case RELATION_OBSERVING_PLAYED:
3464 case RELATION_OBSERVING_STATIC:
3465 if (gamenum == -1) {
3466 /* Old ICC buglet */
3467 relation = RELATION_OBSERVING_STATIC;
3469 newGameMode = IcsObserving;
3471 case RELATION_PLAYING_MYMOVE:
3472 case RELATION_PLAYING_NOTMYMOVE:
3474 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3475 IcsPlayingWhite : IcsPlayingBlack;
3477 case RELATION_EXAMINING:
3478 newGameMode = IcsExamining;
3480 case RELATION_ISOLATED_BOARD:
3482 /* Just display this board. If user was doing something else,
3483 we will forget about it until the next board comes. */
3484 newGameMode = IcsIdle;
3486 case RELATION_STARTING_POSITION:
3487 newGameMode = gameMode;
3491 /* Modify behavior for initial board display on move listing
3494 switch (ics_getting_history) {
3498 case H_GOT_REQ_HEADER:
3499 case H_GOT_UNREQ_HEADER:
3500 /* This is the initial position of the current game */
3501 gamenum = ics_gamenum;
3502 moveNum = 0; /* old ICS bug workaround */
3503 if (to_play == 'B') {
3504 startedFromSetupPosition = TRUE;
3505 blackPlaysFirst = TRUE;
3507 if (forwardMostMove == 0) forwardMostMove = 1;
3508 if (backwardMostMove == 0) backwardMostMove = 1;
3509 if (currentMove == 0) currentMove = 1;
3511 newGameMode = gameMode;
3512 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3514 case H_GOT_UNWANTED_HEADER:
3515 /* This is an initial board that we don't want */
3517 case H_GETTING_MOVES:
3518 /* Should not happen */
3519 DisplayError(_("Error gathering move list: extra board"), 0);
3520 ics_getting_history = H_FALSE;
3524 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3525 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3526 /* [HGM] We seem to have switched variant unexpectedly
3527 * Try to guess new variant from board size
3529 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3530 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3531 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3532 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3533 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3534 if(!weird) newVariant = VariantNormal;
3535 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3536 /* Get a move list just to see the header, which
3537 will tell us whether this is really bug or zh */
3538 if (ics_getting_history == H_FALSE) {
3539 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3540 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3545 /* Take action if this is the first board of a new game, or of a
3546 different game than is currently being displayed. */
3547 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3548 relation == RELATION_ISOLATED_BOARD) {
3550 /* Forget the old game and get the history (if any) of the new one */
3551 if (gameMode != BeginningOfGame) {
3555 if (appData.autoRaiseBoard) BoardToTop();
3557 if (gamenum == -1) {
3558 newGameMode = IcsIdle;
3559 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3560 appData.getMoveList && !reqFlag) {
3561 /* Need to get game history */
3562 ics_getting_history = H_REQUESTED;
3563 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3567 /* Initially flip the board to have black on the bottom if playing
3568 black or if the ICS flip flag is set, but let the user change
3569 it with the Flip View button. */
3570 flipView = appData.autoFlipView ?
3571 (newGameMode == IcsPlayingBlack) || ics_flip :
3574 /* Done with values from previous mode; copy in new ones */
3575 gameMode = newGameMode;
3577 ics_gamenum = gamenum;
3578 if (gamenum == gs_gamenum) {
3579 int klen = strlen(gs_kind);
3580 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3581 sprintf(str, "ICS %s", gs_kind);
3582 gameInfo.event = StrSave(str);
3584 gameInfo.event = StrSave("ICS game");
3586 gameInfo.site = StrSave(appData.icsHost);
3587 gameInfo.date = PGNDate();
3588 gameInfo.round = StrSave("-");
3589 gameInfo.white = StrSave(white);
3590 gameInfo.black = StrSave(black);
3591 timeControl = basetime * 60 * 1000;
3593 timeIncrement = increment * 1000;
3594 movesPerSession = 0;
3595 gameInfo.timeControl = TimeControlTagValue();
3596 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3597 if (appData.debugMode) {
3598 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3599 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3600 setbuf(debugFP, NULL);
3603 gameInfo.outOfBook = NULL;
3605 /* Do we have the ratings? */
3606 if (strcmp(player1Name, white) == 0 &&
3607 strcmp(player2Name, black) == 0) {
3608 if (appData.debugMode)
3609 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3610 player1Rating, player2Rating);
3611 gameInfo.whiteRating = player1Rating;
3612 gameInfo.blackRating = player2Rating;
3613 } else if (strcmp(player2Name, white) == 0 &&
3614 strcmp(player1Name, black) == 0) {
3615 if (appData.debugMode)
3616 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3617 player2Rating, player1Rating);
3618 gameInfo.whiteRating = player2Rating;
3619 gameInfo.blackRating = player1Rating;
3621 player1Name[0] = player2Name[0] = NULLCHAR;
3623 /* Silence shouts if requested */
3624 if (appData.quietPlay &&
3625 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3626 SendToICS(ics_prefix);
3627 SendToICS("set shout 0\n");
3631 /* Deal with midgame name changes */
3633 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3634 if (gameInfo.white) free(gameInfo.white);
3635 gameInfo.white = StrSave(white);
3637 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3638 if (gameInfo.black) free(gameInfo.black);
3639 gameInfo.black = StrSave(black);
3643 /* Throw away game result if anything actually changes in examine mode */
3644 if (gameMode == IcsExamining && !newGame) {
3645 gameInfo.result = GameUnfinished;
3646 if (gameInfo.resultDetails != NULL) {
3647 free(gameInfo.resultDetails);
3648 gameInfo.resultDetails = NULL;
3652 /* In pausing && IcsExamining mode, we ignore boards coming
3653 in if they are in a different variation than we are. */
3654 if (pauseExamInvalid) return;
3655 if (pausing && gameMode == IcsExamining) {
3656 if (moveNum <= pauseExamForwardMostMove) {
3657 pauseExamInvalid = TRUE;
3658 forwardMostMove = pauseExamForwardMostMove;
3663 if (appData.debugMode) {
3664 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3666 /* Parse the board */
3667 for (k = 0; k < ranks; k++) {
3668 for (j = 0; j < files; j++)
3669 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3670 if(gameInfo.holdingsWidth > 1) {
3671 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3672 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3675 CopyBoard(boards[moveNum], board);
3676 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3678 startedFromSetupPosition =
3679 !CompareBoards(board, initialPosition);
3680 if(startedFromSetupPosition)
3681 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3684 /* [HGM] Set castling rights. Take the outermost Rooks,
3685 to make it also work for FRC opening positions. Note that board12
3686 is really defective for later FRC positions, as it has no way to
3687 indicate which Rook can castle if they are on the same side of King.
3688 For the initial position we grant rights to the outermost Rooks,
3689 and remember thos rights, and we then copy them on positions
3690 later in an FRC game. This means WB might not recognize castlings with
3691 Rooks that have moved back to their original position as illegal,
3692 but in ICS mode that is not its job anyway.
3694 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3695 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3697 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3698 if(board[0][i] == WhiteRook) j = i;
3699 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3700 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3701 if(board[0][i] == WhiteRook) j = i;
3702 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3703 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3704 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3705 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3706 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3707 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3708 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3710 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3711 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3713 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3714 if(board[BOARD_HEIGHT-1][k] == bKing)
3715 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3717 r = boards[moveNum][CASTLING][0] = initialRights[0];
3718 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3719 r = boards[moveNum][CASTLING][1] = initialRights[1];
3720 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3721 r = boards[moveNum][CASTLING][3] = initialRights[3];
3722 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3723 r = boards[moveNum][CASTLING][4] = initialRights[4];
3724 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3725 /* wildcastle kludge: always assume King has rights */
3726 r = boards[moveNum][CASTLING][2] = initialRights[2];
3727 r = boards[moveNum][CASTLING][5] = initialRights[5];
3729 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3730 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3733 if (ics_getting_history == H_GOT_REQ_HEADER ||
3734 ics_getting_history == H_GOT_UNREQ_HEADER) {
3735 /* This was an initial position from a move list, not
3736 the current position */
3740 /* Update currentMove and known move number limits */
3741 newMove = newGame || moveNum > forwardMostMove;
3744 forwardMostMove = backwardMostMove = currentMove = moveNum;
3745 if (gameMode == IcsExamining && moveNum == 0) {
3746 /* Workaround for ICS limitation: we are not told the wild
3747 type when starting to examine a game. But if we ask for
3748 the move list, the move list header will tell us */
3749 ics_getting_history = H_REQUESTED;
3750 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3753 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3754 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3756 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3757 /* [HGM] applied this also to an engine that is silently watching */
3758 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3759 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3760 gameInfo.variant == currentlyInitializedVariant) {
3761 takeback = forwardMostMove - moveNum;
3762 for (i = 0; i < takeback; i++) {
3763 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3764 SendToProgram("undo\n", &first);
3769 forwardMostMove = moveNum;
3770 if (!pausing || currentMove > forwardMostMove)
3771 currentMove = forwardMostMove;
3773 /* New part of history that is not contiguous with old part */
3774 if (pausing && gameMode == IcsExamining) {
3775 pauseExamInvalid = TRUE;
3776 forwardMostMove = pauseExamForwardMostMove;
3779 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3781 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3782 // [HGM] when we will receive the move list we now request, it will be
3783 // fed to the engine from the first move on. So if the engine is not
3784 // in the initial position now, bring it there.
3785 InitChessProgram(&first, 0);
3788 ics_getting_history = H_REQUESTED;
3789 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3792 forwardMostMove = backwardMostMove = currentMove = moveNum;
3795 /* Update the clocks */
3796 if (strchr(elapsed_time, '.')) {
3798 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3799 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3801 /* Time is in seconds */
3802 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3803 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3808 if (appData.zippyPlay && newGame &&
3809 gameMode != IcsObserving && gameMode != IcsIdle &&
3810 gameMode != IcsExamining)
3811 ZippyFirstBoard(moveNum, basetime, increment);
3814 /* Put the move on the move list, first converting
3815 to canonical algebraic form. */
3817 if (appData.debugMode) {
3818 if (appData.debugMode) { int f = forwardMostMove;
3819 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3820 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3821 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3823 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3824 fprintf(debugFP, "moveNum = %d\n", moveNum);
3825 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3826 setbuf(debugFP, NULL);
3828 if (moveNum <= backwardMostMove) {
3829 /* We don't know what the board looked like before
3831 strcpy(parseList[moveNum - 1], move_str);
3832 strcat(parseList[moveNum - 1], " ");
3833 strcat(parseList[moveNum - 1], elapsed_time);
3834 moveList[moveNum - 1][0] = NULLCHAR;
3835 } else if (strcmp(move_str, "none") == 0) {
3836 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3837 /* Again, we don't know what the board looked like;
3838 this is really the start of the game. */
3839 parseList[moveNum - 1][0] = NULLCHAR;
3840 moveList[moveNum - 1][0] = NULLCHAR;
3841 backwardMostMove = moveNum;
3842 startedFromSetupPosition = TRUE;
3843 fromX = fromY = toX = toY = -1;
3845 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3846 // So we parse the long-algebraic move string in stead of the SAN move
3847 int valid; char buf[MSG_SIZ], *prom;
3849 // str looks something like "Q/a1-a2"; kill the slash
3851 sprintf(buf, "%c%s", str[0], str+2);
3852 else strcpy(buf, str); // might be castling
3853 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3854 strcat(buf, prom); // long move lacks promo specification!
3855 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3856 if(appData.debugMode)
3857 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3858 strcpy(move_str, buf);
3860 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3861 &fromX, &fromY, &toX, &toY, &promoChar)
3862 || ParseOneMove(buf, moveNum - 1, &moveType,
3863 &fromX, &fromY, &toX, &toY, &promoChar);
3864 // end of long SAN patch
3866 (void) CoordsToAlgebraic(boards[moveNum - 1],
3867 PosFlags(moveNum - 1),
3868 fromY, fromX, toY, toX, promoChar,
3869 parseList[moveNum-1]);
3870 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3876 if(gameInfo.variant != VariantShogi)
3877 strcat(parseList[moveNum - 1], "+");
3880 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3881 strcat(parseList[moveNum - 1], "#");
3884 strcat(parseList[moveNum - 1], " ");
3885 strcat(parseList[moveNum - 1], elapsed_time);
3886 /* currentMoveString is set as a side-effect of ParseOneMove */
3887 strcpy(moveList[moveNum - 1], currentMoveString);
3888 strcat(moveList[moveNum - 1], "\n");
3890 /* Move from ICS was illegal!? Punt. */
3891 if (appData.debugMode) {
3892 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3893 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3895 strcpy(parseList[moveNum - 1], move_str);
3896 strcat(parseList[moveNum - 1], " ");
3897 strcat(parseList[moveNum - 1], elapsed_time);
3898 moveList[moveNum - 1][0] = NULLCHAR;
3899 fromX = fromY = toX = toY = -1;
3902 if (appData.debugMode) {
3903 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3904 setbuf(debugFP, NULL);
3908 /* Send move to chess program (BEFORE animating it). */
3909 if (appData.zippyPlay && !newGame && newMove &&
3910 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3912 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3913 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3914 if (moveList[moveNum - 1][0] == NULLCHAR) {
3915 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3917 DisplayError(str, 0);
3919 if (first.sendTime) {
3920 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3922 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3923 if (firstMove && !bookHit) {
3925 if (first.useColors) {
3926 SendToProgram(gameMode == IcsPlayingWhite ?
3928 "black\ngo\n", &first);
3930 SendToProgram("go\n", &first);
3932 first.maybeThinking = TRUE;
3935 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3936 if (moveList[moveNum - 1][0] == NULLCHAR) {
3937 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3938 DisplayError(str, 0);
3940 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3941 SendMoveToProgram(moveNum - 1, &first);
3948 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3949 /* If move comes from a remote source, animate it. If it
3950 isn't remote, it will have already been animated. */
3951 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3952 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3954 if (!pausing && appData.highlightLastMove) {
3955 SetHighlights(fromX, fromY, toX, toY);
3959 /* Start the clocks */
3960 whiteFlag = blackFlag = FALSE;
3961 appData.clockMode = !(basetime == 0 && increment == 0);
3963 ics_clock_paused = TRUE;
3965 } else if (ticking == 1) {
3966 ics_clock_paused = FALSE;
3968 if (gameMode == IcsIdle ||
3969 relation == RELATION_OBSERVING_STATIC ||
3970 relation == RELATION_EXAMINING ||
3972 DisplayBothClocks();
3976 /* Display opponents and material strengths */
3977 if (gameInfo.variant != VariantBughouse &&
3978 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3979 if (tinyLayout || smallLayout) {
3980 if(gameInfo.variant == VariantNormal)
3981 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3982 gameInfo.white, white_stren, gameInfo.black, black_stren,
3983 basetime, increment);
3985 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3986 gameInfo.white, white_stren, gameInfo.black, black_stren,
3987 basetime, increment, (int) gameInfo.variant);
3989 if(gameInfo.variant == VariantNormal)
3990 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3991 gameInfo.white, white_stren, gameInfo.black, black_stren,
3992 basetime, increment);
3994 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3995 gameInfo.white, white_stren, gameInfo.black, black_stren,
3996 basetime, increment, VariantName(gameInfo.variant));
3999 if (appData.debugMode) {
4000 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4005 /* Display the board */
4006 if (!pausing && !appData.noGUI) {
4008 if (appData.premove)
4010 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4011 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4012 ClearPremoveHighlights();
4014 DrawPosition(FALSE, boards[currentMove]);
4015 DisplayMove(moveNum - 1);
4016 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4017 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4018 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4019 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4023 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4025 if(bookHit) { // [HGM] book: simulate book reply
4026 static char bookMove[MSG_SIZ]; // a bit generous?
4028 programStats.nodes = programStats.depth = programStats.time =
4029 programStats.score = programStats.got_only_move = 0;
4030 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4032 strcpy(bookMove, "move ");
4033 strcat(bookMove, bookHit);
4034 HandleMachineMove(bookMove, &first);
4043 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4044 ics_getting_history = H_REQUESTED;
4045 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4051 AnalysisPeriodicEvent(force)
4054 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4055 && !force) || !appData.periodicUpdates)
4058 /* Send . command to Crafty to collect stats */
4059 SendToProgram(".\n", &first);
4061 /* Don't send another until we get a response (this makes
4062 us stop sending to old Crafty's which don't understand
4063 the "." command (sending illegal cmds resets node count & time,
4064 which looks bad)) */
4065 programStats.ok_to_send = 0;
4068 void ics_update_width(new_width)
4071 ics_printf("set width %d\n", new_width);
4075 SendMoveToProgram(moveNum, cps)
4077 ChessProgramState *cps;
4081 if (cps->useUsermove) {
4082 SendToProgram("usermove ", cps);
4086 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4087 int len = space - parseList[moveNum];
4088 memcpy(buf, parseList[moveNum], len);
4090 buf[len] = NULLCHAR;
4092 sprintf(buf, "%s\n", parseList[moveNum]);
4094 SendToProgram(buf, cps);
4096 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4097 AlphaRank(moveList[moveNum], 4);
4098 SendToProgram(moveList[moveNum], cps);
4099 AlphaRank(moveList[moveNum], 4); // and back
4101 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4102 * the engine. It would be nice to have a better way to identify castle
4104 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4105 && cps->useOOCastle) {
4106 int fromX = moveList[moveNum][0] - AAA;
4107 int fromY = moveList[moveNum][1] - ONE;
4108 int toX = moveList[moveNum][2] - AAA;
4109 int toY = moveList[moveNum][3] - ONE;
4110 if((boards[moveNum][fromY][fromX] == WhiteKing
4111 && boards[moveNum][toY][toX] == WhiteRook)
4112 || (boards[moveNum][fromY][fromX] == BlackKing
4113 && boards[moveNum][toY][toX] == BlackRook)) {
4114 if(toX > fromX) SendToProgram("O-O\n", cps);
4115 else SendToProgram("O-O-O\n", cps);
4117 else SendToProgram(moveList[moveNum], cps);
4119 else SendToProgram(moveList[moveNum], cps);
4120 /* End of additions by Tord */
4123 /* [HGM] setting up the opening has brought engine in force mode! */
4124 /* Send 'go' if we are in a mode where machine should play. */
4125 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4126 (gameMode == TwoMachinesPlay ||
4128 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4130 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4131 SendToProgram("go\n", cps);
4132 if (appData.debugMode) {
4133 fprintf(debugFP, "(extra)\n");
4136 setboardSpoiledMachineBlack = 0;
4140 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4142 int fromX, fromY, toX, toY;
4144 char user_move[MSG_SIZ];
4148 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4149 (int)moveType, fromX, fromY, toX, toY);
4150 DisplayError(user_move + strlen("say "), 0);
4152 case WhiteKingSideCastle:
4153 case BlackKingSideCastle:
4154 case WhiteQueenSideCastleWild:
4155 case BlackQueenSideCastleWild:
4157 case WhiteHSideCastleFR:
4158 case BlackHSideCastleFR:
4160 sprintf(user_move, "o-o\n");
4162 case WhiteQueenSideCastle:
4163 case BlackQueenSideCastle:
4164 case WhiteKingSideCastleWild:
4165 case BlackKingSideCastleWild:
4167 case WhiteASideCastleFR:
4168 case BlackASideCastleFR:
4170 sprintf(user_move, "o-o-o\n");
4172 case WhitePromotionQueen:
4173 case BlackPromotionQueen:
4174 case WhitePromotionRook:
4175 case BlackPromotionRook:
4176 case WhitePromotionBishop:
4177 case BlackPromotionBishop:
4178 case WhitePromotionKnight:
4179 case BlackPromotionKnight:
4180 case WhitePromotionKing:
4181 case BlackPromotionKing:
4182 case WhitePromotionChancellor:
4183 case BlackPromotionChancellor:
4184 case WhitePromotionArchbishop:
4185 case BlackPromotionArchbishop:
4186 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4187 sprintf(user_move, "%c%c%c%c=%c\n",
4188 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189 PieceToChar(WhiteFerz));
4190 else if(gameInfo.variant == VariantGreat)
4191 sprintf(user_move, "%c%c%c%c=%c\n",
4192 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193 PieceToChar(WhiteMan));
4195 sprintf(user_move, "%c%c%c%c=%c\n",
4196 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4197 PieceToChar(PromoPiece(moveType)));
4201 sprintf(user_move, "%c@%c%c\n",
4202 ToUpper(PieceToChar((ChessSquare) fromX)),
4203 AAA + toX, ONE + toY);
4206 case WhiteCapturesEnPassant:
4207 case BlackCapturesEnPassant:
4208 case IllegalMove: /* could be a variant we don't quite understand */
4209 sprintf(user_move, "%c%c%c%c\n",
4210 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4213 SendToICS(user_move);
4214 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4215 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4219 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4224 if (rf == DROP_RANK) {
4225 sprintf(move, "%c@%c%c\n",
4226 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4228 if (promoChar == 'x' || promoChar == NULLCHAR) {
4229 sprintf(move, "%c%c%c%c\n",
4230 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4232 sprintf(move, "%c%c%c%c%c\n",
4233 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4239 ProcessICSInitScript(f)
4244 while (fgets(buf, MSG_SIZ, f)) {
4245 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4252 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4254 AlphaRank(char *move, int n)
4256 // char *p = move, c; int x, y;
4258 if (appData.debugMode) {
4259 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4263 move[2]>='0' && move[2]<='9' &&
4264 move[3]>='a' && move[3]<='x' ) {
4266 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4267 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4269 if(move[0]>='0' && move[0]<='9' &&
4270 move[1]>='a' && move[1]<='x' &&
4271 move[2]>='0' && move[2]<='9' &&
4272 move[3]>='a' && move[3]<='x' ) {
4273 /* input move, Shogi -> normal */
4274 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4275 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4276 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4277 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4280 move[3]>='0' && move[3]<='9' &&
4281 move[2]>='a' && move[2]<='x' ) {
4283 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4284 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4287 move[0]>='a' && move[0]<='x' &&
4288 move[3]>='0' && move[3]<='9' &&
4289 move[2]>='a' && move[2]<='x' ) {
4290 /* output move, normal -> Shogi */
4291 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4292 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4293 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4294 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4295 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4297 if (appData.debugMode) {
4298 fprintf(debugFP, " out = '%s'\n", move);
4302 /* Parser for moves from gnuchess, ICS, or user typein box */
4304 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4307 ChessMove *moveType;
4308 int *fromX, *fromY, *toX, *toY;
4311 if (appData.debugMode) {
4312 fprintf(debugFP, "move to parse: %s\n", move);
4314 *moveType = yylexstr(moveNum, move);
4316 switch (*moveType) {
4317 case WhitePromotionChancellor:
4318 case BlackPromotionChancellor:
4319 case WhitePromotionArchbishop:
4320 case BlackPromotionArchbishop:
4321 case WhitePromotionQueen:
4322 case BlackPromotionQueen:
4323 case WhitePromotionRook:
4324 case BlackPromotionRook:
4325 case WhitePromotionBishop:
4326 case BlackPromotionBishop:
4327 case WhitePromotionKnight:
4328 case BlackPromotionKnight:
4329 case WhitePromotionKing:
4330 case BlackPromotionKing:
4332 case WhiteCapturesEnPassant:
4333 case BlackCapturesEnPassant:
4334 case WhiteKingSideCastle:
4335 case WhiteQueenSideCastle:
4336 case BlackKingSideCastle:
4337 case BlackQueenSideCastle:
4338 case WhiteKingSideCastleWild:
4339 case WhiteQueenSideCastleWild:
4340 case BlackKingSideCastleWild:
4341 case BlackQueenSideCastleWild:
4342 /* Code added by Tord: */
4343 case WhiteHSideCastleFR:
4344 case WhiteASideCastleFR:
4345 case BlackHSideCastleFR:
4346 case BlackASideCastleFR:
4347 /* End of code added by Tord */
4348 case IllegalMove: /* bug or odd chess variant */
4349 *fromX = currentMoveString[0] - AAA;
4350 *fromY = currentMoveString[1] - ONE;
4351 *toX = currentMoveString[2] - AAA;
4352 *toY = currentMoveString[3] - ONE;
4353 *promoChar = currentMoveString[4];
4354 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4355 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4356 if (appData.debugMode) {
4357 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4359 *fromX = *fromY = *toX = *toY = 0;
4362 if (appData.testLegality) {
4363 return (*moveType != IllegalMove);
4365 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4366 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4371 *fromX = *moveType == WhiteDrop ?
4372 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4373 (int) CharToPiece(ToLower(currentMoveString[0]));
4375 *toX = currentMoveString[2] - AAA;
4376 *toY = currentMoveString[3] - ONE;
4377 *promoChar = NULLCHAR;
4381 case ImpossibleMove:
4382 case (ChessMove) 0: /* end of file */
4391 if (appData.debugMode) {
4392 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4395 *fromX = *fromY = *toX = *toY = 0;
4396 *promoChar = NULLCHAR;
4404 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4405 int fromX, fromY, toX, toY; char promoChar;
4410 endPV = forwardMostMove;
4412 while(*pv == ' ') pv++;
4413 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4414 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4415 if(appData.debugMode){
4416 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4418 if(!valid && nr == 0 &&
4419 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4420 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4422 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4423 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4425 if(endPV+1 > framePtr) break; // no space, truncate
4428 CopyBoard(boards[endPV], boards[endPV-1]);
4429 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4430 moveList[endPV-1][0] = fromX + AAA;
4431 moveList[endPV-1][1] = fromY + ONE;
4432 moveList[endPV-1][2] = toX + AAA;
4433 moveList[endPV-1][3] = toY + ONE;
4434 parseList[endPV-1][0] = NULLCHAR;
4438 static int lastX, lastY;
4441 LoadPV(int x, int y)
4442 { // called on right mouse click to load PV
4443 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4444 lastX = x; lastY = y;
4445 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4446 currentMove = endPV;
4447 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4448 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4449 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4450 DrawPosition(TRUE, boards[currentMove]);
4457 if(endPV < 0) return;
4459 currentMove = forwardMostMove;
4460 ClearPremoveHighlights();
4461 DrawPosition(TRUE, boards[currentMove]);
4465 MovePV(int x, int y, int h)
4466 { // step through PV based on mouse coordinates (called on mouse move)
4467 int margin = h>>3, step = 0;
4469 if(endPV < 0) return;
4470 // we must somehow check if right button is still down (might be released off board!)
4471 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4472 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4473 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4475 lastX = x; lastY = y;
4476 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4477 currentMove += step;
4478 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4479 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4480 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4481 DrawPosition(FALSE, boards[currentMove]);
4485 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4486 // All positions will have equal probability, but the current method will not provide a unique
4487 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4493 int piecesLeft[(int)BlackPawn];
4494 int seed, nrOfShuffles;
4496 void GetPositionNumber()
4497 { // sets global variable seed
4500 seed = appData.defaultFrcPosition;
4501 if(seed < 0) { // randomize based on time for negative FRC position numbers
4502 for(i=0; i<50; i++) seed += random();
4503 seed = random() ^ random() >> 8 ^ random() << 8;
4504 if(seed<0) seed = -seed;
4508 int put(Board board, int pieceType, int rank, int n, int shade)
4509 // put the piece on the (n-1)-th empty squares of the given shade
4513 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4514 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4515 board[rank][i] = (ChessSquare) pieceType;
4516 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4518 piecesLeft[pieceType]--;
4526 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4527 // calculate where the next piece goes, (any empty square), and put it there
4531 i = seed % squaresLeft[shade];
4532 nrOfShuffles *= squaresLeft[shade];
4533 seed /= squaresLeft[shade];
4534 put(board, pieceType, rank, i, shade);
4537 void AddTwoPieces(Board board, int pieceType, int rank)
4538 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4540 int i, n=squaresLeft[ANY], j=n-1, k;
4542 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4543 i = seed % k; // pick one
4546 while(i >= j) i -= j--;
4547 j = n - 1 - j; i += j;
4548 put(board, pieceType, rank, j, ANY);
4549 put(board, pieceType, rank, i, ANY);
4552 void SetUpShuffle(Board board, int number)
4556 GetPositionNumber(); nrOfShuffles = 1;
4558 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4559 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4560 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4562 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4564 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4565 p = (int) board[0][i];
4566 if(p < (int) BlackPawn) piecesLeft[p] ++;
4567 board[0][i] = EmptySquare;
4570 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4571 // shuffles restricted to allow normal castling put KRR first
4572 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4573 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4574 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4575 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4576 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4577 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4578 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4579 put(board, WhiteRook, 0, 0, ANY);
4580 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4583 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4584 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4585 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4586 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4587 while(piecesLeft[p] >= 2) {
4588 AddOnePiece(board, p, 0, LITE);
4589 AddOnePiece(board, p, 0, DARK);
4591 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4594 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4595 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4596 // but we leave King and Rooks for last, to possibly obey FRC restriction
4597 if(p == (int)WhiteRook) continue;
4598 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4599 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4602 // now everything is placed, except perhaps King (Unicorn) and Rooks
4604 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4605 // Last King gets castling rights
4606 while(piecesLeft[(int)WhiteUnicorn]) {
4607 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4608 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4611 while(piecesLeft[(int)WhiteKing]) {
4612 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4613 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4618 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4619 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4622 // Only Rooks can be left; simply place them all
4623 while(piecesLeft[(int)WhiteRook]) {
4624 i = put(board, WhiteRook, 0, 0, ANY);
4625 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4628 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4630 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4633 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4634 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4637 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4640 int SetCharTable( char *table, const char * map )
4641 /* [HGM] moved here from winboard.c because of its general usefulness */
4642 /* Basically a safe strcpy that uses the last character as King */
4644 int result = FALSE; int NrPieces;
4646 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4647 && NrPieces >= 12 && !(NrPieces&1)) {
4648 int i; /* [HGM] Accept even length from 12 to 34 */
4650 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4651 for( i=0; i<NrPieces/2-1; i++ ) {
4653 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4655 table[(int) WhiteKing] = map[NrPieces/2-1];
4656 table[(int) BlackKing] = map[NrPieces-1];
4664 void Prelude(Board board)
4665 { // [HGM] superchess: random selection of exo-pieces
4666 int i, j, k; ChessSquare p;
4667 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4669 GetPositionNumber(); // use FRC position number
4671 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4672 SetCharTable(pieceToChar, appData.pieceToCharTable);
4673 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4674 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4677 j = seed%4; seed /= 4;
4678 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4679 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4680 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4681 j = seed%3 + (seed%3 >= j); seed /= 3;
4682 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4683 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4684 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4685 j = seed%3; seed /= 3;
4686 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4687 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4688 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4689 j = seed%2 + (seed%2 >= j); seed /= 2;
4690 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4691 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4692 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4693 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4694 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4695 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4696 put(board, exoPieces[0], 0, 0, ANY);
4697 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4701 InitPosition(redraw)
4704 ChessSquare (* pieces)[BOARD_FILES];
4705 int i, j, pawnRow, overrule,
4706 oldx = gameInfo.boardWidth,
4707 oldy = gameInfo.boardHeight,
4708 oldh = gameInfo.holdingsWidth,
4709 oldv = gameInfo.variant;
4711 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4713 /* [AS] Initialize pv info list [HGM] and game status */
4715 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4716 pvInfoList[i].depth = 0;
4717 boards[i][EP_STATUS] = EP_NONE;
4718 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4721 initialRulePlies = 0; /* 50-move counter start */
4723 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4724 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4728 /* [HGM] logic here is completely changed. In stead of full positions */
4729 /* the initialized data only consist of the two backranks. The switch */
4730 /* selects which one we will use, which is than copied to the Board */
4731 /* initialPosition, which for the rest is initialized by Pawns and */
4732 /* empty squares. This initial position is then copied to boards[0], */
4733 /* possibly after shuffling, so that it remains available. */
4735 gameInfo.holdingsWidth = 0; /* default board sizes */
4736 gameInfo.boardWidth = 8;
4737 gameInfo.boardHeight = 8;
4738 gameInfo.holdingsSize = 0;
4739 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4740 for(i=0; i<BOARD_FILES-2; i++)
4741 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4742 initialPosition[EP_STATUS] = EP_NONE;
4743 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4745 switch (gameInfo.variant) {
4746 case VariantFischeRandom:
4747 shuffleOpenings = TRUE;
4751 case VariantShatranj:
4752 pieces = ShatranjArray;
4753 nrCastlingRights = 0;
4754 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4756 case VariantTwoKings:
4757 pieces = twoKingsArray;
4759 case VariantCapaRandom:
4760 shuffleOpenings = TRUE;
4761 case VariantCapablanca:
4762 pieces = CapablancaArray;
4763 gameInfo.boardWidth = 10;
4764 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4767 pieces = GothicArray;
4768 gameInfo.boardWidth = 10;
4769 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4772 pieces = JanusArray;
4773 gameInfo.boardWidth = 10;
4774 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4775 nrCastlingRights = 6;
4776 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4777 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4778 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4779 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4780 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4781 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4784 pieces = FalconArray;
4785 gameInfo.boardWidth = 10;
4786 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4788 case VariantXiangqi:
4789 pieces = XiangqiArray;
4790 gameInfo.boardWidth = 9;
4791 gameInfo.boardHeight = 10;
4792 nrCastlingRights = 0;
4793 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4796 pieces = ShogiArray;
4797 gameInfo.boardWidth = 9;
4798 gameInfo.boardHeight = 9;
4799 gameInfo.holdingsSize = 7;
4800 nrCastlingRights = 0;
4801 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4803 case VariantCourier:
4804 pieces = CourierArray;
4805 gameInfo.boardWidth = 12;
4806 nrCastlingRights = 0;
4807 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4809 case VariantKnightmate:
4810 pieces = KnightmateArray;
4811 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4814 pieces = fairyArray;
4815 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4818 pieces = GreatArray;
4819 gameInfo.boardWidth = 10;
4820 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4821 gameInfo.holdingsSize = 8;
4825 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4826 gameInfo.holdingsSize = 8;
4827 startedFromSetupPosition = TRUE;
4829 case VariantCrazyhouse:
4830 case VariantBughouse:
4832 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4833 gameInfo.holdingsSize = 5;
4835 case VariantWildCastle:
4837 /* !!?shuffle with kings guaranteed to be on d or e file */
4838 shuffleOpenings = 1;
4840 case VariantNoCastle:
4842 nrCastlingRights = 0;
4843 /* !!?unconstrained back-rank shuffle */
4844 shuffleOpenings = 1;
4849 if(appData.NrFiles >= 0) {
4850 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4851 gameInfo.boardWidth = appData.NrFiles;
4853 if(appData.NrRanks >= 0) {
4854 gameInfo.boardHeight = appData.NrRanks;
4856 if(appData.holdingsSize >= 0) {
4857 i = appData.holdingsSize;
4858 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4859 gameInfo.holdingsSize = i;
4861 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4862 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4863 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4865 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4866 if(pawnRow < 1) pawnRow = 1;
4868 /* User pieceToChar list overrules defaults */
4869 if(appData.pieceToCharTable != NULL)
4870 SetCharTable(pieceToChar, appData.pieceToCharTable);
4872 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4874 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4875 s = (ChessSquare) 0; /* account holding counts in guard band */
4876 for( i=0; i<BOARD_HEIGHT; i++ )
4877 initialPosition[i][j] = s;
4879 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4880 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4881 initialPosition[pawnRow][j] = WhitePawn;
4882 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4883 if(gameInfo.variant == VariantXiangqi) {
4885 initialPosition[pawnRow][j] =
4886 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4887 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4888 initialPosition[2][j] = WhiteCannon;
4889 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4893 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4895 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4898 initialPosition[1][j] = WhiteBishop;
4899 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4901 initialPosition[1][j] = WhiteRook;
4902 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4905 if( nrCastlingRights == -1) {
4906 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4907 /* This sets default castling rights from none to normal corners */
4908 /* Variants with other castling rights must set them themselves above */
4909 nrCastlingRights = 6;
4911 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4912 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4913 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4914 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4915 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4916 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4919 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4920 if(gameInfo.variant == VariantGreat) { // promotion commoners
4921 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4922 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4923 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4924 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4926 if (appData.debugMode) {
4927 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4929 if(shuffleOpenings) {
4930 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4931 startedFromSetupPosition = TRUE;
4933 if(startedFromPositionFile) {
4934 /* [HGM] loadPos: use PositionFile for every new game */
4935 CopyBoard(initialPosition, filePosition);
4936 for(i=0; i<nrCastlingRights; i++)
4937 initialRights[i] = filePosition[CASTLING][i];
4938 startedFromSetupPosition = TRUE;
4941 CopyBoard(boards[0], initialPosition);
4943 if(oldx != gameInfo.boardWidth ||
4944 oldy != gameInfo.boardHeight ||
4945 oldh != gameInfo.holdingsWidth
4947 || oldv == VariantGothic || // For licensing popups
4948 gameInfo.variant == VariantGothic
4951 || oldv == VariantFalcon ||
4952 gameInfo.variant == VariantFalcon
4955 InitDrawingSizes(-2 ,0);
4958 DrawPosition(TRUE, boards[currentMove]);
4962 SendBoard(cps, moveNum)
4963 ChessProgramState *cps;
4966 char message[MSG_SIZ];
4968 if (cps->useSetboard) {
4969 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4970 sprintf(message, "setboard %s\n", fen);
4971 SendToProgram(message, cps);
4977 /* Kludge to set black to move, avoiding the troublesome and now
4978 * deprecated "black" command.
4980 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4982 SendToProgram("edit\n", cps);
4983 SendToProgram("#\n", cps);
4984 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4985 bp = &boards[moveNum][i][BOARD_LEFT];
4986 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4987 if ((int) *bp < (int) BlackPawn) {
4988 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4990 if(message[0] == '+' || message[0] == '~') {
4991 sprintf(message, "%c%c%c+\n",
4992 PieceToChar((ChessSquare)(DEMOTED *bp)),
4995 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4996 message[1] = BOARD_RGHT - 1 - j + '1';
4997 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4999 SendToProgram(message, cps);
5004 SendToProgram("c\n", cps);
5005 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5006 bp = &boards[moveNum][i][BOARD_LEFT];
5007 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5008 if (((int) *bp != (int) EmptySquare)
5009 && ((int) *bp >= (int) BlackPawn)) {
5010 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5012 if(message[0] == '+' || message[0] == '~') {
5013 sprintf(message, "%c%c%c+\n",
5014 PieceToChar((ChessSquare)(DEMOTED *bp)),
5017 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5018 message[1] = BOARD_RGHT - 1 - j + '1';
5019 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5021 SendToProgram(message, cps);
5026 SendToProgram(".\n", cps);
5028 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5032 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5034 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5035 /* [HGM] add Shogi promotions */
5036 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5041 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5042 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5044 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5045 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5048 piece = boards[currentMove][fromY][fromX];
5049 if(gameInfo.variant == VariantShogi) {
5050 promotionZoneSize = 3;
5051 highestPromotingPiece = (int)WhiteFerz;
5054 // next weed out all moves that do not touch the promotion zone at all
5055 if((int)piece >= BlackPawn) {
5056 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5058 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5060 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5061 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5064 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5066 // weed out mandatory Shogi promotions
5067 if(gameInfo.variant == VariantShogi) {
5068 if(piece >= BlackPawn) {
5069 if(toY == 0 && piece == BlackPawn ||
5070 toY == 0 && piece == BlackQueen ||
5071 toY <= 1 && piece == BlackKnight) {
5076 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5077 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5078 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5085 // weed out obviously illegal Pawn moves
5086 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5087 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5088 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5089 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5090 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5091 // note we are not allowed to test for valid (non-)capture, due to premove
5094 // we either have a choice what to promote to, or (in Shogi) whether to promote
5095 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5096 *promoChoice = PieceToChar(BlackFerz); // no choice
5099 if(appData.alwaysPromoteToQueen) { // predetermined
5100 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5101 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5102 else *promoChoice = PieceToChar(BlackQueen);
5106 // suppress promotion popup on illegal moves that are not premoves
5107 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5108 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5109 if(appData.testLegality && !premove) {
5110 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5111 fromY, fromX, toY, toX, NULLCHAR);
5112 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5113 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5121 InPalace(row, column)
5123 { /* [HGM] for Xiangqi */
5124 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5125 column < (BOARD_WIDTH + 4)/2 &&
5126 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5131 PieceForSquare (x, y)
5135 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5138 return boards[currentMove][y][x];
5142 OKToStartUserMove(x, y)
5145 ChessSquare from_piece;
5148 if (matchMode) return FALSE;
5149 if (gameMode == EditPosition) return TRUE;
5151 if (x >= 0 && y >= 0)
5152 from_piece = boards[currentMove][y][x];
5154 from_piece = EmptySquare;
5156 if (from_piece == EmptySquare) return FALSE;
5158 white_piece = (int)from_piece >= (int)WhitePawn &&
5159 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5162 case PlayFromGameFile:
5164 case TwoMachinesPlay:
5172 case MachinePlaysWhite:
5173 case IcsPlayingBlack:
5174 if (appData.zippyPlay) return FALSE;
5176 DisplayMoveError(_("You are playing Black"));
5181 case MachinePlaysBlack:
5182 case IcsPlayingWhite:
5183 if (appData.zippyPlay) return FALSE;
5185 DisplayMoveError(_("You are playing White"));
5191 if (!white_piece && WhiteOnMove(currentMove)) {
5192 DisplayMoveError(_("It is White's turn"));
5195 if (white_piece && !WhiteOnMove(currentMove)) {
5196 DisplayMoveError(_("It is Black's turn"));
5199 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5200 /* Editing correspondence game history */
5201 /* Could disallow this or prompt for confirmation */
5206 case BeginningOfGame:
5207 if (appData.icsActive) return FALSE;
5208 if (!appData.noChessProgram) {
5210 DisplayMoveError(_("You are playing White"));
5217 if (!white_piece && WhiteOnMove(currentMove)) {
5218 DisplayMoveError(_("It is White's turn"));
5221 if (white_piece && !WhiteOnMove(currentMove)) {
5222 DisplayMoveError(_("It is Black's turn"));
5231 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5232 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5233 && gameMode != AnalyzeFile && gameMode != Training) {
5234 DisplayMoveError(_("Displayed position is not current"));
5240 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5241 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5242 int lastLoadGameUseList = FALSE;
5243 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5244 ChessMove lastLoadGameStart = (ChessMove) 0;
5247 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5248 int fromX, fromY, toX, toY;
5253 ChessSquare pdown, pup;
5255 /* Check if the user is playing in turn. This is complicated because we
5256 let the user "pick up" a piece before it is his turn. So the piece he
5257 tried to pick up may have been captured by the time he puts it down!
5258 Therefore we use the color the user is supposed to be playing in this
5259 test, not the color of the piece that is currently on the starting
5260 square---except in EditGame mode, where the user is playing both
5261 sides; fortunately there the capture race can't happen. (It can
5262 now happen in IcsExamining mode, but that's just too bad. The user
5263 will get a somewhat confusing message in that case.)
5267 case PlayFromGameFile:
5269 case TwoMachinesPlay:
5273 /* We switched into a game mode where moves are not accepted,
5274 perhaps while the mouse button was down. */
5275 return ImpossibleMove;
5277 case MachinePlaysWhite:
5278 /* User is moving for Black */
5279 if (WhiteOnMove(currentMove)) {
5280 DisplayMoveError(_("It is White's turn"));
5281 return ImpossibleMove;
5285 case MachinePlaysBlack:
5286 /* User is moving for White */
5287 if (!WhiteOnMove(currentMove)) {
5288 DisplayMoveError(_("It is Black's turn"));
5289 return ImpossibleMove;
5295 case BeginningOfGame:
5298 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5299 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5300 /* User is moving for Black */
5301 if (WhiteOnMove(currentMove)) {
5302 DisplayMoveError(_("It is White's turn"));
5303 return ImpossibleMove;
5306 /* User is moving for White */
5307 if (!WhiteOnMove(currentMove)) {
5308 DisplayMoveError(_("It is Black's turn"));
5309 return ImpossibleMove;
5314 case IcsPlayingBlack:
5315 /* User is moving for Black */
5316 if (WhiteOnMove(currentMove)) {
5317 if (!appData.premove) {
5318 DisplayMoveError(_("It is White's turn"));
5319 } else if (toX >= 0 && toY >= 0) {
5322 premoveFromX = fromX;
5323 premoveFromY = fromY;
5324 premovePromoChar = promoChar;
5326 if (appData.debugMode)
5327 fprintf(debugFP, "Got premove: fromX %d,"
5328 "fromY %d, toX %d, toY %d\n",
5329 fromX, fromY, toX, toY);
5331 return ImpossibleMove;
5335 case IcsPlayingWhite:
5336 /* User is moving for White */
5337 if (!WhiteOnMove(currentMove)) {
5338 if (!appData.premove) {
5339 DisplayMoveError(_("It is Black's turn"));
5340 } else if (toX >= 0 && toY >= 0) {
5343 premoveFromX = fromX;
5344 premoveFromY = fromY;
5345 premovePromoChar = promoChar;
5347 if (appData.debugMode)
5348 fprintf(debugFP, "Got premove: fromX %d,"
5349 "fromY %d, toX %d, toY %d\n",
5350 fromX, fromY, toX, toY);
5352 return ImpossibleMove;
5360 /* EditPosition, empty square, or different color piece;
5361 click-click move is possible */
5362 if (toX == -2 || toY == -2) {
5363 boards[0][fromY][fromX] = EmptySquare;
5364 return AmbiguousMove;
5365 } else if (toX >= 0 && toY >= 0) {
5366 boards[0][toY][toX] = boards[0][fromY][fromX];
5367 boards[0][fromY][fromX] = EmptySquare;
5368 return AmbiguousMove;
5370 return ImpossibleMove;
5373 if(toX < 0 || toY < 0) return ImpossibleMove;
5374 pdown = boards[currentMove][fromY][fromX];
5375 pup = boards[currentMove][toY][toX];
5377 /* [HGM] If move started in holdings, it means a drop */
5378 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5379 if( pup != EmptySquare ) return ImpossibleMove;
5380 if(appData.testLegality) {
5381 /* it would be more logical if LegalityTest() also figured out
5382 * which drops are legal. For now we forbid pawns on back rank.
5383 * Shogi is on its own here...
5385 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5386 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5387 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5389 return WhiteDrop; /* Not needed to specify white or black yet */
5392 userOfferedDraw = FALSE;
5394 /* [HGM] always test for legality, to get promotion info */
5395 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5396 fromY, fromX, toY, toX, promoChar);
5397 /* [HGM] but possibly ignore an IllegalMove result */
5398 if (appData.testLegality) {
5399 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5400 DisplayMoveError(_("Illegal move"));
5401 return ImpossibleMove;
5406 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5407 function is made into one that returns an OK move type if FinishMove
5408 should be called. This to give the calling driver routine the
5409 opportunity to finish the userMove input with a promotion popup,
5410 without bothering the user with this for invalid or illegal moves */
5412 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5415 /* Common tail of UserMoveEvent and DropMenuEvent */
5417 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5419 int fromX, fromY, toX, toY;
5420 /*char*/int promoChar;
5424 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5425 // [HGM] superchess: suppress promotions to non-available piece
5426 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5427 if(WhiteOnMove(currentMove)) {
5428 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5430 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5434 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5435 move type in caller when we know the move is a legal promotion */
5436 if(moveType == NormalMove && promoChar)
5437 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5439 /* [HGM] convert drag-and-drop piece drops to standard form */
5440 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5441 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5442 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5443 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5444 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5445 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5446 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5447 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5451 /* [HGM] <popupFix> The following if has been moved here from
5452 UserMoveEvent(). Because it seemed to belong here (why not allow
5453 piece drops in training games?), and because it can only be
5454 performed after it is known to what we promote. */
5455 if (gameMode == Training) {
5456 /* compare the move played on the board to the next move in the
5457 * game. If they match, display the move and the opponent's response.
5458 * If they don't match, display an error message.
5462 CopyBoard(testBoard, boards[currentMove]);
5463 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5465 if (CompareBoards(testBoard, boards[currentMove+1])) {
5466 ForwardInner(currentMove+1);
5468 /* Autoplay the opponent's response.
5469 * if appData.animate was TRUE when Training mode was entered,
5470 * the response will be animated.
5472 saveAnimate = appData.animate;
5473 appData.animate = animateTraining;
5474 ForwardInner(currentMove+1);
5475 appData.animate = saveAnimate;
5477 /* check for the end of the game */
5478 if (currentMove >= forwardMostMove) {
5479 gameMode = PlayFromGameFile;
5481 SetTrainingModeOff();
5482 DisplayInformation(_("End of game"));
5485 DisplayError(_("Incorrect move"), 0);
5490 /* Ok, now we know that the move is good, so we can kill
5491 the previous line in Analysis Mode */
5492 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5493 && currentMove < forwardMostMove) {
5494 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5497 /* If we need the chess program but it's dead, restart it */
5498 ResurrectChessProgram();
5500 /* A user move restarts a paused game*/
5504 thinkOutput[0] = NULLCHAR;
5506 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5508 if (gameMode == BeginningOfGame) {
5509 if (appData.noChessProgram) {
5510 gameMode = EditGame;
5514 gameMode = MachinePlaysBlack;
5517 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5519 if (first.sendName) {
5520 sprintf(buf, "name %s\n", gameInfo.white);
5521 SendToProgram(buf, &first);
5528 /* Relay move to ICS or chess engine */
5529 if (appData.icsActive) {
5530 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5531 gameMode == IcsExamining) {
5532 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5536 if (first.sendTime && (gameMode == BeginningOfGame ||
5537 gameMode == MachinePlaysWhite ||
5538 gameMode == MachinePlaysBlack)) {
5539 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5541 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5542 // [HGM] book: if program might be playing, let it use book
5543 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5544 first.maybeThinking = TRUE;
5545 } else SendMoveToProgram(forwardMostMove-1, &first);
5546 if (currentMove == cmailOldMove + 1) {
5547 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5551 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5555 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5561 if (WhiteOnMove(currentMove)) {
5562 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5564 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5568 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5573 case MachinePlaysBlack:
5574 case MachinePlaysWhite:
5575 /* disable certain menu options while machine is thinking */
5576 SetMachineThinkingEnables();
5583 if(bookHit) { // [HGM] book: simulate book reply
5584 static char bookMove[MSG_SIZ]; // a bit generous?
5586 programStats.nodes = programStats.depth = programStats.time =
5587 programStats.score = programStats.got_only_move = 0;
5588 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5590 strcpy(bookMove, "move ");
5591 strcat(bookMove, bookHit);
5592 HandleMachineMove(bookMove, &first);
5598 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5599 int fromX, fromY, toX, toY;
5602 /* [HGM] This routine was added to allow calling of its two logical
5603 parts from other modules in the old way. Before, UserMoveEvent()
5604 automatically called FinishMove() if the move was OK, and returned
5605 otherwise. I separated the two, in order to make it possible to
5606 slip a promotion popup in between. But that it always needs two
5607 calls, to the first part, (now called UserMoveTest() ), and to
5608 FinishMove if the first part succeeded. Calls that do not need
5609 to do anything in between, can call this routine the old way.
5611 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5612 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5613 if(moveType == AmbiguousMove)
5614 DrawPosition(FALSE, boards[currentMove]);
5615 else if(moveType != ImpossibleMove && moveType != Comment)
5616 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5619 void LeftClick(ClickType clickType, int xPix, int yPix)
5622 Boolean saveAnimate;
5623 static int second = 0, promotionChoice = 0;
5624 char promoChoice = NULLCHAR;
5626 if (clickType == Press) ErrorPopDown();
5628 x = EventToSquare(xPix, BOARD_WIDTH);
5629 y = EventToSquare(yPix, BOARD_HEIGHT);
5630 if (!flipView && y >= 0) {
5631 y = BOARD_HEIGHT - 1 - y;
5633 if (flipView && x >= 0) {
5634 x = BOARD_WIDTH - 1 - x;
5637 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5638 if(clickType == Release) return; // ignore upclick of click-click destination
5639 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5640 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5641 if(gameInfo.holdingsWidth &&
5642 (WhiteOnMove(currentMove)
5643 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5644 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5645 // click in right holdings, for determining promotion piece
5646 ChessSquare p = boards[currentMove][y][x];
5647 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5648 if(p != EmptySquare) {
5649 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5654 DrawPosition(FALSE, boards[currentMove]);
5658 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5659 if(clickType == Press
5660 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5661 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5662 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5666 if (clickType == Press) {
5668 if (OKToStartUserMove(x, y)) {
5672 DragPieceBegin(xPix, yPix);
5673 if (appData.highlightDragging) {
5674 SetHighlights(x, y, -1, -1);
5682 if (clickType == Press && gameMode != EditPosition) {
5687 // ignore off-board to clicks
5688 if(y < 0 || x < 0) return;
5690 /* Check if clicking again on the same color piece */
5691 fromP = boards[currentMove][fromY][fromX];
5692 toP = boards[currentMove][y][x];
5693 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5694 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5695 WhitePawn <= toP && toP <= WhiteKing &&
5696 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5697 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5698 (BlackPawn <= fromP && fromP <= BlackKing &&
5699 BlackPawn <= toP && toP <= BlackKing &&
5700 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5701 !(fromP == BlackKing && toP == BlackRook && frc))) {
5702 /* Clicked again on same color piece -- changed his mind */
5703 second = (x == fromX && y == fromY);
5704 if (appData.highlightDragging) {
5705 SetHighlights(x, y, -1, -1);
5709 if (OKToStartUserMove(x, y)) {
5712 DragPieceBegin(xPix, yPix);
5716 // ignore clicks on holdings
5717 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5720 if (clickType == Release && x == fromX && y == fromY) {
5721 DragPieceEnd(xPix, yPix);
5722 if (appData.animateDragging) {
5723 /* Undo animation damage if any */
5724 DrawPosition(FALSE, NULL);
5727 /* Second up/down in same square; just abort move */
5732 ClearPremoveHighlights();
5734 /* First upclick in same square; start click-click mode */
5735 SetHighlights(x, y, -1, -1);
5740 /* we now have a different from- and (possibly off-board) to-square */
5741 /* Completed move */
5744 saveAnimate = appData.animate;
5745 if (clickType == Press) {
5746 /* Finish clickclick move */
5747 if (appData.animate || appData.highlightLastMove) {
5748 SetHighlights(fromX, fromY, toX, toY);
5753 /* Finish drag move */
5754 if (appData.highlightLastMove) {
5755 SetHighlights(fromX, fromY, toX, toY);
5759 DragPieceEnd(xPix, yPix);
5760 /* Don't animate move and drag both */
5761 appData.animate = FALSE;
5764 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5765 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5768 DrawPosition(TRUE, NULL);
5772 // off-board moves should not be highlighted
5773 if(x < 0 || x < 0) ClearHighlights();
5775 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5776 SetHighlights(fromX, fromY, toX, toY);
5777 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5778 // [HGM] super: promotion to captured piece selected from holdings
5779 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5780 promotionChoice = TRUE;
5781 // kludge follows to temporarily execute move on display, without promoting yet
5782 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5783 boards[currentMove][toY][toX] = p;
5784 DrawPosition(FALSE, boards[currentMove]);
5785 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5786 boards[currentMove][toY][toX] = q;
5787 DisplayMessage("Click in holdings to choose piece", "");
5792 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5793 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5794 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5797 appData.animate = saveAnimate;
5798 if (appData.animate || appData.animateDragging) {
5799 /* Undo animation damage if needed */
5800 DrawPosition(FALSE, NULL);
5804 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5806 // char * hint = lastHint;
5807 FrontEndProgramStats stats;
5809 stats.which = cps == &first ? 0 : 1;
5810 stats.depth = cpstats->depth;
5811 stats.nodes = cpstats->nodes;
5812 stats.score = cpstats->score;
5813 stats.time = cpstats->time;
5814 stats.pv = cpstats->movelist;
5815 stats.hint = lastHint;
5816 stats.an_move_index = 0;
5817 stats.an_move_count = 0;
5819 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5820 stats.hint = cpstats->move_name;
5821 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5822 stats.an_move_count = cpstats->nr_moves;
5825 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5827 SetProgramStats( &stats );
5830 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5831 { // [HGM] book: this routine intercepts moves to simulate book replies
5832 char *bookHit = NULL;
5834 //first determine if the incoming move brings opponent into his book
5835 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5836 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5837 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5838 if(bookHit != NULL && !cps->bookSuspend) {
5839 // make sure opponent is not going to reply after receiving move to book position
5840 SendToProgram("force\n", cps);
5841 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5843 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5844 // now arrange restart after book miss
5846 // after a book hit we never send 'go', and the code after the call to this routine
5847 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5849 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5850 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5851 SendToProgram(buf, cps);
5852 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5853 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5854 SendToProgram("go\n", cps);
5855 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5856 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5857 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5858 SendToProgram("go\n", cps);
5859 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5861 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5865 ChessProgramState *savedState;
5866 void DeferredBookMove(void)
5868 if(savedState->lastPing != savedState->lastPong)
5869 ScheduleDelayedEvent(DeferredBookMove, 10);
5871 HandleMachineMove(savedMessage, savedState);
5875 HandleMachineMove(message, cps)
5877 ChessProgramState *cps;
5879 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5880 char realname[MSG_SIZ];
5881 int fromX, fromY, toX, toY;
5890 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5892 * Kludge to ignore BEL characters
5894 while (*message == '\007') message++;
5897 * [HGM] engine debug message: ignore lines starting with '#' character
5899 if(cps->debug && *message == '#') return;
5902 * Look for book output
5904 if (cps == &first && bookRequested) {
5905 if (message[0] == '\t' || message[0] == ' ') {
5906 /* Part of the book output is here; append it */
5907 strcat(bookOutput, message);
5908 strcat(bookOutput, " \n");
5910 } else if (bookOutput[0] != NULLCHAR) {
5911 /* All of book output has arrived; display it */
5912 char *p = bookOutput;
5913 while (*p != NULLCHAR) {
5914 if (*p == '\t') *p = ' ';
5917 DisplayInformation(bookOutput);
5918 bookRequested = FALSE;
5919 /* Fall through to parse the current output */
5924 * Look for machine move.
5926 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5927 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5929 /* This method is only useful on engines that support ping */
5930 if (cps->lastPing != cps->lastPong) {
5931 if (gameMode == BeginningOfGame) {
5932 /* Extra move from before last new; ignore */
5933 if (appData.debugMode) {
5934 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5937 if (appData.debugMode) {
5938 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5939 cps->which, gameMode);
5942 SendToProgram("undo\n", cps);
5948 case BeginningOfGame:
5949 /* Extra move from before last reset; ignore */
5950 if (appData.debugMode) {
5951 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5958 /* Extra move after we tried to stop. The mode test is
5959 not a reliable way of detecting this problem, but it's
5960 the best we can do on engines that don't support ping.
5962 if (appData.debugMode) {
5963 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5964 cps->which, gameMode);
5966 SendToProgram("undo\n", cps);
5969 case MachinePlaysWhite:
5970 case IcsPlayingWhite:
5971 machineWhite = TRUE;
5974 case MachinePlaysBlack:
5975 case IcsPlayingBlack:
5976 machineWhite = FALSE;
5979 case TwoMachinesPlay:
5980 machineWhite = (cps->twoMachinesColor[0] == 'w');
5983 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5984 if (appData.debugMode) {
5986 "Ignoring move out of turn by %s, gameMode %d"
5987 ", forwardMost %d\n",
5988 cps->which, gameMode, forwardMostMove);
5993 if (appData.debugMode) { int f = forwardMostMove;
5994 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5995 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5996 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5998 if(cps->alphaRank) AlphaRank(machineMove, 4);
5999 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6000 &fromX, &fromY, &toX, &toY, &promoChar)) {
6001 /* Machine move could not be parsed; ignore it. */
6002 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6003 machineMove, cps->which);
6004 DisplayError(buf1, 0);
6005 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6006 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6007 if (gameMode == TwoMachinesPlay) {
6008 GameEnds(machineWhite ? BlackWins : WhiteWins,
6014 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6015 /* So we have to redo legality test with true e.p. status here, */
6016 /* to make sure an illegal e.p. capture does not slip through, */
6017 /* to cause a forfeit on a justified illegal-move complaint */
6018 /* of the opponent. */
6019 if( gameMode==TwoMachinesPlay && appData.testLegality
6020 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6023 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6024 fromY, fromX, toY, toX, promoChar);
6025 if (appData.debugMode) {
6027 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6028 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6029 fprintf(debugFP, "castling rights\n");
6031 if(moveType == IllegalMove) {
6032 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6033 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6034 GameEnds(machineWhite ? BlackWins : WhiteWins,
6037 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6038 /* [HGM] Kludge to handle engines that send FRC-style castling
6039 when they shouldn't (like TSCP-Gothic) */
6041 case WhiteASideCastleFR:
6042 case BlackASideCastleFR:
6044 currentMoveString[2]++;
6046 case WhiteHSideCastleFR:
6047 case BlackHSideCastleFR:
6049 currentMoveString[2]--;
6051 default: ; // nothing to do, but suppresses warning of pedantic compilers
6054 hintRequested = FALSE;
6055 lastHint[0] = NULLCHAR;
6056 bookRequested = FALSE;
6057 /* Program may be pondering now */
6058 cps->maybeThinking = TRUE;
6059 if (cps->sendTime == 2) cps->sendTime = 1;
6060 if (cps->offeredDraw) cps->offeredDraw--;
6063 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6065 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6067 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6068 char buf[3*MSG_SIZ];
6070 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6071 programStats.score / 100.,
6073 programStats.time / 100.,
6074 (unsigned int)programStats.nodes,
6075 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6076 programStats.movelist);
6078 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6082 /* currentMoveString is set as a side-effect of ParseOneMove */
6083 strcpy(machineMove, currentMoveString);
6084 strcat(machineMove, "\n");
6085 strcpy(moveList[forwardMostMove], machineMove);
6087 /* [AS] Save move info and clear stats for next move */
6088 pvInfoList[ forwardMostMove ].score = programStats.score;
6089 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6090 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6091 ClearProgramStats();
6092 thinkOutput[0] = NULLCHAR;
6093 hiddenThinkOutputState = 0;
6095 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6097 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6098 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6101 while( count < adjudicateLossPlies ) {
6102 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6105 score = -score; /* Flip score for winning side */
6108 if( score > adjudicateLossThreshold ) {
6115 if( count >= adjudicateLossPlies ) {
6116 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6118 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6119 "Xboard adjudication",
6126 if( gameMode == TwoMachinesPlay ) {
6127 // [HGM] some adjudications useful with buggy engines
6128 int k, count = 0; static int bare = 1;
6129 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6132 if( appData.testLegality )
6133 { /* [HGM] Some more adjudications for obstinate engines */
6134 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6135 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6136 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6137 static int moveCount = 6;
6139 char *reason = NULL;
6141 /* Count what is on board. */
6142 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6143 { ChessSquare p = boards[forwardMostMove][i][j];
6147 { /* count B,N,R and other of each side */
6150 NrK++; break; // [HGM] atomic: count Kings
6154 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6155 bishopsColor |= 1 << ((i^j)&1);
6160 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6161 bishopsColor |= 1 << ((i^j)&1);
6176 PawnAdvance += m; NrPawns++;
6178 NrPieces += (p != EmptySquare);
6179 NrW += ((int)p < (int)BlackPawn);
6180 if(gameInfo.variant == VariantXiangqi &&
6181 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6182 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6183 NrW -= ((int)p < (int)BlackPawn);
6187 /* Some material-based adjudications that have to be made before stalemate test */
6188 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6189 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6190 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6191 if(appData.checkMates) {
6192 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6193 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6194 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6195 "Xboard adjudication: King destroyed", GE_XBOARD );
6200 /* Bare King in Shatranj (loses) or Losers (wins) */
6201 if( NrW == 1 || NrPieces - NrW == 1) {
6202 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6203 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6204 if(appData.checkMates) {
6205 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6206 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6207 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6208 "Xboard adjudication: Bare king", GE_XBOARD );
6212 if( gameInfo.variant == VariantShatranj && --bare < 0)
6214 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6215 if(appData.checkMates) {
6216 /* but only adjudicate if adjudication enabled */
6217 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6218 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6219 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6220 "Xboard adjudication: Bare king", GE_XBOARD );
6227 // don't wait for engine to announce game end if we can judge ourselves
6228 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6230 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6231 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6232 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6233 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6236 reason = "Xboard adjudication: 3rd check";
6237 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6247 reason = "Xboard adjudication: Stalemate";
6248 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6249 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6250 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6251 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6252 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6253 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6254 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6255 EP_CHECKMATE : EP_WINS);
6256 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6257 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6261 reason = "Xboard adjudication: Checkmate";
6262 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6266 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6268 result = GameIsDrawn; break;
6270 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6272 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6274 result = (ChessMove) 0;
6276 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6277 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6278 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6279 GameEnds( result, reason, GE_XBOARD );
6283 /* Next absolutely insufficient mating material. */
6284 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6285 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6286 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6287 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6288 { /* KBK, KNK, KK of KBKB with like Bishops */
6290 /* always flag draws, for judging claims */
6291 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6293 if(appData.materialDraws) {
6294 /* but only adjudicate them if adjudication enabled */
6295 SendToProgram("force\n", cps->other); // suppress reply
6296 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6297 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6298 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6303 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6305 ( NrWR == 1 && NrBR == 1 /* KRKR */
6306 || NrWQ==1 && NrBQ==1 /* KQKQ */
6307 || NrWN==2 || NrBN==2 /* KNNK */
6308 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6310 if(--moveCount < 0 && appData.trivialDraws)
6311 { /* if the first 3 moves do not show a tactical win, declare draw */
6312 SendToProgram("force\n", cps->other); // suppress reply
6313 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6314 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6315 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6318 } else moveCount = 6;
6322 if (appData.debugMode) { int i;
6323 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6324 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6325 appData.drawRepeats);
6326 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6327 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6331 /* Check for rep-draws */
6333 for(k = forwardMostMove-2;
6334 k>=backwardMostMove && k>=forwardMostMove-100 &&
6335 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6336 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6339 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6340 /* compare castling rights */
6341 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6342 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6343 rights++; /* King lost rights, while rook still had them */
6344 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6345 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6346 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6347 rights++; /* but at least one rook lost them */
6349 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6350 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6352 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6353 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6354 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6357 if( rights == 0 && ++count > appData.drawRepeats-2
6358 && appData.drawRepeats > 1) {
6359 /* adjudicate after user-specified nr of repeats */
6360 SendToProgram("force\n", cps->other); // suppress reply
6361 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6362 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6363 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6364 // [HGM] xiangqi: check for forbidden perpetuals
6365 int m, ourPerpetual = 1, hisPerpetual = 1;
6366 for(m=forwardMostMove; m>k; m-=2) {
6367 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6368 ourPerpetual = 0; // the current mover did not always check
6369 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6370 hisPerpetual = 0; // the opponent did not always check
6372 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6373 ourPerpetual, hisPerpetual);
6374 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6375 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6376 "Xboard adjudication: perpetual checking", GE_XBOARD );
6379 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6380 break; // (or we would have caught him before). Abort repetition-checking loop.
6381 // Now check for perpetual chases
6382 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6383 hisPerpetual = PerpetualChase(k, forwardMostMove);
6384 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6385 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6386 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6387 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6390 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6391 break; // Abort repetition-checking loop.
6393 // if neither of us is checking or chasing all the time, or both are, it is draw
6395 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6398 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6399 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6403 /* Now we test for 50-move draws. Determine ply count */
6404 count = forwardMostMove;
6405 /* look for last irreversble move */
6406 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6408 /* if we hit starting position, add initial plies */
6409 if( count == backwardMostMove )
6410 count -= initialRulePlies;
6411 count = forwardMostMove - count;
6413 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6414 /* this is used to judge if draw claims are legal */
6415 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6416 SendToProgram("force\n", cps->other); // suppress reply
6417 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6418 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6419 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6423 /* if draw offer is pending, treat it as a draw claim
6424 * when draw condition present, to allow engines a way to
6425 * claim draws before making their move to avoid a race
6426 * condition occurring after their move
6428 if( cps->other->offeredDraw || cps->offeredDraw ) {
6430 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6431 p = "Draw claim: 50-move rule";
6432 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6433 p = "Draw claim: 3-fold repetition";
6434 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6435 p = "Draw claim: insufficient mating material";
6437 SendToProgram("force\n", cps->other); // suppress reply
6438 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6439 GameEnds( GameIsDrawn, p, GE_XBOARD );
6440 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6446 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6447 SendToProgram("force\n", cps->other); // suppress reply
6448 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6449 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6451 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6458 if (gameMode == TwoMachinesPlay) {
6459 /* [HGM] relaying draw offers moved to after reception of move */
6460 /* and interpreting offer as claim if it brings draw condition */
6461 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6462 SendToProgram("draw\n", cps->other);
6464 if (cps->other->sendTime) {
6465 SendTimeRemaining(cps->other,
6466 cps->other->twoMachinesColor[0] == 'w');
6468 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6469 if (firstMove && !bookHit) {
6471 if (cps->other->useColors) {
6472 SendToProgram(cps->other->twoMachinesColor, cps->other);
6474 SendToProgram("go\n", cps->other);
6476 cps->other->maybeThinking = TRUE;
6479 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6481 if (!pausing && appData.ringBellAfterMoves) {
6486 * Reenable menu items that were disabled while
6487 * machine was thinking
6489 if (gameMode != TwoMachinesPlay)
6490 SetUserThinkingEnables();
6492 // [HGM] book: after book hit opponent has received move and is now in force mode
6493 // force the book reply into it, and then fake that it outputted this move by jumping
6494 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6496 static char bookMove[MSG_SIZ]; // a bit generous?
6498 strcpy(bookMove, "move ");
6499 strcat(bookMove, bookHit);
6502 programStats.nodes = programStats.depth = programStats.time =
6503 programStats.score = programStats.got_only_move = 0;
6504 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6506 if(cps->lastPing != cps->lastPong) {
6507 savedMessage = message; // args for deferred call
6509 ScheduleDelayedEvent(DeferredBookMove, 10);
6518 /* Set special modes for chess engines. Later something general
6519 * could be added here; for now there is just one kludge feature,
6520 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6521 * when "xboard" is given as an interactive command.
6523 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6524 cps->useSigint = FALSE;
6525 cps->useSigterm = FALSE;
6527 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6528 ParseFeatures(message+8, cps);
6529 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6532 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6533 * want this, I was asked to put it in, and obliged.
6535 if (!strncmp(message, "setboard ", 9)) {
6536 Board initial_position;
6538 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6540 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6541 DisplayError(_("Bad FEN received from engine"), 0);
6545 CopyBoard(boards[0], initial_position);
6546 initialRulePlies = FENrulePlies;
6547 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6548 else gameMode = MachinePlaysBlack;
6549 DrawPosition(FALSE, boards[currentMove]);
6555 * Look for communication commands
6557 if (!strncmp(message, "telluser ", 9)) {
6558 DisplayNote(message + 9);
6561 if (!strncmp(message, "tellusererror ", 14)) {
6563 DisplayError(message + 14, 0);
6566 if (!strncmp(message, "tellopponent ", 13)) {
6567 if (appData.icsActive) {
6569 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6573 DisplayNote(message + 13);
6577 if (!strncmp(message, "tellothers ", 11)) {
6578 if (appData.icsActive) {
6580 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6586 if (!strncmp(message, "tellall ", 8)) {
6587 if (appData.icsActive) {
6589 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6593 DisplayNote(message + 8);
6597 if (strncmp(message, "warning", 7) == 0) {
6598 /* Undocumented feature, use tellusererror in new code */
6599 DisplayError(message, 0);
6602 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6603 strcpy(realname, cps->tidy);
6604 strcat(realname, " query");
6605 AskQuestion(realname, buf2, buf1, cps->pr);
6608 /* Commands from the engine directly to ICS. We don't allow these to be
6609 * sent until we are logged on. Crafty kibitzes have been known to
6610 * interfere with the login process.
6613 if (!strncmp(message, "tellics ", 8)) {
6614 SendToICS(message + 8);
6618 if (!strncmp(message, "tellicsnoalias ", 15)) {
6619 SendToICS(ics_prefix);
6620 SendToICS(message + 15);
6624 /* The following are for backward compatibility only */
6625 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6626 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6627 SendToICS(ics_prefix);
6633 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6637 * If the move is illegal, cancel it and redraw the board.
6638 * Also deal with other error cases. Matching is rather loose
6639 * here to accommodate engines written before the spec.
6641 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6642 strncmp(message, "Error", 5) == 0) {
6643 if (StrStr(message, "name") ||
6644 StrStr(message, "rating") || StrStr(message, "?") ||
6645 StrStr(message, "result") || StrStr(message, "board") ||
6646 StrStr(message, "bk") || StrStr(message, "computer") ||
6647 StrStr(message, "variant") || StrStr(message, "hint") ||
6648 StrStr(message, "random") || StrStr(message, "depth") ||
6649 StrStr(message, "accepted")) {
6652 if (StrStr(message, "protover")) {
6653 /* Program is responding to input, so it's apparently done
6654 initializing, and this error message indicates it is
6655 protocol version 1. So we don't need to wait any longer
6656 for it to initialize and send feature commands. */
6657 FeatureDone(cps, 1);
6658 cps->protocolVersion = 1;
6661 cps->maybeThinking = FALSE;
6663 if (StrStr(message, "draw")) {
6664 /* Program doesn't have "draw" command */
6665 cps->sendDrawOffers = 0;
6668 if (cps->sendTime != 1 &&
6669 (StrStr(message, "time") || StrStr(message, "otim"))) {
6670 /* Program apparently doesn't have "time" or "otim" command */
6674 if (StrStr(message, "analyze")) {
6675 cps->analysisSupport = FALSE;
6676 cps->analyzing = FALSE;
6678 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6679 DisplayError(buf2, 0);
6682 if (StrStr(message, "(no matching move)st")) {
6683 /* Special kludge for GNU Chess 4 only */
6684 cps->stKludge = TRUE;
6685 SendTimeControl(cps, movesPerSession, timeControl,
6686 timeIncrement, appData.searchDepth,
6690 if (StrStr(message, "(no matching move)sd")) {
6691 /* Special kludge for GNU Chess 4 only */
6692 cps->sdKludge = TRUE;
6693 SendTimeControl(cps, movesPerSession, timeControl,
6694 timeIncrement, appData.searchDepth,
6698 if (!StrStr(message, "llegal")) {
6701 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6702 gameMode == IcsIdle) return;
6703 if (forwardMostMove <= backwardMostMove) return;
6704 if (pausing) PauseEvent();
6705 if(appData.forceIllegal) {
6706 // [HGM] illegal: machine refused move; force position after move into it
6707 SendToProgram("force\n", cps);
6708 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6709 // we have a real problem now, as SendBoard will use the a2a3 kludge
6710 // when black is to move, while there might be nothing on a2 or black
6711 // might already have the move. So send the board as if white has the move.
6712 // But first we must change the stm of the engine, as it refused the last move
6713 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6714 if(WhiteOnMove(forwardMostMove)) {
6715 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6716 SendBoard(cps, forwardMostMove); // kludgeless board
6718 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6719 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6720 SendBoard(cps, forwardMostMove+1); // kludgeless board
6722 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6723 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6724 gameMode == TwoMachinesPlay)
6725 SendToProgram("go\n", cps);
6728 if (gameMode == PlayFromGameFile) {
6729 /* Stop reading this game file */
6730 gameMode = EditGame;
6733 currentMove = --forwardMostMove;
6734 DisplayMove(currentMove-1); /* before DisplayMoveError */
6736 DisplayBothClocks();
6737 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6738 parseList[currentMove], cps->which);
6739 DisplayMoveError(buf1);
6740 DrawPosition(FALSE, boards[currentMove]);
6742 /* [HGM] illegal-move claim should forfeit game when Xboard */
6743 /* only passes fully legal moves */
6744 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6745 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6746 "False illegal-move claim", GE_XBOARD );
6750 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6751 /* Program has a broken "time" command that
6752 outputs a string not ending in newline.
6758 * If chess program startup fails, exit with an error message.
6759 * Attempts to recover here are futile.
6761 if ((StrStr(message, "unknown host") != NULL)
6762 || (StrStr(message, "No remote directory") != NULL)
6763 || (StrStr(message, "not found") != NULL)
6764 || (StrStr(message, "No such file") != NULL)
6765 || (StrStr(message, "can't alloc") != NULL)
6766 || (StrStr(message, "Permission denied") != NULL)) {
6768 cps->maybeThinking = FALSE;
6769 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6770 cps->which, cps->program, cps->host, message);
6771 RemoveInputSource(cps->isr);
6772 DisplayFatalError(buf1, 0, 1);
6777 * Look for hint output
6779 if (sscanf(message, "Hint: %s", buf1) == 1) {
6780 if (cps == &first && hintRequested) {
6781 hintRequested = FALSE;
6782 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6783 &fromX, &fromY, &toX, &toY, &promoChar)) {
6784 (void) CoordsToAlgebraic(boards[forwardMostMove],
6785 PosFlags(forwardMostMove),
6786 fromY, fromX, toY, toX, promoChar, buf1);
6787 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6788 DisplayInformation(buf2);
6790 /* Hint move could not be parsed!? */
6791 snprintf(buf2, sizeof(buf2),
6792 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6794 DisplayError(buf2, 0);
6797 strcpy(lastHint, buf1);
6803 * Ignore other messages if game is not in progress
6805 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6806 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6809 * look for win, lose, draw, or draw offer
6811 if (strncmp(message, "1-0", 3) == 0) {
6812 char *p, *q, *r = "";
6813 p = strchr(message, '{');
6821 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6823 } else if (strncmp(message, "0-1", 3) == 0) {
6824 char *p, *q, *r = "";
6825 p = strchr(message, '{');
6833 /* Kludge for Arasan 4.1 bug */
6834 if (strcmp(r, "Black resigns") == 0) {
6835 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6838 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6840 } else if (strncmp(message, "1/2", 3) == 0) {
6841 char *p, *q, *r = "";
6842 p = strchr(message, '{');
6851 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6854 } else if (strncmp(message, "White resign", 12) == 0) {
6855 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6857 } else if (strncmp(message, "Black resign", 12) == 0) {
6858 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6860 } else if (strncmp(message, "White matches", 13) == 0 ||
6861 strncmp(message, "Black matches", 13) == 0 ) {
6862 /* [HGM] ignore GNUShogi noises */
6864 } else if (strncmp(message, "White", 5) == 0 &&
6865 message[5] != '(' &&
6866 StrStr(message, "Black") == NULL) {
6867 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6869 } else if (strncmp(message, "Black", 5) == 0 &&
6870 message[5] != '(') {
6871 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6873 } else if (strcmp(message, "resign") == 0 ||
6874 strcmp(message, "computer resigns") == 0) {
6876 case MachinePlaysBlack:
6877 case IcsPlayingBlack:
6878 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6880 case MachinePlaysWhite:
6881 case IcsPlayingWhite:
6882 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6884 case TwoMachinesPlay:
6885 if (cps->twoMachinesColor[0] == 'w')
6886 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6888 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6895 } else if (strncmp(message, "opponent mates", 14) == 0) {
6897 case MachinePlaysBlack:
6898 case IcsPlayingBlack:
6899 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6901 case MachinePlaysWhite:
6902 case IcsPlayingWhite:
6903 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6905 case TwoMachinesPlay:
6906 if (cps->twoMachinesColor[0] == 'w')
6907 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6909 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6916 } else if (strncmp(message, "computer mates", 14) == 0) {
6918 case MachinePlaysBlack:
6919 case IcsPlayingBlack:
6920 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6922 case MachinePlaysWhite:
6923 case IcsPlayingWhite:
6924 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6926 case TwoMachinesPlay:
6927 if (cps->twoMachinesColor[0] == 'w')
6928 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6930 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6937 } else if (strncmp(message, "checkmate", 9) == 0) {
6938 if (WhiteOnMove(forwardMostMove)) {
6939 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6941 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6944 } else if (strstr(message, "Draw") != NULL ||
6945 strstr(message, "game is a draw") != NULL) {
6946 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6948 } else if (strstr(message, "offer") != NULL &&
6949 strstr(message, "draw") != NULL) {
6951 if (appData.zippyPlay && first.initDone) {
6952 /* Relay offer to ICS */
6953 SendToICS(ics_prefix);
6954 SendToICS("draw\n");
6957 cps->offeredDraw = 2; /* valid until this engine moves twice */
6958 if (gameMode == TwoMachinesPlay) {
6959 if (cps->other->offeredDraw) {
6960 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6961 /* [HGM] in two-machine mode we delay relaying draw offer */
6962 /* until after we also have move, to see if it is really claim */
6964 } else if (gameMode == MachinePlaysWhite ||
6965 gameMode == MachinePlaysBlack) {
6966 if (userOfferedDraw) {
6967 DisplayInformation(_("Machine accepts your draw offer"));
6968 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6970 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6977 * Look for thinking output
6979 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6980 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6982 int plylev, mvleft, mvtot, curscore, time;
6983 char mvname[MOVE_LEN];
6987 int prefixHint = FALSE;
6988 mvname[0] = NULLCHAR;
6991 case MachinePlaysBlack:
6992 case IcsPlayingBlack:
6993 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6995 case MachinePlaysWhite:
6996 case IcsPlayingWhite:
6997 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7002 case IcsObserving: /* [DM] icsEngineAnalyze */
7003 if (!appData.icsEngineAnalyze) ignore = TRUE;
7005 case TwoMachinesPlay:
7006 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7017 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7018 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7020 if (plyext != ' ' && plyext != '\t') {
7024 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7025 if( cps->scoreIsAbsolute &&
7026 ( gameMode == MachinePlaysBlack ||
7027 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7028 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7029 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7030 !WhiteOnMove(currentMove)
7033 curscore = -curscore;
7037 programStats.depth = plylev;
7038 programStats.nodes = nodes;
7039 programStats.time = time;
7040 programStats.score = curscore;
7041 programStats.got_only_move = 0;
7043 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7046 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7047 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7048 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7049 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7050 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7051 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7052 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7053 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7056 /* Buffer overflow protection */
7057 if (buf1[0] != NULLCHAR) {
7058 if (strlen(buf1) >= sizeof(programStats.movelist)
7059 && appData.debugMode) {
7061 "PV is too long; using the first %u bytes.\n",
7062 (unsigned) sizeof(programStats.movelist) - 1);
7065 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7067 sprintf(programStats.movelist, " no PV\n");
7070 if (programStats.seen_stat) {
7071 programStats.ok_to_send = 1;
7074 if (strchr(programStats.movelist, '(') != NULL) {
7075 programStats.line_is_book = 1;
7076 programStats.nr_moves = 0;
7077 programStats.moves_left = 0;
7079 programStats.line_is_book = 0;
7082 SendProgramStatsToFrontend( cps, &programStats );
7085 [AS] Protect the thinkOutput buffer from overflow... this
7086 is only useful if buf1 hasn't overflowed first!
7088 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7090 (gameMode == TwoMachinesPlay ?
7091 ToUpper(cps->twoMachinesColor[0]) : ' '),
7092 ((double) curscore) / 100.0,
7093 prefixHint ? lastHint : "",
7094 prefixHint ? " " : "" );
7096 if( buf1[0] != NULLCHAR ) {
7097 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7099 if( strlen(buf1) > max_len ) {
7100 if( appData.debugMode) {
7101 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7103 buf1[max_len+1] = '\0';
7106 strcat( thinkOutput, buf1 );
7109 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7110 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7111 DisplayMove(currentMove - 1);
7115 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7116 /* crafty (9.25+) says "(only move) <move>"
7117 * if there is only 1 legal move
7119 sscanf(p, "(only move) %s", buf1);
7120 sprintf(thinkOutput, "%s (only move)", buf1);
7121 sprintf(programStats.movelist, "%s (only move)", buf1);
7122 programStats.depth = 1;
7123 programStats.nr_moves = 1;
7124 programStats.moves_left = 1;
7125 programStats.nodes = 1;
7126 programStats.time = 1;
7127 programStats.got_only_move = 1;
7129 /* Not really, but we also use this member to
7130 mean "line isn't going to change" (Crafty
7131 isn't searching, so stats won't change) */
7132 programStats.line_is_book = 1;
7134 SendProgramStatsToFrontend( cps, &programStats );
7136 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7137 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7138 DisplayMove(currentMove - 1);
7141 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7142 &time, &nodes, &plylev, &mvleft,
7143 &mvtot, mvname) >= 5) {
7144 /* The stat01: line is from Crafty (9.29+) in response
7145 to the "." command */
7146 programStats.seen_stat = 1;
7147 cps->maybeThinking = TRUE;
7149 if (programStats.got_only_move || !appData.periodicUpdates)
7152 programStats.depth = plylev;
7153 programStats.time = time;
7154 programStats.nodes = nodes;
7155 programStats.moves_left = mvleft;
7156 programStats.nr_moves = mvtot;
7157 strcpy(programStats.move_name, mvname);
7158 programStats.ok_to_send = 1;
7159 programStats.movelist[0] = '\0';
7161 SendProgramStatsToFrontend( cps, &programStats );
7165 } else if (strncmp(message,"++",2) == 0) {
7166 /* Crafty 9.29+ outputs this */
7167 programStats.got_fail = 2;
7170 } else if (strncmp(message,"--",2) == 0) {
7171 /* Crafty 9.29+ outputs this */
7172 programStats.got_fail = 1;
7175 } else if (thinkOutput[0] != NULLCHAR &&
7176 strncmp(message, " ", 4) == 0) {
7177 unsigned message_len;
7180 while (*p && *p == ' ') p++;
7182 message_len = strlen( p );
7184 /* [AS] Avoid buffer overflow */
7185 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7186 strcat(thinkOutput, " ");
7187 strcat(thinkOutput, p);
7190 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7191 strcat(programStats.movelist, " ");
7192 strcat(programStats.movelist, p);
7195 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7196 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7197 DisplayMove(currentMove - 1);
7205 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7206 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7208 ChessProgramStats cpstats;
7210 if (plyext != ' ' && plyext != '\t') {
7214 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7215 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7216 curscore = -curscore;
7219 cpstats.depth = plylev;
7220 cpstats.nodes = nodes;
7221 cpstats.time = time;
7222 cpstats.score = curscore;
7223 cpstats.got_only_move = 0;
7224 cpstats.movelist[0] = '\0';
7226 if (buf1[0] != NULLCHAR) {
7227 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7230 cpstats.ok_to_send = 0;
7231 cpstats.line_is_book = 0;
7232 cpstats.nr_moves = 0;
7233 cpstats.moves_left = 0;
7235 SendProgramStatsToFrontend( cps, &cpstats );
7242 /* Parse a game score from the character string "game", and
7243 record it as the history of the current game. The game
7244 score is NOT assumed to start from the standard position.
7245 The display is not updated in any way.
7248 ParseGameHistory(game)
7252 int fromX, fromY, toX, toY, boardIndex;
7257 if (appData.debugMode)
7258 fprintf(debugFP, "Parsing game history: %s\n", game);
7260 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7261 gameInfo.site = StrSave(appData.icsHost);
7262 gameInfo.date = PGNDate();
7263 gameInfo.round = StrSave("-");
7265 /* Parse out names of players */
7266 while (*game == ' ') game++;
7268 while (*game != ' ') *p++ = *game++;
7270 gameInfo.white = StrSave(buf);
7271 while (*game == ' ') game++;
7273 while (*game != ' ' && *game != '\n') *p++ = *game++;
7275 gameInfo.black = StrSave(buf);
7278 boardIndex = blackPlaysFirst ? 1 : 0;
7281 yyboardindex = boardIndex;
7282 moveType = (ChessMove) yylex();
7284 case IllegalMove: /* maybe suicide chess, etc. */
7285 if (appData.debugMode) {
7286 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7287 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7288 setbuf(debugFP, NULL);
7290 case WhitePromotionChancellor:
7291 case BlackPromotionChancellor:
7292 case WhitePromotionArchbishop:
7293 case BlackPromotionArchbishop:
7294 case WhitePromotionQueen:
7295 case BlackPromotionQueen:
7296 case WhitePromotionRook:
7297 case BlackPromotionRook:
7298 case WhitePromotionBishop:
7299 case BlackPromotionBishop:
7300 case WhitePromotionKnight:
7301 case BlackPromotionKnight:
7302 case WhitePromotionKing:
7303 case BlackPromotionKing:
7305 case WhiteCapturesEnPassant:
7306 case BlackCapturesEnPassant:
7307 case WhiteKingSideCastle:
7308 case WhiteQueenSideCastle:
7309 case BlackKingSideCastle:
7310 case BlackQueenSideCastle:
7311 case WhiteKingSideCastleWild:
7312 case WhiteQueenSideCastleWild:
7313 case BlackKingSideCastleWild:
7314 case BlackQueenSideCastleWild:
7316 case WhiteHSideCastleFR:
7317 case WhiteASideCastleFR:
7318 case BlackHSideCastleFR:
7319 case BlackASideCastleFR:
7321 fromX = currentMoveString[0] - AAA;
7322 fromY = currentMoveString[1] - ONE;
7323 toX = currentMoveString[2] - AAA;
7324 toY = currentMoveString[3] - ONE;
7325 promoChar = currentMoveString[4];
7329 fromX = moveType == WhiteDrop ?
7330 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7331 (int) CharToPiece(ToLower(currentMoveString[0]));
7333 toX = currentMoveString[2] - AAA;
7334 toY = currentMoveString[3] - ONE;
7335 promoChar = NULLCHAR;
7339 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7340 if (appData.debugMode) {
7341 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7342 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7343 setbuf(debugFP, NULL);
7345 DisplayError(buf, 0);
7347 case ImpossibleMove:
7349 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7350 if (appData.debugMode) {
7351 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7352 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7353 setbuf(debugFP, NULL);
7355 DisplayError(buf, 0);
7357 case (ChessMove) 0: /* end of file */
7358 if (boardIndex < backwardMostMove) {
7359 /* Oops, gap. How did that happen? */
7360 DisplayError(_("Gap in move list"), 0);
7363 backwardMostMove = blackPlaysFirst ? 1 : 0;
7364 if (boardIndex > forwardMostMove) {
7365 forwardMostMove = boardIndex;
7369 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7370 strcat(parseList[boardIndex-1], " ");
7371 strcat(parseList[boardIndex-1], yy_text);
7383 case GameUnfinished:
7384 if (gameMode == IcsExamining) {
7385 if (boardIndex < backwardMostMove) {
7386 /* Oops, gap. How did that happen? */
7389 backwardMostMove = blackPlaysFirst ? 1 : 0;
7392 gameInfo.result = moveType;
7393 p = strchr(yy_text, '{');
7394 if (p == NULL) p = strchr(yy_text, '(');
7397 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7399 q = strchr(p, *p == '{' ? '}' : ')');
7400 if (q != NULL) *q = NULLCHAR;
7403 gameInfo.resultDetails = StrSave(p);
7406 if (boardIndex >= forwardMostMove &&
7407 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7408 backwardMostMove = blackPlaysFirst ? 1 : 0;
7411 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7412 fromY, fromX, toY, toX, promoChar,
7413 parseList[boardIndex]);
7414 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7415 /* currentMoveString is set as a side-effect of yylex */
7416 strcpy(moveList[boardIndex], currentMoveString);
7417 strcat(moveList[boardIndex], "\n");
7419 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7420 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7426 if(gameInfo.variant != VariantShogi)
7427 strcat(parseList[boardIndex - 1], "+");
7431 strcat(parseList[boardIndex - 1], "#");
7438 /* Apply a move to the given board */
7440 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7441 int fromX, fromY, toX, toY;
7445 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7447 /* [HGM] compute & store e.p. status and castling rights for new position */
7448 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7451 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7452 oldEP = (signed char)board[EP_STATUS];
7453 board[EP_STATUS] = EP_NONE;
7455 if( board[toY][toX] != EmptySquare )
7456 board[EP_STATUS] = EP_CAPTURE;
7458 if( board[fromY][fromX] == WhitePawn ) {
7459 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7460 board[EP_STATUS] = EP_PAWN_MOVE;
7462 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7463 gameInfo.variant != VariantBerolina || toX < fromX)
7464 board[EP_STATUS] = toX | berolina;
7465 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7466 gameInfo.variant != VariantBerolina || toX > fromX)
7467 board[EP_STATUS] = toX;
7470 if( board[fromY][fromX] == BlackPawn ) {
7471 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7472 board[EP_STATUS] = EP_PAWN_MOVE;
7473 if( toY-fromY== -2) {
7474 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7475 gameInfo.variant != VariantBerolina || toX < fromX)
7476 board[EP_STATUS] = toX | berolina;
7477 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7478 gameInfo.variant != VariantBerolina || toX > fromX)
7479 board[EP_STATUS] = toX;
7483 for(i=0; i<nrCastlingRights; i++) {
7484 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7485 board[CASTLING][i] == toX && castlingRank[i] == toY
7486 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7491 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7492 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7493 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7495 if (fromX == toX && fromY == toY) return;
7497 if (fromY == DROP_RANK) {
7499 piece = board[toY][toX] = (ChessSquare) fromX;
7501 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7502 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7503 if(gameInfo.variant == VariantKnightmate)
7504 king += (int) WhiteUnicorn - (int) WhiteKing;
7506 /* Code added by Tord: */
7507 /* FRC castling assumed when king captures friendly rook. */
7508 if (board[fromY][fromX] == WhiteKing &&
7509 board[toY][toX] == WhiteRook) {
7510 board[fromY][fromX] = EmptySquare;
7511 board[toY][toX] = EmptySquare;
7513 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7515 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7517 } else if (board[fromY][fromX] == BlackKing &&
7518 board[toY][toX] == BlackRook) {
7519 board[fromY][fromX] = EmptySquare;
7520 board[toY][toX] = EmptySquare;
7522 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7524 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7526 /* End of code added by Tord */
7528 } else if (board[fromY][fromX] == king
7529 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7530 && toY == fromY && toX > fromX+1) {
7531 board[fromY][fromX] = EmptySquare;
7532 board[toY][toX] = king;
7533 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7534 board[fromY][BOARD_RGHT-1] = EmptySquare;
7535 } else if (board[fromY][fromX] == king
7536 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7537 && toY == fromY && toX < fromX-1) {
7538 board[fromY][fromX] = EmptySquare;
7539 board[toY][toX] = king;
7540 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7541 board[fromY][BOARD_LEFT] = EmptySquare;
7542 } else if (board[fromY][fromX] == WhitePawn
7543 && toY == BOARD_HEIGHT-1
7544 && gameInfo.variant != VariantXiangqi
7546 /* white pawn promotion */
7547 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7548 if (board[toY][toX] == EmptySquare) {
7549 board[toY][toX] = WhiteQueen;
7551 if(gameInfo.variant==VariantBughouse ||
7552 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7553 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7554 board[fromY][fromX] = EmptySquare;
7555 } else if ((fromY == BOARD_HEIGHT-4)
7557 && gameInfo.variant != VariantXiangqi
7558 && gameInfo.variant != VariantBerolina
7559 && (board[fromY][fromX] == WhitePawn)
7560 && (board[toY][toX] == EmptySquare)) {
7561 board[fromY][fromX] = EmptySquare;
7562 board[toY][toX] = WhitePawn;
7563 captured = board[toY - 1][toX];
7564 board[toY - 1][toX] = EmptySquare;
7565 } else if ((fromY == BOARD_HEIGHT-4)
7567 && gameInfo.variant == VariantBerolina
7568 && (board[fromY][fromX] == WhitePawn)
7569 && (board[toY][toX] == EmptySquare)) {
7570 board[fromY][fromX] = EmptySquare;
7571 board[toY][toX] = WhitePawn;
7572 if(oldEP & EP_BEROLIN_A) {
7573 captured = board[fromY][fromX-1];
7574 board[fromY][fromX-1] = EmptySquare;
7575 }else{ captured = board[fromY][fromX+1];
7576 board[fromY][fromX+1] = EmptySquare;
7578 } else if (board[fromY][fromX] == king
7579 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7580 && toY == fromY && toX > fromX+1) {
7581 board[fromY][fromX] = EmptySquare;
7582 board[toY][toX] = king;
7583 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7584 board[fromY][BOARD_RGHT-1] = EmptySquare;
7585 } else if (board[fromY][fromX] == king
7586 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7587 && toY == fromY && toX < fromX-1) {
7588 board[fromY][fromX] = EmptySquare;
7589 board[toY][toX] = king;
7590 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7591 board[fromY][BOARD_LEFT] = EmptySquare;
7592 } else if (fromY == 7 && fromX == 3
7593 && board[fromY][fromX] == BlackKing
7594 && toY == 7 && toX == 5) {
7595 board[fromY][fromX] = EmptySquare;
7596 board[toY][toX] = BlackKing;
7597 board[fromY][7] = EmptySquare;
7598 board[toY][4] = BlackRook;
7599 } else if (fromY == 7 && fromX == 3
7600 && board[fromY][fromX] == BlackKing
7601 && toY == 7 && toX == 1) {
7602 board[fromY][fromX] = EmptySquare;
7603 board[toY][toX] = BlackKing;
7604 board[fromY][0] = EmptySquare;
7605 board[toY][2] = BlackRook;
7606 } else if (board[fromY][fromX] == BlackPawn
7608 && gameInfo.variant != VariantXiangqi
7610 /* black pawn promotion */
7611 board[0][toX] = CharToPiece(ToLower(promoChar));
7612 if (board[0][toX] == EmptySquare) {
7613 board[0][toX] = BlackQueen;
7615 if(gameInfo.variant==VariantBughouse ||
7616 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7617 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7618 board[fromY][fromX] = EmptySquare;
7619 } else if ((fromY == 3)
7621 && gameInfo.variant != VariantXiangqi
7622 && gameInfo.variant != VariantBerolina
7623 && (board[fromY][fromX] == BlackPawn)
7624 && (board[toY][toX] == EmptySquare)) {
7625 board[fromY][fromX] = EmptySquare;
7626 board[toY][toX] = BlackPawn;
7627 captured = board[toY + 1][toX];
7628 board[toY + 1][toX] = EmptySquare;
7629 } else if ((fromY == 3)
7631 && gameInfo.variant == VariantBerolina
7632 && (board[fromY][fromX] == BlackPawn)
7633 && (board[toY][toX] == EmptySquare)) {
7634 board[fromY][fromX] = EmptySquare;
7635 board[toY][toX] = BlackPawn;
7636 if(oldEP & EP_BEROLIN_A) {
7637 captured = board[fromY][fromX-1];
7638 board[fromY][fromX-1] = EmptySquare;
7639 }else{ captured = board[fromY][fromX+1];
7640 board[fromY][fromX+1] = EmptySquare;
7643 board[toY][toX] = board[fromY][fromX];
7644 board[fromY][fromX] = EmptySquare;
7647 /* [HGM] now we promote for Shogi, if needed */
7648 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7649 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7652 if (gameInfo.holdingsWidth != 0) {
7654 /* !!A lot more code needs to be written to support holdings */
7655 /* [HGM] OK, so I have written it. Holdings are stored in the */
7656 /* penultimate board files, so they are automaticlly stored */
7657 /* in the game history. */
7658 if (fromY == DROP_RANK) {
7659 /* Delete from holdings, by decreasing count */
7660 /* and erasing image if necessary */
7662 if(p < (int) BlackPawn) { /* white drop */
7663 p -= (int)WhitePawn;
7664 p = PieceToNumber((ChessSquare)p);
7665 if(p >= gameInfo.holdingsSize) p = 0;
7666 if(--board[p][BOARD_WIDTH-2] <= 0)
7667 board[p][BOARD_WIDTH-1] = EmptySquare;
7668 if((int)board[p][BOARD_WIDTH-2] < 0)
7669 board[p][BOARD_WIDTH-2] = 0;
7670 } else { /* black drop */
7671 p -= (int)BlackPawn;
7672 p = PieceToNumber((ChessSquare)p);
7673 if(p >= gameInfo.holdingsSize) p = 0;
7674 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7675 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7676 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7677 board[BOARD_HEIGHT-1-p][1] = 0;
7680 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7681 && gameInfo.variant != VariantBughouse ) {
7682 /* [HGM] holdings: Add to holdings, if holdings exist */
7683 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7684 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7685 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7688 if (p >= (int) BlackPawn) {
7689 p -= (int)BlackPawn;
7690 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7691 /* in Shogi restore piece to its original first */
7692 captured = (ChessSquare) (DEMOTED captured);
7695 p = PieceToNumber((ChessSquare)p);
7696 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7697 board[p][BOARD_WIDTH-2]++;
7698 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7700 p -= (int)WhitePawn;
7701 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7702 captured = (ChessSquare) (DEMOTED captured);
7705 p = PieceToNumber((ChessSquare)p);
7706 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7707 board[BOARD_HEIGHT-1-p][1]++;
7708 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7711 } else if (gameInfo.variant == VariantAtomic) {
7712 if (captured != EmptySquare) {
7714 for (y = toY-1; y <= toY+1; y++) {
7715 for (x = toX-1; x <= toX+1; x++) {
7716 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7717 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7718 board[y][x] = EmptySquare;
7722 board[toY][toX] = EmptySquare;
7725 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7726 /* [HGM] Shogi promotions */
7727 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7730 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7731 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7732 // [HGM] superchess: take promotion piece out of holdings
7733 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7734 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7735 if(!--board[k][BOARD_WIDTH-2])
7736 board[k][BOARD_WIDTH-1] = EmptySquare;
7738 if(!--board[BOARD_HEIGHT-1-k][1])
7739 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7745 /* Updates forwardMostMove */
7747 MakeMove(fromX, fromY, toX, toY, promoChar)
7748 int fromX, fromY, toX, toY;
7751 // forwardMostMove++; // [HGM] bare: moved downstream
7753 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7754 int timeLeft; static int lastLoadFlag=0; int king, piece;
7755 piece = boards[forwardMostMove][fromY][fromX];
7756 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7757 if(gameInfo.variant == VariantKnightmate)
7758 king += (int) WhiteUnicorn - (int) WhiteKing;
7759 if(forwardMostMove == 0) {
7761 fprintf(serverMoves, "%s;", second.tidy);
7762 fprintf(serverMoves, "%s;", first.tidy);
7763 if(!blackPlaysFirst)
7764 fprintf(serverMoves, "%s;", second.tidy);
7765 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7766 lastLoadFlag = loadFlag;
7768 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7769 // print castling suffix
7770 if( toY == fromY && piece == king ) {
7772 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7774 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7777 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7778 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7779 boards[forwardMostMove][toY][toX] == EmptySquare
7781 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7783 if(promoChar != NULLCHAR)
7784 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7786 fprintf(serverMoves, "/%d/%d",
7787 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7788 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7789 else timeLeft = blackTimeRemaining/1000;
7790 fprintf(serverMoves, "/%d", timeLeft);
7792 fflush(serverMoves);
7795 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7796 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7800 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7801 if (commentList[forwardMostMove+1] != NULL) {
7802 free(commentList[forwardMostMove+1]);
7803 commentList[forwardMostMove+1] = NULL;
7805 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7806 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7807 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7808 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7809 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7810 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7811 gameInfo.result = GameUnfinished;
7812 if (gameInfo.resultDetails != NULL) {
7813 free(gameInfo.resultDetails);
7814 gameInfo.resultDetails = NULL;
7816 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7817 moveList[forwardMostMove - 1]);
7818 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7819 PosFlags(forwardMostMove - 1),
7820 fromY, fromX, toY, toX, promoChar,
7821 parseList[forwardMostMove - 1]);
7822 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7828 if(gameInfo.variant != VariantShogi)
7829 strcat(parseList[forwardMostMove - 1], "+");
7833 strcat(parseList[forwardMostMove - 1], "#");
7836 if (appData.debugMode) {
7837 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7842 /* Updates currentMove if not pausing */
7844 ShowMove(fromX, fromY, toX, toY)
7846 int instant = (gameMode == PlayFromGameFile) ?
7847 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7848 if(appData.noGUI) return;
7849 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7851 if (forwardMostMove == currentMove + 1) {
7852 AnimateMove(boards[forwardMostMove - 1],
7853 fromX, fromY, toX, toY);
7855 if (appData.highlightLastMove) {
7856 SetHighlights(fromX, fromY, toX, toY);
7859 currentMove = forwardMostMove;
7862 if (instant) return;
7864 DisplayMove(currentMove - 1);
7865 DrawPosition(FALSE, boards[currentMove]);
7866 DisplayBothClocks();
7867 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7870 void SendEgtPath(ChessProgramState *cps)
7871 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7872 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7874 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7877 char c, *q = name+1, *r, *s;
7879 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7880 while(*p && *p != ',') *q++ = *p++;
7882 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7883 strcmp(name, ",nalimov:") == 0 ) {
7884 // take nalimov path from the menu-changeable option first, if it is defined
7885 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7886 SendToProgram(buf,cps); // send egtbpath command for nalimov
7888 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7889 (s = StrStr(appData.egtFormats, name)) != NULL) {
7890 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7891 s = r = StrStr(s, ":") + 1; // beginning of path info
7892 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7893 c = *r; *r = 0; // temporarily null-terminate path info
7894 *--q = 0; // strip of trailig ':' from name
7895 sprintf(buf, "egtpath %s %s\n", name+1, s);
7897 SendToProgram(buf,cps); // send egtbpath command for this format
7899 if(*p == ',') p++; // read away comma to position for next format name
7904 InitChessProgram(cps, setup)
7905 ChessProgramState *cps;
7906 int setup; /* [HGM] needed to setup FRC opening position */
7908 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7909 if (appData.noChessProgram) return;
7910 hintRequested = FALSE;
7911 bookRequested = FALSE;
7913 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7914 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7915 if(cps->memSize) { /* [HGM] memory */
7916 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7917 SendToProgram(buf, cps);
7919 SendEgtPath(cps); /* [HGM] EGT */
7920 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7921 sprintf(buf, "cores %d\n", appData.smpCores);
7922 SendToProgram(buf, cps);
7925 SendToProgram(cps->initString, cps);
7926 if (gameInfo.variant != VariantNormal &&
7927 gameInfo.variant != VariantLoadable
7928 /* [HGM] also send variant if board size non-standard */
7929 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7931 char *v = VariantName(gameInfo.variant);
7932 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7933 /* [HGM] in protocol 1 we have to assume all variants valid */
7934 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7935 DisplayFatalError(buf, 0, 1);
7939 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7940 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7941 if( gameInfo.variant == VariantXiangqi )
7942 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7943 if( gameInfo.variant == VariantShogi )
7944 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7945 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7946 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7947 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7948 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7949 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7950 if( gameInfo.variant == VariantCourier )
7951 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7952 if( gameInfo.variant == VariantSuper )
7953 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7954 if( gameInfo.variant == VariantGreat )
7955 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7958 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7959 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7960 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7961 if(StrStr(cps->variants, b) == NULL) {
7962 // specific sized variant not known, check if general sizing allowed
7963 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7964 if(StrStr(cps->variants, "boardsize") == NULL) {
7965 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7966 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7967 DisplayFatalError(buf, 0, 1);
7970 /* [HGM] here we really should compare with the maximum supported board size */
7973 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7974 sprintf(buf, "variant %s\n", b);
7975 SendToProgram(buf, cps);
7977 currentlyInitializedVariant = gameInfo.variant;
7979 /* [HGM] send opening position in FRC to first engine */
7981 SendToProgram("force\n", cps);
7983 /* engine is now in force mode! Set flag to wake it up after first move. */
7984 setboardSpoiledMachineBlack = 1;
7988 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7989 SendToProgram(buf, cps);
7991 cps->maybeThinking = FALSE;
7992 cps->offeredDraw = 0;
7993 if (!appData.icsActive) {
7994 SendTimeControl(cps, movesPerSession, timeControl,
7995 timeIncrement, appData.searchDepth,
7998 if (appData.showThinking
7999 // [HGM] thinking: four options require thinking output to be sent
8000 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8002 SendToProgram("post\n", cps);
8004 SendToProgram("hard\n", cps);
8005 if (!appData.ponderNextMove) {
8006 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8007 it without being sure what state we are in first. "hard"
8008 is not a toggle, so that one is OK.
8010 SendToProgram("easy\n", cps);
8013 sprintf(buf, "ping %d\n", ++cps->lastPing);
8014 SendToProgram(buf, cps);
8016 cps->initDone = TRUE;
8021 StartChessProgram(cps)
8022 ChessProgramState *cps;
8027 if (appData.noChessProgram) return;
8028 cps->initDone = FALSE;
8030 if (strcmp(cps->host, "localhost") == 0) {
8031 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8032 } else if (*appData.remoteShell == NULLCHAR) {
8033 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8035 if (*appData.remoteUser == NULLCHAR) {
8036 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8039 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8040 cps->host, appData.remoteUser, cps->program);
8042 err = StartChildProcess(buf, "", &cps->pr);
8046 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8047 DisplayFatalError(buf, err, 1);
8053 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8054 if (cps->protocolVersion > 1) {
8055 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8056 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8057 cps->comboCnt = 0; // and values of combo boxes
8058 SendToProgram(buf, cps);
8060 SendToProgram("xboard\n", cps);
8066 TwoMachinesEventIfReady P((void))
8068 if (first.lastPing != first.lastPong) {
8069 DisplayMessage("", _("Waiting for first chess program"));
8070 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8073 if (second.lastPing != second.lastPong) {
8074 DisplayMessage("", _("Waiting for second chess program"));
8075 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8083 NextMatchGame P((void))
8085 int index; /* [HGM] autoinc: step load index during match */
8087 if (*appData.loadGameFile != NULLCHAR) {
8088 index = appData.loadGameIndex;
8089 if(index < 0) { // [HGM] autoinc
8090 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8091 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8093 LoadGameFromFile(appData.loadGameFile,
8095 appData.loadGameFile, FALSE);
8096 } else if (*appData.loadPositionFile != NULLCHAR) {
8097 index = appData.loadPositionIndex;
8098 if(index < 0) { // [HGM] autoinc
8099 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8100 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8102 LoadPositionFromFile(appData.loadPositionFile,
8104 appData.loadPositionFile);
8106 TwoMachinesEventIfReady();
8109 void UserAdjudicationEvent( int result )
8111 ChessMove gameResult = GameIsDrawn;
8114 gameResult = WhiteWins;
8116 else if( result < 0 ) {
8117 gameResult = BlackWins;
8120 if( gameMode == TwoMachinesPlay ) {
8121 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8126 // [HGM] save: calculate checksum of game to make games easily identifiable
8127 int StringCheckSum(char *s)
8130 if(s==NULL) return 0;
8131 while(*s) i = i*259 + *s++;
8138 for(i=backwardMostMove; i<forwardMostMove; i++) {
8139 sum += pvInfoList[i].depth;
8140 sum += StringCheckSum(parseList[i]);
8141 sum += StringCheckSum(commentList[i]);
8144 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8145 return sum + StringCheckSum(commentList[i]);
8146 } // end of save patch
8149 GameEnds(result, resultDetails, whosays)
8151 char *resultDetails;
8154 GameMode nextGameMode;
8158 if(endingGame) return; /* [HGM] crash: forbid recursion */
8161 if (appData.debugMode) {
8162 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8163 result, resultDetails ? resultDetails : "(null)", whosays);
8166 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8167 /* If we are playing on ICS, the server decides when the
8168 game is over, but the engine can offer to draw, claim
8172 if (appData.zippyPlay && first.initDone) {
8173 if (result == GameIsDrawn) {
8174 /* In case draw still needs to be claimed */
8175 SendToICS(ics_prefix);
8176 SendToICS("draw\n");
8177 } else if (StrCaseStr(resultDetails, "resign")) {
8178 SendToICS(ics_prefix);
8179 SendToICS("resign\n");
8183 endingGame = 0; /* [HGM] crash */
8187 /* If we're loading the game from a file, stop */
8188 if (whosays == GE_FILE) {
8189 (void) StopLoadGameTimer();
8193 /* Cancel draw offers */
8194 first.offeredDraw = second.offeredDraw = 0;
8196 /* If this is an ICS game, only ICS can really say it's done;
8197 if not, anyone can. */
8198 isIcsGame = (gameMode == IcsPlayingWhite ||
8199 gameMode == IcsPlayingBlack ||
8200 gameMode == IcsObserving ||
8201 gameMode == IcsExamining);
8203 if (!isIcsGame || whosays == GE_ICS) {
8204 /* OK -- not an ICS game, or ICS said it was done */
8206 if (!isIcsGame && !appData.noChessProgram)
8207 SetUserThinkingEnables();
8209 /* [HGM] if a machine claims the game end we verify this claim */
8210 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8211 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8213 ChessMove trueResult = (ChessMove) -1;
8215 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8216 first.twoMachinesColor[0] :
8217 second.twoMachinesColor[0] ;
8219 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8220 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8221 /* [HGM] verify: engine mate claims accepted if they were flagged */
8222 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8224 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8225 /* [HGM] verify: engine mate claims accepted if they were flagged */
8226 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8228 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8229 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8232 // now verify win claims, but not in drop games, as we don't understand those yet
8233 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8234 || gameInfo.variant == VariantGreat) &&
8235 (result == WhiteWins && claimer == 'w' ||
8236 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8237 if (appData.debugMode) {
8238 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8239 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8241 if(result != trueResult) {
8242 sprintf(buf, "False win claim: '%s'", resultDetails);
8243 result = claimer == 'w' ? BlackWins : WhiteWins;
8244 resultDetails = buf;
8247 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8248 && (forwardMostMove <= backwardMostMove ||
8249 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8250 (claimer=='b')==(forwardMostMove&1))
8252 /* [HGM] verify: draws that were not flagged are false claims */
8253 sprintf(buf, "False draw claim: '%s'", resultDetails);
8254 result = claimer == 'w' ? BlackWins : WhiteWins;
8255 resultDetails = buf;
8257 /* (Claiming a loss is accepted no questions asked!) */
8259 /* [HGM] bare: don't allow bare King to win */
8260 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8261 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8262 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8263 && result != GameIsDrawn)
8264 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8265 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8266 int p = (signed char)boards[forwardMostMove][i][j] - color;
8267 if(p >= 0 && p <= (int)WhiteKing) k++;
8269 if (appData.debugMode) {
8270 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8271 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8274 result = GameIsDrawn;
8275 sprintf(buf, "%s but bare king", resultDetails);
8276 resultDetails = buf;
8282 if(serverMoves != NULL && !loadFlag) { char c = '=';
8283 if(result==WhiteWins) c = '+';
8284 if(result==BlackWins) c = '-';
8285 if(resultDetails != NULL)
8286 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8288 if (resultDetails != NULL) {
8289 gameInfo.result = result;
8290 gameInfo.resultDetails = StrSave(resultDetails);
8292 /* display last move only if game was not loaded from file */
8293 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8294 DisplayMove(currentMove - 1);
8296 if (forwardMostMove != 0) {
8297 if (gameMode != PlayFromGameFile && gameMode != EditGame
8298 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8300 if (*appData.saveGameFile != NULLCHAR) {
8301 SaveGameToFile(appData.saveGameFile, TRUE);
8302 } else if (appData.autoSaveGames) {
8305 if (*appData.savePositionFile != NULLCHAR) {
8306 SavePositionToFile(appData.savePositionFile);
8311 /* Tell program how game ended in case it is learning */
8312 /* [HGM] Moved this to after saving the PGN, just in case */
8313 /* engine died and we got here through time loss. In that */
8314 /* case we will get a fatal error writing the pipe, which */
8315 /* would otherwise lose us the PGN. */
8316 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8317 /* output during GameEnds should never be fatal anymore */
8318 if (gameMode == MachinePlaysWhite ||
8319 gameMode == MachinePlaysBlack ||
8320 gameMode == TwoMachinesPlay ||
8321 gameMode == IcsPlayingWhite ||
8322 gameMode == IcsPlayingBlack ||
8323 gameMode == BeginningOfGame) {
8325 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8327 if (first.pr != NoProc) {
8328 SendToProgram(buf, &first);
8330 if (second.pr != NoProc &&
8331 gameMode == TwoMachinesPlay) {
8332 SendToProgram(buf, &second);
8337 if (appData.icsActive) {
8338 if (appData.quietPlay &&
8339 (gameMode == IcsPlayingWhite ||
8340 gameMode == IcsPlayingBlack)) {
8341 SendToICS(ics_prefix);
8342 SendToICS("set shout 1\n");
8344 nextGameMode = IcsIdle;
8345 ics_user_moved = FALSE;
8346 /* clean up premove. It's ugly when the game has ended and the
8347 * premove highlights are still on the board.
8351 ClearPremoveHighlights();
8352 DrawPosition(FALSE, boards[currentMove]);
8354 if (whosays == GE_ICS) {
8357 if (gameMode == IcsPlayingWhite)
8359 else if(gameMode == IcsPlayingBlack)
8363 if (gameMode == IcsPlayingBlack)
8365 else if(gameMode == IcsPlayingWhite)
8372 PlayIcsUnfinishedSound();
8375 } else if (gameMode == EditGame ||
8376 gameMode == PlayFromGameFile ||
8377 gameMode == AnalyzeMode ||
8378 gameMode == AnalyzeFile) {
8379 nextGameMode = gameMode;
8381 nextGameMode = EndOfGame;
8386 nextGameMode = gameMode;
8389 if (appData.noChessProgram) {
8390 gameMode = nextGameMode;
8392 endingGame = 0; /* [HGM] crash */
8397 /* Put first chess program into idle state */
8398 if (first.pr != NoProc &&
8399 (gameMode == MachinePlaysWhite ||
8400 gameMode == MachinePlaysBlack ||
8401 gameMode == TwoMachinesPlay ||
8402 gameMode == IcsPlayingWhite ||
8403 gameMode == IcsPlayingBlack ||
8404 gameMode == BeginningOfGame)) {
8405 SendToProgram("force\n", &first);
8406 if (first.usePing) {
8408 sprintf(buf, "ping %d\n", ++first.lastPing);
8409 SendToProgram(buf, &first);
8412 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8413 /* Kill off first chess program */
8414 if (first.isr != NULL)
8415 RemoveInputSource(first.isr);
8418 if (first.pr != NoProc) {
8420 DoSleep( appData.delayBeforeQuit );
8421 SendToProgram("quit\n", &first);
8422 DoSleep( appData.delayAfterQuit );
8423 DestroyChildProcess(first.pr, first.useSigterm);
8428 /* Put second chess program into idle state */
8429 if (second.pr != NoProc &&
8430 gameMode == TwoMachinesPlay) {
8431 SendToProgram("force\n", &second);
8432 if (second.usePing) {
8434 sprintf(buf, "ping %d\n", ++second.lastPing);
8435 SendToProgram(buf, &second);
8438 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8439 /* Kill off second chess program */
8440 if (second.isr != NULL)
8441 RemoveInputSource(second.isr);
8444 if (second.pr != NoProc) {
8445 DoSleep( appData.delayBeforeQuit );
8446 SendToProgram("quit\n", &second);
8447 DoSleep( appData.delayAfterQuit );
8448 DestroyChildProcess(second.pr, second.useSigterm);
8453 if (matchMode && gameMode == TwoMachinesPlay) {
8456 if (first.twoMachinesColor[0] == 'w') {
8463 if (first.twoMachinesColor[0] == 'b') {
8472 if (matchGame < appData.matchGames) {
8474 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8475 tmp = first.twoMachinesColor;
8476 first.twoMachinesColor = second.twoMachinesColor;
8477 second.twoMachinesColor = tmp;
8479 gameMode = nextGameMode;
8481 if(appData.matchPause>10000 || appData.matchPause<10)
8482 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8483 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8484 endingGame = 0; /* [HGM] crash */
8488 gameMode = nextGameMode;
8489 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8490 first.tidy, second.tidy,
8491 first.matchWins, second.matchWins,
8492 appData.matchGames - (first.matchWins + second.matchWins));
8493 DisplayFatalError(buf, 0, 0);
8496 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8497 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8499 gameMode = nextGameMode;
8501 endingGame = 0; /* [HGM] crash */
8504 /* Assumes program was just initialized (initString sent).
8505 Leaves program in force mode. */
8507 FeedMovesToProgram(cps, upto)
8508 ChessProgramState *cps;
8513 if (appData.debugMode)
8514 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8515 startedFromSetupPosition ? "position and " : "",
8516 backwardMostMove, upto, cps->which);
8517 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8518 // [HGM] variantswitch: make engine aware of new variant
8519 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8520 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8521 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8522 SendToProgram(buf, cps);
8523 currentlyInitializedVariant = gameInfo.variant;
8525 SendToProgram("force\n", cps);
8526 if (startedFromSetupPosition) {
8527 SendBoard(cps, backwardMostMove);
8528 if (appData.debugMode) {
8529 fprintf(debugFP, "feedMoves\n");
8532 for (i = backwardMostMove; i < upto; i++) {
8533 SendMoveToProgram(i, cps);
8539 ResurrectChessProgram()
8541 /* The chess program may have exited.
8542 If so, restart it and feed it all the moves made so far. */
8544 if (appData.noChessProgram || first.pr != NoProc) return;
8546 StartChessProgram(&first);
8547 InitChessProgram(&first, FALSE);
8548 FeedMovesToProgram(&first, currentMove);
8550 if (!first.sendTime) {
8551 /* can't tell gnuchess what its clock should read,
8552 so we bow to its notion. */
8554 timeRemaining[0][currentMove] = whiteTimeRemaining;
8555 timeRemaining[1][currentMove] = blackTimeRemaining;
8558 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8559 appData.icsEngineAnalyze) && first.analysisSupport) {
8560 SendToProgram("analyze\n", &first);
8561 first.analyzing = TRUE;
8574 if (appData.debugMode) {
8575 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8576 redraw, init, gameMode);
8578 CleanupTail(); // [HGM] vari: delete any stored variations
8579 pausing = pauseExamInvalid = FALSE;
8580 startedFromSetupPosition = blackPlaysFirst = FALSE;
8582 whiteFlag = blackFlag = FALSE;
8583 userOfferedDraw = FALSE;
8584 hintRequested = bookRequested = FALSE;
8585 first.maybeThinking = FALSE;
8586 second.maybeThinking = FALSE;
8587 first.bookSuspend = FALSE; // [HGM] book
8588 second.bookSuspend = FALSE;
8589 thinkOutput[0] = NULLCHAR;
8590 lastHint[0] = NULLCHAR;
8591 ClearGameInfo(&gameInfo);
8592 gameInfo.variant = StringToVariant(appData.variant);
8593 ics_user_moved = ics_clock_paused = FALSE;
8594 ics_getting_history = H_FALSE;
8596 white_holding[0] = black_holding[0] = NULLCHAR;
8597 ClearProgramStats();
8598 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8602 flipView = appData.flipView;
8603 ClearPremoveHighlights();
8605 alarmSounded = FALSE;
8607 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8608 if(appData.serverMovesName != NULL) {
8609 /* [HGM] prepare to make moves file for broadcasting */
8610 clock_t t = clock();
8611 if(serverMoves != NULL) fclose(serverMoves);
8612 serverMoves = fopen(appData.serverMovesName, "r");
8613 if(serverMoves != NULL) {
8614 fclose(serverMoves);
8615 /* delay 15 sec before overwriting, so all clients can see end */
8616 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8618 serverMoves = fopen(appData.serverMovesName, "w");
8622 gameMode = BeginningOfGame;
8624 if(appData.icsActive) gameInfo.variant = VariantNormal;
8625 currentMove = forwardMostMove = backwardMostMove = 0;
8626 InitPosition(redraw);
8627 for (i = 0; i < MAX_MOVES; i++) {
8628 if (commentList[i] != NULL) {
8629 free(commentList[i]);
8630 commentList[i] = NULL;
8634 timeRemaining[0][0] = whiteTimeRemaining;
8635 timeRemaining[1][0] = blackTimeRemaining;
8636 if (first.pr == NULL) {
8637 StartChessProgram(&first);
8640 InitChessProgram(&first, startedFromSetupPosition);
8643 DisplayMessage("", "");
8644 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8645 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8652 if (!AutoPlayOneMove())
8654 if (matchMode || appData.timeDelay == 0)
8656 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8658 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8667 int fromX, fromY, toX, toY;
8669 if (appData.debugMode) {
8670 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8673 if (gameMode != PlayFromGameFile)
8676 if (currentMove >= forwardMostMove) {
8677 gameMode = EditGame;
8680 /* [AS] Clear current move marker at the end of a game */
8681 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8686 toX = moveList[currentMove][2] - AAA;
8687 toY = moveList[currentMove][3] - ONE;
8689 if (moveList[currentMove][1] == '@') {
8690 if (appData.highlightLastMove) {
8691 SetHighlights(-1, -1, toX, toY);
8694 fromX = moveList[currentMove][0] - AAA;
8695 fromY = moveList[currentMove][1] - ONE;
8697 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8699 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8701 if (appData.highlightLastMove) {
8702 SetHighlights(fromX, fromY, toX, toY);
8705 DisplayMove(currentMove);
8706 SendMoveToProgram(currentMove++, &first);
8707 DisplayBothClocks();
8708 DrawPosition(FALSE, boards[currentMove]);
8709 // [HGM] PV info: always display, routine tests if empty
8710 DisplayComment(currentMove - 1, commentList[currentMove]);
8716 LoadGameOneMove(readAhead)
8717 ChessMove readAhead;
8719 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8720 char promoChar = NULLCHAR;
8725 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8726 gameMode != AnalyzeMode && gameMode != Training) {
8731 yyboardindex = forwardMostMove;
8732 if (readAhead != (ChessMove)0) {
8733 moveType = readAhead;
8735 if (gameFileFP == NULL)
8737 moveType = (ChessMove) yylex();
8743 if (appData.debugMode)
8744 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8747 /* append the comment but don't display it */
8748 AppendComment(currentMove, p, FALSE);
8751 case WhiteCapturesEnPassant:
8752 case BlackCapturesEnPassant:
8753 case WhitePromotionChancellor:
8754 case BlackPromotionChancellor:
8755 case WhitePromotionArchbishop:
8756 case BlackPromotionArchbishop:
8757 case WhitePromotionCentaur:
8758 case BlackPromotionCentaur:
8759 case WhitePromotionQueen:
8760 case BlackPromotionQueen:
8761 case WhitePromotionRook:
8762 case BlackPromotionRook:
8763 case WhitePromotionBishop:
8764 case BlackPromotionBishop:
8765 case WhitePromotionKnight:
8766 case BlackPromotionKnight:
8767 case WhitePromotionKing:
8768 case BlackPromotionKing:
8770 case WhiteKingSideCastle:
8771 case WhiteQueenSideCastle:
8772 case BlackKingSideCastle:
8773 case BlackQueenSideCastle:
8774 case WhiteKingSideCastleWild:
8775 case WhiteQueenSideCastleWild:
8776 case BlackKingSideCastleWild:
8777 case BlackQueenSideCastleWild:
8779 case WhiteHSideCastleFR:
8780 case WhiteASideCastleFR:
8781 case BlackHSideCastleFR:
8782 case BlackASideCastleFR:
8784 if (appData.debugMode)
8785 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8786 fromX = currentMoveString[0] - AAA;
8787 fromY = currentMoveString[1] - ONE;
8788 toX = currentMoveString[2] - AAA;
8789 toY = currentMoveString[3] - ONE;
8790 promoChar = currentMoveString[4];
8795 if (appData.debugMode)
8796 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8797 fromX = moveType == WhiteDrop ?
8798 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8799 (int) CharToPiece(ToLower(currentMoveString[0]));
8801 toX = currentMoveString[2] - AAA;
8802 toY = currentMoveString[3] - ONE;
8808 case GameUnfinished:
8809 if (appData.debugMode)
8810 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8811 p = strchr(yy_text, '{');
8812 if (p == NULL) p = strchr(yy_text, '(');
8815 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8817 q = strchr(p, *p == '{' ? '}' : ')');
8818 if (q != NULL) *q = NULLCHAR;
8821 GameEnds(moveType, p, GE_FILE);
8823 if (cmailMsgLoaded) {
8825 flipView = WhiteOnMove(currentMove);
8826 if (moveType == GameUnfinished) flipView = !flipView;
8827 if (appData.debugMode)
8828 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8832 case (ChessMove) 0: /* end of file */
8833 if (appData.debugMode)
8834 fprintf(debugFP, "Parser hit end of file\n");
8835 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8841 if (WhiteOnMove(currentMove)) {
8842 GameEnds(BlackWins, "Black mates", GE_FILE);
8844 GameEnds(WhiteWins, "White mates", GE_FILE);
8848 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8855 if (lastLoadGameStart == GNUChessGame) {
8856 /* GNUChessGames have numbers, but they aren't move numbers */
8857 if (appData.debugMode)
8858 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8859 yy_text, (int) moveType);
8860 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8862 /* else fall thru */
8867 /* Reached start of next game in file */
8868 if (appData.debugMode)
8869 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8870 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8876 if (WhiteOnMove(currentMove)) {
8877 GameEnds(BlackWins, "Black mates", GE_FILE);
8879 GameEnds(WhiteWins, "White mates", GE_FILE);
8883 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8889 case PositionDiagram: /* should not happen; ignore */
8890 case ElapsedTime: /* ignore */
8891 case NAG: /* ignore */
8892 if (appData.debugMode)
8893 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8894 yy_text, (int) moveType);
8895 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8898 if (appData.testLegality) {
8899 if (appData.debugMode)
8900 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8901 sprintf(move, _("Illegal move: %d.%s%s"),
8902 (forwardMostMove / 2) + 1,
8903 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8904 DisplayError(move, 0);
8907 if (appData.debugMode)
8908 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8909 yy_text, currentMoveString);
8910 fromX = currentMoveString[0] - AAA;
8911 fromY = currentMoveString[1] - ONE;
8912 toX = currentMoveString[2] - AAA;
8913 toY = currentMoveString[3] - ONE;
8914 promoChar = currentMoveString[4];
8919 if (appData.debugMode)
8920 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8921 sprintf(move, _("Ambiguous move: %d.%s%s"),
8922 (forwardMostMove / 2) + 1,
8923 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8924 DisplayError(move, 0);
8929 case ImpossibleMove:
8930 if (appData.debugMode)
8931 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8932 sprintf(move, _("Illegal move: %d.%s%s"),
8933 (forwardMostMove / 2) + 1,
8934 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8935 DisplayError(move, 0);
8941 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8942 DrawPosition(FALSE, boards[currentMove]);
8943 DisplayBothClocks();
8944 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8945 DisplayComment(currentMove - 1, commentList[currentMove]);
8947 (void) StopLoadGameTimer();
8949 cmailOldMove = forwardMostMove;
8952 /* currentMoveString is set as a side-effect of yylex */
8953 strcat(currentMoveString, "\n");
8954 strcpy(moveList[forwardMostMove], currentMoveString);
8956 thinkOutput[0] = NULLCHAR;
8957 MakeMove(fromX, fromY, toX, toY, promoChar);
8958 currentMove = forwardMostMove;
8963 /* Load the nth game from the given file */
8965 LoadGameFromFile(filename, n, title, useList)
8969 /*Boolean*/ int useList;
8974 if (strcmp(filename, "-") == 0) {
8978 f = fopen(filename, "rb");
8980 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8981 DisplayError(buf, errno);
8985 if (fseek(f, 0, 0) == -1) {
8986 /* f is not seekable; probably a pipe */
8989 if (useList && n == 0) {
8990 int error = GameListBuild(f);
8992 DisplayError(_("Cannot build game list"), error);
8993 } else if (!ListEmpty(&gameList) &&
8994 ((ListGame *) gameList.tailPred)->number > 1) {
8995 GameListPopUp(f, title);
9002 return LoadGame(f, n, title, FALSE);
9007 MakeRegisteredMove()
9009 int fromX, fromY, toX, toY;
9011 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9012 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9015 if (appData.debugMode)
9016 fprintf(debugFP, "Restoring %s for game %d\n",
9017 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9019 thinkOutput[0] = NULLCHAR;
9020 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9021 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9022 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9023 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9024 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9025 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9026 MakeMove(fromX, fromY, toX, toY, promoChar);
9027 ShowMove(fromX, fromY, toX, toY);
9029 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9036 if (WhiteOnMove(currentMove)) {
9037 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9039 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9044 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9051 if (WhiteOnMove(currentMove)) {
9052 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9054 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9059 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9070 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9072 CmailLoadGame(f, gameNumber, title, useList)
9080 if (gameNumber > nCmailGames) {
9081 DisplayError(_("No more games in this message"), 0);
9084 if (f == lastLoadGameFP) {
9085 int offset = gameNumber - lastLoadGameNumber;
9087 cmailMsg[0] = NULLCHAR;
9088 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9089 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9090 nCmailMovesRegistered--;
9092 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9093 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9094 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9097 if (! RegisterMove()) return FALSE;
9101 retVal = LoadGame(f, gameNumber, title, useList);
9103 /* Make move registered during previous look at this game, if any */
9104 MakeRegisteredMove();
9106 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9107 commentList[currentMove]
9108 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9109 DisplayComment(currentMove - 1, commentList[currentMove]);
9115 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9120 int gameNumber = lastLoadGameNumber + offset;
9121 if (lastLoadGameFP == NULL) {
9122 DisplayError(_("No game has been loaded yet"), 0);
9125 if (gameNumber <= 0) {
9126 DisplayError(_("Can't back up any further"), 0);
9129 if (cmailMsgLoaded) {
9130 return CmailLoadGame(lastLoadGameFP, gameNumber,
9131 lastLoadGameTitle, lastLoadGameUseList);
9133 return LoadGame(lastLoadGameFP, gameNumber,
9134 lastLoadGameTitle, lastLoadGameUseList);
9140 /* Load the nth game from open file f */
9142 LoadGame(f, gameNumber, title, useList)
9150 int gn = gameNumber;
9151 ListGame *lg = NULL;
9154 GameMode oldGameMode;
9155 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9157 if (appData.debugMode)
9158 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9160 if (gameMode == Training )
9161 SetTrainingModeOff();
9163 oldGameMode = gameMode;
9164 if (gameMode != BeginningOfGame) {
9169 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9170 fclose(lastLoadGameFP);
9174 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9177 fseek(f, lg->offset, 0);
9178 GameListHighlight(gameNumber);
9182 DisplayError(_("Game number out of range"), 0);
9187 if (fseek(f, 0, 0) == -1) {
9188 if (f == lastLoadGameFP ?
9189 gameNumber == lastLoadGameNumber + 1 :
9193 DisplayError(_("Can't seek on game file"), 0);
9199 lastLoadGameNumber = gameNumber;
9200 strcpy(lastLoadGameTitle, title);
9201 lastLoadGameUseList = useList;
9205 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9206 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9207 lg->gameInfo.black);
9209 } else if (*title != NULLCHAR) {
9210 if (gameNumber > 1) {
9211 sprintf(buf, "%s %d", title, gameNumber);
9214 DisplayTitle(title);
9218 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9219 gameMode = PlayFromGameFile;
9223 currentMove = forwardMostMove = backwardMostMove = 0;
9224 CopyBoard(boards[0], initialPosition);
9228 * Skip the first gn-1 games in the file.
9229 * Also skip over anything that precedes an identifiable
9230 * start of game marker, to avoid being confused by
9231 * garbage at the start of the file. Currently
9232 * recognized start of game markers are the move number "1",
9233 * the pattern "gnuchess .* game", the pattern
9234 * "^[#;%] [^ ]* game file", and a PGN tag block.
9235 * A game that starts with one of the latter two patterns
9236 * will also have a move number 1, possibly
9237 * following a position diagram.
9238 * 5-4-02: Let's try being more lenient and allowing a game to
9239 * start with an unnumbered move. Does that break anything?
9241 cm = lastLoadGameStart = (ChessMove) 0;
9243 yyboardindex = forwardMostMove;
9244 cm = (ChessMove) yylex();
9247 if (cmailMsgLoaded) {
9248 nCmailGames = CMAIL_MAX_GAMES - gn;
9251 DisplayError(_("Game not found in file"), 0);
9258 lastLoadGameStart = cm;
9262 switch (lastLoadGameStart) {
9269 gn--; /* count this game */
9270 lastLoadGameStart = cm;
9279 switch (lastLoadGameStart) {
9284 gn--; /* count this game */
9285 lastLoadGameStart = cm;
9288 lastLoadGameStart = cm; /* game counted already */
9296 yyboardindex = forwardMostMove;
9297 cm = (ChessMove) yylex();
9298 } while (cm == PGNTag || cm == Comment);
9305 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9306 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9307 != CMAIL_OLD_RESULT) {
9309 cmailResult[ CMAIL_MAX_GAMES
9310 - gn - 1] = CMAIL_OLD_RESULT;
9316 /* Only a NormalMove can be at the start of a game
9317 * without a position diagram. */
9318 if (lastLoadGameStart == (ChessMove) 0) {
9320 lastLoadGameStart = MoveNumberOne;
9329 if (appData.debugMode)
9330 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9332 if (cm == XBoardGame) {
9333 /* Skip any header junk before position diagram and/or move 1 */
9335 yyboardindex = forwardMostMove;
9336 cm = (ChessMove) yylex();
9338 if (cm == (ChessMove) 0 ||
9339 cm == GNUChessGame || cm == XBoardGame) {
9340 /* Empty game; pretend end-of-file and handle later */
9345 if (cm == MoveNumberOne || cm == PositionDiagram ||
9346 cm == PGNTag || cm == Comment)
9349 } else if (cm == GNUChessGame) {
9350 if (gameInfo.event != NULL) {
9351 free(gameInfo.event);
9353 gameInfo.event = StrSave(yy_text);
9356 startedFromSetupPosition = FALSE;
9357 while (cm == PGNTag) {
9358 if (appData.debugMode)
9359 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9360 err = ParsePGNTag(yy_text, &gameInfo);
9361 if (!err) numPGNTags++;
9363 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9364 if(gameInfo.variant != oldVariant) {
9365 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9367 oldVariant = gameInfo.variant;
9368 if (appData.debugMode)
9369 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9373 if (gameInfo.fen != NULL) {
9374 Board initial_position;
9375 startedFromSetupPosition = TRUE;
9376 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9378 DisplayError(_("Bad FEN position in file"), 0);
9381 CopyBoard(boards[0], initial_position);
9382 if (blackPlaysFirst) {
9383 currentMove = forwardMostMove = backwardMostMove = 1;
9384 CopyBoard(boards[1], initial_position);
9385 strcpy(moveList[0], "");
9386 strcpy(parseList[0], "");
9387 timeRemaining[0][1] = whiteTimeRemaining;
9388 timeRemaining[1][1] = blackTimeRemaining;
9389 if (commentList[0] != NULL) {
9390 commentList[1] = commentList[0];
9391 commentList[0] = NULL;
9394 currentMove = forwardMostMove = backwardMostMove = 0;
9396 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9398 initialRulePlies = FENrulePlies;
9399 for( i=0; i< nrCastlingRights; i++ )
9400 initialRights[i] = initial_position[CASTLING][i];
9402 yyboardindex = forwardMostMove;
9404 gameInfo.fen = NULL;
9407 yyboardindex = forwardMostMove;
9408 cm = (ChessMove) yylex();
9410 /* Handle comments interspersed among the tags */
9411 while (cm == Comment) {
9413 if (appData.debugMode)
9414 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9416 AppendComment(currentMove, p, FALSE);
9417 yyboardindex = forwardMostMove;
9418 cm = (ChessMove) yylex();
9422 /* don't rely on existence of Event tag since if game was
9423 * pasted from clipboard the Event tag may not exist
9425 if (numPGNTags > 0){
9427 if (gameInfo.variant == VariantNormal) {
9428 gameInfo.variant = StringToVariant(gameInfo.event);
9431 if( appData.autoDisplayTags ) {
9432 tags = PGNTags(&gameInfo);
9433 TagsPopUp(tags, CmailMsg());
9438 /* Make something up, but don't display it now */
9443 if (cm == PositionDiagram) {
9446 Board initial_position;
9448 if (appData.debugMode)
9449 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9451 if (!startedFromSetupPosition) {
9453 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9454 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9464 initial_position[i][j++] = CharToPiece(*p);
9467 while (*p == ' ' || *p == '\t' ||
9468 *p == '\n' || *p == '\r') p++;
9470 if (strncmp(p, "black", strlen("black"))==0)
9471 blackPlaysFirst = TRUE;
9473 blackPlaysFirst = FALSE;
9474 startedFromSetupPosition = TRUE;
9476 CopyBoard(boards[0], initial_position);
9477 if (blackPlaysFirst) {
9478 currentMove = forwardMostMove = backwardMostMove = 1;
9479 CopyBoard(boards[1], initial_position);
9480 strcpy(moveList[0], "");
9481 strcpy(parseList[0], "");
9482 timeRemaining[0][1] = whiteTimeRemaining;
9483 timeRemaining[1][1] = blackTimeRemaining;
9484 if (commentList[0] != NULL) {
9485 commentList[1] = commentList[0];
9486 commentList[0] = NULL;
9489 currentMove = forwardMostMove = backwardMostMove = 0;
9492 yyboardindex = forwardMostMove;
9493 cm = (ChessMove) yylex();
9496 if (first.pr == NoProc) {
9497 StartChessProgram(&first);
9499 InitChessProgram(&first, FALSE);
9500 SendToProgram("force\n", &first);
9501 if (startedFromSetupPosition) {
9502 SendBoard(&first, forwardMostMove);
9503 if (appData.debugMode) {
9504 fprintf(debugFP, "Load Game\n");
9506 DisplayBothClocks();
9509 /* [HGM] server: flag to write setup moves in broadcast file as one */
9510 loadFlag = appData.suppressLoadMoves;
9512 while (cm == Comment) {
9514 if (appData.debugMode)
9515 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9517 AppendComment(currentMove, p, FALSE);
9518 yyboardindex = forwardMostMove;
9519 cm = (ChessMove) yylex();
9522 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9523 cm == WhiteWins || cm == BlackWins ||
9524 cm == GameIsDrawn || cm == GameUnfinished) {
9525 DisplayMessage("", _("No moves in game"));
9526 if (cmailMsgLoaded) {
9527 if (appData.debugMode)
9528 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9532 DrawPosition(FALSE, boards[currentMove]);
9533 DisplayBothClocks();
9534 gameMode = EditGame;
9541 // [HGM] PV info: routine tests if comment empty
9542 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9543 DisplayComment(currentMove - 1, commentList[currentMove]);
9545 if (!matchMode && appData.timeDelay != 0)
9546 DrawPosition(FALSE, boards[currentMove]);
9548 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9549 programStats.ok_to_send = 1;
9552 /* if the first token after the PGN tags is a move
9553 * and not move number 1, retrieve it from the parser
9555 if (cm != MoveNumberOne)
9556 LoadGameOneMove(cm);
9558 /* load the remaining moves from the file */
9559 while (LoadGameOneMove((ChessMove)0)) {
9560 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9561 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9564 /* rewind to the start of the game */
9565 currentMove = backwardMostMove;
9567 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9569 if (oldGameMode == AnalyzeFile ||
9570 oldGameMode == AnalyzeMode) {
9574 if (matchMode || appData.timeDelay == 0) {
9576 gameMode = EditGame;
9578 } else if (appData.timeDelay > 0) {
9582 if (appData.debugMode)
9583 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9585 loadFlag = 0; /* [HGM] true game starts */
9589 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9591 ReloadPosition(offset)
9594 int positionNumber = lastLoadPositionNumber + offset;
9595 if (lastLoadPositionFP == NULL) {
9596 DisplayError(_("No position has been loaded yet"), 0);
9599 if (positionNumber <= 0) {
9600 DisplayError(_("Can't back up any further"), 0);
9603 return LoadPosition(lastLoadPositionFP, positionNumber,
9604 lastLoadPositionTitle);
9607 /* Load the nth position from the given file */
9609 LoadPositionFromFile(filename, n, title)
9617 if (strcmp(filename, "-") == 0) {
9618 return LoadPosition(stdin, n, "stdin");
9620 f = fopen(filename, "rb");
9622 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9623 DisplayError(buf, errno);
9626 return LoadPosition(f, n, title);
9631 /* Load the nth position from the given open file, and close it */
9633 LoadPosition(f, positionNumber, title)
9638 char *p, line[MSG_SIZ];
9639 Board initial_position;
9640 int i, j, fenMode, pn;
9642 if (gameMode == Training )
9643 SetTrainingModeOff();
9645 if (gameMode != BeginningOfGame) {
9648 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9649 fclose(lastLoadPositionFP);
9651 if (positionNumber == 0) positionNumber = 1;
9652 lastLoadPositionFP = f;
9653 lastLoadPositionNumber = positionNumber;
9654 strcpy(lastLoadPositionTitle, title);
9655 if (first.pr == NoProc) {
9656 StartChessProgram(&first);
9657 InitChessProgram(&first, FALSE);
9659 pn = positionNumber;
9660 if (positionNumber < 0) {
9661 /* Negative position number means to seek to that byte offset */
9662 if (fseek(f, -positionNumber, 0) == -1) {
9663 DisplayError(_("Can't seek on position file"), 0);
9668 if (fseek(f, 0, 0) == -1) {
9669 if (f == lastLoadPositionFP ?
9670 positionNumber == lastLoadPositionNumber + 1 :
9671 positionNumber == 1) {
9674 DisplayError(_("Can't seek on position file"), 0);
9679 /* See if this file is FEN or old-style xboard */
9680 if (fgets(line, MSG_SIZ, f) == NULL) {
9681 DisplayError(_("Position not found in file"), 0);
9684 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9685 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9688 if (fenMode || line[0] == '#') pn--;
9690 /* skip positions before number pn */
9691 if (fgets(line, MSG_SIZ, f) == NULL) {
9693 DisplayError(_("Position not found in file"), 0);
9696 if (fenMode || line[0] == '#') pn--;
9701 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9702 DisplayError(_("Bad FEN position in file"), 0);
9706 (void) fgets(line, MSG_SIZ, f);
9707 (void) fgets(line, MSG_SIZ, f);
9709 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9710 (void) fgets(line, MSG_SIZ, f);
9711 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9714 initial_position[i][j++] = CharToPiece(*p);
9718 blackPlaysFirst = FALSE;
9720 (void) fgets(line, MSG_SIZ, f);
9721 if (strncmp(line, "black", strlen("black"))==0)
9722 blackPlaysFirst = TRUE;
9725 startedFromSetupPosition = TRUE;
9727 SendToProgram("force\n", &first);
9728 CopyBoard(boards[0], initial_position);
9729 if (blackPlaysFirst) {
9730 currentMove = forwardMostMove = backwardMostMove = 1;
9731 strcpy(moveList[0], "");
9732 strcpy(parseList[0], "");
9733 CopyBoard(boards[1], initial_position);
9734 DisplayMessage("", _("Black to play"));
9736 currentMove = forwardMostMove = backwardMostMove = 0;
9737 DisplayMessage("", _("White to play"));
9739 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9740 SendBoard(&first, forwardMostMove);
9741 if (appData.debugMode) {
9743 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9744 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9745 fprintf(debugFP, "Load Position\n");
9748 if (positionNumber > 1) {
9749 sprintf(line, "%s %d", title, positionNumber);
9752 DisplayTitle(title);
9754 gameMode = EditGame;
9757 timeRemaining[0][1] = whiteTimeRemaining;
9758 timeRemaining[1][1] = blackTimeRemaining;
9759 DrawPosition(FALSE, boards[currentMove]);
9766 CopyPlayerNameIntoFileName(dest, src)
9769 while (*src != NULLCHAR && *src != ',') {
9774 *(*dest)++ = *src++;
9779 char *DefaultFileName(ext)
9782 static char def[MSG_SIZ];
9785 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9787 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9789 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9798 /* Save the current game to the given file */
9800 SaveGameToFile(filename, append)
9807 if (strcmp(filename, "-") == 0) {
9808 return SaveGame(stdout, 0, NULL);
9810 f = fopen(filename, append ? "a" : "w");
9812 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9813 DisplayError(buf, errno);
9816 return SaveGame(f, 0, NULL);
9825 static char buf[MSG_SIZ];
9828 p = strchr(str, ' ');
9829 if (p == NULL) return str;
9830 strncpy(buf, str, p - str);
9831 buf[p - str] = NULLCHAR;
9835 #define PGN_MAX_LINE 75
9837 #define PGN_SIDE_WHITE 0
9838 #define PGN_SIDE_BLACK 1
9841 static int FindFirstMoveOutOfBook( int side )
9845 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9846 int index = backwardMostMove;
9847 int has_book_hit = 0;
9849 if( (index % 2) != side ) {
9853 while( index < forwardMostMove ) {
9854 /* Check to see if engine is in book */
9855 int depth = pvInfoList[index].depth;
9856 int score = pvInfoList[index].score;
9862 else if( score == 0 && depth == 63 ) {
9863 in_book = 1; /* Zappa */
9865 else if( score == 2 && depth == 99 ) {
9866 in_book = 1; /* Abrok */
9869 has_book_hit += in_book;
9885 void GetOutOfBookInfo( char * buf )
9889 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9891 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9892 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9896 if( oob[0] >= 0 || oob[1] >= 0 ) {
9897 for( i=0; i<2; i++ ) {
9901 if( i > 0 && oob[0] >= 0 ) {
9905 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9906 sprintf( buf+strlen(buf), "%s%.2f",
9907 pvInfoList[idx].score >= 0 ? "+" : "",
9908 pvInfoList[idx].score / 100.0 );
9914 /* Save game in PGN style and close the file */
9919 int i, offset, linelen, newblock;
9923 int movelen, numlen, blank;
9924 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9926 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9928 tm = time((time_t *) NULL);
9930 PrintPGNTags(f, &gameInfo);
9932 if (backwardMostMove > 0 || startedFromSetupPosition) {
9933 char *fen = PositionToFEN(backwardMostMove, NULL);
9934 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9935 fprintf(f, "\n{--------------\n");
9936 PrintPosition(f, backwardMostMove);
9937 fprintf(f, "--------------}\n");
9941 /* [AS] Out of book annotation */
9942 if( appData.saveOutOfBookInfo ) {
9945 GetOutOfBookInfo( buf );
9947 if( buf[0] != '\0' ) {
9948 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9955 i = backwardMostMove;
9959 while (i < forwardMostMove) {
9960 /* Print comments preceding this move */
9961 if (commentList[i] != NULL) {
9962 if (linelen > 0) fprintf(f, "\n");
9963 fprintf(f, "%s", commentList[i]);
9968 /* Format move number */
9970 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9973 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9975 numtext[0] = NULLCHAR;
9978 numlen = strlen(numtext);
9981 /* Print move number */
9982 blank = linelen > 0 && numlen > 0;
9983 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9992 fprintf(f, "%s", numtext);
9996 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9997 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10000 blank = linelen > 0 && movelen > 0;
10001 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10010 fprintf(f, "%s", move_buffer);
10011 linelen += movelen;
10013 /* [AS] Add PV info if present */
10014 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10015 /* [HGM] add time */
10016 char buf[MSG_SIZ]; int seconds;
10018 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10020 if( seconds <= 0) buf[0] = 0; else
10021 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10022 seconds = (seconds + 4)/10; // round to full seconds
10023 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10024 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10027 sprintf( move_buffer, "{%s%.2f/%d%s}",
10028 pvInfoList[i].score >= 0 ? "+" : "",
10029 pvInfoList[i].score / 100.0,
10030 pvInfoList[i].depth,
10033 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10035 /* Print score/depth */
10036 blank = linelen > 0 && movelen > 0;
10037 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10046 fprintf(f, "%s", move_buffer);
10047 linelen += movelen;
10053 /* Start a new line */
10054 if (linelen > 0) fprintf(f, "\n");
10056 /* Print comments after last move */
10057 if (commentList[i] != NULL) {
10058 fprintf(f, "%s\n", commentList[i]);
10062 if (gameInfo.resultDetails != NULL &&
10063 gameInfo.resultDetails[0] != NULLCHAR) {
10064 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10065 PGNResult(gameInfo.result));
10067 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10071 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10075 /* Save game in old style and close the file */
10077 SaveGameOldStyle(f)
10083 tm = time((time_t *) NULL);
10085 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10088 if (backwardMostMove > 0 || startedFromSetupPosition) {
10089 fprintf(f, "\n[--------------\n");
10090 PrintPosition(f, backwardMostMove);
10091 fprintf(f, "--------------]\n");
10096 i = backwardMostMove;
10097 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10099 while (i < forwardMostMove) {
10100 if (commentList[i] != NULL) {
10101 fprintf(f, "[%s]\n", commentList[i]);
10104 if ((i % 2) == 1) {
10105 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10108 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10110 if (commentList[i] != NULL) {
10114 if (i >= forwardMostMove) {
10118 fprintf(f, "%s\n", parseList[i]);
10123 if (commentList[i] != NULL) {
10124 fprintf(f, "[%s]\n", commentList[i]);
10127 /* This isn't really the old style, but it's close enough */
10128 if (gameInfo.resultDetails != NULL &&
10129 gameInfo.resultDetails[0] != NULLCHAR) {
10130 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10131 gameInfo.resultDetails);
10133 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10140 /* Save the current game to open file f and close the file */
10142 SaveGame(f, dummy, dummy2)
10147 if (gameMode == EditPosition) EditPositionDone(TRUE);
10148 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10149 if (appData.oldSaveStyle)
10150 return SaveGameOldStyle(f);
10152 return SaveGamePGN(f);
10155 /* Save the current position to the given file */
10157 SavePositionToFile(filename)
10163 if (strcmp(filename, "-") == 0) {
10164 return SavePosition(stdout, 0, NULL);
10166 f = fopen(filename, "a");
10168 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10169 DisplayError(buf, errno);
10172 SavePosition(f, 0, NULL);
10178 /* Save the current position to the given open file and close the file */
10180 SavePosition(f, dummy, dummy2)
10188 if (gameMode == EditPosition) EditPositionDone(TRUE);
10189 if (appData.oldSaveStyle) {
10190 tm = time((time_t *) NULL);
10192 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10194 fprintf(f, "[--------------\n");
10195 PrintPosition(f, currentMove);
10196 fprintf(f, "--------------]\n");
10198 fen = PositionToFEN(currentMove, NULL);
10199 fprintf(f, "%s\n", fen);
10207 ReloadCmailMsgEvent(unregister)
10211 static char *inFilename = NULL;
10212 static char *outFilename;
10214 struct stat inbuf, outbuf;
10217 /* Any registered moves are unregistered if unregister is set, */
10218 /* i.e. invoked by the signal handler */
10220 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10221 cmailMoveRegistered[i] = FALSE;
10222 if (cmailCommentList[i] != NULL) {
10223 free(cmailCommentList[i]);
10224 cmailCommentList[i] = NULL;
10227 nCmailMovesRegistered = 0;
10230 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10231 cmailResult[i] = CMAIL_NOT_RESULT;
10235 if (inFilename == NULL) {
10236 /* Because the filenames are static they only get malloced once */
10237 /* and they never get freed */
10238 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10239 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10241 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10242 sprintf(outFilename, "%s.out", appData.cmailGameName);
10245 status = stat(outFilename, &outbuf);
10247 cmailMailedMove = FALSE;
10249 status = stat(inFilename, &inbuf);
10250 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10253 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10254 counts the games, notes how each one terminated, etc.
10256 It would be nice to remove this kludge and instead gather all
10257 the information while building the game list. (And to keep it
10258 in the game list nodes instead of having a bunch of fixed-size
10259 parallel arrays.) Note this will require getting each game's
10260 termination from the PGN tags, as the game list builder does
10261 not process the game moves. --mann
10263 cmailMsgLoaded = TRUE;
10264 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10266 /* Load first game in the file or popup game menu */
10267 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10269 #endif /* !WIN32 */
10277 char string[MSG_SIZ];
10279 if ( cmailMailedMove
10280 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10281 return TRUE; /* Allow free viewing */
10284 /* Unregister move to ensure that we don't leave RegisterMove */
10285 /* with the move registered when the conditions for registering no */
10287 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10288 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10289 nCmailMovesRegistered --;
10291 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10293 free(cmailCommentList[lastLoadGameNumber - 1]);
10294 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10298 if (cmailOldMove == -1) {
10299 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10303 if (currentMove > cmailOldMove + 1) {
10304 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10308 if (currentMove < cmailOldMove) {
10309 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10313 if (forwardMostMove > currentMove) {
10314 /* Silently truncate extra moves */
10318 if ( (currentMove == cmailOldMove + 1)
10319 || ( (currentMove == cmailOldMove)
10320 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10321 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10322 if (gameInfo.result != GameUnfinished) {
10323 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10326 if (commentList[currentMove] != NULL) {
10327 cmailCommentList[lastLoadGameNumber - 1]
10328 = StrSave(commentList[currentMove]);
10330 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10332 if (appData.debugMode)
10333 fprintf(debugFP, "Saving %s for game %d\n",
10334 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10337 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10339 f = fopen(string, "w");
10340 if (appData.oldSaveStyle) {
10341 SaveGameOldStyle(f); /* also closes the file */
10343 sprintf(string, "%s.pos.out", appData.cmailGameName);
10344 f = fopen(string, "w");
10345 SavePosition(f, 0, NULL); /* also closes the file */
10347 fprintf(f, "{--------------\n");
10348 PrintPosition(f, currentMove);
10349 fprintf(f, "--------------}\n\n");
10351 SaveGame(f, 0, NULL); /* also closes the file*/
10354 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10355 nCmailMovesRegistered ++;
10356 } else if (nCmailGames == 1) {
10357 DisplayError(_("You have not made a move yet"), 0);
10368 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10369 FILE *commandOutput;
10370 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10371 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10377 if (! cmailMsgLoaded) {
10378 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10382 if (nCmailGames == nCmailResults) {
10383 DisplayError(_("No unfinished games"), 0);
10387 #if CMAIL_PROHIBIT_REMAIL
10388 if (cmailMailedMove) {
10389 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);
10390 DisplayError(msg, 0);
10395 if (! (cmailMailedMove || RegisterMove())) return;
10397 if ( cmailMailedMove
10398 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10399 sprintf(string, partCommandString,
10400 appData.debugMode ? " -v" : "", appData.cmailGameName);
10401 commandOutput = popen(string, "r");
10403 if (commandOutput == NULL) {
10404 DisplayError(_("Failed to invoke cmail"), 0);
10406 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10407 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10409 if (nBuffers > 1) {
10410 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10411 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10412 nBytes = MSG_SIZ - 1;
10414 (void) memcpy(msg, buffer, nBytes);
10416 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10418 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10419 cmailMailedMove = TRUE; /* Prevent >1 moves */
10422 for (i = 0; i < nCmailGames; i ++) {
10423 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10428 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10430 sprintf(buffer, "%s/%s.%s.archive",
10432 appData.cmailGameName,
10434 LoadGameFromFile(buffer, 1, buffer, FALSE);
10435 cmailMsgLoaded = FALSE;
10439 DisplayInformation(msg);
10440 pclose(commandOutput);
10443 if ((*cmailMsg) != '\0') {
10444 DisplayInformation(cmailMsg);
10449 #endif /* !WIN32 */
10458 int prependComma = 0;
10460 char string[MSG_SIZ]; /* Space for game-list */
10463 if (!cmailMsgLoaded) return "";
10465 if (cmailMailedMove) {
10466 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10468 /* Create a list of games left */
10469 sprintf(string, "[");
10470 for (i = 0; i < nCmailGames; i ++) {
10471 if (! ( cmailMoveRegistered[i]
10472 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10473 if (prependComma) {
10474 sprintf(number, ",%d", i + 1);
10476 sprintf(number, "%d", i + 1);
10480 strcat(string, number);
10483 strcat(string, "]");
10485 if (nCmailMovesRegistered + nCmailResults == 0) {
10486 switch (nCmailGames) {
10489 _("Still need to make move for game\n"));
10494 _("Still need to make moves for both games\n"));
10499 _("Still need to make moves for all %d games\n"),
10504 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10507 _("Still need to make a move for game %s\n"),
10512 if (nCmailResults == nCmailGames) {
10513 sprintf(cmailMsg, _("No unfinished games\n"));
10515 sprintf(cmailMsg, _("Ready to send mail\n"));
10521 _("Still need to make moves for games %s\n"),
10533 if (gameMode == Training)
10534 SetTrainingModeOff();
10537 cmailMsgLoaded = FALSE;
10538 if (appData.icsActive) {
10539 SendToICS(ics_prefix);
10540 SendToICS("refresh\n");
10550 /* Give up on clean exit */
10554 /* Keep trying for clean exit */
10558 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10560 if (telnetISR != NULL) {
10561 RemoveInputSource(telnetISR);
10563 if (icsPR != NoProc) {
10564 DestroyChildProcess(icsPR, TRUE);
10567 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10568 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10570 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10571 /* make sure this other one finishes before killing it! */
10572 if(endingGame) { int count = 0;
10573 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10574 while(endingGame && count++ < 10) DoSleep(1);
10575 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10578 /* Kill off chess programs */
10579 if (first.pr != NoProc) {
10582 DoSleep( appData.delayBeforeQuit );
10583 SendToProgram("quit\n", &first);
10584 DoSleep( appData.delayAfterQuit );
10585 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10587 if (second.pr != NoProc) {
10588 DoSleep( appData.delayBeforeQuit );
10589 SendToProgram("quit\n", &second);
10590 DoSleep( appData.delayAfterQuit );
10591 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10593 if (first.isr != NULL) {
10594 RemoveInputSource(first.isr);
10596 if (second.isr != NULL) {
10597 RemoveInputSource(second.isr);
10600 ShutDownFrontEnd();
10607 if (appData.debugMode)
10608 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10612 if (gameMode == MachinePlaysWhite ||
10613 gameMode == MachinePlaysBlack) {
10616 DisplayBothClocks();
10618 if (gameMode == PlayFromGameFile) {
10619 if (appData.timeDelay >= 0)
10620 AutoPlayGameLoop();
10621 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10622 Reset(FALSE, TRUE);
10623 SendToICS(ics_prefix);
10624 SendToICS("refresh\n");
10625 } else if (currentMove < forwardMostMove) {
10626 ForwardInner(forwardMostMove);
10628 pauseExamInvalid = FALSE;
10630 switch (gameMode) {
10634 pauseExamForwardMostMove = forwardMostMove;
10635 pauseExamInvalid = FALSE;
10638 case IcsPlayingWhite:
10639 case IcsPlayingBlack:
10643 case PlayFromGameFile:
10644 (void) StopLoadGameTimer();
10648 case BeginningOfGame:
10649 if (appData.icsActive) return;
10650 /* else fall through */
10651 case MachinePlaysWhite:
10652 case MachinePlaysBlack:
10653 case TwoMachinesPlay:
10654 if (forwardMostMove == 0)
10655 return; /* don't pause if no one has moved */
10656 if ((gameMode == MachinePlaysWhite &&
10657 !WhiteOnMove(forwardMostMove)) ||
10658 (gameMode == MachinePlaysBlack &&
10659 WhiteOnMove(forwardMostMove))) {
10672 char title[MSG_SIZ];
10674 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10675 strcpy(title, _("Edit comment"));
10677 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10678 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10679 parseList[currentMove - 1]);
10682 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10689 char *tags = PGNTags(&gameInfo);
10690 EditTagsPopUp(tags);
10697 if (appData.noChessProgram || gameMode == AnalyzeMode)
10700 if (gameMode != AnalyzeFile) {
10701 if (!appData.icsEngineAnalyze) {
10703 if (gameMode != EditGame) return;
10705 ResurrectChessProgram();
10706 SendToProgram("analyze\n", &first);
10707 first.analyzing = TRUE;
10708 /*first.maybeThinking = TRUE;*/
10709 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10710 EngineOutputPopUp();
10712 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10717 StartAnalysisClock();
10718 GetTimeMark(&lastNodeCountTime);
10725 if (appData.noChessProgram || gameMode == AnalyzeFile)
10728 if (gameMode != AnalyzeMode) {
10730 if (gameMode != EditGame) return;
10731 ResurrectChessProgram();
10732 SendToProgram("analyze\n", &first);
10733 first.analyzing = TRUE;
10734 /*first.maybeThinking = TRUE;*/
10735 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10736 EngineOutputPopUp();
10738 gameMode = AnalyzeFile;
10743 StartAnalysisClock();
10744 GetTimeMark(&lastNodeCountTime);
10749 MachineWhiteEvent()
10752 char *bookHit = NULL;
10754 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10758 if (gameMode == PlayFromGameFile ||
10759 gameMode == TwoMachinesPlay ||
10760 gameMode == Training ||
10761 gameMode == AnalyzeMode ||
10762 gameMode == EndOfGame)
10765 if (gameMode == EditPosition)
10766 EditPositionDone(TRUE);
10768 if (!WhiteOnMove(currentMove)) {
10769 DisplayError(_("It is not White's turn"), 0);
10773 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10776 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10777 gameMode == AnalyzeFile)
10780 ResurrectChessProgram(); /* in case it isn't running */
10781 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10782 gameMode = MachinePlaysWhite;
10785 gameMode = MachinePlaysWhite;
10789 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10791 if (first.sendName) {
10792 sprintf(buf, "name %s\n", gameInfo.black);
10793 SendToProgram(buf, &first);
10795 if (first.sendTime) {
10796 if (first.useColors) {
10797 SendToProgram("black\n", &first); /*gnu kludge*/
10799 SendTimeRemaining(&first, TRUE);
10801 if (first.useColors) {
10802 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10804 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10805 SetMachineThinkingEnables();
10806 first.maybeThinking = TRUE;
10810 if (appData.autoFlipView && !flipView) {
10811 flipView = !flipView;
10812 DrawPosition(FALSE, NULL);
10813 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10816 if(bookHit) { // [HGM] book: simulate book reply
10817 static char bookMove[MSG_SIZ]; // a bit generous?
10819 programStats.nodes = programStats.depth = programStats.time =
10820 programStats.score = programStats.got_only_move = 0;
10821 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10823 strcpy(bookMove, "move ");
10824 strcat(bookMove, bookHit);
10825 HandleMachineMove(bookMove, &first);
10830 MachineBlackEvent()
10833 char *bookHit = NULL;
10835 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10839 if (gameMode == PlayFromGameFile ||
10840 gameMode == TwoMachinesPlay ||
10841 gameMode == Training ||
10842 gameMode == AnalyzeMode ||
10843 gameMode == EndOfGame)
10846 if (gameMode == EditPosition)
10847 EditPositionDone(TRUE);
10849 if (WhiteOnMove(currentMove)) {
10850 DisplayError(_("It is not Black's turn"), 0);
10854 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10857 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10858 gameMode == AnalyzeFile)
10861 ResurrectChessProgram(); /* in case it isn't running */
10862 gameMode = MachinePlaysBlack;
10866 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10868 if (first.sendName) {
10869 sprintf(buf, "name %s\n", gameInfo.white);
10870 SendToProgram(buf, &first);
10872 if (first.sendTime) {
10873 if (first.useColors) {
10874 SendToProgram("white\n", &first); /*gnu kludge*/
10876 SendTimeRemaining(&first, FALSE);
10878 if (first.useColors) {
10879 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10881 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10882 SetMachineThinkingEnables();
10883 first.maybeThinking = TRUE;
10886 if (appData.autoFlipView && flipView) {
10887 flipView = !flipView;
10888 DrawPosition(FALSE, NULL);
10889 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10891 if(bookHit) { // [HGM] book: simulate book reply
10892 static char bookMove[MSG_SIZ]; // a bit generous?
10894 programStats.nodes = programStats.depth = programStats.time =
10895 programStats.score = programStats.got_only_move = 0;
10896 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10898 strcpy(bookMove, "move ");
10899 strcat(bookMove, bookHit);
10900 HandleMachineMove(bookMove, &first);
10906 DisplayTwoMachinesTitle()
10909 if (appData.matchGames > 0) {
10910 if (first.twoMachinesColor[0] == 'w') {
10911 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10912 gameInfo.white, gameInfo.black,
10913 first.matchWins, second.matchWins,
10914 matchGame - 1 - (first.matchWins + second.matchWins));
10916 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10917 gameInfo.white, gameInfo.black,
10918 second.matchWins, first.matchWins,
10919 matchGame - 1 - (first.matchWins + second.matchWins));
10922 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10928 TwoMachinesEvent P((void))
10932 ChessProgramState *onmove;
10933 char *bookHit = NULL;
10935 if (appData.noChessProgram) return;
10937 switch (gameMode) {
10938 case TwoMachinesPlay:
10940 case MachinePlaysWhite:
10941 case MachinePlaysBlack:
10942 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10943 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10947 case BeginningOfGame:
10948 case PlayFromGameFile:
10951 if (gameMode != EditGame) return;
10954 EditPositionDone(TRUE);
10965 // forwardMostMove = currentMove;
10966 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10967 ResurrectChessProgram(); /* in case first program isn't running */
10969 if (second.pr == NULL) {
10970 StartChessProgram(&second);
10971 if (second.protocolVersion == 1) {
10972 TwoMachinesEventIfReady();
10974 /* kludge: allow timeout for initial "feature" command */
10976 DisplayMessage("", _("Starting second chess program"));
10977 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10981 DisplayMessage("", "");
10982 InitChessProgram(&second, FALSE);
10983 SendToProgram("force\n", &second);
10984 if (startedFromSetupPosition) {
10985 SendBoard(&second, backwardMostMove);
10986 if (appData.debugMode) {
10987 fprintf(debugFP, "Two Machines\n");
10990 for (i = backwardMostMove; i < forwardMostMove; i++) {
10991 SendMoveToProgram(i, &second);
10994 gameMode = TwoMachinesPlay;
10998 DisplayTwoMachinesTitle();
11000 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11006 SendToProgram(first.computerString, &first);
11007 if (first.sendName) {
11008 sprintf(buf, "name %s\n", second.tidy);
11009 SendToProgram(buf, &first);
11011 SendToProgram(second.computerString, &second);
11012 if (second.sendName) {
11013 sprintf(buf, "name %s\n", first.tidy);
11014 SendToProgram(buf, &second);
11018 if (!first.sendTime || !second.sendTime) {
11019 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11020 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11022 if (onmove->sendTime) {
11023 if (onmove->useColors) {
11024 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11026 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11028 if (onmove->useColors) {
11029 SendToProgram(onmove->twoMachinesColor, onmove);
11031 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11032 // SendToProgram("go\n", onmove);
11033 onmove->maybeThinking = TRUE;
11034 SetMachineThinkingEnables();
11038 if(bookHit) { // [HGM] book: simulate book reply
11039 static char bookMove[MSG_SIZ]; // a bit generous?
11041 programStats.nodes = programStats.depth = programStats.time =
11042 programStats.score = programStats.got_only_move = 0;
11043 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11045 strcpy(bookMove, "move ");
11046 strcat(bookMove, bookHit);
11047 savedMessage = bookMove; // args for deferred call
11048 savedState = onmove;
11049 ScheduleDelayedEvent(DeferredBookMove, 1);
11056 if (gameMode == Training) {
11057 SetTrainingModeOff();
11058 gameMode = PlayFromGameFile;
11059 DisplayMessage("", _("Training mode off"));
11061 gameMode = Training;
11062 animateTraining = appData.animate;
11064 /* make sure we are not already at the end of the game */
11065 if (currentMove < forwardMostMove) {
11066 SetTrainingModeOn();
11067 DisplayMessage("", _("Training mode on"));
11069 gameMode = PlayFromGameFile;
11070 DisplayError(_("Already at end of game"), 0);
11079 if (!appData.icsActive) return;
11080 switch (gameMode) {
11081 case IcsPlayingWhite:
11082 case IcsPlayingBlack:
11085 case BeginningOfGame:
11093 EditPositionDone(TRUE);
11106 gameMode = IcsIdle;
11117 switch (gameMode) {
11119 SetTrainingModeOff();
11121 case MachinePlaysWhite:
11122 case MachinePlaysBlack:
11123 case BeginningOfGame:
11124 SendToProgram("force\n", &first);
11125 SetUserThinkingEnables();
11127 case PlayFromGameFile:
11128 (void) StopLoadGameTimer();
11129 if (gameFileFP != NULL) {
11134 EditPositionDone(TRUE);
11139 SendToProgram("force\n", &first);
11141 case TwoMachinesPlay:
11142 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11143 ResurrectChessProgram();
11144 SetUserThinkingEnables();
11147 ResurrectChessProgram();
11149 case IcsPlayingBlack:
11150 case IcsPlayingWhite:
11151 DisplayError(_("Warning: You are still playing a game"), 0);
11154 DisplayError(_("Warning: You are still observing a game"), 0);
11157 DisplayError(_("Warning: You are still examining a game"), 0);
11168 first.offeredDraw = second.offeredDraw = 0;
11170 if (gameMode == PlayFromGameFile) {
11171 whiteTimeRemaining = timeRemaining[0][currentMove];
11172 blackTimeRemaining = timeRemaining[1][currentMove];
11176 if (gameMode == MachinePlaysWhite ||
11177 gameMode == MachinePlaysBlack ||
11178 gameMode == TwoMachinesPlay ||
11179 gameMode == EndOfGame) {
11180 i = forwardMostMove;
11181 while (i > currentMove) {
11182 SendToProgram("undo\n", &first);
11185 whiteTimeRemaining = timeRemaining[0][currentMove];
11186 blackTimeRemaining = timeRemaining[1][currentMove];
11187 DisplayBothClocks();
11188 if (whiteFlag || blackFlag) {
11189 whiteFlag = blackFlag = 0;
11194 gameMode = EditGame;
11201 EditPositionEvent()
11203 if (gameMode == EditPosition) {
11209 if (gameMode != EditGame) return;
11211 gameMode = EditPosition;
11214 if (currentMove > 0)
11215 CopyBoard(boards[0], boards[currentMove]);
11217 blackPlaysFirst = !WhiteOnMove(currentMove);
11219 currentMove = forwardMostMove = backwardMostMove = 0;
11220 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11227 /* [DM] icsEngineAnalyze - possible call from other functions */
11228 if (appData.icsEngineAnalyze) {
11229 appData.icsEngineAnalyze = FALSE;
11231 DisplayMessage("",_("Close ICS engine analyze..."));
11233 if (first.analysisSupport && first.analyzing) {
11234 SendToProgram("exit\n", &first);
11235 first.analyzing = FALSE;
11237 thinkOutput[0] = NULLCHAR;
11241 EditPositionDone(Boolean fakeRights)
11243 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11245 startedFromSetupPosition = TRUE;
11246 InitChessProgram(&first, FALSE);
11247 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11248 boards[0][EP_STATUS] = EP_NONE;
11249 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11250 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11251 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11252 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11253 } else boards[0][CASTLING][2] = NoRights;
11254 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11255 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11256 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11257 } else boards[0][CASTLING][5] = NoRights;
11259 SendToProgram("force\n", &first);
11260 if (blackPlaysFirst) {
11261 strcpy(moveList[0], "");
11262 strcpy(parseList[0], "");
11263 currentMove = forwardMostMove = backwardMostMove = 1;
11264 CopyBoard(boards[1], boards[0]);
11266 currentMove = forwardMostMove = backwardMostMove = 0;
11268 SendBoard(&first, forwardMostMove);
11269 if (appData.debugMode) {
11270 fprintf(debugFP, "EditPosDone\n");
11273 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11274 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11275 gameMode = EditGame;
11277 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11278 ClearHighlights(); /* [AS] */
11281 /* Pause for `ms' milliseconds */
11282 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11292 } while (SubtractTimeMarks(&m2, &m1) < ms);
11295 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11297 SendMultiLineToICS(buf)
11300 char temp[MSG_SIZ+1], *p;
11307 strncpy(temp, buf, len);
11312 if (*p == '\n' || *p == '\r')
11317 strcat(temp, "\n");
11319 SendToPlayer(temp, strlen(temp));
11323 SetWhiteToPlayEvent()
11325 if (gameMode == EditPosition) {
11326 blackPlaysFirst = FALSE;
11327 DisplayBothClocks(); /* works because currentMove is 0 */
11328 } else if (gameMode == IcsExamining) {
11329 SendToICS(ics_prefix);
11330 SendToICS("tomove white\n");
11335 SetBlackToPlayEvent()
11337 if (gameMode == EditPosition) {
11338 blackPlaysFirst = TRUE;
11339 currentMove = 1; /* kludge */
11340 DisplayBothClocks();
11342 } else if (gameMode == IcsExamining) {
11343 SendToICS(ics_prefix);
11344 SendToICS("tomove black\n");
11349 EditPositionMenuEvent(selection, x, y)
11350 ChessSquare selection;
11354 ChessSquare piece = boards[0][y][x];
11356 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11358 switch (selection) {
11360 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11361 SendToICS(ics_prefix);
11362 SendToICS("bsetup clear\n");
11363 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11364 SendToICS(ics_prefix);
11365 SendToICS("clearboard\n");
11367 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11368 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11369 for (y = 0; y < BOARD_HEIGHT; y++) {
11370 if (gameMode == IcsExamining) {
11371 if (boards[currentMove][y][x] != EmptySquare) {
11372 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11377 boards[0][y][x] = p;
11382 if (gameMode == EditPosition) {
11383 DrawPosition(FALSE, boards[0]);
11388 SetWhiteToPlayEvent();
11392 SetBlackToPlayEvent();
11396 if (gameMode == IcsExamining) {
11397 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11400 boards[0][y][x] = EmptySquare;
11401 DrawPosition(FALSE, boards[0]);
11406 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11407 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11408 selection = (ChessSquare) (PROMOTED piece);
11409 } else if(piece == EmptySquare) selection = WhiteSilver;
11410 else selection = (ChessSquare)((int)piece - 1);
11414 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11415 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11416 selection = (ChessSquare) (DEMOTED piece);
11417 } else if(piece == EmptySquare) selection = BlackSilver;
11418 else selection = (ChessSquare)((int)piece + 1);
11423 if(gameInfo.variant == VariantShatranj ||
11424 gameInfo.variant == VariantXiangqi ||
11425 gameInfo.variant == VariantCourier )
11426 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11431 if(gameInfo.variant == VariantXiangqi)
11432 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11433 if(gameInfo.variant == VariantKnightmate)
11434 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11437 if (gameMode == IcsExamining) {
11438 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11439 PieceToChar(selection), AAA + x, ONE + y);
11442 boards[0][y][x] = selection;
11443 DrawPosition(FALSE, boards[0]);
11451 DropMenuEvent(selection, x, y)
11452 ChessSquare selection;
11455 ChessMove moveType;
11457 switch (gameMode) {
11458 case IcsPlayingWhite:
11459 case MachinePlaysBlack:
11460 if (!WhiteOnMove(currentMove)) {
11461 DisplayMoveError(_("It is Black's turn"));
11464 moveType = WhiteDrop;
11466 case IcsPlayingBlack:
11467 case MachinePlaysWhite:
11468 if (WhiteOnMove(currentMove)) {
11469 DisplayMoveError(_("It is White's turn"));
11472 moveType = BlackDrop;
11475 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11481 if (moveType == BlackDrop && selection < BlackPawn) {
11482 selection = (ChessSquare) ((int) selection
11483 + (int) BlackPawn - (int) WhitePawn);
11485 if (boards[currentMove][y][x] != EmptySquare) {
11486 DisplayMoveError(_("That square is occupied"));
11490 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11496 /* Accept a pending offer of any kind from opponent */
11498 if (appData.icsActive) {
11499 SendToICS(ics_prefix);
11500 SendToICS("accept\n");
11501 } else if (cmailMsgLoaded) {
11502 if (currentMove == cmailOldMove &&
11503 commentList[cmailOldMove] != NULL &&
11504 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11505 "Black offers a draw" : "White offers a draw")) {
11507 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11508 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11510 DisplayError(_("There is no pending offer on this move"), 0);
11511 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11514 /* Not used for offers from chess program */
11521 /* Decline a pending offer of any kind from opponent */
11523 if (appData.icsActive) {
11524 SendToICS(ics_prefix);
11525 SendToICS("decline\n");
11526 } else if (cmailMsgLoaded) {
11527 if (currentMove == cmailOldMove &&
11528 commentList[cmailOldMove] != NULL &&
11529 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11530 "Black offers a draw" : "White offers a draw")) {
11532 AppendComment(cmailOldMove, "Draw declined", TRUE);
11533 DisplayComment(cmailOldMove - 1, "Draw declined");
11536 DisplayError(_("There is no pending offer on this move"), 0);
11539 /* Not used for offers from chess program */
11546 /* Issue ICS rematch command */
11547 if (appData.icsActive) {
11548 SendToICS(ics_prefix);
11549 SendToICS("rematch\n");
11556 /* Call your opponent's flag (claim a win on time) */
11557 if (appData.icsActive) {
11558 SendToICS(ics_prefix);
11559 SendToICS("flag\n");
11561 switch (gameMode) {
11564 case MachinePlaysWhite:
11567 GameEnds(GameIsDrawn, "Both players ran out of time",
11570 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11572 DisplayError(_("Your opponent is not out of time"), 0);
11575 case MachinePlaysBlack:
11578 GameEnds(GameIsDrawn, "Both players ran out of time",
11581 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11583 DisplayError(_("Your opponent is not out of time"), 0);
11593 /* Offer draw or accept pending draw offer from opponent */
11595 if (appData.icsActive) {
11596 /* Note: tournament rules require draw offers to be
11597 made after you make your move but before you punch
11598 your clock. Currently ICS doesn't let you do that;
11599 instead, you immediately punch your clock after making
11600 a move, but you can offer a draw at any time. */
11602 SendToICS(ics_prefix);
11603 SendToICS("draw\n");
11604 } else if (cmailMsgLoaded) {
11605 if (currentMove == cmailOldMove &&
11606 commentList[cmailOldMove] != NULL &&
11607 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11608 "Black offers a draw" : "White offers a draw")) {
11609 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11610 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11611 } else if (currentMove == cmailOldMove + 1) {
11612 char *offer = WhiteOnMove(cmailOldMove) ?
11613 "White offers a draw" : "Black offers a draw";
11614 AppendComment(currentMove, offer, TRUE);
11615 DisplayComment(currentMove - 1, offer);
11616 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11618 DisplayError(_("You must make your move before offering a draw"), 0);
11619 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11621 } else if (first.offeredDraw) {
11622 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11624 if (first.sendDrawOffers) {
11625 SendToProgram("draw\n", &first);
11626 userOfferedDraw = TRUE;
11634 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11636 if (appData.icsActive) {
11637 SendToICS(ics_prefix);
11638 SendToICS("adjourn\n");
11640 /* Currently GNU Chess doesn't offer or accept Adjourns */
11648 /* Offer Abort or accept pending Abort offer from opponent */
11650 if (appData.icsActive) {
11651 SendToICS(ics_prefix);
11652 SendToICS("abort\n");
11654 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11661 /* Resign. You can do this even if it's not your turn. */
11663 if (appData.icsActive) {
11664 SendToICS(ics_prefix);
11665 SendToICS("resign\n");
11667 switch (gameMode) {
11668 case MachinePlaysWhite:
11669 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11671 case MachinePlaysBlack:
11672 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11675 if (cmailMsgLoaded) {
11677 if (WhiteOnMove(cmailOldMove)) {
11678 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11680 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11682 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11693 StopObservingEvent()
11695 /* Stop observing current games */
11696 SendToICS(ics_prefix);
11697 SendToICS("unobserve\n");
11701 StopExaminingEvent()
11703 /* Stop observing current game */
11704 SendToICS(ics_prefix);
11705 SendToICS("unexamine\n");
11709 ForwardInner(target)
11714 if (appData.debugMode)
11715 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11716 target, currentMove, forwardMostMove);
11718 if (gameMode == EditPosition)
11721 if (gameMode == PlayFromGameFile && !pausing)
11724 if (gameMode == IcsExamining && pausing)
11725 limit = pauseExamForwardMostMove;
11727 limit = forwardMostMove;
11729 if (target > limit) target = limit;
11731 if (target > 0 && moveList[target - 1][0]) {
11732 int fromX, fromY, toX, toY;
11733 toX = moveList[target - 1][2] - AAA;
11734 toY = moveList[target - 1][3] - ONE;
11735 if (moveList[target - 1][1] == '@') {
11736 if (appData.highlightLastMove) {
11737 SetHighlights(-1, -1, toX, toY);
11740 fromX = moveList[target - 1][0] - AAA;
11741 fromY = moveList[target - 1][1] - ONE;
11742 if (target == currentMove + 1) {
11743 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11745 if (appData.highlightLastMove) {
11746 SetHighlights(fromX, fromY, toX, toY);
11750 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11751 gameMode == Training || gameMode == PlayFromGameFile ||
11752 gameMode == AnalyzeFile) {
11753 while (currentMove < target) {
11754 SendMoveToProgram(currentMove++, &first);
11757 currentMove = target;
11760 if (gameMode == EditGame || gameMode == EndOfGame) {
11761 whiteTimeRemaining = timeRemaining[0][currentMove];
11762 blackTimeRemaining = timeRemaining[1][currentMove];
11764 DisplayBothClocks();
11765 DisplayMove(currentMove - 1);
11766 DrawPosition(FALSE, boards[currentMove]);
11767 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11768 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11769 DisplayComment(currentMove - 1, commentList[currentMove]);
11777 if (gameMode == IcsExamining && !pausing) {
11778 SendToICS(ics_prefix);
11779 SendToICS("forward\n");
11781 ForwardInner(currentMove + 1);
11788 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11789 /* to optimze, we temporarily turn off analysis mode while we feed
11790 * the remaining moves to the engine. Otherwise we get analysis output
11793 if (first.analysisSupport) {
11794 SendToProgram("exit\nforce\n", &first);
11795 first.analyzing = FALSE;
11799 if (gameMode == IcsExamining && !pausing) {
11800 SendToICS(ics_prefix);
11801 SendToICS("forward 999999\n");
11803 ForwardInner(forwardMostMove);
11806 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11807 /* we have fed all the moves, so reactivate analysis mode */
11808 SendToProgram("analyze\n", &first);
11809 first.analyzing = TRUE;
11810 /*first.maybeThinking = TRUE;*/
11811 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11816 BackwardInner(target)
11819 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11821 if (appData.debugMode)
11822 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11823 target, currentMove, forwardMostMove);
11825 if (gameMode == EditPosition) return;
11826 if (currentMove <= backwardMostMove) {
11828 DrawPosition(full_redraw, boards[currentMove]);
11831 if (gameMode == PlayFromGameFile && !pausing)
11834 if (moveList[target][0]) {
11835 int fromX, fromY, toX, toY;
11836 toX = moveList[target][2] - AAA;
11837 toY = moveList[target][3] - ONE;
11838 if (moveList[target][1] == '@') {
11839 if (appData.highlightLastMove) {
11840 SetHighlights(-1, -1, toX, toY);
11843 fromX = moveList[target][0] - AAA;
11844 fromY = moveList[target][1] - ONE;
11845 if (target == currentMove - 1) {
11846 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11848 if (appData.highlightLastMove) {
11849 SetHighlights(fromX, fromY, toX, toY);
11853 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11854 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11855 while (currentMove > target) {
11856 SendToProgram("undo\n", &first);
11860 currentMove = target;
11863 if (gameMode == EditGame || gameMode == EndOfGame) {
11864 whiteTimeRemaining = timeRemaining[0][currentMove];
11865 blackTimeRemaining = timeRemaining[1][currentMove];
11867 DisplayBothClocks();
11868 DisplayMove(currentMove - 1);
11869 DrawPosition(full_redraw, boards[currentMove]);
11870 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11871 // [HGM] PV info: routine tests if comment empty
11872 DisplayComment(currentMove - 1, commentList[currentMove]);
11878 if (gameMode == IcsExamining && !pausing) {
11879 SendToICS(ics_prefix);
11880 SendToICS("backward\n");
11882 BackwardInner(currentMove - 1);
11889 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11890 /* to optimize, we temporarily turn off analysis mode while we undo
11891 * all the moves. Otherwise we get analysis output after each undo.
11893 if (first.analysisSupport) {
11894 SendToProgram("exit\nforce\n", &first);
11895 first.analyzing = FALSE;
11899 if (gameMode == IcsExamining && !pausing) {
11900 SendToICS(ics_prefix);
11901 SendToICS("backward 999999\n");
11903 BackwardInner(backwardMostMove);
11906 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11907 /* we have fed all the moves, so reactivate analysis mode */
11908 SendToProgram("analyze\n", &first);
11909 first.analyzing = TRUE;
11910 /*first.maybeThinking = TRUE;*/
11911 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11918 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11919 if (to >= forwardMostMove) to = forwardMostMove;
11920 if (to <= backwardMostMove) to = backwardMostMove;
11921 if (to < currentMove) {
11931 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11934 if (gameMode != IcsExamining) {
11935 DisplayError(_("You are not examining a game"), 0);
11939 DisplayError(_("You can't revert while pausing"), 0);
11942 SendToICS(ics_prefix);
11943 SendToICS("revert\n");
11949 switch (gameMode) {
11950 case MachinePlaysWhite:
11951 case MachinePlaysBlack:
11952 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11953 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11956 if (forwardMostMove < 2) return;
11957 currentMove = forwardMostMove = forwardMostMove - 2;
11958 whiteTimeRemaining = timeRemaining[0][currentMove];
11959 blackTimeRemaining = timeRemaining[1][currentMove];
11960 DisplayBothClocks();
11961 DisplayMove(currentMove - 1);
11962 ClearHighlights();/*!! could figure this out*/
11963 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11964 SendToProgram("remove\n", &first);
11965 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11968 case BeginningOfGame:
11972 case IcsPlayingWhite:
11973 case IcsPlayingBlack:
11974 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11975 SendToICS(ics_prefix);
11976 SendToICS("takeback 2\n");
11978 SendToICS(ics_prefix);
11979 SendToICS("takeback 1\n");
11988 ChessProgramState *cps;
11990 switch (gameMode) {
11991 case MachinePlaysWhite:
11992 if (!WhiteOnMove(forwardMostMove)) {
11993 DisplayError(_("It is your turn"), 0);
11998 case MachinePlaysBlack:
11999 if (WhiteOnMove(forwardMostMove)) {
12000 DisplayError(_("It is your turn"), 0);
12005 case TwoMachinesPlay:
12006 if (WhiteOnMove(forwardMostMove) ==
12007 (first.twoMachinesColor[0] == 'w')) {
12013 case BeginningOfGame:
12017 SendToProgram("?\n", cps);
12021 TruncateGameEvent()
12024 if (gameMode != EditGame) return;
12031 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12032 if (forwardMostMove > currentMove) {
12033 if (gameInfo.resultDetails != NULL) {
12034 free(gameInfo.resultDetails);
12035 gameInfo.resultDetails = NULL;
12036 gameInfo.result = GameUnfinished;
12038 forwardMostMove = currentMove;
12039 HistorySet(parseList, backwardMostMove, forwardMostMove,
12047 if (appData.noChessProgram) return;
12048 switch (gameMode) {
12049 case MachinePlaysWhite:
12050 if (WhiteOnMove(forwardMostMove)) {
12051 DisplayError(_("Wait until your turn"), 0);
12055 case BeginningOfGame:
12056 case MachinePlaysBlack:
12057 if (!WhiteOnMove(forwardMostMove)) {
12058 DisplayError(_("Wait until your turn"), 0);
12063 DisplayError(_("No hint available"), 0);
12066 SendToProgram("hint\n", &first);
12067 hintRequested = TRUE;
12073 if (appData.noChessProgram) return;
12074 switch (gameMode) {
12075 case MachinePlaysWhite:
12076 if (WhiteOnMove(forwardMostMove)) {
12077 DisplayError(_("Wait until your turn"), 0);
12081 case BeginningOfGame:
12082 case MachinePlaysBlack:
12083 if (!WhiteOnMove(forwardMostMove)) {
12084 DisplayError(_("Wait until your turn"), 0);
12089 EditPositionDone(TRUE);
12091 case TwoMachinesPlay:
12096 SendToProgram("bk\n", &first);
12097 bookOutput[0] = NULLCHAR;
12098 bookRequested = TRUE;
12104 char *tags = PGNTags(&gameInfo);
12105 TagsPopUp(tags, CmailMsg());
12109 /* end button procedures */
12112 PrintPosition(fp, move)
12118 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12119 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12120 char c = PieceToChar(boards[move][i][j]);
12121 fputc(c == 'x' ? '.' : c, fp);
12122 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12125 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12126 fprintf(fp, "white to play\n");
12128 fprintf(fp, "black to play\n");
12135 if (gameInfo.white != NULL) {
12136 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12142 /* Find last component of program's own name, using some heuristics */
12144 TidyProgramName(prog, host, buf)
12145 char *prog, *host, buf[MSG_SIZ];
12148 int local = (strcmp(host, "localhost") == 0);
12149 while (!local && (p = strchr(prog, ';')) != NULL) {
12151 while (*p == ' ') p++;
12154 if (*prog == '"' || *prog == '\'') {
12155 q = strchr(prog + 1, *prog);
12157 q = strchr(prog, ' ');
12159 if (q == NULL) q = prog + strlen(prog);
12161 while (p >= prog && *p != '/' && *p != '\\') p--;
12163 if(p == prog && *p == '"') p++;
12164 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12165 memcpy(buf, p, q - p);
12166 buf[q - p] = NULLCHAR;
12174 TimeControlTagValue()
12177 if (!appData.clockMode) {
12179 } else if (movesPerSession > 0) {
12180 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12181 } else if (timeIncrement == 0) {
12182 sprintf(buf, "%ld", timeControl/1000);
12184 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12186 return StrSave(buf);
12192 /* This routine is used only for certain modes */
12193 VariantClass v = gameInfo.variant;
12194 ChessMove r = GameUnfinished;
12197 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12198 r = gameInfo.result;
12199 p = gameInfo.resultDetails;
12200 gameInfo.resultDetails = NULL;
12202 ClearGameInfo(&gameInfo);
12203 gameInfo.variant = v;
12205 switch (gameMode) {
12206 case MachinePlaysWhite:
12207 gameInfo.event = StrSave( appData.pgnEventHeader );
12208 gameInfo.site = StrSave(HostName());
12209 gameInfo.date = PGNDate();
12210 gameInfo.round = StrSave("-");
12211 gameInfo.white = StrSave(first.tidy);
12212 gameInfo.black = StrSave(UserName());
12213 gameInfo.timeControl = TimeControlTagValue();
12216 case MachinePlaysBlack:
12217 gameInfo.event = StrSave( appData.pgnEventHeader );
12218 gameInfo.site = StrSave(HostName());
12219 gameInfo.date = PGNDate();
12220 gameInfo.round = StrSave("-");
12221 gameInfo.white = StrSave(UserName());
12222 gameInfo.black = StrSave(first.tidy);
12223 gameInfo.timeControl = TimeControlTagValue();
12226 case TwoMachinesPlay:
12227 gameInfo.event = StrSave( appData.pgnEventHeader );
12228 gameInfo.site = StrSave(HostName());
12229 gameInfo.date = PGNDate();
12230 if (matchGame > 0) {
12232 sprintf(buf, "%d", matchGame);
12233 gameInfo.round = StrSave(buf);
12235 gameInfo.round = StrSave("-");
12237 if (first.twoMachinesColor[0] == 'w') {
12238 gameInfo.white = StrSave(first.tidy);
12239 gameInfo.black = StrSave(second.tidy);
12241 gameInfo.white = StrSave(second.tidy);
12242 gameInfo.black = StrSave(first.tidy);
12244 gameInfo.timeControl = TimeControlTagValue();
12248 gameInfo.event = StrSave("Edited game");
12249 gameInfo.site = StrSave(HostName());
12250 gameInfo.date = PGNDate();
12251 gameInfo.round = StrSave("-");
12252 gameInfo.white = StrSave("-");
12253 gameInfo.black = StrSave("-");
12254 gameInfo.result = r;
12255 gameInfo.resultDetails = p;
12259 gameInfo.event = StrSave("Edited position");
12260 gameInfo.site = StrSave(HostName());
12261 gameInfo.date = PGNDate();
12262 gameInfo.round = StrSave("-");
12263 gameInfo.white = StrSave("-");
12264 gameInfo.black = StrSave("-");
12267 case IcsPlayingWhite:
12268 case IcsPlayingBlack:
12273 case PlayFromGameFile:
12274 gameInfo.event = StrSave("Game from non-PGN file");
12275 gameInfo.site = StrSave(HostName());
12276 gameInfo.date = PGNDate();
12277 gameInfo.round = StrSave("-");
12278 gameInfo.white = StrSave("?");
12279 gameInfo.black = StrSave("?");
12288 ReplaceComment(index, text)
12294 while (*text == '\n') text++;
12295 len = strlen(text);
12296 while (len > 0 && text[len - 1] == '\n') len--;
12298 if (commentList[index] != NULL)
12299 free(commentList[index]);
12302 commentList[index] = NULL;
12305 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12306 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12307 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12308 commentList[index] = (char *) malloc(len + 2);
12309 strncpy(commentList[index], text, len);
12310 commentList[index][len] = '\n';
12311 commentList[index][len + 1] = NULLCHAR;
12313 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12315 commentList[index] = (char *) malloc(len + 6);
12316 strcpy(commentList[index], "{\n");
12317 strncpy(commentList[index]+2, text, len);
12318 commentList[index][len+2] = NULLCHAR;
12319 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12320 strcat(commentList[index], "\n}\n");
12334 if (ch == '\r') continue;
12336 } while (ch != '\0');
12340 AppendComment(index, text, addBraces)
12343 Boolean addBraces; // [HGM] braces: tells if we should add {}
12348 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12349 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12352 while (*text == '\n') text++;
12353 len = strlen(text);
12354 while (len > 0 && text[len - 1] == '\n') len--;
12356 if (len == 0) return;
12358 if (commentList[index] != NULL) {
12359 old = commentList[index];
12360 oldlen = strlen(old);
12361 while(commentList[index][oldlen-1] == '\n')
12362 commentList[index][--oldlen] = NULLCHAR;
12363 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12364 strcpy(commentList[index], old);
12366 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12367 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12368 if(addBraces) addBraces = FALSE; else { text++; len--; }
12369 while (*text == '\n') { text++; len--; }
12370 commentList[index][--oldlen] = NULLCHAR;
12372 if(addBraces) strcat(commentList[index], "\n{\n");
12373 else strcat(commentList[index], "\n");
12374 strcat(commentList[index], text);
12375 if(addBraces) strcat(commentList[index], "\n}\n");
12376 else strcat(commentList[index], "\n");
12378 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12380 strcpy(commentList[index], "{\n");
12381 else commentList[index][0] = NULLCHAR;
12382 strcat(commentList[index], text);
12383 strcat(commentList[index], "\n");
12384 if(addBraces) strcat(commentList[index], "}\n");
12388 static char * FindStr( char * text, char * sub_text )
12390 char * result = strstr( text, sub_text );
12392 if( result != NULL ) {
12393 result += strlen( sub_text );
12399 /* [AS] Try to extract PV info from PGN comment */
12400 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12401 char *GetInfoFromComment( int index, char * text )
12405 if( text != NULL && index > 0 ) {
12408 int time = -1, sec = 0, deci;
12409 char * s_eval = FindStr( text, "[%eval " );
12410 char * s_emt = FindStr( text, "[%emt " );
12412 if( s_eval != NULL || s_emt != NULL ) {
12416 if( s_eval != NULL ) {
12417 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12421 if( delim != ']' ) {
12426 if( s_emt != NULL ) {
12431 /* We expect something like: [+|-]nnn.nn/dd */
12434 if(*text != '{') return text; // [HGM] braces: must be normal comment
12436 sep = strchr( text, '/' );
12437 if( sep == NULL || sep < (text+4) ) {
12441 time = -1; sec = -1; deci = -1;
12442 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12443 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12444 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12445 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12449 if( score_lo < 0 || score_lo >= 100 ) {
12453 if(sec >= 0) time = 600*time + 10*sec; else
12454 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12456 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12458 /* [HGM] PV time: now locate end of PV info */
12459 while( *++sep >= '0' && *sep <= '9'); // strip depth
12461 while( *++sep >= '0' && *sep <= '9'); // strip time
12463 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12465 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12466 while(*sep == ' ') sep++;
12477 pvInfoList[index-1].depth = depth;
12478 pvInfoList[index-1].score = score;
12479 pvInfoList[index-1].time = 10*time; // centi-sec
12480 if(*sep == '}') *sep = 0; else *--sep = '{';
12486 SendToProgram(message, cps)
12488 ChessProgramState *cps;
12490 int count, outCount, error;
12493 if (cps->pr == NULL) return;
12496 if (appData.debugMode) {
12499 fprintf(debugFP, "%ld >%-6s: %s",
12500 SubtractTimeMarks(&now, &programStartTime),
12501 cps->which, message);
12504 count = strlen(message);
12505 outCount = OutputToProcess(cps->pr, message, count, &error);
12506 if (outCount < count && !exiting
12507 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12508 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12509 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12510 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12511 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12512 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12514 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12516 gameInfo.resultDetails = StrSave(buf);
12518 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12523 ReceiveFromProgram(isr, closure, message, count, error)
12524 InputSourceRef isr;
12532 ChessProgramState *cps = (ChessProgramState *)closure;
12534 if (isr != cps->isr) return; /* Killed intentionally */
12538 _("Error: %s chess program (%s) exited unexpectedly"),
12539 cps->which, cps->program);
12540 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12541 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12542 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12543 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12545 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12547 gameInfo.resultDetails = StrSave(buf);
12549 RemoveInputSource(cps->isr);
12550 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12553 _("Error reading from %s chess program (%s)"),
12554 cps->which, cps->program);
12555 RemoveInputSource(cps->isr);
12557 /* [AS] Program is misbehaving badly... kill it */
12558 if( count == -2 ) {
12559 DestroyChildProcess( cps->pr, 9 );
12563 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12568 if ((end_str = strchr(message, '\r')) != NULL)
12569 *end_str = NULLCHAR;
12570 if ((end_str = strchr(message, '\n')) != NULL)
12571 *end_str = NULLCHAR;
12573 if (appData.debugMode) {
12574 TimeMark now; int print = 1;
12575 char *quote = ""; char c; int i;
12577 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12578 char start = message[0];
12579 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12580 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12581 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12582 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12583 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12584 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12585 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12586 sscanf(message, "pong %c", &c)!=1 && start != '#')
12587 { quote = "# "; print = (appData.engineComments == 2); }
12588 message[0] = start; // restore original message
12592 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12593 SubtractTimeMarks(&now, &programStartTime), cps->which,
12599 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12600 if (appData.icsEngineAnalyze) {
12601 if (strstr(message, "whisper") != NULL ||
12602 strstr(message, "kibitz") != NULL ||
12603 strstr(message, "tellics") != NULL) return;
12606 HandleMachineMove(message, cps);
12611 SendTimeControl(cps, mps, tc, inc, sd, st)
12612 ChessProgramState *cps;
12613 int mps, inc, sd, st;
12619 if( timeControl_2 > 0 ) {
12620 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12621 tc = timeControl_2;
12624 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12625 inc /= cps->timeOdds;
12626 st /= cps->timeOdds;
12628 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12631 /* Set exact time per move, normally using st command */
12632 if (cps->stKludge) {
12633 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12635 if (seconds == 0) {
12636 sprintf(buf, "level 1 %d\n", st/60);
12638 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12641 sprintf(buf, "st %d\n", st);
12644 /* Set conventional or incremental time control, using level command */
12645 if (seconds == 0) {
12646 /* Note old gnuchess bug -- minutes:seconds used to not work.
12647 Fixed in later versions, but still avoid :seconds
12648 when seconds is 0. */
12649 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12651 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12652 seconds, inc/1000);
12655 SendToProgram(buf, cps);
12657 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12658 /* Orthogonally, limit search to given depth */
12660 if (cps->sdKludge) {
12661 sprintf(buf, "depth\n%d\n", sd);
12663 sprintf(buf, "sd %d\n", sd);
12665 SendToProgram(buf, cps);
12668 if(cps->nps > 0) { /* [HGM] nps */
12669 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12671 sprintf(buf, "nps %d\n", cps->nps);
12672 SendToProgram(buf, cps);
12677 ChessProgramState *WhitePlayer()
12678 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12680 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12681 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12687 SendTimeRemaining(cps, machineWhite)
12688 ChessProgramState *cps;
12689 int /*boolean*/ machineWhite;
12691 char message[MSG_SIZ];
12694 /* Note: this routine must be called when the clocks are stopped
12695 or when they have *just* been set or switched; otherwise
12696 it will be off by the time since the current tick started.
12698 if (machineWhite) {
12699 time = whiteTimeRemaining / 10;
12700 otime = blackTimeRemaining / 10;
12702 time = blackTimeRemaining / 10;
12703 otime = whiteTimeRemaining / 10;
12705 /* [HGM] translate opponent's time by time-odds factor */
12706 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12707 if (appData.debugMode) {
12708 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12711 if (time <= 0) time = 1;
12712 if (otime <= 0) otime = 1;
12714 sprintf(message, "time %ld\n", time);
12715 SendToProgram(message, cps);
12717 sprintf(message, "otim %ld\n", otime);
12718 SendToProgram(message, cps);
12722 BoolFeature(p, name, loc, cps)
12726 ChessProgramState *cps;
12729 int len = strlen(name);
12731 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12733 sscanf(*p, "%d", &val);
12735 while (**p && **p != ' ') (*p)++;
12736 sprintf(buf, "accepted %s\n", name);
12737 SendToProgram(buf, cps);
12744 IntFeature(p, name, loc, cps)
12748 ChessProgramState *cps;
12751 int len = strlen(name);
12752 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12754 sscanf(*p, "%d", loc);
12755 while (**p && **p != ' ') (*p)++;
12756 sprintf(buf, "accepted %s\n", name);
12757 SendToProgram(buf, cps);
12764 StringFeature(p, name, loc, cps)
12768 ChessProgramState *cps;
12771 int len = strlen(name);
12772 if (strncmp((*p), name, len) == 0
12773 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12775 sscanf(*p, "%[^\"]", loc);
12776 while (**p && **p != '\"') (*p)++;
12777 if (**p == '\"') (*p)++;
12778 sprintf(buf, "accepted %s\n", name);
12779 SendToProgram(buf, cps);
12786 ParseOption(Option *opt, ChessProgramState *cps)
12787 // [HGM] options: process the string that defines an engine option, and determine
12788 // name, type, default value, and allowed value range
12790 char *p, *q, buf[MSG_SIZ];
12791 int n, min = (-1)<<31, max = 1<<31, def;
12793 if(p = strstr(opt->name, " -spin ")) {
12794 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12795 if(max < min) max = min; // enforce consistency
12796 if(def < min) def = min;
12797 if(def > max) def = max;
12802 } else if((p = strstr(opt->name, " -slider "))) {
12803 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12804 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12805 if(max < min) max = min; // enforce consistency
12806 if(def < min) def = min;
12807 if(def > max) def = max;
12811 opt->type = Spin; // Slider;
12812 } else if((p = strstr(opt->name, " -string "))) {
12813 opt->textValue = p+9;
12814 opt->type = TextBox;
12815 } else if((p = strstr(opt->name, " -file "))) {
12816 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12817 opt->textValue = p+7;
12818 opt->type = TextBox; // FileName;
12819 } else if((p = strstr(opt->name, " -path "))) {
12820 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12821 opt->textValue = p+7;
12822 opt->type = TextBox; // PathName;
12823 } else if(p = strstr(opt->name, " -check ")) {
12824 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12825 opt->value = (def != 0);
12826 opt->type = CheckBox;
12827 } else if(p = strstr(opt->name, " -combo ")) {
12828 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12829 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12830 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12831 opt->value = n = 0;
12832 while(q = StrStr(q, " /// ")) {
12833 n++; *q = 0; // count choices, and null-terminate each of them
12835 if(*q == '*') { // remember default, which is marked with * prefix
12839 cps->comboList[cps->comboCnt++] = q;
12841 cps->comboList[cps->comboCnt++] = NULL;
12843 opt->type = ComboBox;
12844 } else if(p = strstr(opt->name, " -button")) {
12845 opt->type = Button;
12846 } else if(p = strstr(opt->name, " -save")) {
12847 opt->type = SaveButton;
12848 } else return FALSE;
12849 *p = 0; // terminate option name
12850 // now look if the command-line options define a setting for this engine option.
12851 if(cps->optionSettings && cps->optionSettings[0])
12852 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12853 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12854 sprintf(buf, "option %s", p);
12855 if(p = strstr(buf, ",")) *p = 0;
12857 SendToProgram(buf, cps);
12863 FeatureDone(cps, val)
12864 ChessProgramState* cps;
12867 DelayedEventCallback cb = GetDelayedEvent();
12868 if ((cb == InitBackEnd3 && cps == &first) ||
12869 (cb == TwoMachinesEventIfReady && cps == &second)) {
12870 CancelDelayedEvent();
12871 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12873 cps->initDone = val;
12876 /* Parse feature command from engine */
12878 ParseFeatures(args, cps)
12880 ChessProgramState *cps;
12888 while (*p == ' ') p++;
12889 if (*p == NULLCHAR) return;
12891 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12892 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12893 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12894 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12895 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12896 if (BoolFeature(&p, "reuse", &val, cps)) {
12897 /* Engine can disable reuse, but can't enable it if user said no */
12898 if (!val) cps->reuse = FALSE;
12901 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12902 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12903 if (gameMode == TwoMachinesPlay) {
12904 DisplayTwoMachinesTitle();
12910 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12911 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12912 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12913 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12914 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12915 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12916 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12917 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12918 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12919 if (IntFeature(&p, "done", &val, cps)) {
12920 FeatureDone(cps, val);
12923 /* Added by Tord: */
12924 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12925 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12926 /* End of additions by Tord */
12928 /* [HGM] added features: */
12929 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12930 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12931 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12932 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12933 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12934 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12935 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12936 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12937 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12938 SendToProgram(buf, cps);
12941 if(cps->nrOptions >= MAX_OPTIONS) {
12943 sprintf(buf, "%s engine has too many options\n", cps->which);
12944 DisplayError(buf, 0);
12948 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12949 /* End of additions by HGM */
12951 /* unknown feature: complain and skip */
12953 while (*q && *q != '=') q++;
12954 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12955 SendToProgram(buf, cps);
12961 while (*p && *p != '\"') p++;
12962 if (*p == '\"') p++;
12964 while (*p && *p != ' ') p++;
12972 PeriodicUpdatesEvent(newState)
12975 if (newState == appData.periodicUpdates)
12978 appData.periodicUpdates=newState;
12980 /* Display type changes, so update it now */
12981 // DisplayAnalysis();
12983 /* Get the ball rolling again... */
12985 AnalysisPeriodicEvent(1);
12986 StartAnalysisClock();
12991 PonderNextMoveEvent(newState)
12994 if (newState == appData.ponderNextMove) return;
12995 if (gameMode == EditPosition) EditPositionDone(TRUE);
12997 SendToProgram("hard\n", &first);
12998 if (gameMode == TwoMachinesPlay) {
12999 SendToProgram("hard\n", &second);
13002 SendToProgram("easy\n", &first);
13003 thinkOutput[0] = NULLCHAR;
13004 if (gameMode == TwoMachinesPlay) {
13005 SendToProgram("easy\n", &second);
13008 appData.ponderNextMove = newState;
13012 NewSettingEvent(option, command, value)
13018 if (gameMode == EditPosition) EditPositionDone(TRUE);
13019 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13020 SendToProgram(buf, &first);
13021 if (gameMode == TwoMachinesPlay) {
13022 SendToProgram(buf, &second);
13027 ShowThinkingEvent()
13028 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13030 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13031 int newState = appData.showThinking
13032 // [HGM] thinking: other features now need thinking output as well
13033 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13035 if (oldState == newState) return;
13036 oldState = newState;
13037 if (gameMode == EditPosition) EditPositionDone(TRUE);
13039 SendToProgram("post\n", &first);
13040 if (gameMode == TwoMachinesPlay) {
13041 SendToProgram("post\n", &second);
13044 SendToProgram("nopost\n", &first);
13045 thinkOutput[0] = NULLCHAR;
13046 if (gameMode == TwoMachinesPlay) {
13047 SendToProgram("nopost\n", &second);
13050 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13054 AskQuestionEvent(title, question, replyPrefix, which)
13055 char *title; char *question; char *replyPrefix; char *which;
13057 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13058 if (pr == NoProc) return;
13059 AskQuestion(title, question, replyPrefix, pr);
13063 DisplayMove(moveNumber)
13066 char message[MSG_SIZ];
13068 char cpThinkOutput[MSG_SIZ];
13070 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13072 if (moveNumber == forwardMostMove - 1 ||
13073 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13075 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13077 if (strchr(cpThinkOutput, '\n')) {
13078 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13081 *cpThinkOutput = NULLCHAR;
13084 /* [AS] Hide thinking from human user */
13085 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13086 *cpThinkOutput = NULLCHAR;
13087 if( thinkOutput[0] != NULLCHAR ) {
13090 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13091 cpThinkOutput[i] = '.';
13093 cpThinkOutput[i] = NULLCHAR;
13094 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13098 if (moveNumber == forwardMostMove - 1 &&
13099 gameInfo.resultDetails != NULL) {
13100 if (gameInfo.resultDetails[0] == NULLCHAR) {
13101 sprintf(res, " %s", PGNResult(gameInfo.result));
13103 sprintf(res, " {%s} %s",
13104 gameInfo.resultDetails, PGNResult(gameInfo.result));
13110 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13111 DisplayMessage(res, cpThinkOutput);
13113 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13114 WhiteOnMove(moveNumber) ? " " : ".. ",
13115 parseList[moveNumber], res);
13116 DisplayMessage(message, cpThinkOutput);
13121 DisplayComment(moveNumber, text)
13125 char title[MSG_SIZ];
13126 char buf[8000]; // comment can be long!
13129 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13130 strcpy(title, "Comment");
13132 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13133 WhiteOnMove(moveNumber) ? " " : ".. ",
13134 parseList[moveNumber]);
13136 // [HGM] PV info: display PV info together with (or as) comment
13137 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13138 if(text == NULL) text = "";
13139 score = pvInfoList[moveNumber].score;
13140 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13141 depth, (pvInfoList[moveNumber].time+50)/100, text);
13144 if (text != NULL && (appData.autoDisplayComment || commentUp))
13145 CommentPopUp(title, text);
13148 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13149 * might be busy thinking or pondering. It can be omitted if your
13150 * gnuchess is configured to stop thinking immediately on any user
13151 * input. However, that gnuchess feature depends on the FIONREAD
13152 * ioctl, which does not work properly on some flavors of Unix.
13156 ChessProgramState *cps;
13159 if (!cps->useSigint) return;
13160 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13161 switch (gameMode) {
13162 case MachinePlaysWhite:
13163 case MachinePlaysBlack:
13164 case TwoMachinesPlay:
13165 case IcsPlayingWhite:
13166 case IcsPlayingBlack:
13169 /* Skip if we know it isn't thinking */
13170 if (!cps->maybeThinking) return;
13171 if (appData.debugMode)
13172 fprintf(debugFP, "Interrupting %s\n", cps->which);
13173 InterruptChildProcess(cps->pr);
13174 cps->maybeThinking = FALSE;
13179 #endif /*ATTENTION*/
13185 if (whiteTimeRemaining <= 0) {
13188 if (appData.icsActive) {
13189 if (appData.autoCallFlag &&
13190 gameMode == IcsPlayingBlack && !blackFlag) {
13191 SendToICS(ics_prefix);
13192 SendToICS("flag\n");
13196 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13198 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13199 if (appData.autoCallFlag) {
13200 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13207 if (blackTimeRemaining <= 0) {
13210 if (appData.icsActive) {
13211 if (appData.autoCallFlag &&
13212 gameMode == IcsPlayingWhite && !whiteFlag) {
13213 SendToICS(ics_prefix);
13214 SendToICS("flag\n");
13218 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13220 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13221 if (appData.autoCallFlag) {
13222 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13235 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13236 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13239 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13241 if ( !WhiteOnMove(forwardMostMove) )
13242 /* White made time control */
13243 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13244 /* [HGM] time odds: correct new time quota for time odds! */
13245 / WhitePlayer()->timeOdds;
13247 /* Black made time control */
13248 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13249 / WhitePlayer()->other->timeOdds;
13253 DisplayBothClocks()
13255 int wom = gameMode == EditPosition ?
13256 !blackPlaysFirst : WhiteOnMove(currentMove);
13257 DisplayWhiteClock(whiteTimeRemaining, wom);
13258 DisplayBlackClock(blackTimeRemaining, !wom);
13262 /* Timekeeping seems to be a portability nightmare. I think everyone
13263 has ftime(), but I'm really not sure, so I'm including some ifdefs
13264 to use other calls if you don't. Clocks will be less accurate if
13265 you have neither ftime nor gettimeofday.
13268 /* VS 2008 requires the #include outside of the function */
13269 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13270 #include <sys/timeb.h>
13273 /* Get the current time as a TimeMark */
13278 #if HAVE_GETTIMEOFDAY
13280 struct timeval timeVal;
13281 struct timezone timeZone;
13283 gettimeofday(&timeVal, &timeZone);
13284 tm->sec = (long) timeVal.tv_sec;
13285 tm->ms = (int) (timeVal.tv_usec / 1000L);
13287 #else /*!HAVE_GETTIMEOFDAY*/
13290 // include <sys/timeb.h> / moved to just above start of function
13291 struct timeb timeB;
13294 tm->sec = (long) timeB.time;
13295 tm->ms = (int) timeB.millitm;
13297 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13298 tm->sec = (long) time(NULL);
13304 /* Return the difference in milliseconds between two
13305 time marks. We assume the difference will fit in a long!
13308 SubtractTimeMarks(tm2, tm1)
13309 TimeMark *tm2, *tm1;
13311 return 1000L*(tm2->sec - tm1->sec) +
13312 (long) (tm2->ms - tm1->ms);
13317 * Code to manage the game clocks.
13319 * In tournament play, black starts the clock and then white makes a move.
13320 * We give the human user a slight advantage if he is playing white---the
13321 * clocks don't run until he makes his first move, so it takes zero time.
13322 * Also, we don't account for network lag, so we could get out of sync
13323 * with GNU Chess's clock -- but then, referees are always right.
13326 static TimeMark tickStartTM;
13327 static long intendedTickLength;
13330 NextTickLength(timeRemaining)
13331 long timeRemaining;
13333 long nominalTickLength, nextTickLength;
13335 if (timeRemaining > 0L && timeRemaining <= 10000L)
13336 nominalTickLength = 100L;
13338 nominalTickLength = 1000L;
13339 nextTickLength = timeRemaining % nominalTickLength;
13340 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13342 return nextTickLength;
13345 /* Adjust clock one minute up or down */
13347 AdjustClock(Boolean which, int dir)
13349 if(which) blackTimeRemaining += 60000*dir;
13350 else whiteTimeRemaining += 60000*dir;
13351 DisplayBothClocks();
13354 /* Stop clocks and reset to a fresh time control */
13358 (void) StopClockTimer();
13359 if (appData.icsActive) {
13360 whiteTimeRemaining = blackTimeRemaining = 0;
13361 } else if (searchTime) {
13362 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13363 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13364 } else { /* [HGM] correct new time quote for time odds */
13365 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13366 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13368 if (whiteFlag || blackFlag) {
13370 whiteFlag = blackFlag = FALSE;
13372 DisplayBothClocks();
13375 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13377 /* Decrement running clock by amount of time that has passed */
13381 long timeRemaining;
13382 long lastTickLength, fudge;
13385 if (!appData.clockMode) return;
13386 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13390 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13392 /* Fudge if we woke up a little too soon */
13393 fudge = intendedTickLength - lastTickLength;
13394 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13396 if (WhiteOnMove(forwardMostMove)) {
13397 if(whiteNPS >= 0) lastTickLength = 0;
13398 timeRemaining = whiteTimeRemaining -= lastTickLength;
13399 DisplayWhiteClock(whiteTimeRemaining - fudge,
13400 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13402 if(blackNPS >= 0) lastTickLength = 0;
13403 timeRemaining = blackTimeRemaining -= lastTickLength;
13404 DisplayBlackClock(blackTimeRemaining - fudge,
13405 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13408 if (CheckFlags()) return;
13411 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13412 StartClockTimer(intendedTickLength);
13414 /* if the time remaining has fallen below the alarm threshold, sound the
13415 * alarm. if the alarm has sounded and (due to a takeback or time control
13416 * with increment) the time remaining has increased to a level above the
13417 * threshold, reset the alarm so it can sound again.
13420 if (appData.icsActive && appData.icsAlarm) {
13422 /* make sure we are dealing with the user's clock */
13423 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13424 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13427 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13428 alarmSounded = FALSE;
13429 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13431 alarmSounded = TRUE;
13437 /* A player has just moved, so stop the previously running
13438 clock and (if in clock mode) start the other one.
13439 We redisplay both clocks in case we're in ICS mode, because
13440 ICS gives us an update to both clocks after every move.
13441 Note that this routine is called *after* forwardMostMove
13442 is updated, so the last fractional tick must be subtracted
13443 from the color that is *not* on move now.
13448 long lastTickLength;
13450 int flagged = FALSE;
13454 if (StopClockTimer() && appData.clockMode) {
13455 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13456 if (WhiteOnMove(forwardMostMove)) {
13457 if(blackNPS >= 0) lastTickLength = 0;
13458 blackTimeRemaining -= lastTickLength;
13459 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13460 // if(pvInfoList[forwardMostMove-1].time == -1)
13461 pvInfoList[forwardMostMove-1].time = // use GUI time
13462 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13464 if(whiteNPS >= 0) lastTickLength = 0;
13465 whiteTimeRemaining -= lastTickLength;
13466 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13467 // if(pvInfoList[forwardMostMove-1].time == -1)
13468 pvInfoList[forwardMostMove-1].time =
13469 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13471 flagged = CheckFlags();
13473 CheckTimeControl();
13475 if (flagged || !appData.clockMode) return;
13477 switch (gameMode) {
13478 case MachinePlaysBlack:
13479 case MachinePlaysWhite:
13480 case BeginningOfGame:
13481 if (pausing) return;
13485 case PlayFromGameFile:
13493 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13494 if(WhiteOnMove(forwardMostMove))
13495 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13496 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13500 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13501 whiteTimeRemaining : blackTimeRemaining);
13502 StartClockTimer(intendedTickLength);
13506 /* Stop both clocks */
13510 long lastTickLength;
13513 if (!StopClockTimer()) return;
13514 if (!appData.clockMode) return;
13518 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13519 if (WhiteOnMove(forwardMostMove)) {
13520 if(whiteNPS >= 0) lastTickLength = 0;
13521 whiteTimeRemaining -= lastTickLength;
13522 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13524 if(blackNPS >= 0) lastTickLength = 0;
13525 blackTimeRemaining -= lastTickLength;
13526 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13531 /* Start clock of player on move. Time may have been reset, so
13532 if clock is already running, stop and restart it. */
13536 (void) StopClockTimer(); /* in case it was running already */
13537 DisplayBothClocks();
13538 if (CheckFlags()) return;
13540 if (!appData.clockMode) return;
13541 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13543 GetTimeMark(&tickStartTM);
13544 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13545 whiteTimeRemaining : blackTimeRemaining);
13547 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13548 whiteNPS = blackNPS = -1;
13549 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13550 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13551 whiteNPS = first.nps;
13552 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13553 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13554 blackNPS = first.nps;
13555 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13556 whiteNPS = second.nps;
13557 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13558 blackNPS = second.nps;
13559 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13561 StartClockTimer(intendedTickLength);
13568 long second, minute, hour, day;
13570 static char buf[32];
13572 if (ms > 0 && ms <= 9900) {
13573 /* convert milliseconds to tenths, rounding up */
13574 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13576 sprintf(buf, " %03.1f ", tenths/10.0);
13580 /* convert milliseconds to seconds, rounding up */
13581 /* use floating point to avoid strangeness of integer division
13582 with negative dividends on many machines */
13583 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13590 day = second / (60 * 60 * 24);
13591 second = second % (60 * 60 * 24);
13592 hour = second / (60 * 60);
13593 second = second % (60 * 60);
13594 minute = second / 60;
13595 second = second % 60;
13598 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13599 sign, day, hour, minute, second);
13601 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13603 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13610 * This is necessary because some C libraries aren't ANSI C compliant yet.
13613 StrStr(string, match)
13614 char *string, *match;
13618 length = strlen(match);
13620 for (i = strlen(string) - length; i >= 0; i--, string++)
13621 if (!strncmp(match, string, length))
13628 StrCaseStr(string, match)
13629 char *string, *match;
13633 length = strlen(match);
13635 for (i = strlen(string) - length; i >= 0; i--, string++) {
13636 for (j = 0; j < length; j++) {
13637 if (ToLower(match[j]) != ToLower(string[j]))
13640 if (j == length) return string;
13654 c1 = ToLower(*s1++);
13655 c2 = ToLower(*s2++);
13656 if (c1 > c2) return 1;
13657 if (c1 < c2) return -1;
13658 if (c1 == NULLCHAR) return 0;
13667 return isupper(c) ? tolower(c) : c;
13675 return islower(c) ? toupper(c) : c;
13677 #endif /* !_amigados */
13685 if ((ret = (char *) malloc(strlen(s) + 1))) {
13692 StrSavePtr(s, savePtr)
13693 char *s, **savePtr;
13698 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13699 strcpy(*savePtr, s);
13711 clock = time((time_t *)NULL);
13712 tm = localtime(&clock);
13713 sprintf(buf, "%04d.%02d.%02d",
13714 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13715 return StrSave(buf);
13720 PositionToFEN(move, overrideCastling)
13722 char *overrideCastling;
13724 int i, j, fromX, fromY, toX, toY;
13731 whiteToPlay = (gameMode == EditPosition) ?
13732 !blackPlaysFirst : (move % 2 == 0);
13735 /* Piece placement data */
13736 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13738 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13739 if (boards[move][i][j] == EmptySquare) {
13741 } else { ChessSquare piece = boards[move][i][j];
13742 if (emptycount > 0) {
13743 if(emptycount<10) /* [HGM] can be >= 10 */
13744 *p++ = '0' + emptycount;
13745 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13748 if(PieceToChar(piece) == '+') {
13749 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13751 piece = (ChessSquare)(DEMOTED piece);
13753 *p++ = PieceToChar(piece);
13755 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13756 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13761 if (emptycount > 0) {
13762 if(emptycount<10) /* [HGM] can be >= 10 */
13763 *p++ = '0' + emptycount;
13764 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13771 /* [HGM] print Crazyhouse or Shogi holdings */
13772 if( gameInfo.holdingsWidth ) {
13773 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13775 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13776 piece = boards[move][i][BOARD_WIDTH-1];
13777 if( piece != EmptySquare )
13778 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13779 *p++ = PieceToChar(piece);
13781 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13782 piece = boards[move][BOARD_HEIGHT-i-1][0];
13783 if( piece != EmptySquare )
13784 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13785 *p++ = PieceToChar(piece);
13788 if( q == p ) *p++ = '-';
13794 *p++ = whiteToPlay ? 'w' : 'b';
13797 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13798 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13800 if(nrCastlingRights) {
13802 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13803 /* [HGM] write directly from rights */
13804 if(boards[move][CASTLING][2] != NoRights &&
13805 boards[move][CASTLING][0] != NoRights )
13806 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13807 if(boards[move][CASTLING][2] != NoRights &&
13808 boards[move][CASTLING][1] != NoRights )
13809 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13810 if(boards[move][CASTLING][5] != NoRights &&
13811 boards[move][CASTLING][3] != NoRights )
13812 *p++ = boards[move][CASTLING][3] + AAA;
13813 if(boards[move][CASTLING][5] != NoRights &&
13814 boards[move][CASTLING][4] != NoRights )
13815 *p++ = boards[move][CASTLING][4] + AAA;
13818 /* [HGM] write true castling rights */
13819 if( nrCastlingRights == 6 ) {
13820 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13821 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13822 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13823 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13824 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13825 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13826 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13827 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13830 if (q == p) *p++ = '-'; /* No castling rights */
13834 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13835 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13836 /* En passant target square */
13837 if (move > backwardMostMove) {
13838 fromX = moveList[move - 1][0] - AAA;
13839 fromY = moveList[move - 1][1] - ONE;
13840 toX = moveList[move - 1][2] - AAA;
13841 toY = moveList[move - 1][3] - ONE;
13842 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13843 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13844 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13846 /* 2-square pawn move just happened */
13848 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13852 } else if(move == backwardMostMove) {
13853 // [HGM] perhaps we should always do it like this, and forget the above?
13854 if((signed char)boards[move][EP_STATUS] >= 0) {
13855 *p++ = boards[move][EP_STATUS] + AAA;
13856 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13867 /* [HGM] find reversible plies */
13868 { int i = 0, j=move;
13870 if (appData.debugMode) { int k;
13871 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13872 for(k=backwardMostMove; k<=forwardMostMove; k++)
13873 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13877 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13878 if( j == backwardMostMove ) i += initialRulePlies;
13879 sprintf(p, "%d ", i);
13880 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13882 /* Fullmove number */
13883 sprintf(p, "%d", (move / 2) + 1);
13885 return StrSave(buf);
13889 ParseFEN(board, blackPlaysFirst, fen)
13891 int *blackPlaysFirst;
13901 /* [HGM] by default clear Crazyhouse holdings, if present */
13902 if(gameInfo.holdingsWidth) {
13903 for(i=0; i<BOARD_HEIGHT; i++) {
13904 board[i][0] = EmptySquare; /* black holdings */
13905 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13906 board[i][1] = (ChessSquare) 0; /* black counts */
13907 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13911 /* Piece placement data */
13912 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13915 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13916 if (*p == '/') p++;
13917 emptycount = gameInfo.boardWidth - j;
13918 while (emptycount--)
13919 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13921 #if(BOARD_FILES >= 10)
13922 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13923 p++; emptycount=10;
13924 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13925 while (emptycount--)
13926 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13928 } else if (isdigit(*p)) {
13929 emptycount = *p++ - '0';
13930 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13931 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13932 while (emptycount--)
13933 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13934 } else if (*p == '+' || isalpha(*p)) {
13935 if (j >= gameInfo.boardWidth) return FALSE;
13937 piece = CharToPiece(*++p);
13938 if(piece == EmptySquare) return FALSE; /* unknown piece */
13939 piece = (ChessSquare) (PROMOTED piece ); p++;
13940 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13941 } else piece = CharToPiece(*p++);
13943 if(piece==EmptySquare) return FALSE; /* unknown piece */
13944 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13945 piece = (ChessSquare) (PROMOTED piece);
13946 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13949 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13955 while (*p == '/' || *p == ' ') p++;
13957 /* [HGM] look for Crazyhouse holdings here */
13958 while(*p==' ') p++;
13959 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13961 if(*p == '-' ) *p++; /* empty holdings */ else {
13962 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13963 /* if we would allow FEN reading to set board size, we would */
13964 /* have to add holdings and shift the board read so far here */
13965 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13967 if((int) piece >= (int) BlackPawn ) {
13968 i = (int)piece - (int)BlackPawn;
13969 i = PieceToNumber((ChessSquare)i);
13970 if( i >= gameInfo.holdingsSize ) return FALSE;
13971 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13972 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13974 i = (int)piece - (int)WhitePawn;
13975 i = PieceToNumber((ChessSquare)i);
13976 if( i >= gameInfo.holdingsSize ) return FALSE;
13977 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13978 board[i][BOARD_WIDTH-2]++; /* black holdings */
13982 if(*p == ']') *p++;
13985 while(*p == ' ') p++;
13990 *blackPlaysFirst = FALSE;
13993 *blackPlaysFirst = TRUE;
13999 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14000 /* return the extra info in global variiables */
14002 /* set defaults in case FEN is incomplete */
14003 board[EP_STATUS] = EP_UNKNOWN;
14004 for(i=0; i<nrCastlingRights; i++ ) {
14005 board[CASTLING][i] =
14006 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14007 } /* assume possible unless obviously impossible */
14008 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14009 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14010 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14011 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14012 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14013 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14016 while(*p==' ') p++;
14017 if(nrCastlingRights) {
14018 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14019 /* castling indicator present, so default becomes no castlings */
14020 for(i=0; i<nrCastlingRights; i++ ) {
14021 board[CASTLING][i] = NoRights;
14024 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14025 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14026 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14027 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14028 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14030 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14031 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14032 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14036 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14037 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14038 board[CASTLING][2] = whiteKingFile;
14041 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14042 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14043 board[CASTLING][2] = whiteKingFile;
14046 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14047 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14048 board[CASTLING][5] = blackKingFile;
14051 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14052 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14053 board[CASTLING][5] = blackKingFile;
14056 default: /* FRC castlings */
14057 if(c >= 'a') { /* black rights */
14058 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14059 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14060 if(i == BOARD_RGHT) break;
14061 board[CASTLING][5] = i;
14063 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14064 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14066 board[CASTLING][3] = c;
14068 board[CASTLING][4] = c;
14069 } else { /* white rights */
14070 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14071 if(board[0][i] == WhiteKing) break;
14072 if(i == BOARD_RGHT) break;
14073 board[CASTLING][2] = i;
14074 c -= AAA - 'a' + 'A';
14075 if(board[0][c] >= WhiteKing) break;
14077 board[CASTLING][0] = c;
14079 board[CASTLING][1] = c;
14083 if (appData.debugMode) {
14084 fprintf(debugFP, "FEN castling rights:");
14085 for(i=0; i<nrCastlingRights; i++)
14086 fprintf(debugFP, " %d", board[CASTLING][i]);
14087 fprintf(debugFP, "\n");
14090 while(*p==' ') p++;
14093 /* read e.p. field in games that know e.p. capture */
14094 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14095 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14097 p++; board[EP_STATUS] = EP_NONE;
14099 char c = *p++ - AAA;
14101 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14102 if(*p >= '0' && *p <='9') *p++;
14103 board[EP_STATUS] = c;
14108 if(sscanf(p, "%d", &i) == 1) {
14109 FENrulePlies = i; /* 50-move ply counter */
14110 /* (The move number is still ignored) */
14117 EditPositionPasteFEN(char *fen)
14120 Board initial_position;
14122 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14123 DisplayError(_("Bad FEN position in clipboard"), 0);
14126 int savedBlackPlaysFirst = blackPlaysFirst;
14127 EditPositionEvent();
14128 blackPlaysFirst = savedBlackPlaysFirst;
14129 CopyBoard(boards[0], initial_position);
14130 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14131 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14132 DisplayBothClocks();
14133 DrawPosition(FALSE, boards[currentMove]);
14138 static char cseq[12] = "\\ ";
14140 Boolean set_cont_sequence(char *new_seq)
14145 // handle bad attempts to set the sequence
14147 return 0; // acceptable error - no debug
14149 len = strlen(new_seq);
14150 ret = (len > 0) && (len < sizeof(cseq));
14152 strcpy(cseq, new_seq);
14153 else if (appData.debugMode)
14154 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14159 reformat a source message so words don't cross the width boundary. internal
14160 newlines are not removed. returns the wrapped size (no null character unless
14161 included in source message). If dest is NULL, only calculate the size required
14162 for the dest buffer. lp argument indicats line position upon entry, and it's
14163 passed back upon exit.
14165 int wrap(char *dest, char *src, int count, int width, int *lp)
14167 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14169 cseq_len = strlen(cseq);
14170 old_line = line = *lp;
14171 ansi = len = clen = 0;
14173 for (i=0; i < count; i++)
14175 if (src[i] == '\033')
14178 // if we hit the width, back up
14179 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14181 // store i & len in case the word is too long
14182 old_i = i, old_len = len;
14184 // find the end of the last word
14185 while (i && src[i] != ' ' && src[i] != '\n')
14191 // word too long? restore i & len before splitting it
14192 if ((old_i-i+clen) >= width)
14199 if (i && src[i-1] == ' ')
14202 if (src[i] != ' ' && src[i] != '\n')
14209 // now append the newline and continuation sequence
14214 strncpy(dest+len, cseq, cseq_len);
14222 dest[len] = src[i];
14226 if (src[i] == '\n')
14231 if (dest && appData.debugMode)
14233 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14234 count, width, line, len, *lp);
14235 show_bytes(debugFP, src, count);
14236 fprintf(debugFP, "\ndest: ");
14237 show_bytes(debugFP, dest, len);
14238 fprintf(debugFP, "\n");
14240 *lp = dest ? line : old_line;
14245 // [HGM] vari: routines for shelving variations
14248 PushTail(int firstMove, int lastMove)
14250 int i, j, nrMoves = lastMove - firstMove;
14252 if(appData.icsActive) { // only in local mode
14253 forwardMostMove = currentMove; // mimic old ICS behavior
14256 if(storedGames >= MAX_VARIATIONS-1) return;
14258 // push current tail of game on stack
14259 savedResult[storedGames] = gameInfo.result;
14260 savedDetails[storedGames] = gameInfo.resultDetails;
14261 gameInfo.resultDetails = NULL;
14262 savedFirst[storedGames] = firstMove;
14263 savedLast [storedGames] = lastMove;
14264 savedFramePtr[storedGames] = framePtr;
14265 framePtr -= nrMoves; // reserve space for the boards
14266 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14267 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14268 for(j=0; j<MOVE_LEN; j++)
14269 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14270 for(j=0; j<2*MOVE_LEN; j++)
14271 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14272 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14273 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14274 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14275 pvInfoList[firstMove+i-1].depth = 0;
14276 commentList[framePtr+i] = commentList[firstMove+i];
14277 commentList[firstMove+i] = NULL;
14281 forwardMostMove = currentMove; // truncte game so we can start variation
14282 if(storedGames == 1) GreyRevert(FALSE);
14286 PopTail(Boolean annotate)
14289 char buf[8000], moveBuf[20];
14291 if(appData.icsActive) return FALSE; // only in local mode
14292 if(!storedGames) return FALSE; // sanity
14295 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14296 nrMoves = savedLast[storedGames] - currentMove;
14299 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14300 else strcpy(buf, "(");
14301 for(i=currentMove; i<forwardMostMove; i++) {
14303 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14304 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14305 strcat(buf, moveBuf);
14306 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14310 for(i=1; i<nrMoves; i++) { // copy last variation back
14311 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14312 for(j=0; j<MOVE_LEN; j++)
14313 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14314 for(j=0; j<2*MOVE_LEN; j++)
14315 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14316 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14317 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14318 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14319 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14320 commentList[currentMove+i] = commentList[framePtr+i];
14321 commentList[framePtr+i] = NULL;
14323 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14324 framePtr = savedFramePtr[storedGames];
14325 gameInfo.result = savedResult[storedGames];
14326 if(gameInfo.resultDetails != NULL) {
14327 free(gameInfo.resultDetails);
14329 gameInfo.resultDetails = savedDetails[storedGames];
14330 forwardMostMove = currentMove + nrMoves;
14331 if(storedGames == 0) GreyRevert(TRUE);
14337 { // remove all shelved variations
14339 for(i=0; i<storedGames; i++) {
14340 if(savedDetails[i])
14341 free(savedDetails[i]);
14342 savedDetails[i] = NULL;
14344 for(i=framePtr; i<MAX_MOVES; i++) {
14345 if(commentList[i]) free(commentList[i]);
14346 commentList[i] = NULL;
14348 framePtr = MAX_MOVES-1;