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;
4436 currentMove = endPV;
4437 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4438 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4439 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4440 DrawPosition(TRUE, boards[currentMove]);
4443 static int lastX, lastY;
4446 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4450 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4451 lastX = x; lastY = y;
4452 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4454 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4456 while(buf[index] && buf[index] != '\n') index++;
4458 ParsePV(buf+startPV);
4459 *start = startPV; *end = index-1;
4464 LoadPV(int x, int y)
4465 { // called on right mouse click to load PV
4466 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4467 lastX = x; lastY = y;
4468 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4475 if(endPV < 0) return;
4477 currentMove = forwardMostMove;
4478 ClearPremoveHighlights();
4479 DrawPosition(TRUE, boards[currentMove]);
4483 MovePV(int x, int y, int h)
4484 { // step through PV based on mouse coordinates (called on mouse move)
4485 int margin = h>>3, step = 0;
4487 if(endPV < 0) return;
4488 // we must somehow check if right button is still down (might be released off board!)
4489 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4490 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4491 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4493 lastX = x; lastY = y;
4494 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4495 currentMove += step;
4496 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4497 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4498 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4499 DrawPosition(FALSE, boards[currentMove]);
4503 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4504 // All positions will have equal probability, but the current method will not provide a unique
4505 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4511 int piecesLeft[(int)BlackPawn];
4512 int seed, nrOfShuffles;
4514 void GetPositionNumber()
4515 { // sets global variable seed
4518 seed = appData.defaultFrcPosition;
4519 if(seed < 0) { // randomize based on time for negative FRC position numbers
4520 for(i=0; i<50; i++) seed += random();
4521 seed = random() ^ random() >> 8 ^ random() << 8;
4522 if(seed<0) seed = -seed;
4526 int put(Board board, int pieceType, int rank, int n, int shade)
4527 // put the piece on the (n-1)-th empty squares of the given shade
4531 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4532 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4533 board[rank][i] = (ChessSquare) pieceType;
4534 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4536 piecesLeft[pieceType]--;
4544 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4545 // calculate where the next piece goes, (any empty square), and put it there
4549 i = seed % squaresLeft[shade];
4550 nrOfShuffles *= squaresLeft[shade];
4551 seed /= squaresLeft[shade];
4552 put(board, pieceType, rank, i, shade);
4555 void AddTwoPieces(Board board, int pieceType, int rank)
4556 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4558 int i, n=squaresLeft[ANY], j=n-1, k;
4560 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4561 i = seed % k; // pick one
4564 while(i >= j) i -= j--;
4565 j = n - 1 - j; i += j;
4566 put(board, pieceType, rank, j, ANY);
4567 put(board, pieceType, rank, i, ANY);
4570 void SetUpShuffle(Board board, int number)
4574 GetPositionNumber(); nrOfShuffles = 1;
4576 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4577 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4578 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4580 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4582 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4583 p = (int) board[0][i];
4584 if(p < (int) BlackPawn) piecesLeft[p] ++;
4585 board[0][i] = EmptySquare;
4588 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4589 // shuffles restricted to allow normal castling put KRR first
4590 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4591 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4592 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4593 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4594 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4595 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4596 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4597 put(board, WhiteRook, 0, 0, ANY);
4598 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4601 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4602 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4603 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4604 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4605 while(piecesLeft[p] >= 2) {
4606 AddOnePiece(board, p, 0, LITE);
4607 AddOnePiece(board, p, 0, DARK);
4609 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4612 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4613 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4614 // but we leave King and Rooks for last, to possibly obey FRC restriction
4615 if(p == (int)WhiteRook) continue;
4616 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4617 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4620 // now everything is placed, except perhaps King (Unicorn) and Rooks
4622 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4623 // Last King gets castling rights
4624 while(piecesLeft[(int)WhiteUnicorn]) {
4625 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4626 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4629 while(piecesLeft[(int)WhiteKing]) {
4630 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4631 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4636 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4637 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4640 // Only Rooks can be left; simply place them all
4641 while(piecesLeft[(int)WhiteRook]) {
4642 i = put(board, WhiteRook, 0, 0, ANY);
4643 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4646 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4648 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4651 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4652 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4655 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4658 int SetCharTable( char *table, const char * map )
4659 /* [HGM] moved here from winboard.c because of its general usefulness */
4660 /* Basically a safe strcpy that uses the last character as King */
4662 int result = FALSE; int NrPieces;
4664 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4665 && NrPieces >= 12 && !(NrPieces&1)) {
4666 int i; /* [HGM] Accept even length from 12 to 34 */
4668 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4669 for( i=0; i<NrPieces/2-1; i++ ) {
4671 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4673 table[(int) WhiteKing] = map[NrPieces/2-1];
4674 table[(int) BlackKing] = map[NrPieces-1];
4682 void Prelude(Board board)
4683 { // [HGM] superchess: random selection of exo-pieces
4684 int i, j, k; ChessSquare p;
4685 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4687 GetPositionNumber(); // use FRC position number
4689 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4690 SetCharTable(pieceToChar, appData.pieceToCharTable);
4691 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4692 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4695 j = seed%4; seed /= 4;
4696 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4697 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4698 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4699 j = seed%3 + (seed%3 >= j); seed /= 3;
4700 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4701 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4702 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4703 j = seed%3; seed /= 3;
4704 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4705 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4706 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4707 j = seed%2 + (seed%2 >= j); seed /= 2;
4708 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4709 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4710 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4711 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4712 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4713 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4714 put(board, exoPieces[0], 0, 0, ANY);
4715 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4719 InitPosition(redraw)
4722 ChessSquare (* pieces)[BOARD_FILES];
4723 int i, j, pawnRow, overrule,
4724 oldx = gameInfo.boardWidth,
4725 oldy = gameInfo.boardHeight,
4726 oldh = gameInfo.holdingsWidth,
4727 oldv = gameInfo.variant;
4729 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4731 /* [AS] Initialize pv info list [HGM] and game status */
4733 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4734 pvInfoList[i].depth = 0;
4735 boards[i][EP_STATUS] = EP_NONE;
4736 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4739 initialRulePlies = 0; /* 50-move counter start */
4741 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4742 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4746 /* [HGM] logic here is completely changed. In stead of full positions */
4747 /* the initialized data only consist of the two backranks. The switch */
4748 /* selects which one we will use, which is than copied to the Board */
4749 /* initialPosition, which for the rest is initialized by Pawns and */
4750 /* empty squares. This initial position is then copied to boards[0], */
4751 /* possibly after shuffling, so that it remains available. */
4753 gameInfo.holdingsWidth = 0; /* default board sizes */
4754 gameInfo.boardWidth = 8;
4755 gameInfo.boardHeight = 8;
4756 gameInfo.holdingsSize = 0;
4757 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4758 for(i=0; i<BOARD_FILES-2; i++)
4759 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4760 initialPosition[EP_STATUS] = EP_NONE;
4761 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4763 switch (gameInfo.variant) {
4764 case VariantFischeRandom:
4765 shuffleOpenings = TRUE;
4769 case VariantShatranj:
4770 pieces = ShatranjArray;
4771 nrCastlingRights = 0;
4772 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4774 case VariantTwoKings:
4775 pieces = twoKingsArray;
4777 case VariantCapaRandom:
4778 shuffleOpenings = TRUE;
4779 case VariantCapablanca:
4780 pieces = CapablancaArray;
4781 gameInfo.boardWidth = 10;
4782 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4785 pieces = GothicArray;
4786 gameInfo.boardWidth = 10;
4787 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4790 pieces = JanusArray;
4791 gameInfo.boardWidth = 10;
4792 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4793 nrCastlingRights = 6;
4794 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4795 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4796 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4797 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4798 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4799 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4802 pieces = FalconArray;
4803 gameInfo.boardWidth = 10;
4804 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4806 case VariantXiangqi:
4807 pieces = XiangqiArray;
4808 gameInfo.boardWidth = 9;
4809 gameInfo.boardHeight = 10;
4810 nrCastlingRights = 0;
4811 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4814 pieces = ShogiArray;
4815 gameInfo.boardWidth = 9;
4816 gameInfo.boardHeight = 9;
4817 gameInfo.holdingsSize = 7;
4818 nrCastlingRights = 0;
4819 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4821 case VariantCourier:
4822 pieces = CourierArray;
4823 gameInfo.boardWidth = 12;
4824 nrCastlingRights = 0;
4825 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4827 case VariantKnightmate:
4828 pieces = KnightmateArray;
4829 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4832 pieces = fairyArray;
4833 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4836 pieces = GreatArray;
4837 gameInfo.boardWidth = 10;
4838 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4839 gameInfo.holdingsSize = 8;
4843 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4844 gameInfo.holdingsSize = 8;
4845 startedFromSetupPosition = TRUE;
4847 case VariantCrazyhouse:
4848 case VariantBughouse:
4850 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4851 gameInfo.holdingsSize = 5;
4853 case VariantWildCastle:
4855 /* !!?shuffle with kings guaranteed to be on d or e file */
4856 shuffleOpenings = 1;
4858 case VariantNoCastle:
4860 nrCastlingRights = 0;
4861 /* !!?unconstrained back-rank shuffle */
4862 shuffleOpenings = 1;
4867 if(appData.NrFiles >= 0) {
4868 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4869 gameInfo.boardWidth = appData.NrFiles;
4871 if(appData.NrRanks >= 0) {
4872 gameInfo.boardHeight = appData.NrRanks;
4874 if(appData.holdingsSize >= 0) {
4875 i = appData.holdingsSize;
4876 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4877 gameInfo.holdingsSize = i;
4879 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4880 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4881 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4883 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4884 if(pawnRow < 1) pawnRow = 1;
4886 /* User pieceToChar list overrules defaults */
4887 if(appData.pieceToCharTable != NULL)
4888 SetCharTable(pieceToChar, appData.pieceToCharTable);
4890 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4892 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4893 s = (ChessSquare) 0; /* account holding counts in guard band */
4894 for( i=0; i<BOARD_HEIGHT; i++ )
4895 initialPosition[i][j] = s;
4897 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4898 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4899 initialPosition[pawnRow][j] = WhitePawn;
4900 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4901 if(gameInfo.variant == VariantXiangqi) {
4903 initialPosition[pawnRow][j] =
4904 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4905 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4906 initialPosition[2][j] = WhiteCannon;
4907 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4911 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4913 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4916 initialPosition[1][j] = WhiteBishop;
4917 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4919 initialPosition[1][j] = WhiteRook;
4920 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4923 if( nrCastlingRights == -1) {
4924 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4925 /* This sets default castling rights from none to normal corners */
4926 /* Variants with other castling rights must set them themselves above */
4927 nrCastlingRights = 6;
4929 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4930 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4931 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4932 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4933 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4934 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4937 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4938 if(gameInfo.variant == VariantGreat) { // promotion commoners
4939 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4940 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4941 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4942 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4944 if (appData.debugMode) {
4945 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4947 if(shuffleOpenings) {
4948 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4949 startedFromSetupPosition = TRUE;
4951 if(startedFromPositionFile) {
4952 /* [HGM] loadPos: use PositionFile for every new game */
4953 CopyBoard(initialPosition, filePosition);
4954 for(i=0; i<nrCastlingRights; i++)
4955 initialRights[i] = filePosition[CASTLING][i];
4956 startedFromSetupPosition = TRUE;
4959 CopyBoard(boards[0], initialPosition);
4961 if(oldx != gameInfo.boardWidth ||
4962 oldy != gameInfo.boardHeight ||
4963 oldh != gameInfo.holdingsWidth
4965 || oldv == VariantGothic || // For licensing popups
4966 gameInfo.variant == VariantGothic
4969 || oldv == VariantFalcon ||
4970 gameInfo.variant == VariantFalcon
4973 InitDrawingSizes(-2 ,0);
4976 DrawPosition(TRUE, boards[currentMove]);
4980 SendBoard(cps, moveNum)
4981 ChessProgramState *cps;
4984 char message[MSG_SIZ];
4986 if (cps->useSetboard) {
4987 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4988 sprintf(message, "setboard %s\n", fen);
4989 SendToProgram(message, cps);
4995 /* Kludge to set black to move, avoiding the troublesome and now
4996 * deprecated "black" command.
4998 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5000 SendToProgram("edit\n", cps);
5001 SendToProgram("#\n", cps);
5002 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5003 bp = &boards[moveNum][i][BOARD_LEFT];
5004 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5005 if ((int) *bp < (int) BlackPawn) {
5006 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5008 if(message[0] == '+' || message[0] == '~') {
5009 sprintf(message, "%c%c%c+\n",
5010 PieceToChar((ChessSquare)(DEMOTED *bp)),
5013 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5014 message[1] = BOARD_RGHT - 1 - j + '1';
5015 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5017 SendToProgram(message, cps);
5022 SendToProgram("c\n", cps);
5023 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5024 bp = &boards[moveNum][i][BOARD_LEFT];
5025 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5026 if (((int) *bp != (int) EmptySquare)
5027 && ((int) *bp >= (int) BlackPawn)) {
5028 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5030 if(message[0] == '+' || message[0] == '~') {
5031 sprintf(message, "%c%c%c+\n",
5032 PieceToChar((ChessSquare)(DEMOTED *bp)),
5035 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5036 message[1] = BOARD_RGHT - 1 - j + '1';
5037 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5039 SendToProgram(message, cps);
5044 SendToProgram(".\n", cps);
5046 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5050 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5052 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5053 /* [HGM] add Shogi promotions */
5054 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5059 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5060 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5062 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5063 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5066 piece = boards[currentMove][fromY][fromX];
5067 if(gameInfo.variant == VariantShogi) {
5068 promotionZoneSize = 3;
5069 highestPromotingPiece = (int)WhiteFerz;
5072 // next weed out all moves that do not touch the promotion zone at all
5073 if((int)piece >= BlackPawn) {
5074 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5076 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5078 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5079 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5082 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5084 // weed out mandatory Shogi promotions
5085 if(gameInfo.variant == VariantShogi) {
5086 if(piece >= BlackPawn) {
5087 if(toY == 0 && piece == BlackPawn ||
5088 toY == 0 && piece == BlackQueen ||
5089 toY <= 1 && piece == BlackKnight) {
5094 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5095 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5096 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5103 // weed out obviously illegal Pawn moves
5104 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5105 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5106 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5107 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5108 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5109 // note we are not allowed to test for valid (non-)capture, due to premove
5112 // we either have a choice what to promote to, or (in Shogi) whether to promote
5113 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5114 *promoChoice = PieceToChar(BlackFerz); // no choice
5117 if(appData.alwaysPromoteToQueen) { // predetermined
5118 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5119 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5120 else *promoChoice = PieceToChar(BlackQueen);
5124 // suppress promotion popup on illegal moves that are not premoves
5125 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5126 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5127 if(appData.testLegality && !premove) {
5128 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5129 fromY, fromX, toY, toX, NULLCHAR);
5130 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5131 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5139 InPalace(row, column)
5141 { /* [HGM] for Xiangqi */
5142 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5143 column < (BOARD_WIDTH + 4)/2 &&
5144 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5149 PieceForSquare (x, y)
5153 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5156 return boards[currentMove][y][x];
5160 OKToStartUserMove(x, y)
5163 ChessSquare from_piece;
5166 if (matchMode) return FALSE;
5167 if (gameMode == EditPosition) return TRUE;
5169 if (x >= 0 && y >= 0)
5170 from_piece = boards[currentMove][y][x];
5172 from_piece = EmptySquare;
5174 if (from_piece == EmptySquare) return FALSE;
5176 white_piece = (int)from_piece >= (int)WhitePawn &&
5177 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5180 case PlayFromGameFile:
5182 case TwoMachinesPlay:
5190 case MachinePlaysWhite:
5191 case IcsPlayingBlack:
5192 if (appData.zippyPlay) return FALSE;
5194 DisplayMoveError(_("You are playing Black"));
5199 case MachinePlaysBlack:
5200 case IcsPlayingWhite:
5201 if (appData.zippyPlay) return FALSE;
5203 DisplayMoveError(_("You are playing White"));
5209 if (!white_piece && WhiteOnMove(currentMove)) {
5210 DisplayMoveError(_("It is White's turn"));
5213 if (white_piece && !WhiteOnMove(currentMove)) {
5214 DisplayMoveError(_("It is Black's turn"));
5217 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5218 /* Editing correspondence game history */
5219 /* Could disallow this or prompt for confirmation */
5224 case BeginningOfGame:
5225 if (appData.icsActive) return FALSE;
5226 if (!appData.noChessProgram) {
5228 DisplayMoveError(_("You are playing White"));
5235 if (!white_piece && WhiteOnMove(currentMove)) {
5236 DisplayMoveError(_("It is White's turn"));
5239 if (white_piece && !WhiteOnMove(currentMove)) {
5240 DisplayMoveError(_("It is Black's turn"));
5249 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5250 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5251 && gameMode != AnalyzeFile && gameMode != Training) {
5252 DisplayMoveError(_("Displayed position is not current"));
5258 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5259 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5260 int lastLoadGameUseList = FALSE;
5261 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5262 ChessMove lastLoadGameStart = (ChessMove) 0;
5265 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5266 int fromX, fromY, toX, toY;
5271 ChessSquare pdown, pup;
5273 /* Check if the user is playing in turn. This is complicated because we
5274 let the user "pick up" a piece before it is his turn. So the piece he
5275 tried to pick up may have been captured by the time he puts it down!
5276 Therefore we use the color the user is supposed to be playing in this
5277 test, not the color of the piece that is currently on the starting
5278 square---except in EditGame mode, where the user is playing both
5279 sides; fortunately there the capture race can't happen. (It can
5280 now happen in IcsExamining mode, but that's just too bad. The user
5281 will get a somewhat confusing message in that case.)
5285 case PlayFromGameFile:
5287 case TwoMachinesPlay:
5291 /* We switched into a game mode where moves are not accepted,
5292 perhaps while the mouse button was down. */
5293 return ImpossibleMove;
5295 case MachinePlaysWhite:
5296 /* User is moving for Black */
5297 if (WhiteOnMove(currentMove)) {
5298 DisplayMoveError(_("It is White's turn"));
5299 return ImpossibleMove;
5303 case MachinePlaysBlack:
5304 /* User is moving for White */
5305 if (!WhiteOnMove(currentMove)) {
5306 DisplayMoveError(_("It is Black's turn"));
5307 return ImpossibleMove;
5313 case BeginningOfGame:
5316 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5317 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5318 /* User is moving for Black */
5319 if (WhiteOnMove(currentMove)) {
5320 DisplayMoveError(_("It is White's turn"));
5321 return ImpossibleMove;
5324 /* User is moving for White */
5325 if (!WhiteOnMove(currentMove)) {
5326 DisplayMoveError(_("It is Black's turn"));
5327 return ImpossibleMove;
5332 case IcsPlayingBlack:
5333 /* User is moving for Black */
5334 if (WhiteOnMove(currentMove)) {
5335 if (!appData.premove) {
5336 DisplayMoveError(_("It is White's turn"));
5337 } else if (toX >= 0 && toY >= 0) {
5340 premoveFromX = fromX;
5341 premoveFromY = fromY;
5342 premovePromoChar = promoChar;
5344 if (appData.debugMode)
5345 fprintf(debugFP, "Got premove: fromX %d,"
5346 "fromY %d, toX %d, toY %d\n",
5347 fromX, fromY, toX, toY);
5349 return ImpossibleMove;
5353 case IcsPlayingWhite:
5354 /* User is moving for White */
5355 if (!WhiteOnMove(currentMove)) {
5356 if (!appData.premove) {
5357 DisplayMoveError(_("It is Black's turn"));
5358 } else if (toX >= 0 && toY >= 0) {
5361 premoveFromX = fromX;
5362 premoveFromY = fromY;
5363 premovePromoChar = promoChar;
5365 if (appData.debugMode)
5366 fprintf(debugFP, "Got premove: fromX %d,"
5367 "fromY %d, toX %d, toY %d\n",
5368 fromX, fromY, toX, toY);
5370 return ImpossibleMove;
5378 /* EditPosition, empty square, or different color piece;
5379 click-click move is possible */
5380 if (toX == -2 || toY == -2) {
5381 boards[0][fromY][fromX] = EmptySquare;
5382 return AmbiguousMove;
5383 } else if (toX >= 0 && toY >= 0) {
5384 boards[0][toY][toX] = boards[0][fromY][fromX];
5385 boards[0][fromY][fromX] = EmptySquare;
5386 return AmbiguousMove;
5388 return ImpossibleMove;
5391 if(toX < 0 || toY < 0) return ImpossibleMove;
5392 pdown = boards[currentMove][fromY][fromX];
5393 pup = boards[currentMove][toY][toX];
5395 /* [HGM] If move started in holdings, it means a drop */
5396 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5397 if( pup != EmptySquare ) return ImpossibleMove;
5398 if(appData.testLegality) {
5399 /* it would be more logical if LegalityTest() also figured out
5400 * which drops are legal. For now we forbid pawns on back rank.
5401 * Shogi is on its own here...
5403 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5404 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5405 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5407 return WhiteDrop; /* Not needed to specify white or black yet */
5410 userOfferedDraw = FALSE;
5412 /* [HGM] always test for legality, to get promotion info */
5413 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5414 fromY, fromX, toY, toX, promoChar);
5415 /* [HGM] but possibly ignore an IllegalMove result */
5416 if (appData.testLegality) {
5417 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5418 DisplayMoveError(_("Illegal move"));
5419 return ImpossibleMove;
5424 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5425 function is made into one that returns an OK move type if FinishMove
5426 should be called. This to give the calling driver routine the
5427 opportunity to finish the userMove input with a promotion popup,
5428 without bothering the user with this for invalid or illegal moves */
5430 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5433 /* Common tail of UserMoveEvent and DropMenuEvent */
5435 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5437 int fromX, fromY, toX, toY;
5438 /*char*/int promoChar;
5442 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5443 // [HGM] superchess: suppress promotions to non-available piece
5444 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5445 if(WhiteOnMove(currentMove)) {
5446 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5448 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5452 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5453 move type in caller when we know the move is a legal promotion */
5454 if(moveType == NormalMove && promoChar)
5455 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5457 /* [HGM] convert drag-and-drop piece drops to standard form */
5458 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5459 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5460 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5461 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5462 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5463 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5464 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5465 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5469 /* [HGM] <popupFix> The following if has been moved here from
5470 UserMoveEvent(). Because it seemed to belong here (why not allow
5471 piece drops in training games?), and because it can only be
5472 performed after it is known to what we promote. */
5473 if (gameMode == Training) {
5474 /* compare the move played on the board to the next move in the
5475 * game. If they match, display the move and the opponent's response.
5476 * If they don't match, display an error message.
5480 CopyBoard(testBoard, boards[currentMove]);
5481 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5483 if (CompareBoards(testBoard, boards[currentMove+1])) {
5484 ForwardInner(currentMove+1);
5486 /* Autoplay the opponent's response.
5487 * if appData.animate was TRUE when Training mode was entered,
5488 * the response will be animated.
5490 saveAnimate = appData.animate;
5491 appData.animate = animateTraining;
5492 ForwardInner(currentMove+1);
5493 appData.animate = saveAnimate;
5495 /* check for the end of the game */
5496 if (currentMove >= forwardMostMove) {
5497 gameMode = PlayFromGameFile;
5499 SetTrainingModeOff();
5500 DisplayInformation(_("End of game"));
5503 DisplayError(_("Incorrect move"), 0);
5508 /* Ok, now we know that the move is good, so we can kill
5509 the previous line in Analysis Mode */
5510 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5511 && currentMove < forwardMostMove) {
5512 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5515 /* If we need the chess program but it's dead, restart it */
5516 ResurrectChessProgram();
5518 /* A user move restarts a paused game*/
5522 thinkOutput[0] = NULLCHAR;
5524 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5526 if (gameMode == BeginningOfGame) {
5527 if (appData.noChessProgram) {
5528 gameMode = EditGame;
5532 gameMode = MachinePlaysBlack;
5535 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5537 if (first.sendName) {
5538 sprintf(buf, "name %s\n", gameInfo.white);
5539 SendToProgram(buf, &first);
5546 /* Relay move to ICS or chess engine */
5547 if (appData.icsActive) {
5548 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5549 gameMode == IcsExamining) {
5550 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5554 if (first.sendTime && (gameMode == BeginningOfGame ||
5555 gameMode == MachinePlaysWhite ||
5556 gameMode == MachinePlaysBlack)) {
5557 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5559 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5560 // [HGM] book: if program might be playing, let it use book
5561 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5562 first.maybeThinking = TRUE;
5563 } else SendMoveToProgram(forwardMostMove-1, &first);
5564 if (currentMove == cmailOldMove + 1) {
5565 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5569 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5573 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5579 if (WhiteOnMove(currentMove)) {
5580 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5582 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5586 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5591 case MachinePlaysBlack:
5592 case MachinePlaysWhite:
5593 /* disable certain menu options while machine is thinking */
5594 SetMachineThinkingEnables();
5601 if(bookHit) { // [HGM] book: simulate book reply
5602 static char bookMove[MSG_SIZ]; // a bit generous?
5604 programStats.nodes = programStats.depth = programStats.time =
5605 programStats.score = programStats.got_only_move = 0;
5606 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5608 strcpy(bookMove, "move ");
5609 strcat(bookMove, bookHit);
5610 HandleMachineMove(bookMove, &first);
5616 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5617 int fromX, fromY, toX, toY;
5620 /* [HGM] This routine was added to allow calling of its two logical
5621 parts from other modules in the old way. Before, UserMoveEvent()
5622 automatically called FinishMove() if the move was OK, and returned
5623 otherwise. I separated the two, in order to make it possible to
5624 slip a promotion popup in between. But that it always needs two
5625 calls, to the first part, (now called UserMoveTest() ), and to
5626 FinishMove if the first part succeeded. Calls that do not need
5627 to do anything in between, can call this routine the old way.
5629 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5630 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5631 if(moveType == AmbiguousMove)
5632 DrawPosition(FALSE, boards[currentMove]);
5633 else if(moveType != ImpossibleMove && moveType != Comment)
5634 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5637 void LeftClick(ClickType clickType, int xPix, int yPix)
5640 Boolean saveAnimate;
5641 static int second = 0, promotionChoice = 0;
5642 char promoChoice = NULLCHAR;
5644 if (clickType == Press) ErrorPopDown();
5646 x = EventToSquare(xPix, BOARD_WIDTH);
5647 y = EventToSquare(yPix, BOARD_HEIGHT);
5648 if (!flipView && y >= 0) {
5649 y = BOARD_HEIGHT - 1 - y;
5651 if (flipView && x >= 0) {
5652 x = BOARD_WIDTH - 1 - x;
5655 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5656 if(clickType == Release) return; // ignore upclick of click-click destination
5657 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5658 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5659 if(gameInfo.holdingsWidth &&
5660 (WhiteOnMove(currentMove)
5661 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5662 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5663 // click in right holdings, for determining promotion piece
5664 ChessSquare p = boards[currentMove][y][x];
5665 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5666 if(p != EmptySquare) {
5667 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5672 DrawPosition(FALSE, boards[currentMove]);
5676 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5677 if(clickType == Press
5678 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5679 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5680 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5684 if (clickType == Press) {
5686 if (OKToStartUserMove(x, y)) {
5690 DragPieceBegin(xPix, yPix);
5691 if (appData.highlightDragging) {
5692 SetHighlights(x, y, -1, -1);
5700 if (clickType == Press && gameMode != EditPosition) {
5705 // ignore off-board to clicks
5706 if(y < 0 || x < 0) return;
5708 /* Check if clicking again on the same color piece */
5709 fromP = boards[currentMove][fromY][fromX];
5710 toP = boards[currentMove][y][x];
5711 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5712 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5713 WhitePawn <= toP && toP <= WhiteKing &&
5714 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5715 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5716 (BlackPawn <= fromP && fromP <= BlackKing &&
5717 BlackPawn <= toP && toP <= BlackKing &&
5718 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5719 !(fromP == BlackKing && toP == BlackRook && frc))) {
5720 /* Clicked again on same color piece -- changed his mind */
5721 second = (x == fromX && y == fromY);
5722 if (appData.highlightDragging) {
5723 SetHighlights(x, y, -1, -1);
5727 if (OKToStartUserMove(x, y)) {
5730 DragPieceBegin(xPix, yPix);
5734 // ignore clicks on holdings
5735 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5738 if (clickType == Release && x == fromX && y == fromY) {
5739 DragPieceEnd(xPix, yPix);
5740 if (appData.animateDragging) {
5741 /* Undo animation damage if any */
5742 DrawPosition(FALSE, NULL);
5745 /* Second up/down in same square; just abort move */
5750 ClearPremoveHighlights();
5752 /* First upclick in same square; start click-click mode */
5753 SetHighlights(x, y, -1, -1);
5758 /* we now have a different from- and (possibly off-board) to-square */
5759 /* Completed move */
5762 saveAnimate = appData.animate;
5763 if (clickType == Press) {
5764 /* Finish clickclick move */
5765 if (appData.animate || appData.highlightLastMove) {
5766 SetHighlights(fromX, fromY, toX, toY);
5771 /* Finish drag move */
5772 if (appData.highlightLastMove) {
5773 SetHighlights(fromX, fromY, toX, toY);
5777 DragPieceEnd(xPix, yPix);
5778 /* Don't animate move and drag both */
5779 appData.animate = FALSE;
5782 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5783 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5786 DrawPosition(TRUE, NULL);
5790 // off-board moves should not be highlighted
5791 if(x < 0 || x < 0) ClearHighlights();
5793 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5794 SetHighlights(fromX, fromY, toX, toY);
5795 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5796 // [HGM] super: promotion to captured piece selected from holdings
5797 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5798 promotionChoice = TRUE;
5799 // kludge follows to temporarily execute move on display, without promoting yet
5800 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5801 boards[currentMove][toY][toX] = p;
5802 DrawPosition(FALSE, boards[currentMove]);
5803 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5804 boards[currentMove][toY][toX] = q;
5805 DisplayMessage("Click in holdings to choose piece", "");
5810 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5811 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5812 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5815 appData.animate = saveAnimate;
5816 if (appData.animate || appData.animateDragging) {
5817 /* Undo animation damage if needed */
5818 DrawPosition(FALSE, NULL);
5822 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5824 // char * hint = lastHint;
5825 FrontEndProgramStats stats;
5827 stats.which = cps == &first ? 0 : 1;
5828 stats.depth = cpstats->depth;
5829 stats.nodes = cpstats->nodes;
5830 stats.score = cpstats->score;
5831 stats.time = cpstats->time;
5832 stats.pv = cpstats->movelist;
5833 stats.hint = lastHint;
5834 stats.an_move_index = 0;
5835 stats.an_move_count = 0;
5837 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5838 stats.hint = cpstats->move_name;
5839 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5840 stats.an_move_count = cpstats->nr_moves;
5843 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5845 SetProgramStats( &stats );
5848 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5849 { // [HGM] book: this routine intercepts moves to simulate book replies
5850 char *bookHit = NULL;
5852 //first determine if the incoming move brings opponent into his book
5853 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5854 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5855 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5856 if(bookHit != NULL && !cps->bookSuspend) {
5857 // make sure opponent is not going to reply after receiving move to book position
5858 SendToProgram("force\n", cps);
5859 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5861 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5862 // now arrange restart after book miss
5864 // after a book hit we never send 'go', and the code after the call to this routine
5865 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5867 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5868 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5869 SendToProgram(buf, cps);
5870 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5871 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5872 SendToProgram("go\n", cps);
5873 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5874 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5875 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5876 SendToProgram("go\n", cps);
5877 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5879 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5883 ChessProgramState *savedState;
5884 void DeferredBookMove(void)
5886 if(savedState->lastPing != savedState->lastPong)
5887 ScheduleDelayedEvent(DeferredBookMove, 10);
5889 HandleMachineMove(savedMessage, savedState);
5893 HandleMachineMove(message, cps)
5895 ChessProgramState *cps;
5897 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5898 char realname[MSG_SIZ];
5899 int fromX, fromY, toX, toY;
5908 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5910 * Kludge to ignore BEL characters
5912 while (*message == '\007') message++;
5915 * [HGM] engine debug message: ignore lines starting with '#' character
5917 if(cps->debug && *message == '#') return;
5920 * Look for book output
5922 if (cps == &first && bookRequested) {
5923 if (message[0] == '\t' || message[0] == ' ') {
5924 /* Part of the book output is here; append it */
5925 strcat(bookOutput, message);
5926 strcat(bookOutput, " \n");
5928 } else if (bookOutput[0] != NULLCHAR) {
5929 /* All of book output has arrived; display it */
5930 char *p = bookOutput;
5931 while (*p != NULLCHAR) {
5932 if (*p == '\t') *p = ' ';
5935 DisplayInformation(bookOutput);
5936 bookRequested = FALSE;
5937 /* Fall through to parse the current output */
5942 * Look for machine move.
5944 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5945 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5947 /* This method is only useful on engines that support ping */
5948 if (cps->lastPing != cps->lastPong) {
5949 if (gameMode == BeginningOfGame) {
5950 /* Extra move from before last new; ignore */
5951 if (appData.debugMode) {
5952 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5955 if (appData.debugMode) {
5956 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5957 cps->which, gameMode);
5960 SendToProgram("undo\n", cps);
5966 case BeginningOfGame:
5967 /* Extra move from before last reset; ignore */
5968 if (appData.debugMode) {
5969 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5976 /* Extra move after we tried to stop. The mode test is
5977 not a reliable way of detecting this problem, but it's
5978 the best we can do on engines that don't support ping.
5980 if (appData.debugMode) {
5981 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5982 cps->which, gameMode);
5984 SendToProgram("undo\n", cps);
5987 case MachinePlaysWhite:
5988 case IcsPlayingWhite:
5989 machineWhite = TRUE;
5992 case MachinePlaysBlack:
5993 case IcsPlayingBlack:
5994 machineWhite = FALSE;
5997 case TwoMachinesPlay:
5998 machineWhite = (cps->twoMachinesColor[0] == 'w');
6001 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6002 if (appData.debugMode) {
6004 "Ignoring move out of turn by %s, gameMode %d"
6005 ", forwardMost %d\n",
6006 cps->which, gameMode, forwardMostMove);
6011 if (appData.debugMode) { int f = forwardMostMove;
6012 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6013 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6014 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6016 if(cps->alphaRank) AlphaRank(machineMove, 4);
6017 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6018 &fromX, &fromY, &toX, &toY, &promoChar)) {
6019 /* Machine move could not be parsed; ignore it. */
6020 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6021 machineMove, cps->which);
6022 DisplayError(buf1, 0);
6023 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6024 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6025 if (gameMode == TwoMachinesPlay) {
6026 GameEnds(machineWhite ? BlackWins : WhiteWins,
6032 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6033 /* So we have to redo legality test with true e.p. status here, */
6034 /* to make sure an illegal e.p. capture does not slip through, */
6035 /* to cause a forfeit on a justified illegal-move complaint */
6036 /* of the opponent. */
6037 if( gameMode==TwoMachinesPlay && appData.testLegality
6038 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6041 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6042 fromY, fromX, toY, toX, promoChar);
6043 if (appData.debugMode) {
6045 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6046 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6047 fprintf(debugFP, "castling rights\n");
6049 if(moveType == IllegalMove) {
6050 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6051 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6052 GameEnds(machineWhite ? BlackWins : WhiteWins,
6055 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6056 /* [HGM] Kludge to handle engines that send FRC-style castling
6057 when they shouldn't (like TSCP-Gothic) */
6059 case WhiteASideCastleFR:
6060 case BlackASideCastleFR:
6062 currentMoveString[2]++;
6064 case WhiteHSideCastleFR:
6065 case BlackHSideCastleFR:
6067 currentMoveString[2]--;
6069 default: ; // nothing to do, but suppresses warning of pedantic compilers
6072 hintRequested = FALSE;
6073 lastHint[0] = NULLCHAR;
6074 bookRequested = FALSE;
6075 /* Program may be pondering now */
6076 cps->maybeThinking = TRUE;
6077 if (cps->sendTime == 2) cps->sendTime = 1;
6078 if (cps->offeredDraw) cps->offeredDraw--;
6081 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6083 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6085 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6086 char buf[3*MSG_SIZ];
6088 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6089 programStats.score / 100.,
6091 programStats.time / 100.,
6092 (unsigned int)programStats.nodes,
6093 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6094 programStats.movelist);
6096 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6100 /* currentMoveString is set as a side-effect of ParseOneMove */
6101 strcpy(machineMove, currentMoveString);
6102 strcat(machineMove, "\n");
6103 strcpy(moveList[forwardMostMove], machineMove);
6105 /* [AS] Save move info and clear stats for next move */
6106 pvInfoList[ forwardMostMove ].score = programStats.score;
6107 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6108 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6109 ClearProgramStats();
6110 thinkOutput[0] = NULLCHAR;
6111 hiddenThinkOutputState = 0;
6113 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6115 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6116 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6119 while( count < adjudicateLossPlies ) {
6120 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6123 score = -score; /* Flip score for winning side */
6126 if( score > adjudicateLossThreshold ) {
6133 if( count >= adjudicateLossPlies ) {
6134 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6136 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6137 "Xboard adjudication",
6144 if( gameMode == TwoMachinesPlay ) {
6145 // [HGM] some adjudications useful with buggy engines
6146 int k, count = 0; static int bare = 1;
6147 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6150 if( appData.testLegality )
6151 { /* [HGM] Some more adjudications for obstinate engines */
6152 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6153 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6154 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6155 static int moveCount = 6;
6157 char *reason = NULL;
6159 /* Count what is on board. */
6160 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6161 { ChessSquare p = boards[forwardMostMove][i][j];
6165 { /* count B,N,R and other of each side */
6168 NrK++; break; // [HGM] atomic: count Kings
6172 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6173 bishopsColor |= 1 << ((i^j)&1);
6178 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6179 bishopsColor |= 1 << ((i^j)&1);
6194 PawnAdvance += m; NrPawns++;
6196 NrPieces += (p != EmptySquare);
6197 NrW += ((int)p < (int)BlackPawn);
6198 if(gameInfo.variant == VariantXiangqi &&
6199 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6200 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6201 NrW -= ((int)p < (int)BlackPawn);
6205 /* Some material-based adjudications that have to be made before stalemate test */
6206 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6207 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6208 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6209 if(appData.checkMates) {
6210 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6211 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6212 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6213 "Xboard adjudication: King destroyed", GE_XBOARD );
6218 /* Bare King in Shatranj (loses) or Losers (wins) */
6219 if( NrW == 1 || NrPieces - NrW == 1) {
6220 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6221 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6222 if(appData.checkMates) {
6223 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6224 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6225 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6226 "Xboard adjudication: Bare king", GE_XBOARD );
6230 if( gameInfo.variant == VariantShatranj && --bare < 0)
6232 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6233 if(appData.checkMates) {
6234 /* but only adjudicate if adjudication enabled */
6235 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6236 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6237 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6238 "Xboard adjudication: Bare king", GE_XBOARD );
6245 // don't wait for engine to announce game end if we can judge ourselves
6246 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6248 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6249 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6250 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6251 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6254 reason = "Xboard adjudication: 3rd check";
6255 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6265 reason = "Xboard adjudication: Stalemate";
6266 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6267 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6268 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6269 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6270 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6271 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6272 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6273 EP_CHECKMATE : EP_WINS);
6274 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6275 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6279 reason = "Xboard adjudication: Checkmate";
6280 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6284 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6286 result = GameIsDrawn; break;
6288 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6290 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6292 result = (ChessMove) 0;
6294 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6295 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6296 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6297 GameEnds( result, reason, GE_XBOARD );
6301 /* Next absolutely insufficient mating material. */
6302 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6303 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6304 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6305 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6306 { /* KBK, KNK, KK of KBKB with like Bishops */
6308 /* always flag draws, for judging claims */
6309 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6311 if(appData.materialDraws) {
6312 /* but only adjudicate them if adjudication enabled */
6313 SendToProgram("force\n", cps->other); // suppress reply
6314 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6315 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6316 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6321 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6323 ( NrWR == 1 && NrBR == 1 /* KRKR */
6324 || NrWQ==1 && NrBQ==1 /* KQKQ */
6325 || NrWN==2 || NrBN==2 /* KNNK */
6326 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6328 if(--moveCount < 0 && appData.trivialDraws)
6329 { /* if the first 3 moves do not show a tactical win, declare draw */
6330 SendToProgram("force\n", cps->other); // suppress reply
6331 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6332 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6333 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6336 } else moveCount = 6;
6340 if (appData.debugMode) { int i;
6341 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6342 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6343 appData.drawRepeats);
6344 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6345 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6349 /* Check for rep-draws */
6351 for(k = forwardMostMove-2;
6352 k>=backwardMostMove && k>=forwardMostMove-100 &&
6353 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6354 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6357 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6358 /* compare castling rights */
6359 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6360 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6361 rights++; /* King lost rights, while rook still had them */
6362 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6363 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6364 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6365 rights++; /* but at least one rook lost them */
6367 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6368 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6370 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6371 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6372 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6375 if( rights == 0 && ++count > appData.drawRepeats-2
6376 && appData.drawRepeats > 1) {
6377 /* adjudicate after user-specified nr of repeats */
6378 SendToProgram("force\n", cps->other); // suppress reply
6379 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6380 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6381 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6382 // [HGM] xiangqi: check for forbidden perpetuals
6383 int m, ourPerpetual = 1, hisPerpetual = 1;
6384 for(m=forwardMostMove; m>k; m-=2) {
6385 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6386 ourPerpetual = 0; // the current mover did not always check
6387 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6388 hisPerpetual = 0; // the opponent did not always check
6390 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6391 ourPerpetual, hisPerpetual);
6392 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6393 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6394 "Xboard adjudication: perpetual checking", GE_XBOARD );
6397 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6398 break; // (or we would have caught him before). Abort repetition-checking loop.
6399 // Now check for perpetual chases
6400 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6401 hisPerpetual = PerpetualChase(k, forwardMostMove);
6402 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6403 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6404 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6405 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6408 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6409 break; // Abort repetition-checking loop.
6411 // if neither of us is checking or chasing all the time, or both are, it is draw
6413 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6416 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6417 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6421 /* Now we test for 50-move draws. Determine ply count */
6422 count = forwardMostMove;
6423 /* look for last irreversble move */
6424 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6426 /* if we hit starting position, add initial plies */
6427 if( count == backwardMostMove )
6428 count -= initialRulePlies;
6429 count = forwardMostMove - count;
6431 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6432 /* this is used to judge if draw claims are legal */
6433 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6434 SendToProgram("force\n", cps->other); // suppress reply
6435 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6436 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6437 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6441 /* if draw offer is pending, treat it as a draw claim
6442 * when draw condition present, to allow engines a way to
6443 * claim draws before making their move to avoid a race
6444 * condition occurring after their move
6446 if( cps->other->offeredDraw || cps->offeredDraw ) {
6448 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6449 p = "Draw claim: 50-move rule";
6450 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6451 p = "Draw claim: 3-fold repetition";
6452 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6453 p = "Draw claim: insufficient mating material";
6455 SendToProgram("force\n", cps->other); // suppress reply
6456 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6457 GameEnds( GameIsDrawn, p, GE_XBOARD );
6458 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6464 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6465 SendToProgram("force\n", cps->other); // suppress reply
6466 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6467 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6469 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6476 if (gameMode == TwoMachinesPlay) {
6477 /* [HGM] relaying draw offers moved to after reception of move */
6478 /* and interpreting offer as claim if it brings draw condition */
6479 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6480 SendToProgram("draw\n", cps->other);
6482 if (cps->other->sendTime) {
6483 SendTimeRemaining(cps->other,
6484 cps->other->twoMachinesColor[0] == 'w');
6486 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6487 if (firstMove && !bookHit) {
6489 if (cps->other->useColors) {
6490 SendToProgram(cps->other->twoMachinesColor, cps->other);
6492 SendToProgram("go\n", cps->other);
6494 cps->other->maybeThinking = TRUE;
6497 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6499 if (!pausing && appData.ringBellAfterMoves) {
6504 * Reenable menu items that were disabled while
6505 * machine was thinking
6507 if (gameMode != TwoMachinesPlay)
6508 SetUserThinkingEnables();
6510 // [HGM] book: after book hit opponent has received move and is now in force mode
6511 // force the book reply into it, and then fake that it outputted this move by jumping
6512 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6514 static char bookMove[MSG_SIZ]; // a bit generous?
6516 strcpy(bookMove, "move ");
6517 strcat(bookMove, bookHit);
6520 programStats.nodes = programStats.depth = programStats.time =
6521 programStats.score = programStats.got_only_move = 0;
6522 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6524 if(cps->lastPing != cps->lastPong) {
6525 savedMessage = message; // args for deferred call
6527 ScheduleDelayedEvent(DeferredBookMove, 10);
6536 /* Set special modes for chess engines. Later something general
6537 * could be added here; for now there is just one kludge feature,
6538 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6539 * when "xboard" is given as an interactive command.
6541 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6542 cps->useSigint = FALSE;
6543 cps->useSigterm = FALSE;
6545 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6546 ParseFeatures(message+8, cps);
6547 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6550 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6551 * want this, I was asked to put it in, and obliged.
6553 if (!strncmp(message, "setboard ", 9)) {
6554 Board initial_position;
6556 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6558 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6559 DisplayError(_("Bad FEN received from engine"), 0);
6563 CopyBoard(boards[0], initial_position);
6564 initialRulePlies = FENrulePlies;
6565 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6566 else gameMode = MachinePlaysBlack;
6567 DrawPosition(FALSE, boards[currentMove]);
6573 * Look for communication commands
6575 if (!strncmp(message, "telluser ", 9)) {
6576 DisplayNote(message + 9);
6579 if (!strncmp(message, "tellusererror ", 14)) {
6581 DisplayError(message + 14, 0);
6584 if (!strncmp(message, "tellopponent ", 13)) {
6585 if (appData.icsActive) {
6587 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6591 DisplayNote(message + 13);
6595 if (!strncmp(message, "tellothers ", 11)) {
6596 if (appData.icsActive) {
6598 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6604 if (!strncmp(message, "tellall ", 8)) {
6605 if (appData.icsActive) {
6607 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6611 DisplayNote(message + 8);
6615 if (strncmp(message, "warning", 7) == 0) {
6616 /* Undocumented feature, use tellusererror in new code */
6617 DisplayError(message, 0);
6620 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6621 strcpy(realname, cps->tidy);
6622 strcat(realname, " query");
6623 AskQuestion(realname, buf2, buf1, cps->pr);
6626 /* Commands from the engine directly to ICS. We don't allow these to be
6627 * sent until we are logged on. Crafty kibitzes have been known to
6628 * interfere with the login process.
6631 if (!strncmp(message, "tellics ", 8)) {
6632 SendToICS(message + 8);
6636 if (!strncmp(message, "tellicsnoalias ", 15)) {
6637 SendToICS(ics_prefix);
6638 SendToICS(message + 15);
6642 /* The following are for backward compatibility only */
6643 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6644 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6645 SendToICS(ics_prefix);
6651 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6655 * If the move is illegal, cancel it and redraw the board.
6656 * Also deal with other error cases. Matching is rather loose
6657 * here to accommodate engines written before the spec.
6659 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6660 strncmp(message, "Error", 5) == 0) {
6661 if (StrStr(message, "name") ||
6662 StrStr(message, "rating") || StrStr(message, "?") ||
6663 StrStr(message, "result") || StrStr(message, "board") ||
6664 StrStr(message, "bk") || StrStr(message, "computer") ||
6665 StrStr(message, "variant") || StrStr(message, "hint") ||
6666 StrStr(message, "random") || StrStr(message, "depth") ||
6667 StrStr(message, "accepted")) {
6670 if (StrStr(message, "protover")) {
6671 /* Program is responding to input, so it's apparently done
6672 initializing, and this error message indicates it is
6673 protocol version 1. So we don't need to wait any longer
6674 for it to initialize and send feature commands. */
6675 FeatureDone(cps, 1);
6676 cps->protocolVersion = 1;
6679 cps->maybeThinking = FALSE;
6681 if (StrStr(message, "draw")) {
6682 /* Program doesn't have "draw" command */
6683 cps->sendDrawOffers = 0;
6686 if (cps->sendTime != 1 &&
6687 (StrStr(message, "time") || StrStr(message, "otim"))) {
6688 /* Program apparently doesn't have "time" or "otim" command */
6692 if (StrStr(message, "analyze")) {
6693 cps->analysisSupport = FALSE;
6694 cps->analyzing = FALSE;
6696 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6697 DisplayError(buf2, 0);
6700 if (StrStr(message, "(no matching move)st")) {
6701 /* Special kludge for GNU Chess 4 only */
6702 cps->stKludge = TRUE;
6703 SendTimeControl(cps, movesPerSession, timeControl,
6704 timeIncrement, appData.searchDepth,
6708 if (StrStr(message, "(no matching move)sd")) {
6709 /* Special kludge for GNU Chess 4 only */
6710 cps->sdKludge = TRUE;
6711 SendTimeControl(cps, movesPerSession, timeControl,
6712 timeIncrement, appData.searchDepth,
6716 if (!StrStr(message, "llegal")) {
6719 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6720 gameMode == IcsIdle) return;
6721 if (forwardMostMove <= backwardMostMove) return;
6722 if (pausing) PauseEvent();
6723 if(appData.forceIllegal) {
6724 // [HGM] illegal: machine refused move; force position after move into it
6725 SendToProgram("force\n", cps);
6726 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6727 // we have a real problem now, as SendBoard will use the a2a3 kludge
6728 // when black is to move, while there might be nothing on a2 or black
6729 // might already have the move. So send the board as if white has the move.
6730 // But first we must change the stm of the engine, as it refused the last move
6731 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6732 if(WhiteOnMove(forwardMostMove)) {
6733 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6734 SendBoard(cps, forwardMostMove); // kludgeless board
6736 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6737 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6738 SendBoard(cps, forwardMostMove+1); // kludgeless board
6740 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6741 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6742 gameMode == TwoMachinesPlay)
6743 SendToProgram("go\n", cps);
6746 if (gameMode == PlayFromGameFile) {
6747 /* Stop reading this game file */
6748 gameMode = EditGame;
6751 currentMove = --forwardMostMove;
6752 DisplayMove(currentMove-1); /* before DisplayMoveError */
6754 DisplayBothClocks();
6755 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6756 parseList[currentMove], cps->which);
6757 DisplayMoveError(buf1);
6758 DrawPosition(FALSE, boards[currentMove]);
6760 /* [HGM] illegal-move claim should forfeit game when Xboard */
6761 /* only passes fully legal moves */
6762 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6763 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6764 "False illegal-move claim", GE_XBOARD );
6768 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6769 /* Program has a broken "time" command that
6770 outputs a string not ending in newline.
6776 * If chess program startup fails, exit with an error message.
6777 * Attempts to recover here are futile.
6779 if ((StrStr(message, "unknown host") != NULL)
6780 || (StrStr(message, "No remote directory") != NULL)
6781 || (StrStr(message, "not found") != NULL)
6782 || (StrStr(message, "No such file") != NULL)
6783 || (StrStr(message, "can't alloc") != NULL)
6784 || (StrStr(message, "Permission denied") != NULL)) {
6786 cps->maybeThinking = FALSE;
6787 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6788 cps->which, cps->program, cps->host, message);
6789 RemoveInputSource(cps->isr);
6790 DisplayFatalError(buf1, 0, 1);
6795 * Look for hint output
6797 if (sscanf(message, "Hint: %s", buf1) == 1) {
6798 if (cps == &first && hintRequested) {
6799 hintRequested = FALSE;
6800 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6801 &fromX, &fromY, &toX, &toY, &promoChar)) {
6802 (void) CoordsToAlgebraic(boards[forwardMostMove],
6803 PosFlags(forwardMostMove),
6804 fromY, fromX, toY, toX, promoChar, buf1);
6805 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6806 DisplayInformation(buf2);
6808 /* Hint move could not be parsed!? */
6809 snprintf(buf2, sizeof(buf2),
6810 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6812 DisplayError(buf2, 0);
6815 strcpy(lastHint, buf1);
6821 * Ignore other messages if game is not in progress
6823 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6824 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6827 * look for win, lose, draw, or draw offer
6829 if (strncmp(message, "1-0", 3) == 0) {
6830 char *p, *q, *r = "";
6831 p = strchr(message, '{');
6839 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6841 } else if (strncmp(message, "0-1", 3) == 0) {
6842 char *p, *q, *r = "";
6843 p = strchr(message, '{');
6851 /* Kludge for Arasan 4.1 bug */
6852 if (strcmp(r, "Black resigns") == 0) {
6853 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6856 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6858 } else if (strncmp(message, "1/2", 3) == 0) {
6859 char *p, *q, *r = "";
6860 p = strchr(message, '{');
6869 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6872 } else if (strncmp(message, "White resign", 12) == 0) {
6873 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6875 } else if (strncmp(message, "Black resign", 12) == 0) {
6876 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6878 } else if (strncmp(message, "White matches", 13) == 0 ||
6879 strncmp(message, "Black matches", 13) == 0 ) {
6880 /* [HGM] ignore GNUShogi noises */
6882 } else if (strncmp(message, "White", 5) == 0 &&
6883 message[5] != '(' &&
6884 StrStr(message, "Black") == NULL) {
6885 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6887 } else if (strncmp(message, "Black", 5) == 0 &&
6888 message[5] != '(') {
6889 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6891 } else if (strcmp(message, "resign") == 0 ||
6892 strcmp(message, "computer resigns") == 0) {
6894 case MachinePlaysBlack:
6895 case IcsPlayingBlack:
6896 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6898 case MachinePlaysWhite:
6899 case IcsPlayingWhite:
6900 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6902 case TwoMachinesPlay:
6903 if (cps->twoMachinesColor[0] == 'w')
6904 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6906 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6913 } else if (strncmp(message, "opponent mates", 14) == 0) {
6915 case MachinePlaysBlack:
6916 case IcsPlayingBlack:
6917 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6919 case MachinePlaysWhite:
6920 case IcsPlayingWhite:
6921 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6923 case TwoMachinesPlay:
6924 if (cps->twoMachinesColor[0] == 'w')
6925 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6927 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6934 } else if (strncmp(message, "computer mates", 14) == 0) {
6936 case MachinePlaysBlack:
6937 case IcsPlayingBlack:
6938 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6940 case MachinePlaysWhite:
6941 case IcsPlayingWhite:
6942 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6944 case TwoMachinesPlay:
6945 if (cps->twoMachinesColor[0] == 'w')
6946 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6948 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6955 } else if (strncmp(message, "checkmate", 9) == 0) {
6956 if (WhiteOnMove(forwardMostMove)) {
6957 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6959 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6962 } else if (strstr(message, "Draw") != NULL ||
6963 strstr(message, "game is a draw") != NULL) {
6964 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6966 } else if (strstr(message, "offer") != NULL &&
6967 strstr(message, "draw") != NULL) {
6969 if (appData.zippyPlay && first.initDone) {
6970 /* Relay offer to ICS */
6971 SendToICS(ics_prefix);
6972 SendToICS("draw\n");
6975 cps->offeredDraw = 2; /* valid until this engine moves twice */
6976 if (gameMode == TwoMachinesPlay) {
6977 if (cps->other->offeredDraw) {
6978 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6979 /* [HGM] in two-machine mode we delay relaying draw offer */
6980 /* until after we also have move, to see if it is really claim */
6982 } else if (gameMode == MachinePlaysWhite ||
6983 gameMode == MachinePlaysBlack) {
6984 if (userOfferedDraw) {
6985 DisplayInformation(_("Machine accepts your draw offer"));
6986 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6988 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6995 * Look for thinking output
6997 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6998 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7000 int plylev, mvleft, mvtot, curscore, time;
7001 char mvname[MOVE_LEN];
7005 int prefixHint = FALSE;
7006 mvname[0] = NULLCHAR;
7009 case MachinePlaysBlack:
7010 case IcsPlayingBlack:
7011 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7013 case MachinePlaysWhite:
7014 case IcsPlayingWhite:
7015 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7020 case IcsObserving: /* [DM] icsEngineAnalyze */
7021 if (!appData.icsEngineAnalyze) ignore = TRUE;
7023 case TwoMachinesPlay:
7024 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7035 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7036 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7038 if (plyext != ' ' && plyext != '\t') {
7042 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7043 if( cps->scoreIsAbsolute &&
7044 ( gameMode == MachinePlaysBlack ||
7045 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7046 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7047 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7048 !WhiteOnMove(currentMove)
7051 curscore = -curscore;
7055 programStats.depth = plylev;
7056 programStats.nodes = nodes;
7057 programStats.time = time;
7058 programStats.score = curscore;
7059 programStats.got_only_move = 0;
7061 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7064 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7065 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7066 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7067 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7068 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7069 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7070 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7071 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7074 /* Buffer overflow protection */
7075 if (buf1[0] != NULLCHAR) {
7076 if (strlen(buf1) >= sizeof(programStats.movelist)
7077 && appData.debugMode) {
7079 "PV is too long; using the first %u bytes.\n",
7080 (unsigned) sizeof(programStats.movelist) - 1);
7083 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7085 sprintf(programStats.movelist, " no PV\n");
7088 if (programStats.seen_stat) {
7089 programStats.ok_to_send = 1;
7092 if (strchr(programStats.movelist, '(') != NULL) {
7093 programStats.line_is_book = 1;
7094 programStats.nr_moves = 0;
7095 programStats.moves_left = 0;
7097 programStats.line_is_book = 0;
7100 SendProgramStatsToFrontend( cps, &programStats );
7103 [AS] Protect the thinkOutput buffer from overflow... this
7104 is only useful if buf1 hasn't overflowed first!
7106 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7108 (gameMode == TwoMachinesPlay ?
7109 ToUpper(cps->twoMachinesColor[0]) : ' '),
7110 ((double) curscore) / 100.0,
7111 prefixHint ? lastHint : "",
7112 prefixHint ? " " : "" );
7114 if( buf1[0] != NULLCHAR ) {
7115 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7117 if( strlen(buf1) > max_len ) {
7118 if( appData.debugMode) {
7119 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7121 buf1[max_len+1] = '\0';
7124 strcat( thinkOutput, buf1 );
7127 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7128 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7129 DisplayMove(currentMove - 1);
7133 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7134 /* crafty (9.25+) says "(only move) <move>"
7135 * if there is only 1 legal move
7137 sscanf(p, "(only move) %s", buf1);
7138 sprintf(thinkOutput, "%s (only move)", buf1);
7139 sprintf(programStats.movelist, "%s (only move)", buf1);
7140 programStats.depth = 1;
7141 programStats.nr_moves = 1;
7142 programStats.moves_left = 1;
7143 programStats.nodes = 1;
7144 programStats.time = 1;
7145 programStats.got_only_move = 1;
7147 /* Not really, but we also use this member to
7148 mean "line isn't going to change" (Crafty
7149 isn't searching, so stats won't change) */
7150 programStats.line_is_book = 1;
7152 SendProgramStatsToFrontend( cps, &programStats );
7154 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7155 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7156 DisplayMove(currentMove - 1);
7159 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7160 &time, &nodes, &plylev, &mvleft,
7161 &mvtot, mvname) >= 5) {
7162 /* The stat01: line is from Crafty (9.29+) in response
7163 to the "." command */
7164 programStats.seen_stat = 1;
7165 cps->maybeThinking = TRUE;
7167 if (programStats.got_only_move || !appData.periodicUpdates)
7170 programStats.depth = plylev;
7171 programStats.time = time;
7172 programStats.nodes = nodes;
7173 programStats.moves_left = mvleft;
7174 programStats.nr_moves = mvtot;
7175 strcpy(programStats.move_name, mvname);
7176 programStats.ok_to_send = 1;
7177 programStats.movelist[0] = '\0';
7179 SendProgramStatsToFrontend( cps, &programStats );
7183 } else if (strncmp(message,"++",2) == 0) {
7184 /* Crafty 9.29+ outputs this */
7185 programStats.got_fail = 2;
7188 } else if (strncmp(message,"--",2) == 0) {
7189 /* Crafty 9.29+ outputs this */
7190 programStats.got_fail = 1;
7193 } else if (thinkOutput[0] != NULLCHAR &&
7194 strncmp(message, " ", 4) == 0) {
7195 unsigned message_len;
7198 while (*p && *p == ' ') p++;
7200 message_len = strlen( p );
7202 /* [AS] Avoid buffer overflow */
7203 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7204 strcat(thinkOutput, " ");
7205 strcat(thinkOutput, p);
7208 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7209 strcat(programStats.movelist, " ");
7210 strcat(programStats.movelist, p);
7213 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7214 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7215 DisplayMove(currentMove - 1);
7223 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7224 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7226 ChessProgramStats cpstats;
7228 if (plyext != ' ' && plyext != '\t') {
7232 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7233 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7234 curscore = -curscore;
7237 cpstats.depth = plylev;
7238 cpstats.nodes = nodes;
7239 cpstats.time = time;
7240 cpstats.score = curscore;
7241 cpstats.got_only_move = 0;
7242 cpstats.movelist[0] = '\0';
7244 if (buf1[0] != NULLCHAR) {
7245 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7248 cpstats.ok_to_send = 0;
7249 cpstats.line_is_book = 0;
7250 cpstats.nr_moves = 0;
7251 cpstats.moves_left = 0;
7253 SendProgramStatsToFrontend( cps, &cpstats );
7260 /* Parse a game score from the character string "game", and
7261 record it as the history of the current game. The game
7262 score is NOT assumed to start from the standard position.
7263 The display is not updated in any way.
7266 ParseGameHistory(game)
7270 int fromX, fromY, toX, toY, boardIndex;
7275 if (appData.debugMode)
7276 fprintf(debugFP, "Parsing game history: %s\n", game);
7278 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7279 gameInfo.site = StrSave(appData.icsHost);
7280 gameInfo.date = PGNDate();
7281 gameInfo.round = StrSave("-");
7283 /* Parse out names of players */
7284 while (*game == ' ') game++;
7286 while (*game != ' ') *p++ = *game++;
7288 gameInfo.white = StrSave(buf);
7289 while (*game == ' ') game++;
7291 while (*game != ' ' && *game != '\n') *p++ = *game++;
7293 gameInfo.black = StrSave(buf);
7296 boardIndex = blackPlaysFirst ? 1 : 0;
7299 yyboardindex = boardIndex;
7300 moveType = (ChessMove) yylex();
7302 case IllegalMove: /* maybe suicide chess, etc. */
7303 if (appData.debugMode) {
7304 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7305 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7306 setbuf(debugFP, NULL);
7308 case WhitePromotionChancellor:
7309 case BlackPromotionChancellor:
7310 case WhitePromotionArchbishop:
7311 case BlackPromotionArchbishop:
7312 case WhitePromotionQueen:
7313 case BlackPromotionQueen:
7314 case WhitePromotionRook:
7315 case BlackPromotionRook:
7316 case WhitePromotionBishop:
7317 case BlackPromotionBishop:
7318 case WhitePromotionKnight:
7319 case BlackPromotionKnight:
7320 case WhitePromotionKing:
7321 case BlackPromotionKing:
7323 case WhiteCapturesEnPassant:
7324 case BlackCapturesEnPassant:
7325 case WhiteKingSideCastle:
7326 case WhiteQueenSideCastle:
7327 case BlackKingSideCastle:
7328 case BlackQueenSideCastle:
7329 case WhiteKingSideCastleWild:
7330 case WhiteQueenSideCastleWild:
7331 case BlackKingSideCastleWild:
7332 case BlackQueenSideCastleWild:
7334 case WhiteHSideCastleFR:
7335 case WhiteASideCastleFR:
7336 case BlackHSideCastleFR:
7337 case BlackASideCastleFR:
7339 fromX = currentMoveString[0] - AAA;
7340 fromY = currentMoveString[1] - ONE;
7341 toX = currentMoveString[2] - AAA;
7342 toY = currentMoveString[3] - ONE;
7343 promoChar = currentMoveString[4];
7347 fromX = moveType == WhiteDrop ?
7348 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7349 (int) CharToPiece(ToLower(currentMoveString[0]));
7351 toX = currentMoveString[2] - AAA;
7352 toY = currentMoveString[3] - ONE;
7353 promoChar = NULLCHAR;
7357 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7358 if (appData.debugMode) {
7359 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7360 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7361 setbuf(debugFP, NULL);
7363 DisplayError(buf, 0);
7365 case ImpossibleMove:
7367 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7368 if (appData.debugMode) {
7369 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7370 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7371 setbuf(debugFP, NULL);
7373 DisplayError(buf, 0);
7375 case (ChessMove) 0: /* end of file */
7376 if (boardIndex < backwardMostMove) {
7377 /* Oops, gap. How did that happen? */
7378 DisplayError(_("Gap in move list"), 0);
7381 backwardMostMove = blackPlaysFirst ? 1 : 0;
7382 if (boardIndex > forwardMostMove) {
7383 forwardMostMove = boardIndex;
7387 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7388 strcat(parseList[boardIndex-1], " ");
7389 strcat(parseList[boardIndex-1], yy_text);
7401 case GameUnfinished:
7402 if (gameMode == IcsExamining) {
7403 if (boardIndex < backwardMostMove) {
7404 /* Oops, gap. How did that happen? */
7407 backwardMostMove = blackPlaysFirst ? 1 : 0;
7410 gameInfo.result = moveType;
7411 p = strchr(yy_text, '{');
7412 if (p == NULL) p = strchr(yy_text, '(');
7415 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7417 q = strchr(p, *p == '{' ? '}' : ')');
7418 if (q != NULL) *q = NULLCHAR;
7421 gameInfo.resultDetails = StrSave(p);
7424 if (boardIndex >= forwardMostMove &&
7425 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7426 backwardMostMove = blackPlaysFirst ? 1 : 0;
7429 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7430 fromY, fromX, toY, toX, promoChar,
7431 parseList[boardIndex]);
7432 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7433 /* currentMoveString is set as a side-effect of yylex */
7434 strcpy(moveList[boardIndex], currentMoveString);
7435 strcat(moveList[boardIndex], "\n");
7437 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7438 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7444 if(gameInfo.variant != VariantShogi)
7445 strcat(parseList[boardIndex - 1], "+");
7449 strcat(parseList[boardIndex - 1], "#");
7456 /* Apply a move to the given board */
7458 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7459 int fromX, fromY, toX, toY;
7463 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7465 /* [HGM] compute & store e.p. status and castling rights for new position */
7466 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7469 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7470 oldEP = (signed char)board[EP_STATUS];
7471 board[EP_STATUS] = EP_NONE;
7473 if( board[toY][toX] != EmptySquare )
7474 board[EP_STATUS] = EP_CAPTURE;
7476 if( board[fromY][fromX] == WhitePawn ) {
7477 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7478 board[EP_STATUS] = EP_PAWN_MOVE;
7480 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7481 gameInfo.variant != VariantBerolina || toX < fromX)
7482 board[EP_STATUS] = toX | berolina;
7483 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7484 gameInfo.variant != VariantBerolina || toX > fromX)
7485 board[EP_STATUS] = toX;
7488 if( board[fromY][fromX] == BlackPawn ) {
7489 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7490 board[EP_STATUS] = EP_PAWN_MOVE;
7491 if( toY-fromY== -2) {
7492 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7493 gameInfo.variant != VariantBerolina || toX < fromX)
7494 board[EP_STATUS] = toX | berolina;
7495 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7496 gameInfo.variant != VariantBerolina || toX > fromX)
7497 board[EP_STATUS] = toX;
7501 for(i=0; i<nrCastlingRights; i++) {
7502 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7503 board[CASTLING][i] == toX && castlingRank[i] == toY
7504 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7509 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7510 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7511 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7513 if (fromX == toX && fromY == toY) return;
7515 if (fromY == DROP_RANK) {
7517 piece = board[toY][toX] = (ChessSquare) fromX;
7519 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7520 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7521 if(gameInfo.variant == VariantKnightmate)
7522 king += (int) WhiteUnicorn - (int) WhiteKing;
7524 /* Code added by Tord: */
7525 /* FRC castling assumed when king captures friendly rook. */
7526 if (board[fromY][fromX] == WhiteKing &&
7527 board[toY][toX] == WhiteRook) {
7528 board[fromY][fromX] = EmptySquare;
7529 board[toY][toX] = EmptySquare;
7531 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7533 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7535 } else if (board[fromY][fromX] == BlackKing &&
7536 board[toY][toX] == BlackRook) {
7537 board[fromY][fromX] = EmptySquare;
7538 board[toY][toX] = EmptySquare;
7540 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7542 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7544 /* End of code added by Tord */
7546 } else if (board[fromY][fromX] == king
7547 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7548 && toY == fromY && toX > fromX+1) {
7549 board[fromY][fromX] = EmptySquare;
7550 board[toY][toX] = king;
7551 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7552 board[fromY][BOARD_RGHT-1] = EmptySquare;
7553 } else if (board[fromY][fromX] == king
7554 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7555 && toY == fromY && toX < fromX-1) {
7556 board[fromY][fromX] = EmptySquare;
7557 board[toY][toX] = king;
7558 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7559 board[fromY][BOARD_LEFT] = EmptySquare;
7560 } else if (board[fromY][fromX] == WhitePawn
7561 && toY == BOARD_HEIGHT-1
7562 && gameInfo.variant != VariantXiangqi
7564 /* white pawn promotion */
7565 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7566 if (board[toY][toX] == EmptySquare) {
7567 board[toY][toX] = WhiteQueen;
7569 if(gameInfo.variant==VariantBughouse ||
7570 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7571 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7572 board[fromY][fromX] = EmptySquare;
7573 } else if ((fromY == BOARD_HEIGHT-4)
7575 && gameInfo.variant != VariantXiangqi
7576 && gameInfo.variant != VariantBerolina
7577 && (board[fromY][fromX] == WhitePawn)
7578 && (board[toY][toX] == EmptySquare)) {
7579 board[fromY][fromX] = EmptySquare;
7580 board[toY][toX] = WhitePawn;
7581 captured = board[toY - 1][toX];
7582 board[toY - 1][toX] = EmptySquare;
7583 } else if ((fromY == BOARD_HEIGHT-4)
7585 && gameInfo.variant == VariantBerolina
7586 && (board[fromY][fromX] == WhitePawn)
7587 && (board[toY][toX] == EmptySquare)) {
7588 board[fromY][fromX] = EmptySquare;
7589 board[toY][toX] = WhitePawn;
7590 if(oldEP & EP_BEROLIN_A) {
7591 captured = board[fromY][fromX-1];
7592 board[fromY][fromX-1] = EmptySquare;
7593 }else{ captured = board[fromY][fromX+1];
7594 board[fromY][fromX+1] = EmptySquare;
7596 } else if (board[fromY][fromX] == king
7597 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7598 && toY == fromY && toX > fromX+1) {
7599 board[fromY][fromX] = EmptySquare;
7600 board[toY][toX] = king;
7601 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7602 board[fromY][BOARD_RGHT-1] = EmptySquare;
7603 } else if (board[fromY][fromX] == king
7604 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7605 && toY == fromY && toX < fromX-1) {
7606 board[fromY][fromX] = EmptySquare;
7607 board[toY][toX] = king;
7608 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7609 board[fromY][BOARD_LEFT] = EmptySquare;
7610 } else if (fromY == 7 && fromX == 3
7611 && board[fromY][fromX] == BlackKing
7612 && toY == 7 && toX == 5) {
7613 board[fromY][fromX] = EmptySquare;
7614 board[toY][toX] = BlackKing;
7615 board[fromY][7] = EmptySquare;
7616 board[toY][4] = BlackRook;
7617 } else if (fromY == 7 && fromX == 3
7618 && board[fromY][fromX] == BlackKing
7619 && toY == 7 && toX == 1) {
7620 board[fromY][fromX] = EmptySquare;
7621 board[toY][toX] = BlackKing;
7622 board[fromY][0] = EmptySquare;
7623 board[toY][2] = BlackRook;
7624 } else if (board[fromY][fromX] == BlackPawn
7626 && gameInfo.variant != VariantXiangqi
7628 /* black pawn promotion */
7629 board[0][toX] = CharToPiece(ToLower(promoChar));
7630 if (board[0][toX] == EmptySquare) {
7631 board[0][toX] = BlackQueen;
7633 if(gameInfo.variant==VariantBughouse ||
7634 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7635 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7636 board[fromY][fromX] = EmptySquare;
7637 } else if ((fromY == 3)
7639 && gameInfo.variant != VariantXiangqi
7640 && gameInfo.variant != VariantBerolina
7641 && (board[fromY][fromX] == BlackPawn)
7642 && (board[toY][toX] == EmptySquare)) {
7643 board[fromY][fromX] = EmptySquare;
7644 board[toY][toX] = BlackPawn;
7645 captured = board[toY + 1][toX];
7646 board[toY + 1][toX] = EmptySquare;
7647 } else if ((fromY == 3)
7649 && gameInfo.variant == VariantBerolina
7650 && (board[fromY][fromX] == BlackPawn)
7651 && (board[toY][toX] == EmptySquare)) {
7652 board[fromY][fromX] = EmptySquare;
7653 board[toY][toX] = BlackPawn;
7654 if(oldEP & EP_BEROLIN_A) {
7655 captured = board[fromY][fromX-1];
7656 board[fromY][fromX-1] = EmptySquare;
7657 }else{ captured = board[fromY][fromX+1];
7658 board[fromY][fromX+1] = EmptySquare;
7661 board[toY][toX] = board[fromY][fromX];
7662 board[fromY][fromX] = EmptySquare;
7665 /* [HGM] now we promote for Shogi, if needed */
7666 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7667 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7670 if (gameInfo.holdingsWidth != 0) {
7672 /* !!A lot more code needs to be written to support holdings */
7673 /* [HGM] OK, so I have written it. Holdings are stored in the */
7674 /* penultimate board files, so they are automaticlly stored */
7675 /* in the game history. */
7676 if (fromY == DROP_RANK) {
7677 /* Delete from holdings, by decreasing count */
7678 /* and erasing image if necessary */
7680 if(p < (int) BlackPawn) { /* white drop */
7681 p -= (int)WhitePawn;
7682 p = PieceToNumber((ChessSquare)p);
7683 if(p >= gameInfo.holdingsSize) p = 0;
7684 if(--board[p][BOARD_WIDTH-2] <= 0)
7685 board[p][BOARD_WIDTH-1] = EmptySquare;
7686 if((int)board[p][BOARD_WIDTH-2] < 0)
7687 board[p][BOARD_WIDTH-2] = 0;
7688 } else { /* black drop */
7689 p -= (int)BlackPawn;
7690 p = PieceToNumber((ChessSquare)p);
7691 if(p >= gameInfo.holdingsSize) p = 0;
7692 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7693 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7694 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7695 board[BOARD_HEIGHT-1-p][1] = 0;
7698 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7699 && gameInfo.variant != VariantBughouse ) {
7700 /* [HGM] holdings: Add to holdings, if holdings exist */
7701 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7702 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7703 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7706 if (p >= (int) BlackPawn) {
7707 p -= (int)BlackPawn;
7708 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7709 /* in Shogi restore piece to its original first */
7710 captured = (ChessSquare) (DEMOTED captured);
7713 p = PieceToNumber((ChessSquare)p);
7714 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7715 board[p][BOARD_WIDTH-2]++;
7716 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7718 p -= (int)WhitePawn;
7719 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7720 captured = (ChessSquare) (DEMOTED captured);
7723 p = PieceToNumber((ChessSquare)p);
7724 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7725 board[BOARD_HEIGHT-1-p][1]++;
7726 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7729 } else if (gameInfo.variant == VariantAtomic) {
7730 if (captured != EmptySquare) {
7732 for (y = toY-1; y <= toY+1; y++) {
7733 for (x = toX-1; x <= toX+1; x++) {
7734 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7735 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7736 board[y][x] = EmptySquare;
7740 board[toY][toX] = EmptySquare;
7743 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7744 /* [HGM] Shogi promotions */
7745 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7748 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7749 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7750 // [HGM] superchess: take promotion piece out of holdings
7751 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7752 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7753 if(!--board[k][BOARD_WIDTH-2])
7754 board[k][BOARD_WIDTH-1] = EmptySquare;
7756 if(!--board[BOARD_HEIGHT-1-k][1])
7757 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7763 /* Updates forwardMostMove */
7765 MakeMove(fromX, fromY, toX, toY, promoChar)
7766 int fromX, fromY, toX, toY;
7769 // forwardMostMove++; // [HGM] bare: moved downstream
7771 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7772 int timeLeft; static int lastLoadFlag=0; int king, piece;
7773 piece = boards[forwardMostMove][fromY][fromX];
7774 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7775 if(gameInfo.variant == VariantKnightmate)
7776 king += (int) WhiteUnicorn - (int) WhiteKing;
7777 if(forwardMostMove == 0) {
7779 fprintf(serverMoves, "%s;", second.tidy);
7780 fprintf(serverMoves, "%s;", first.tidy);
7781 if(!blackPlaysFirst)
7782 fprintf(serverMoves, "%s;", second.tidy);
7783 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7784 lastLoadFlag = loadFlag;
7786 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7787 // print castling suffix
7788 if( toY == fromY && piece == king ) {
7790 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7792 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7795 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7796 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7797 boards[forwardMostMove][toY][toX] == EmptySquare
7799 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7801 if(promoChar != NULLCHAR)
7802 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7804 fprintf(serverMoves, "/%d/%d",
7805 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7806 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7807 else timeLeft = blackTimeRemaining/1000;
7808 fprintf(serverMoves, "/%d", timeLeft);
7810 fflush(serverMoves);
7813 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7814 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7818 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7819 if (commentList[forwardMostMove+1] != NULL) {
7820 free(commentList[forwardMostMove+1]);
7821 commentList[forwardMostMove+1] = NULL;
7823 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7824 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7825 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7826 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7827 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7828 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7829 gameInfo.result = GameUnfinished;
7830 if (gameInfo.resultDetails != NULL) {
7831 free(gameInfo.resultDetails);
7832 gameInfo.resultDetails = NULL;
7834 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7835 moveList[forwardMostMove - 1]);
7836 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7837 PosFlags(forwardMostMove - 1),
7838 fromY, fromX, toY, toX, promoChar,
7839 parseList[forwardMostMove - 1]);
7840 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7846 if(gameInfo.variant != VariantShogi)
7847 strcat(parseList[forwardMostMove - 1], "+");
7851 strcat(parseList[forwardMostMove - 1], "#");
7854 if (appData.debugMode) {
7855 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7860 /* Updates currentMove if not pausing */
7862 ShowMove(fromX, fromY, toX, toY)
7864 int instant = (gameMode == PlayFromGameFile) ?
7865 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7866 if(appData.noGUI) return;
7867 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7869 if (forwardMostMove == currentMove + 1) {
7870 AnimateMove(boards[forwardMostMove - 1],
7871 fromX, fromY, toX, toY);
7873 if (appData.highlightLastMove) {
7874 SetHighlights(fromX, fromY, toX, toY);
7877 currentMove = forwardMostMove;
7880 if (instant) return;
7882 DisplayMove(currentMove - 1);
7883 DrawPosition(FALSE, boards[currentMove]);
7884 DisplayBothClocks();
7885 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7888 void SendEgtPath(ChessProgramState *cps)
7889 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7890 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7892 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7895 char c, *q = name+1, *r, *s;
7897 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7898 while(*p && *p != ',') *q++ = *p++;
7900 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7901 strcmp(name, ",nalimov:") == 0 ) {
7902 // take nalimov path from the menu-changeable option first, if it is defined
7903 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7904 SendToProgram(buf,cps); // send egtbpath command for nalimov
7906 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7907 (s = StrStr(appData.egtFormats, name)) != NULL) {
7908 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7909 s = r = StrStr(s, ":") + 1; // beginning of path info
7910 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7911 c = *r; *r = 0; // temporarily null-terminate path info
7912 *--q = 0; // strip of trailig ':' from name
7913 sprintf(buf, "egtpath %s %s\n", name+1, s);
7915 SendToProgram(buf,cps); // send egtbpath command for this format
7917 if(*p == ',') p++; // read away comma to position for next format name
7922 InitChessProgram(cps, setup)
7923 ChessProgramState *cps;
7924 int setup; /* [HGM] needed to setup FRC opening position */
7926 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7927 if (appData.noChessProgram) return;
7928 hintRequested = FALSE;
7929 bookRequested = FALSE;
7931 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7932 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7933 if(cps->memSize) { /* [HGM] memory */
7934 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7935 SendToProgram(buf, cps);
7937 SendEgtPath(cps); /* [HGM] EGT */
7938 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7939 sprintf(buf, "cores %d\n", appData.smpCores);
7940 SendToProgram(buf, cps);
7943 SendToProgram(cps->initString, cps);
7944 if (gameInfo.variant != VariantNormal &&
7945 gameInfo.variant != VariantLoadable
7946 /* [HGM] also send variant if board size non-standard */
7947 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7949 char *v = VariantName(gameInfo.variant);
7950 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7951 /* [HGM] in protocol 1 we have to assume all variants valid */
7952 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7953 DisplayFatalError(buf, 0, 1);
7957 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7958 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7959 if( gameInfo.variant == VariantXiangqi )
7960 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7961 if( gameInfo.variant == VariantShogi )
7962 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7963 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7964 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7965 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7966 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7967 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7968 if( gameInfo.variant == VariantCourier )
7969 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7970 if( gameInfo.variant == VariantSuper )
7971 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7972 if( gameInfo.variant == VariantGreat )
7973 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7976 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7977 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7978 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7979 if(StrStr(cps->variants, b) == NULL) {
7980 // specific sized variant not known, check if general sizing allowed
7981 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7982 if(StrStr(cps->variants, "boardsize") == NULL) {
7983 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7984 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7985 DisplayFatalError(buf, 0, 1);
7988 /* [HGM] here we really should compare with the maximum supported board size */
7991 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7992 sprintf(buf, "variant %s\n", b);
7993 SendToProgram(buf, cps);
7995 currentlyInitializedVariant = gameInfo.variant;
7997 /* [HGM] send opening position in FRC to first engine */
7999 SendToProgram("force\n", cps);
8001 /* engine is now in force mode! Set flag to wake it up after first move. */
8002 setboardSpoiledMachineBlack = 1;
8006 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8007 SendToProgram(buf, cps);
8009 cps->maybeThinking = FALSE;
8010 cps->offeredDraw = 0;
8011 if (!appData.icsActive) {
8012 SendTimeControl(cps, movesPerSession, timeControl,
8013 timeIncrement, appData.searchDepth,
8016 if (appData.showThinking
8017 // [HGM] thinking: four options require thinking output to be sent
8018 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8020 SendToProgram("post\n", cps);
8022 SendToProgram("hard\n", cps);
8023 if (!appData.ponderNextMove) {
8024 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8025 it without being sure what state we are in first. "hard"
8026 is not a toggle, so that one is OK.
8028 SendToProgram("easy\n", cps);
8031 sprintf(buf, "ping %d\n", ++cps->lastPing);
8032 SendToProgram(buf, cps);
8034 cps->initDone = TRUE;
8039 StartChessProgram(cps)
8040 ChessProgramState *cps;
8045 if (appData.noChessProgram) return;
8046 cps->initDone = FALSE;
8048 if (strcmp(cps->host, "localhost") == 0) {
8049 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8050 } else if (*appData.remoteShell == NULLCHAR) {
8051 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8053 if (*appData.remoteUser == NULLCHAR) {
8054 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8057 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8058 cps->host, appData.remoteUser, cps->program);
8060 err = StartChildProcess(buf, "", &cps->pr);
8064 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8065 DisplayFatalError(buf, err, 1);
8071 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8072 if (cps->protocolVersion > 1) {
8073 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8074 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8075 cps->comboCnt = 0; // and values of combo boxes
8076 SendToProgram(buf, cps);
8078 SendToProgram("xboard\n", cps);
8084 TwoMachinesEventIfReady P((void))
8086 if (first.lastPing != first.lastPong) {
8087 DisplayMessage("", _("Waiting for first chess program"));
8088 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8091 if (second.lastPing != second.lastPong) {
8092 DisplayMessage("", _("Waiting for second chess program"));
8093 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8101 NextMatchGame P((void))
8103 int index; /* [HGM] autoinc: step load index during match */
8105 if (*appData.loadGameFile != NULLCHAR) {
8106 index = appData.loadGameIndex;
8107 if(index < 0) { // [HGM] autoinc
8108 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8109 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8111 LoadGameFromFile(appData.loadGameFile,
8113 appData.loadGameFile, FALSE);
8114 } else if (*appData.loadPositionFile != NULLCHAR) {
8115 index = appData.loadPositionIndex;
8116 if(index < 0) { // [HGM] autoinc
8117 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8118 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8120 LoadPositionFromFile(appData.loadPositionFile,
8122 appData.loadPositionFile);
8124 TwoMachinesEventIfReady();
8127 void UserAdjudicationEvent( int result )
8129 ChessMove gameResult = GameIsDrawn;
8132 gameResult = WhiteWins;
8134 else if( result < 0 ) {
8135 gameResult = BlackWins;
8138 if( gameMode == TwoMachinesPlay ) {
8139 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8144 // [HGM] save: calculate checksum of game to make games easily identifiable
8145 int StringCheckSum(char *s)
8148 if(s==NULL) return 0;
8149 while(*s) i = i*259 + *s++;
8156 for(i=backwardMostMove; i<forwardMostMove; i++) {
8157 sum += pvInfoList[i].depth;
8158 sum += StringCheckSum(parseList[i]);
8159 sum += StringCheckSum(commentList[i]);
8162 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8163 return sum + StringCheckSum(commentList[i]);
8164 } // end of save patch
8167 GameEnds(result, resultDetails, whosays)
8169 char *resultDetails;
8172 GameMode nextGameMode;
8176 if(endingGame) return; /* [HGM] crash: forbid recursion */
8179 if (appData.debugMode) {
8180 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8181 result, resultDetails ? resultDetails : "(null)", whosays);
8184 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8185 /* If we are playing on ICS, the server decides when the
8186 game is over, but the engine can offer to draw, claim
8190 if (appData.zippyPlay && first.initDone) {
8191 if (result == GameIsDrawn) {
8192 /* In case draw still needs to be claimed */
8193 SendToICS(ics_prefix);
8194 SendToICS("draw\n");
8195 } else if (StrCaseStr(resultDetails, "resign")) {
8196 SendToICS(ics_prefix);
8197 SendToICS("resign\n");
8201 endingGame = 0; /* [HGM] crash */
8205 /* If we're loading the game from a file, stop */
8206 if (whosays == GE_FILE) {
8207 (void) StopLoadGameTimer();
8211 /* Cancel draw offers */
8212 first.offeredDraw = second.offeredDraw = 0;
8214 /* If this is an ICS game, only ICS can really say it's done;
8215 if not, anyone can. */
8216 isIcsGame = (gameMode == IcsPlayingWhite ||
8217 gameMode == IcsPlayingBlack ||
8218 gameMode == IcsObserving ||
8219 gameMode == IcsExamining);
8221 if (!isIcsGame || whosays == GE_ICS) {
8222 /* OK -- not an ICS game, or ICS said it was done */
8224 if (!isIcsGame && !appData.noChessProgram)
8225 SetUserThinkingEnables();
8227 /* [HGM] if a machine claims the game end we verify this claim */
8228 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8229 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8231 ChessMove trueResult = (ChessMove) -1;
8233 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8234 first.twoMachinesColor[0] :
8235 second.twoMachinesColor[0] ;
8237 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8238 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8239 /* [HGM] verify: engine mate claims accepted if they were flagged */
8240 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8242 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8243 /* [HGM] verify: engine mate claims accepted if they were flagged */
8244 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8246 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8247 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8250 // now verify win claims, but not in drop games, as we don't understand those yet
8251 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8252 || gameInfo.variant == VariantGreat) &&
8253 (result == WhiteWins && claimer == 'w' ||
8254 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8255 if (appData.debugMode) {
8256 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8257 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8259 if(result != trueResult) {
8260 sprintf(buf, "False win claim: '%s'", resultDetails);
8261 result = claimer == 'w' ? BlackWins : WhiteWins;
8262 resultDetails = buf;
8265 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8266 && (forwardMostMove <= backwardMostMove ||
8267 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8268 (claimer=='b')==(forwardMostMove&1))
8270 /* [HGM] verify: draws that were not flagged are false claims */
8271 sprintf(buf, "False draw claim: '%s'", resultDetails);
8272 result = claimer == 'w' ? BlackWins : WhiteWins;
8273 resultDetails = buf;
8275 /* (Claiming a loss is accepted no questions asked!) */
8277 /* [HGM] bare: don't allow bare King to win */
8278 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8279 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8280 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8281 && result != GameIsDrawn)
8282 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8283 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8284 int p = (signed char)boards[forwardMostMove][i][j] - color;
8285 if(p >= 0 && p <= (int)WhiteKing) k++;
8287 if (appData.debugMode) {
8288 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8289 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8292 result = GameIsDrawn;
8293 sprintf(buf, "%s but bare king", resultDetails);
8294 resultDetails = buf;
8300 if(serverMoves != NULL && !loadFlag) { char c = '=';
8301 if(result==WhiteWins) c = '+';
8302 if(result==BlackWins) c = '-';
8303 if(resultDetails != NULL)
8304 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8306 if (resultDetails != NULL) {
8307 gameInfo.result = result;
8308 gameInfo.resultDetails = StrSave(resultDetails);
8310 /* display last move only if game was not loaded from file */
8311 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8312 DisplayMove(currentMove - 1);
8314 if (forwardMostMove != 0) {
8315 if (gameMode != PlayFromGameFile && gameMode != EditGame
8316 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8318 if (*appData.saveGameFile != NULLCHAR) {
8319 SaveGameToFile(appData.saveGameFile, TRUE);
8320 } else if (appData.autoSaveGames) {
8323 if (*appData.savePositionFile != NULLCHAR) {
8324 SavePositionToFile(appData.savePositionFile);
8329 /* Tell program how game ended in case it is learning */
8330 /* [HGM] Moved this to after saving the PGN, just in case */
8331 /* engine died and we got here through time loss. In that */
8332 /* case we will get a fatal error writing the pipe, which */
8333 /* would otherwise lose us the PGN. */
8334 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8335 /* output during GameEnds should never be fatal anymore */
8336 if (gameMode == MachinePlaysWhite ||
8337 gameMode == MachinePlaysBlack ||
8338 gameMode == TwoMachinesPlay ||
8339 gameMode == IcsPlayingWhite ||
8340 gameMode == IcsPlayingBlack ||
8341 gameMode == BeginningOfGame) {
8343 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8345 if (first.pr != NoProc) {
8346 SendToProgram(buf, &first);
8348 if (second.pr != NoProc &&
8349 gameMode == TwoMachinesPlay) {
8350 SendToProgram(buf, &second);
8355 if (appData.icsActive) {
8356 if (appData.quietPlay &&
8357 (gameMode == IcsPlayingWhite ||
8358 gameMode == IcsPlayingBlack)) {
8359 SendToICS(ics_prefix);
8360 SendToICS("set shout 1\n");
8362 nextGameMode = IcsIdle;
8363 ics_user_moved = FALSE;
8364 /* clean up premove. It's ugly when the game has ended and the
8365 * premove highlights are still on the board.
8369 ClearPremoveHighlights();
8370 DrawPosition(FALSE, boards[currentMove]);
8372 if (whosays == GE_ICS) {
8375 if (gameMode == IcsPlayingWhite)
8377 else if(gameMode == IcsPlayingBlack)
8381 if (gameMode == IcsPlayingBlack)
8383 else if(gameMode == IcsPlayingWhite)
8390 PlayIcsUnfinishedSound();
8393 } else if (gameMode == EditGame ||
8394 gameMode == PlayFromGameFile ||
8395 gameMode == AnalyzeMode ||
8396 gameMode == AnalyzeFile) {
8397 nextGameMode = gameMode;
8399 nextGameMode = EndOfGame;
8404 nextGameMode = gameMode;
8407 if (appData.noChessProgram) {
8408 gameMode = nextGameMode;
8410 endingGame = 0; /* [HGM] crash */
8415 /* Put first chess program into idle state */
8416 if (first.pr != NoProc &&
8417 (gameMode == MachinePlaysWhite ||
8418 gameMode == MachinePlaysBlack ||
8419 gameMode == TwoMachinesPlay ||
8420 gameMode == IcsPlayingWhite ||
8421 gameMode == IcsPlayingBlack ||
8422 gameMode == BeginningOfGame)) {
8423 SendToProgram("force\n", &first);
8424 if (first.usePing) {
8426 sprintf(buf, "ping %d\n", ++first.lastPing);
8427 SendToProgram(buf, &first);
8430 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8431 /* Kill off first chess program */
8432 if (first.isr != NULL)
8433 RemoveInputSource(first.isr);
8436 if (first.pr != NoProc) {
8438 DoSleep( appData.delayBeforeQuit );
8439 SendToProgram("quit\n", &first);
8440 DoSleep( appData.delayAfterQuit );
8441 DestroyChildProcess(first.pr, first.useSigterm);
8446 /* Put second chess program into idle state */
8447 if (second.pr != NoProc &&
8448 gameMode == TwoMachinesPlay) {
8449 SendToProgram("force\n", &second);
8450 if (second.usePing) {
8452 sprintf(buf, "ping %d\n", ++second.lastPing);
8453 SendToProgram(buf, &second);
8456 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8457 /* Kill off second chess program */
8458 if (second.isr != NULL)
8459 RemoveInputSource(second.isr);
8462 if (second.pr != NoProc) {
8463 DoSleep( appData.delayBeforeQuit );
8464 SendToProgram("quit\n", &second);
8465 DoSleep( appData.delayAfterQuit );
8466 DestroyChildProcess(second.pr, second.useSigterm);
8471 if (matchMode && gameMode == TwoMachinesPlay) {
8474 if (first.twoMachinesColor[0] == 'w') {
8481 if (first.twoMachinesColor[0] == 'b') {
8490 if (matchGame < appData.matchGames) {
8492 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8493 tmp = first.twoMachinesColor;
8494 first.twoMachinesColor = second.twoMachinesColor;
8495 second.twoMachinesColor = tmp;
8497 gameMode = nextGameMode;
8499 if(appData.matchPause>10000 || appData.matchPause<10)
8500 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8501 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8502 endingGame = 0; /* [HGM] crash */
8506 gameMode = nextGameMode;
8507 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8508 first.tidy, second.tidy,
8509 first.matchWins, second.matchWins,
8510 appData.matchGames - (first.matchWins + second.matchWins));
8511 DisplayFatalError(buf, 0, 0);
8514 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8515 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8517 gameMode = nextGameMode;
8519 endingGame = 0; /* [HGM] crash */
8522 /* Assumes program was just initialized (initString sent).
8523 Leaves program in force mode. */
8525 FeedMovesToProgram(cps, upto)
8526 ChessProgramState *cps;
8531 if (appData.debugMode)
8532 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8533 startedFromSetupPosition ? "position and " : "",
8534 backwardMostMove, upto, cps->which);
8535 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8536 // [HGM] variantswitch: make engine aware of new variant
8537 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8538 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8539 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8540 SendToProgram(buf, cps);
8541 currentlyInitializedVariant = gameInfo.variant;
8543 SendToProgram("force\n", cps);
8544 if (startedFromSetupPosition) {
8545 SendBoard(cps, backwardMostMove);
8546 if (appData.debugMode) {
8547 fprintf(debugFP, "feedMoves\n");
8550 for (i = backwardMostMove; i < upto; i++) {
8551 SendMoveToProgram(i, cps);
8557 ResurrectChessProgram()
8559 /* The chess program may have exited.
8560 If so, restart it and feed it all the moves made so far. */
8562 if (appData.noChessProgram || first.pr != NoProc) return;
8564 StartChessProgram(&first);
8565 InitChessProgram(&first, FALSE);
8566 FeedMovesToProgram(&first, currentMove);
8568 if (!first.sendTime) {
8569 /* can't tell gnuchess what its clock should read,
8570 so we bow to its notion. */
8572 timeRemaining[0][currentMove] = whiteTimeRemaining;
8573 timeRemaining[1][currentMove] = blackTimeRemaining;
8576 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8577 appData.icsEngineAnalyze) && first.analysisSupport) {
8578 SendToProgram("analyze\n", &first);
8579 first.analyzing = TRUE;
8592 if (appData.debugMode) {
8593 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8594 redraw, init, gameMode);
8596 CleanupTail(); // [HGM] vari: delete any stored variations
8597 pausing = pauseExamInvalid = FALSE;
8598 startedFromSetupPosition = blackPlaysFirst = FALSE;
8600 whiteFlag = blackFlag = FALSE;
8601 userOfferedDraw = FALSE;
8602 hintRequested = bookRequested = FALSE;
8603 first.maybeThinking = FALSE;
8604 second.maybeThinking = FALSE;
8605 first.bookSuspend = FALSE; // [HGM] book
8606 second.bookSuspend = FALSE;
8607 thinkOutput[0] = NULLCHAR;
8608 lastHint[0] = NULLCHAR;
8609 ClearGameInfo(&gameInfo);
8610 gameInfo.variant = StringToVariant(appData.variant);
8611 ics_user_moved = ics_clock_paused = FALSE;
8612 ics_getting_history = H_FALSE;
8614 white_holding[0] = black_holding[0] = NULLCHAR;
8615 ClearProgramStats();
8616 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8620 flipView = appData.flipView;
8621 ClearPremoveHighlights();
8623 alarmSounded = FALSE;
8625 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8626 if(appData.serverMovesName != NULL) {
8627 /* [HGM] prepare to make moves file for broadcasting */
8628 clock_t t = clock();
8629 if(serverMoves != NULL) fclose(serverMoves);
8630 serverMoves = fopen(appData.serverMovesName, "r");
8631 if(serverMoves != NULL) {
8632 fclose(serverMoves);
8633 /* delay 15 sec before overwriting, so all clients can see end */
8634 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8636 serverMoves = fopen(appData.serverMovesName, "w");
8640 gameMode = BeginningOfGame;
8642 if(appData.icsActive) gameInfo.variant = VariantNormal;
8643 currentMove = forwardMostMove = backwardMostMove = 0;
8644 InitPosition(redraw);
8645 for (i = 0; i < MAX_MOVES; i++) {
8646 if (commentList[i] != NULL) {
8647 free(commentList[i]);
8648 commentList[i] = NULL;
8652 timeRemaining[0][0] = whiteTimeRemaining;
8653 timeRemaining[1][0] = blackTimeRemaining;
8654 if (first.pr == NULL) {
8655 StartChessProgram(&first);
8658 InitChessProgram(&first, startedFromSetupPosition);
8661 DisplayMessage("", "");
8662 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8663 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8670 if (!AutoPlayOneMove())
8672 if (matchMode || appData.timeDelay == 0)
8674 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8676 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8685 int fromX, fromY, toX, toY;
8687 if (appData.debugMode) {
8688 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8691 if (gameMode != PlayFromGameFile)
8694 if (currentMove >= forwardMostMove) {
8695 gameMode = EditGame;
8698 /* [AS] Clear current move marker at the end of a game */
8699 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8704 toX = moveList[currentMove][2] - AAA;
8705 toY = moveList[currentMove][3] - ONE;
8707 if (moveList[currentMove][1] == '@') {
8708 if (appData.highlightLastMove) {
8709 SetHighlights(-1, -1, toX, toY);
8712 fromX = moveList[currentMove][0] - AAA;
8713 fromY = moveList[currentMove][1] - ONE;
8715 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8717 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8719 if (appData.highlightLastMove) {
8720 SetHighlights(fromX, fromY, toX, toY);
8723 DisplayMove(currentMove);
8724 SendMoveToProgram(currentMove++, &first);
8725 DisplayBothClocks();
8726 DrawPosition(FALSE, boards[currentMove]);
8727 // [HGM] PV info: always display, routine tests if empty
8728 DisplayComment(currentMove - 1, commentList[currentMove]);
8734 LoadGameOneMove(readAhead)
8735 ChessMove readAhead;
8737 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8738 char promoChar = NULLCHAR;
8743 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8744 gameMode != AnalyzeMode && gameMode != Training) {
8749 yyboardindex = forwardMostMove;
8750 if (readAhead != (ChessMove)0) {
8751 moveType = readAhead;
8753 if (gameFileFP == NULL)
8755 moveType = (ChessMove) yylex();
8761 if (appData.debugMode)
8762 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8765 /* append the comment but don't display it */
8766 AppendComment(currentMove, p, FALSE);
8769 case WhiteCapturesEnPassant:
8770 case BlackCapturesEnPassant:
8771 case WhitePromotionChancellor:
8772 case BlackPromotionChancellor:
8773 case WhitePromotionArchbishop:
8774 case BlackPromotionArchbishop:
8775 case WhitePromotionCentaur:
8776 case BlackPromotionCentaur:
8777 case WhitePromotionQueen:
8778 case BlackPromotionQueen:
8779 case WhitePromotionRook:
8780 case BlackPromotionRook:
8781 case WhitePromotionBishop:
8782 case BlackPromotionBishop:
8783 case WhitePromotionKnight:
8784 case BlackPromotionKnight:
8785 case WhitePromotionKing:
8786 case BlackPromotionKing:
8788 case WhiteKingSideCastle:
8789 case WhiteQueenSideCastle:
8790 case BlackKingSideCastle:
8791 case BlackQueenSideCastle:
8792 case WhiteKingSideCastleWild:
8793 case WhiteQueenSideCastleWild:
8794 case BlackKingSideCastleWild:
8795 case BlackQueenSideCastleWild:
8797 case WhiteHSideCastleFR:
8798 case WhiteASideCastleFR:
8799 case BlackHSideCastleFR:
8800 case BlackASideCastleFR:
8802 if (appData.debugMode)
8803 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8804 fromX = currentMoveString[0] - AAA;
8805 fromY = currentMoveString[1] - ONE;
8806 toX = currentMoveString[2] - AAA;
8807 toY = currentMoveString[3] - ONE;
8808 promoChar = currentMoveString[4];
8813 if (appData.debugMode)
8814 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8815 fromX = moveType == WhiteDrop ?
8816 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8817 (int) CharToPiece(ToLower(currentMoveString[0]));
8819 toX = currentMoveString[2] - AAA;
8820 toY = currentMoveString[3] - ONE;
8826 case GameUnfinished:
8827 if (appData.debugMode)
8828 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8829 p = strchr(yy_text, '{');
8830 if (p == NULL) p = strchr(yy_text, '(');
8833 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8835 q = strchr(p, *p == '{' ? '}' : ')');
8836 if (q != NULL) *q = NULLCHAR;
8839 GameEnds(moveType, p, GE_FILE);
8841 if (cmailMsgLoaded) {
8843 flipView = WhiteOnMove(currentMove);
8844 if (moveType == GameUnfinished) flipView = !flipView;
8845 if (appData.debugMode)
8846 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8850 case (ChessMove) 0: /* end of file */
8851 if (appData.debugMode)
8852 fprintf(debugFP, "Parser hit end of file\n");
8853 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8859 if (WhiteOnMove(currentMove)) {
8860 GameEnds(BlackWins, "Black mates", GE_FILE);
8862 GameEnds(WhiteWins, "White mates", GE_FILE);
8866 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8873 if (lastLoadGameStart == GNUChessGame) {
8874 /* GNUChessGames have numbers, but they aren't move numbers */
8875 if (appData.debugMode)
8876 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8877 yy_text, (int) moveType);
8878 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8880 /* else fall thru */
8885 /* Reached start of next game in file */
8886 if (appData.debugMode)
8887 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8888 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8894 if (WhiteOnMove(currentMove)) {
8895 GameEnds(BlackWins, "Black mates", GE_FILE);
8897 GameEnds(WhiteWins, "White mates", GE_FILE);
8901 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8907 case PositionDiagram: /* should not happen; ignore */
8908 case ElapsedTime: /* ignore */
8909 case NAG: /* ignore */
8910 if (appData.debugMode)
8911 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8912 yy_text, (int) moveType);
8913 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8916 if (appData.testLegality) {
8917 if (appData.debugMode)
8918 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8919 sprintf(move, _("Illegal move: %d.%s%s"),
8920 (forwardMostMove / 2) + 1,
8921 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8922 DisplayError(move, 0);
8925 if (appData.debugMode)
8926 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8927 yy_text, currentMoveString);
8928 fromX = currentMoveString[0] - AAA;
8929 fromY = currentMoveString[1] - ONE;
8930 toX = currentMoveString[2] - AAA;
8931 toY = currentMoveString[3] - ONE;
8932 promoChar = currentMoveString[4];
8937 if (appData.debugMode)
8938 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8939 sprintf(move, _("Ambiguous move: %d.%s%s"),
8940 (forwardMostMove / 2) + 1,
8941 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8942 DisplayError(move, 0);
8947 case ImpossibleMove:
8948 if (appData.debugMode)
8949 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8950 sprintf(move, _("Illegal move: %d.%s%s"),
8951 (forwardMostMove / 2) + 1,
8952 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8953 DisplayError(move, 0);
8959 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8960 DrawPosition(FALSE, boards[currentMove]);
8961 DisplayBothClocks();
8962 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8963 DisplayComment(currentMove - 1, commentList[currentMove]);
8965 (void) StopLoadGameTimer();
8967 cmailOldMove = forwardMostMove;
8970 /* currentMoveString is set as a side-effect of yylex */
8971 strcat(currentMoveString, "\n");
8972 strcpy(moveList[forwardMostMove], currentMoveString);
8974 thinkOutput[0] = NULLCHAR;
8975 MakeMove(fromX, fromY, toX, toY, promoChar);
8976 currentMove = forwardMostMove;
8981 /* Load the nth game from the given file */
8983 LoadGameFromFile(filename, n, title, useList)
8987 /*Boolean*/ int useList;
8992 if (strcmp(filename, "-") == 0) {
8996 f = fopen(filename, "rb");
8998 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8999 DisplayError(buf, errno);
9003 if (fseek(f, 0, 0) == -1) {
9004 /* f is not seekable; probably a pipe */
9007 if (useList && n == 0) {
9008 int error = GameListBuild(f);
9010 DisplayError(_("Cannot build game list"), error);
9011 } else if (!ListEmpty(&gameList) &&
9012 ((ListGame *) gameList.tailPred)->number > 1) {
9013 GameListPopUp(f, title);
9020 return LoadGame(f, n, title, FALSE);
9025 MakeRegisteredMove()
9027 int fromX, fromY, toX, toY;
9029 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9030 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9033 if (appData.debugMode)
9034 fprintf(debugFP, "Restoring %s for game %d\n",
9035 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9037 thinkOutput[0] = NULLCHAR;
9038 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9039 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9040 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9041 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9042 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9043 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9044 MakeMove(fromX, fromY, toX, toY, promoChar);
9045 ShowMove(fromX, fromY, toX, toY);
9047 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9054 if (WhiteOnMove(currentMove)) {
9055 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9057 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9062 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9069 if (WhiteOnMove(currentMove)) {
9070 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9072 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9077 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9088 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9090 CmailLoadGame(f, gameNumber, title, useList)
9098 if (gameNumber > nCmailGames) {
9099 DisplayError(_("No more games in this message"), 0);
9102 if (f == lastLoadGameFP) {
9103 int offset = gameNumber - lastLoadGameNumber;
9105 cmailMsg[0] = NULLCHAR;
9106 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9107 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9108 nCmailMovesRegistered--;
9110 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9111 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9112 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9115 if (! RegisterMove()) return FALSE;
9119 retVal = LoadGame(f, gameNumber, title, useList);
9121 /* Make move registered during previous look at this game, if any */
9122 MakeRegisteredMove();
9124 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9125 commentList[currentMove]
9126 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9127 DisplayComment(currentMove - 1, commentList[currentMove]);
9133 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9138 int gameNumber = lastLoadGameNumber + offset;
9139 if (lastLoadGameFP == NULL) {
9140 DisplayError(_("No game has been loaded yet"), 0);
9143 if (gameNumber <= 0) {
9144 DisplayError(_("Can't back up any further"), 0);
9147 if (cmailMsgLoaded) {
9148 return CmailLoadGame(lastLoadGameFP, gameNumber,
9149 lastLoadGameTitle, lastLoadGameUseList);
9151 return LoadGame(lastLoadGameFP, gameNumber,
9152 lastLoadGameTitle, lastLoadGameUseList);
9158 /* Load the nth game from open file f */
9160 LoadGame(f, gameNumber, title, useList)
9168 int gn = gameNumber;
9169 ListGame *lg = NULL;
9172 GameMode oldGameMode;
9173 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9175 if (appData.debugMode)
9176 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9178 if (gameMode == Training )
9179 SetTrainingModeOff();
9181 oldGameMode = gameMode;
9182 if (gameMode != BeginningOfGame) {
9187 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9188 fclose(lastLoadGameFP);
9192 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9195 fseek(f, lg->offset, 0);
9196 GameListHighlight(gameNumber);
9200 DisplayError(_("Game number out of range"), 0);
9205 if (fseek(f, 0, 0) == -1) {
9206 if (f == lastLoadGameFP ?
9207 gameNumber == lastLoadGameNumber + 1 :
9211 DisplayError(_("Can't seek on game file"), 0);
9217 lastLoadGameNumber = gameNumber;
9218 strcpy(lastLoadGameTitle, title);
9219 lastLoadGameUseList = useList;
9223 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9224 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9225 lg->gameInfo.black);
9227 } else if (*title != NULLCHAR) {
9228 if (gameNumber > 1) {
9229 sprintf(buf, "%s %d", title, gameNumber);
9232 DisplayTitle(title);
9236 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9237 gameMode = PlayFromGameFile;
9241 currentMove = forwardMostMove = backwardMostMove = 0;
9242 CopyBoard(boards[0], initialPosition);
9246 * Skip the first gn-1 games in the file.
9247 * Also skip over anything that precedes an identifiable
9248 * start of game marker, to avoid being confused by
9249 * garbage at the start of the file. Currently
9250 * recognized start of game markers are the move number "1",
9251 * the pattern "gnuchess .* game", the pattern
9252 * "^[#;%] [^ ]* game file", and a PGN tag block.
9253 * A game that starts with one of the latter two patterns
9254 * will also have a move number 1, possibly
9255 * following a position diagram.
9256 * 5-4-02: Let's try being more lenient and allowing a game to
9257 * start with an unnumbered move. Does that break anything?
9259 cm = lastLoadGameStart = (ChessMove) 0;
9261 yyboardindex = forwardMostMove;
9262 cm = (ChessMove) yylex();
9265 if (cmailMsgLoaded) {
9266 nCmailGames = CMAIL_MAX_GAMES - gn;
9269 DisplayError(_("Game not found in file"), 0);
9276 lastLoadGameStart = cm;
9280 switch (lastLoadGameStart) {
9287 gn--; /* count this game */
9288 lastLoadGameStart = cm;
9297 switch (lastLoadGameStart) {
9302 gn--; /* count this game */
9303 lastLoadGameStart = cm;
9306 lastLoadGameStart = cm; /* game counted already */
9314 yyboardindex = forwardMostMove;
9315 cm = (ChessMove) yylex();
9316 } while (cm == PGNTag || cm == Comment);
9323 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9324 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9325 != CMAIL_OLD_RESULT) {
9327 cmailResult[ CMAIL_MAX_GAMES
9328 - gn - 1] = CMAIL_OLD_RESULT;
9334 /* Only a NormalMove can be at the start of a game
9335 * without a position diagram. */
9336 if (lastLoadGameStart == (ChessMove) 0) {
9338 lastLoadGameStart = MoveNumberOne;
9347 if (appData.debugMode)
9348 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9350 if (cm == XBoardGame) {
9351 /* Skip any header junk before position diagram and/or move 1 */
9353 yyboardindex = forwardMostMove;
9354 cm = (ChessMove) yylex();
9356 if (cm == (ChessMove) 0 ||
9357 cm == GNUChessGame || cm == XBoardGame) {
9358 /* Empty game; pretend end-of-file and handle later */
9363 if (cm == MoveNumberOne || cm == PositionDiagram ||
9364 cm == PGNTag || cm == Comment)
9367 } else if (cm == GNUChessGame) {
9368 if (gameInfo.event != NULL) {
9369 free(gameInfo.event);
9371 gameInfo.event = StrSave(yy_text);
9374 startedFromSetupPosition = FALSE;
9375 while (cm == PGNTag) {
9376 if (appData.debugMode)
9377 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9378 err = ParsePGNTag(yy_text, &gameInfo);
9379 if (!err) numPGNTags++;
9381 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9382 if(gameInfo.variant != oldVariant) {
9383 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9385 oldVariant = gameInfo.variant;
9386 if (appData.debugMode)
9387 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9391 if (gameInfo.fen != NULL) {
9392 Board initial_position;
9393 startedFromSetupPosition = TRUE;
9394 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9396 DisplayError(_("Bad FEN position in file"), 0);
9399 CopyBoard(boards[0], initial_position);
9400 if (blackPlaysFirst) {
9401 currentMove = forwardMostMove = backwardMostMove = 1;
9402 CopyBoard(boards[1], initial_position);
9403 strcpy(moveList[0], "");
9404 strcpy(parseList[0], "");
9405 timeRemaining[0][1] = whiteTimeRemaining;
9406 timeRemaining[1][1] = blackTimeRemaining;
9407 if (commentList[0] != NULL) {
9408 commentList[1] = commentList[0];
9409 commentList[0] = NULL;
9412 currentMove = forwardMostMove = backwardMostMove = 0;
9414 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9416 initialRulePlies = FENrulePlies;
9417 for( i=0; i< nrCastlingRights; i++ )
9418 initialRights[i] = initial_position[CASTLING][i];
9420 yyboardindex = forwardMostMove;
9422 gameInfo.fen = NULL;
9425 yyboardindex = forwardMostMove;
9426 cm = (ChessMove) yylex();
9428 /* Handle comments interspersed among the tags */
9429 while (cm == Comment) {
9431 if (appData.debugMode)
9432 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9434 AppendComment(currentMove, p, FALSE);
9435 yyboardindex = forwardMostMove;
9436 cm = (ChessMove) yylex();
9440 /* don't rely on existence of Event tag since if game was
9441 * pasted from clipboard the Event tag may not exist
9443 if (numPGNTags > 0){
9445 if (gameInfo.variant == VariantNormal) {
9446 gameInfo.variant = StringToVariant(gameInfo.event);
9449 if( appData.autoDisplayTags ) {
9450 tags = PGNTags(&gameInfo);
9451 TagsPopUp(tags, CmailMsg());
9456 /* Make something up, but don't display it now */
9461 if (cm == PositionDiagram) {
9464 Board initial_position;
9466 if (appData.debugMode)
9467 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9469 if (!startedFromSetupPosition) {
9471 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9472 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9482 initial_position[i][j++] = CharToPiece(*p);
9485 while (*p == ' ' || *p == '\t' ||
9486 *p == '\n' || *p == '\r') p++;
9488 if (strncmp(p, "black", strlen("black"))==0)
9489 blackPlaysFirst = TRUE;
9491 blackPlaysFirst = FALSE;
9492 startedFromSetupPosition = TRUE;
9494 CopyBoard(boards[0], initial_position);
9495 if (blackPlaysFirst) {
9496 currentMove = forwardMostMove = backwardMostMove = 1;
9497 CopyBoard(boards[1], initial_position);
9498 strcpy(moveList[0], "");
9499 strcpy(parseList[0], "");
9500 timeRemaining[0][1] = whiteTimeRemaining;
9501 timeRemaining[1][1] = blackTimeRemaining;
9502 if (commentList[0] != NULL) {
9503 commentList[1] = commentList[0];
9504 commentList[0] = NULL;
9507 currentMove = forwardMostMove = backwardMostMove = 0;
9510 yyboardindex = forwardMostMove;
9511 cm = (ChessMove) yylex();
9514 if (first.pr == NoProc) {
9515 StartChessProgram(&first);
9517 InitChessProgram(&first, FALSE);
9518 SendToProgram("force\n", &first);
9519 if (startedFromSetupPosition) {
9520 SendBoard(&first, forwardMostMove);
9521 if (appData.debugMode) {
9522 fprintf(debugFP, "Load Game\n");
9524 DisplayBothClocks();
9527 /* [HGM] server: flag to write setup moves in broadcast file as one */
9528 loadFlag = appData.suppressLoadMoves;
9530 while (cm == Comment) {
9532 if (appData.debugMode)
9533 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9535 AppendComment(currentMove, p, FALSE);
9536 yyboardindex = forwardMostMove;
9537 cm = (ChessMove) yylex();
9540 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9541 cm == WhiteWins || cm == BlackWins ||
9542 cm == GameIsDrawn || cm == GameUnfinished) {
9543 DisplayMessage("", _("No moves in game"));
9544 if (cmailMsgLoaded) {
9545 if (appData.debugMode)
9546 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9550 DrawPosition(FALSE, boards[currentMove]);
9551 DisplayBothClocks();
9552 gameMode = EditGame;
9559 // [HGM] PV info: routine tests if comment empty
9560 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9561 DisplayComment(currentMove - 1, commentList[currentMove]);
9563 if (!matchMode && appData.timeDelay != 0)
9564 DrawPosition(FALSE, boards[currentMove]);
9566 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9567 programStats.ok_to_send = 1;
9570 /* if the first token after the PGN tags is a move
9571 * and not move number 1, retrieve it from the parser
9573 if (cm != MoveNumberOne)
9574 LoadGameOneMove(cm);
9576 /* load the remaining moves from the file */
9577 while (LoadGameOneMove((ChessMove)0)) {
9578 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9579 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9582 /* rewind to the start of the game */
9583 currentMove = backwardMostMove;
9585 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9587 if (oldGameMode == AnalyzeFile ||
9588 oldGameMode == AnalyzeMode) {
9592 if (matchMode || appData.timeDelay == 0) {
9594 gameMode = EditGame;
9596 } else if (appData.timeDelay > 0) {
9600 if (appData.debugMode)
9601 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9603 loadFlag = 0; /* [HGM] true game starts */
9607 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9609 ReloadPosition(offset)
9612 int positionNumber = lastLoadPositionNumber + offset;
9613 if (lastLoadPositionFP == NULL) {
9614 DisplayError(_("No position has been loaded yet"), 0);
9617 if (positionNumber <= 0) {
9618 DisplayError(_("Can't back up any further"), 0);
9621 return LoadPosition(lastLoadPositionFP, positionNumber,
9622 lastLoadPositionTitle);
9625 /* Load the nth position from the given file */
9627 LoadPositionFromFile(filename, n, title)
9635 if (strcmp(filename, "-") == 0) {
9636 return LoadPosition(stdin, n, "stdin");
9638 f = fopen(filename, "rb");
9640 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9641 DisplayError(buf, errno);
9644 return LoadPosition(f, n, title);
9649 /* Load the nth position from the given open file, and close it */
9651 LoadPosition(f, positionNumber, title)
9656 char *p, line[MSG_SIZ];
9657 Board initial_position;
9658 int i, j, fenMode, pn;
9660 if (gameMode == Training )
9661 SetTrainingModeOff();
9663 if (gameMode != BeginningOfGame) {
9666 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9667 fclose(lastLoadPositionFP);
9669 if (positionNumber == 0) positionNumber = 1;
9670 lastLoadPositionFP = f;
9671 lastLoadPositionNumber = positionNumber;
9672 strcpy(lastLoadPositionTitle, title);
9673 if (first.pr == NoProc) {
9674 StartChessProgram(&first);
9675 InitChessProgram(&first, FALSE);
9677 pn = positionNumber;
9678 if (positionNumber < 0) {
9679 /* Negative position number means to seek to that byte offset */
9680 if (fseek(f, -positionNumber, 0) == -1) {
9681 DisplayError(_("Can't seek on position file"), 0);
9686 if (fseek(f, 0, 0) == -1) {
9687 if (f == lastLoadPositionFP ?
9688 positionNumber == lastLoadPositionNumber + 1 :
9689 positionNumber == 1) {
9692 DisplayError(_("Can't seek on position file"), 0);
9697 /* See if this file is FEN or old-style xboard */
9698 if (fgets(line, MSG_SIZ, f) == NULL) {
9699 DisplayError(_("Position not found in file"), 0);
9702 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9703 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9706 if (fenMode || line[0] == '#') pn--;
9708 /* skip positions before number pn */
9709 if (fgets(line, MSG_SIZ, f) == NULL) {
9711 DisplayError(_("Position not found in file"), 0);
9714 if (fenMode || line[0] == '#') pn--;
9719 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9720 DisplayError(_("Bad FEN position in file"), 0);
9724 (void) fgets(line, MSG_SIZ, f);
9725 (void) fgets(line, MSG_SIZ, f);
9727 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9728 (void) fgets(line, MSG_SIZ, f);
9729 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9732 initial_position[i][j++] = CharToPiece(*p);
9736 blackPlaysFirst = FALSE;
9738 (void) fgets(line, MSG_SIZ, f);
9739 if (strncmp(line, "black", strlen("black"))==0)
9740 blackPlaysFirst = TRUE;
9743 startedFromSetupPosition = TRUE;
9745 SendToProgram("force\n", &first);
9746 CopyBoard(boards[0], initial_position);
9747 if (blackPlaysFirst) {
9748 currentMove = forwardMostMove = backwardMostMove = 1;
9749 strcpy(moveList[0], "");
9750 strcpy(parseList[0], "");
9751 CopyBoard(boards[1], initial_position);
9752 DisplayMessage("", _("Black to play"));
9754 currentMove = forwardMostMove = backwardMostMove = 0;
9755 DisplayMessage("", _("White to play"));
9757 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9758 SendBoard(&first, forwardMostMove);
9759 if (appData.debugMode) {
9761 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9762 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9763 fprintf(debugFP, "Load Position\n");
9766 if (positionNumber > 1) {
9767 sprintf(line, "%s %d", title, positionNumber);
9770 DisplayTitle(title);
9772 gameMode = EditGame;
9775 timeRemaining[0][1] = whiteTimeRemaining;
9776 timeRemaining[1][1] = blackTimeRemaining;
9777 DrawPosition(FALSE, boards[currentMove]);
9784 CopyPlayerNameIntoFileName(dest, src)
9787 while (*src != NULLCHAR && *src != ',') {
9792 *(*dest)++ = *src++;
9797 char *DefaultFileName(ext)
9800 static char def[MSG_SIZ];
9803 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9805 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9807 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9816 /* Save the current game to the given file */
9818 SaveGameToFile(filename, append)
9825 if (strcmp(filename, "-") == 0) {
9826 return SaveGame(stdout, 0, NULL);
9828 f = fopen(filename, append ? "a" : "w");
9830 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9831 DisplayError(buf, errno);
9834 return SaveGame(f, 0, NULL);
9843 static char buf[MSG_SIZ];
9846 p = strchr(str, ' ');
9847 if (p == NULL) return str;
9848 strncpy(buf, str, p - str);
9849 buf[p - str] = NULLCHAR;
9853 #define PGN_MAX_LINE 75
9855 #define PGN_SIDE_WHITE 0
9856 #define PGN_SIDE_BLACK 1
9859 static int FindFirstMoveOutOfBook( int side )
9863 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9864 int index = backwardMostMove;
9865 int has_book_hit = 0;
9867 if( (index % 2) != side ) {
9871 while( index < forwardMostMove ) {
9872 /* Check to see if engine is in book */
9873 int depth = pvInfoList[index].depth;
9874 int score = pvInfoList[index].score;
9880 else if( score == 0 && depth == 63 ) {
9881 in_book = 1; /* Zappa */
9883 else if( score == 2 && depth == 99 ) {
9884 in_book = 1; /* Abrok */
9887 has_book_hit += in_book;
9903 void GetOutOfBookInfo( char * buf )
9907 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9909 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9910 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9914 if( oob[0] >= 0 || oob[1] >= 0 ) {
9915 for( i=0; i<2; i++ ) {
9919 if( i > 0 && oob[0] >= 0 ) {
9923 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9924 sprintf( buf+strlen(buf), "%s%.2f",
9925 pvInfoList[idx].score >= 0 ? "+" : "",
9926 pvInfoList[idx].score / 100.0 );
9932 /* Save game in PGN style and close the file */
9937 int i, offset, linelen, newblock;
9941 int movelen, numlen, blank;
9942 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9944 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9946 tm = time((time_t *) NULL);
9948 PrintPGNTags(f, &gameInfo);
9950 if (backwardMostMove > 0 || startedFromSetupPosition) {
9951 char *fen = PositionToFEN(backwardMostMove, NULL);
9952 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9953 fprintf(f, "\n{--------------\n");
9954 PrintPosition(f, backwardMostMove);
9955 fprintf(f, "--------------}\n");
9959 /* [AS] Out of book annotation */
9960 if( appData.saveOutOfBookInfo ) {
9963 GetOutOfBookInfo( buf );
9965 if( buf[0] != '\0' ) {
9966 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9973 i = backwardMostMove;
9977 while (i < forwardMostMove) {
9978 /* Print comments preceding this move */
9979 if (commentList[i] != NULL) {
9980 if (linelen > 0) fprintf(f, "\n");
9981 fprintf(f, "%s", commentList[i]);
9986 /* Format move number */
9988 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9991 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9993 numtext[0] = NULLCHAR;
9996 numlen = strlen(numtext);
9999 /* Print move number */
10000 blank = linelen > 0 && numlen > 0;
10001 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10010 fprintf(f, "%s", numtext);
10014 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10015 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10018 blank = linelen > 0 && movelen > 0;
10019 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10028 fprintf(f, "%s", move_buffer);
10029 linelen += movelen;
10031 /* [AS] Add PV info if present */
10032 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10033 /* [HGM] add time */
10034 char buf[MSG_SIZ]; int seconds;
10036 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10038 if( seconds <= 0) buf[0] = 0; else
10039 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10040 seconds = (seconds + 4)/10; // round to full seconds
10041 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10042 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10045 sprintf( move_buffer, "{%s%.2f/%d%s}",
10046 pvInfoList[i].score >= 0 ? "+" : "",
10047 pvInfoList[i].score / 100.0,
10048 pvInfoList[i].depth,
10051 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10053 /* Print score/depth */
10054 blank = linelen > 0 && movelen > 0;
10055 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10064 fprintf(f, "%s", move_buffer);
10065 linelen += movelen;
10071 /* Start a new line */
10072 if (linelen > 0) fprintf(f, "\n");
10074 /* Print comments after last move */
10075 if (commentList[i] != NULL) {
10076 fprintf(f, "%s\n", commentList[i]);
10080 if (gameInfo.resultDetails != NULL &&
10081 gameInfo.resultDetails[0] != NULLCHAR) {
10082 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10083 PGNResult(gameInfo.result));
10085 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10089 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10093 /* Save game in old style and close the file */
10095 SaveGameOldStyle(f)
10101 tm = time((time_t *) NULL);
10103 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10106 if (backwardMostMove > 0 || startedFromSetupPosition) {
10107 fprintf(f, "\n[--------------\n");
10108 PrintPosition(f, backwardMostMove);
10109 fprintf(f, "--------------]\n");
10114 i = backwardMostMove;
10115 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10117 while (i < forwardMostMove) {
10118 if (commentList[i] != NULL) {
10119 fprintf(f, "[%s]\n", commentList[i]);
10122 if ((i % 2) == 1) {
10123 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10126 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10128 if (commentList[i] != NULL) {
10132 if (i >= forwardMostMove) {
10136 fprintf(f, "%s\n", parseList[i]);
10141 if (commentList[i] != NULL) {
10142 fprintf(f, "[%s]\n", commentList[i]);
10145 /* This isn't really the old style, but it's close enough */
10146 if (gameInfo.resultDetails != NULL &&
10147 gameInfo.resultDetails[0] != NULLCHAR) {
10148 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10149 gameInfo.resultDetails);
10151 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10158 /* Save the current game to open file f and close the file */
10160 SaveGame(f, dummy, dummy2)
10165 if (gameMode == EditPosition) EditPositionDone(TRUE);
10166 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10167 if (appData.oldSaveStyle)
10168 return SaveGameOldStyle(f);
10170 return SaveGamePGN(f);
10173 /* Save the current position to the given file */
10175 SavePositionToFile(filename)
10181 if (strcmp(filename, "-") == 0) {
10182 return SavePosition(stdout, 0, NULL);
10184 f = fopen(filename, "a");
10186 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10187 DisplayError(buf, errno);
10190 SavePosition(f, 0, NULL);
10196 /* Save the current position to the given open file and close the file */
10198 SavePosition(f, dummy, dummy2)
10206 if (gameMode == EditPosition) EditPositionDone(TRUE);
10207 if (appData.oldSaveStyle) {
10208 tm = time((time_t *) NULL);
10210 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10212 fprintf(f, "[--------------\n");
10213 PrintPosition(f, currentMove);
10214 fprintf(f, "--------------]\n");
10216 fen = PositionToFEN(currentMove, NULL);
10217 fprintf(f, "%s\n", fen);
10225 ReloadCmailMsgEvent(unregister)
10229 static char *inFilename = NULL;
10230 static char *outFilename;
10232 struct stat inbuf, outbuf;
10235 /* Any registered moves are unregistered if unregister is set, */
10236 /* i.e. invoked by the signal handler */
10238 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10239 cmailMoveRegistered[i] = FALSE;
10240 if (cmailCommentList[i] != NULL) {
10241 free(cmailCommentList[i]);
10242 cmailCommentList[i] = NULL;
10245 nCmailMovesRegistered = 0;
10248 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10249 cmailResult[i] = CMAIL_NOT_RESULT;
10253 if (inFilename == NULL) {
10254 /* Because the filenames are static they only get malloced once */
10255 /* and they never get freed */
10256 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10257 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10259 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10260 sprintf(outFilename, "%s.out", appData.cmailGameName);
10263 status = stat(outFilename, &outbuf);
10265 cmailMailedMove = FALSE;
10267 status = stat(inFilename, &inbuf);
10268 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10271 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10272 counts the games, notes how each one terminated, etc.
10274 It would be nice to remove this kludge and instead gather all
10275 the information while building the game list. (And to keep it
10276 in the game list nodes instead of having a bunch of fixed-size
10277 parallel arrays.) Note this will require getting each game's
10278 termination from the PGN tags, as the game list builder does
10279 not process the game moves. --mann
10281 cmailMsgLoaded = TRUE;
10282 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10284 /* Load first game in the file or popup game menu */
10285 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10287 #endif /* !WIN32 */
10295 char string[MSG_SIZ];
10297 if ( cmailMailedMove
10298 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10299 return TRUE; /* Allow free viewing */
10302 /* Unregister move to ensure that we don't leave RegisterMove */
10303 /* with the move registered when the conditions for registering no */
10305 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10306 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10307 nCmailMovesRegistered --;
10309 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10311 free(cmailCommentList[lastLoadGameNumber - 1]);
10312 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10316 if (cmailOldMove == -1) {
10317 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10321 if (currentMove > cmailOldMove + 1) {
10322 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10326 if (currentMove < cmailOldMove) {
10327 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10331 if (forwardMostMove > currentMove) {
10332 /* Silently truncate extra moves */
10336 if ( (currentMove == cmailOldMove + 1)
10337 || ( (currentMove == cmailOldMove)
10338 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10339 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10340 if (gameInfo.result != GameUnfinished) {
10341 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10344 if (commentList[currentMove] != NULL) {
10345 cmailCommentList[lastLoadGameNumber - 1]
10346 = StrSave(commentList[currentMove]);
10348 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10350 if (appData.debugMode)
10351 fprintf(debugFP, "Saving %s for game %d\n",
10352 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10355 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10357 f = fopen(string, "w");
10358 if (appData.oldSaveStyle) {
10359 SaveGameOldStyle(f); /* also closes the file */
10361 sprintf(string, "%s.pos.out", appData.cmailGameName);
10362 f = fopen(string, "w");
10363 SavePosition(f, 0, NULL); /* also closes the file */
10365 fprintf(f, "{--------------\n");
10366 PrintPosition(f, currentMove);
10367 fprintf(f, "--------------}\n\n");
10369 SaveGame(f, 0, NULL); /* also closes the file*/
10372 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10373 nCmailMovesRegistered ++;
10374 } else if (nCmailGames == 1) {
10375 DisplayError(_("You have not made a move yet"), 0);
10386 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10387 FILE *commandOutput;
10388 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10389 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10395 if (! cmailMsgLoaded) {
10396 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10400 if (nCmailGames == nCmailResults) {
10401 DisplayError(_("No unfinished games"), 0);
10405 #if CMAIL_PROHIBIT_REMAIL
10406 if (cmailMailedMove) {
10407 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);
10408 DisplayError(msg, 0);
10413 if (! (cmailMailedMove || RegisterMove())) return;
10415 if ( cmailMailedMove
10416 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10417 sprintf(string, partCommandString,
10418 appData.debugMode ? " -v" : "", appData.cmailGameName);
10419 commandOutput = popen(string, "r");
10421 if (commandOutput == NULL) {
10422 DisplayError(_("Failed to invoke cmail"), 0);
10424 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10425 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10427 if (nBuffers > 1) {
10428 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10429 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10430 nBytes = MSG_SIZ - 1;
10432 (void) memcpy(msg, buffer, nBytes);
10434 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10436 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10437 cmailMailedMove = TRUE; /* Prevent >1 moves */
10440 for (i = 0; i < nCmailGames; i ++) {
10441 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10446 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10448 sprintf(buffer, "%s/%s.%s.archive",
10450 appData.cmailGameName,
10452 LoadGameFromFile(buffer, 1, buffer, FALSE);
10453 cmailMsgLoaded = FALSE;
10457 DisplayInformation(msg);
10458 pclose(commandOutput);
10461 if ((*cmailMsg) != '\0') {
10462 DisplayInformation(cmailMsg);
10467 #endif /* !WIN32 */
10476 int prependComma = 0;
10478 char string[MSG_SIZ]; /* Space for game-list */
10481 if (!cmailMsgLoaded) return "";
10483 if (cmailMailedMove) {
10484 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10486 /* Create a list of games left */
10487 sprintf(string, "[");
10488 for (i = 0; i < nCmailGames; i ++) {
10489 if (! ( cmailMoveRegistered[i]
10490 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10491 if (prependComma) {
10492 sprintf(number, ",%d", i + 1);
10494 sprintf(number, "%d", i + 1);
10498 strcat(string, number);
10501 strcat(string, "]");
10503 if (nCmailMovesRegistered + nCmailResults == 0) {
10504 switch (nCmailGames) {
10507 _("Still need to make move for game\n"));
10512 _("Still need to make moves for both games\n"));
10517 _("Still need to make moves for all %d games\n"),
10522 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10525 _("Still need to make a move for game %s\n"),
10530 if (nCmailResults == nCmailGames) {
10531 sprintf(cmailMsg, _("No unfinished games\n"));
10533 sprintf(cmailMsg, _("Ready to send mail\n"));
10539 _("Still need to make moves for games %s\n"),
10551 if (gameMode == Training)
10552 SetTrainingModeOff();
10555 cmailMsgLoaded = FALSE;
10556 if (appData.icsActive) {
10557 SendToICS(ics_prefix);
10558 SendToICS("refresh\n");
10568 /* Give up on clean exit */
10572 /* Keep trying for clean exit */
10576 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10578 if (telnetISR != NULL) {
10579 RemoveInputSource(telnetISR);
10581 if (icsPR != NoProc) {
10582 DestroyChildProcess(icsPR, TRUE);
10585 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10586 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10588 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10589 /* make sure this other one finishes before killing it! */
10590 if(endingGame) { int count = 0;
10591 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10592 while(endingGame && count++ < 10) DoSleep(1);
10593 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10596 /* Kill off chess programs */
10597 if (first.pr != NoProc) {
10600 DoSleep( appData.delayBeforeQuit );
10601 SendToProgram("quit\n", &first);
10602 DoSleep( appData.delayAfterQuit );
10603 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10605 if (second.pr != NoProc) {
10606 DoSleep( appData.delayBeforeQuit );
10607 SendToProgram("quit\n", &second);
10608 DoSleep( appData.delayAfterQuit );
10609 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10611 if (first.isr != NULL) {
10612 RemoveInputSource(first.isr);
10614 if (second.isr != NULL) {
10615 RemoveInputSource(second.isr);
10618 ShutDownFrontEnd();
10625 if (appData.debugMode)
10626 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10630 if (gameMode == MachinePlaysWhite ||
10631 gameMode == MachinePlaysBlack) {
10634 DisplayBothClocks();
10636 if (gameMode == PlayFromGameFile) {
10637 if (appData.timeDelay >= 0)
10638 AutoPlayGameLoop();
10639 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10640 Reset(FALSE, TRUE);
10641 SendToICS(ics_prefix);
10642 SendToICS("refresh\n");
10643 } else if (currentMove < forwardMostMove) {
10644 ForwardInner(forwardMostMove);
10646 pauseExamInvalid = FALSE;
10648 switch (gameMode) {
10652 pauseExamForwardMostMove = forwardMostMove;
10653 pauseExamInvalid = FALSE;
10656 case IcsPlayingWhite:
10657 case IcsPlayingBlack:
10661 case PlayFromGameFile:
10662 (void) StopLoadGameTimer();
10666 case BeginningOfGame:
10667 if (appData.icsActive) return;
10668 /* else fall through */
10669 case MachinePlaysWhite:
10670 case MachinePlaysBlack:
10671 case TwoMachinesPlay:
10672 if (forwardMostMove == 0)
10673 return; /* don't pause if no one has moved */
10674 if ((gameMode == MachinePlaysWhite &&
10675 !WhiteOnMove(forwardMostMove)) ||
10676 (gameMode == MachinePlaysBlack &&
10677 WhiteOnMove(forwardMostMove))) {
10690 char title[MSG_SIZ];
10692 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10693 strcpy(title, _("Edit comment"));
10695 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10696 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10697 parseList[currentMove - 1]);
10700 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10707 char *tags = PGNTags(&gameInfo);
10708 EditTagsPopUp(tags);
10715 if (appData.noChessProgram || gameMode == AnalyzeMode)
10718 if (gameMode != AnalyzeFile) {
10719 if (!appData.icsEngineAnalyze) {
10721 if (gameMode != EditGame) return;
10723 ResurrectChessProgram();
10724 SendToProgram("analyze\n", &first);
10725 first.analyzing = TRUE;
10726 /*first.maybeThinking = TRUE;*/
10727 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10728 EngineOutputPopUp();
10730 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10735 StartAnalysisClock();
10736 GetTimeMark(&lastNodeCountTime);
10743 if (appData.noChessProgram || gameMode == AnalyzeFile)
10746 if (gameMode != AnalyzeMode) {
10748 if (gameMode != EditGame) return;
10749 ResurrectChessProgram();
10750 SendToProgram("analyze\n", &first);
10751 first.analyzing = TRUE;
10752 /*first.maybeThinking = TRUE;*/
10753 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10754 EngineOutputPopUp();
10756 gameMode = AnalyzeFile;
10761 StartAnalysisClock();
10762 GetTimeMark(&lastNodeCountTime);
10767 MachineWhiteEvent()
10770 char *bookHit = NULL;
10772 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10776 if (gameMode == PlayFromGameFile ||
10777 gameMode == TwoMachinesPlay ||
10778 gameMode == Training ||
10779 gameMode == AnalyzeMode ||
10780 gameMode == EndOfGame)
10783 if (gameMode == EditPosition)
10784 EditPositionDone(TRUE);
10786 if (!WhiteOnMove(currentMove)) {
10787 DisplayError(_("It is not White's turn"), 0);
10791 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10794 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10795 gameMode == AnalyzeFile)
10798 ResurrectChessProgram(); /* in case it isn't running */
10799 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10800 gameMode = MachinePlaysWhite;
10803 gameMode = MachinePlaysWhite;
10807 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10809 if (first.sendName) {
10810 sprintf(buf, "name %s\n", gameInfo.black);
10811 SendToProgram(buf, &first);
10813 if (first.sendTime) {
10814 if (first.useColors) {
10815 SendToProgram("black\n", &first); /*gnu kludge*/
10817 SendTimeRemaining(&first, TRUE);
10819 if (first.useColors) {
10820 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10822 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10823 SetMachineThinkingEnables();
10824 first.maybeThinking = TRUE;
10828 if (appData.autoFlipView && !flipView) {
10829 flipView = !flipView;
10830 DrawPosition(FALSE, NULL);
10831 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10834 if(bookHit) { // [HGM] book: simulate book reply
10835 static char bookMove[MSG_SIZ]; // a bit generous?
10837 programStats.nodes = programStats.depth = programStats.time =
10838 programStats.score = programStats.got_only_move = 0;
10839 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10841 strcpy(bookMove, "move ");
10842 strcat(bookMove, bookHit);
10843 HandleMachineMove(bookMove, &first);
10848 MachineBlackEvent()
10851 char *bookHit = NULL;
10853 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10857 if (gameMode == PlayFromGameFile ||
10858 gameMode == TwoMachinesPlay ||
10859 gameMode == Training ||
10860 gameMode == AnalyzeMode ||
10861 gameMode == EndOfGame)
10864 if (gameMode == EditPosition)
10865 EditPositionDone(TRUE);
10867 if (WhiteOnMove(currentMove)) {
10868 DisplayError(_("It is not Black's turn"), 0);
10872 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10875 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10876 gameMode == AnalyzeFile)
10879 ResurrectChessProgram(); /* in case it isn't running */
10880 gameMode = MachinePlaysBlack;
10884 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10886 if (first.sendName) {
10887 sprintf(buf, "name %s\n", gameInfo.white);
10888 SendToProgram(buf, &first);
10890 if (first.sendTime) {
10891 if (first.useColors) {
10892 SendToProgram("white\n", &first); /*gnu kludge*/
10894 SendTimeRemaining(&first, FALSE);
10896 if (first.useColors) {
10897 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10899 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10900 SetMachineThinkingEnables();
10901 first.maybeThinking = TRUE;
10904 if (appData.autoFlipView && flipView) {
10905 flipView = !flipView;
10906 DrawPosition(FALSE, NULL);
10907 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10909 if(bookHit) { // [HGM] book: simulate book reply
10910 static char bookMove[MSG_SIZ]; // a bit generous?
10912 programStats.nodes = programStats.depth = programStats.time =
10913 programStats.score = programStats.got_only_move = 0;
10914 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10916 strcpy(bookMove, "move ");
10917 strcat(bookMove, bookHit);
10918 HandleMachineMove(bookMove, &first);
10924 DisplayTwoMachinesTitle()
10927 if (appData.matchGames > 0) {
10928 if (first.twoMachinesColor[0] == 'w') {
10929 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10930 gameInfo.white, gameInfo.black,
10931 first.matchWins, second.matchWins,
10932 matchGame - 1 - (first.matchWins + second.matchWins));
10934 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10935 gameInfo.white, gameInfo.black,
10936 second.matchWins, first.matchWins,
10937 matchGame - 1 - (first.matchWins + second.matchWins));
10940 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10946 TwoMachinesEvent P((void))
10950 ChessProgramState *onmove;
10951 char *bookHit = NULL;
10953 if (appData.noChessProgram) return;
10955 switch (gameMode) {
10956 case TwoMachinesPlay:
10958 case MachinePlaysWhite:
10959 case MachinePlaysBlack:
10960 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10961 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10965 case BeginningOfGame:
10966 case PlayFromGameFile:
10969 if (gameMode != EditGame) return;
10972 EditPositionDone(TRUE);
10983 // forwardMostMove = currentMove;
10984 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10985 ResurrectChessProgram(); /* in case first program isn't running */
10987 if (second.pr == NULL) {
10988 StartChessProgram(&second);
10989 if (second.protocolVersion == 1) {
10990 TwoMachinesEventIfReady();
10992 /* kludge: allow timeout for initial "feature" command */
10994 DisplayMessage("", _("Starting second chess program"));
10995 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10999 DisplayMessage("", "");
11000 InitChessProgram(&second, FALSE);
11001 SendToProgram("force\n", &second);
11002 if (startedFromSetupPosition) {
11003 SendBoard(&second, backwardMostMove);
11004 if (appData.debugMode) {
11005 fprintf(debugFP, "Two Machines\n");
11008 for (i = backwardMostMove; i < forwardMostMove; i++) {
11009 SendMoveToProgram(i, &second);
11012 gameMode = TwoMachinesPlay;
11016 DisplayTwoMachinesTitle();
11018 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11024 SendToProgram(first.computerString, &first);
11025 if (first.sendName) {
11026 sprintf(buf, "name %s\n", second.tidy);
11027 SendToProgram(buf, &first);
11029 SendToProgram(second.computerString, &second);
11030 if (second.sendName) {
11031 sprintf(buf, "name %s\n", first.tidy);
11032 SendToProgram(buf, &second);
11036 if (!first.sendTime || !second.sendTime) {
11037 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11038 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11040 if (onmove->sendTime) {
11041 if (onmove->useColors) {
11042 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11044 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11046 if (onmove->useColors) {
11047 SendToProgram(onmove->twoMachinesColor, onmove);
11049 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11050 // SendToProgram("go\n", onmove);
11051 onmove->maybeThinking = TRUE;
11052 SetMachineThinkingEnables();
11056 if(bookHit) { // [HGM] book: simulate book reply
11057 static char bookMove[MSG_SIZ]; // a bit generous?
11059 programStats.nodes = programStats.depth = programStats.time =
11060 programStats.score = programStats.got_only_move = 0;
11061 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11063 strcpy(bookMove, "move ");
11064 strcat(bookMove, bookHit);
11065 savedMessage = bookMove; // args for deferred call
11066 savedState = onmove;
11067 ScheduleDelayedEvent(DeferredBookMove, 1);
11074 if (gameMode == Training) {
11075 SetTrainingModeOff();
11076 gameMode = PlayFromGameFile;
11077 DisplayMessage("", _("Training mode off"));
11079 gameMode = Training;
11080 animateTraining = appData.animate;
11082 /* make sure we are not already at the end of the game */
11083 if (currentMove < forwardMostMove) {
11084 SetTrainingModeOn();
11085 DisplayMessage("", _("Training mode on"));
11087 gameMode = PlayFromGameFile;
11088 DisplayError(_("Already at end of game"), 0);
11097 if (!appData.icsActive) return;
11098 switch (gameMode) {
11099 case IcsPlayingWhite:
11100 case IcsPlayingBlack:
11103 case BeginningOfGame:
11111 EditPositionDone(TRUE);
11124 gameMode = IcsIdle;
11135 switch (gameMode) {
11137 SetTrainingModeOff();
11139 case MachinePlaysWhite:
11140 case MachinePlaysBlack:
11141 case BeginningOfGame:
11142 SendToProgram("force\n", &first);
11143 SetUserThinkingEnables();
11145 case PlayFromGameFile:
11146 (void) StopLoadGameTimer();
11147 if (gameFileFP != NULL) {
11152 EditPositionDone(TRUE);
11157 SendToProgram("force\n", &first);
11159 case TwoMachinesPlay:
11160 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11161 ResurrectChessProgram();
11162 SetUserThinkingEnables();
11165 ResurrectChessProgram();
11167 case IcsPlayingBlack:
11168 case IcsPlayingWhite:
11169 DisplayError(_("Warning: You are still playing a game"), 0);
11172 DisplayError(_("Warning: You are still observing a game"), 0);
11175 DisplayError(_("Warning: You are still examining a game"), 0);
11186 first.offeredDraw = second.offeredDraw = 0;
11188 if (gameMode == PlayFromGameFile) {
11189 whiteTimeRemaining = timeRemaining[0][currentMove];
11190 blackTimeRemaining = timeRemaining[1][currentMove];
11194 if (gameMode == MachinePlaysWhite ||
11195 gameMode == MachinePlaysBlack ||
11196 gameMode == TwoMachinesPlay ||
11197 gameMode == EndOfGame) {
11198 i = forwardMostMove;
11199 while (i > currentMove) {
11200 SendToProgram("undo\n", &first);
11203 whiteTimeRemaining = timeRemaining[0][currentMove];
11204 blackTimeRemaining = timeRemaining[1][currentMove];
11205 DisplayBothClocks();
11206 if (whiteFlag || blackFlag) {
11207 whiteFlag = blackFlag = 0;
11212 gameMode = EditGame;
11219 EditPositionEvent()
11221 if (gameMode == EditPosition) {
11227 if (gameMode != EditGame) return;
11229 gameMode = EditPosition;
11232 if (currentMove > 0)
11233 CopyBoard(boards[0], boards[currentMove]);
11235 blackPlaysFirst = !WhiteOnMove(currentMove);
11237 currentMove = forwardMostMove = backwardMostMove = 0;
11238 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11245 /* [DM] icsEngineAnalyze - possible call from other functions */
11246 if (appData.icsEngineAnalyze) {
11247 appData.icsEngineAnalyze = FALSE;
11249 DisplayMessage("",_("Close ICS engine analyze..."));
11251 if (first.analysisSupport && first.analyzing) {
11252 SendToProgram("exit\n", &first);
11253 first.analyzing = FALSE;
11255 thinkOutput[0] = NULLCHAR;
11259 EditPositionDone(Boolean fakeRights)
11261 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11263 startedFromSetupPosition = TRUE;
11264 InitChessProgram(&first, FALSE);
11265 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11266 boards[0][EP_STATUS] = EP_NONE;
11267 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11268 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11269 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11270 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11271 } else boards[0][CASTLING][2] = NoRights;
11272 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11273 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11274 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11275 } else boards[0][CASTLING][5] = NoRights;
11277 SendToProgram("force\n", &first);
11278 if (blackPlaysFirst) {
11279 strcpy(moveList[0], "");
11280 strcpy(parseList[0], "");
11281 currentMove = forwardMostMove = backwardMostMove = 1;
11282 CopyBoard(boards[1], boards[0]);
11284 currentMove = forwardMostMove = backwardMostMove = 0;
11286 SendBoard(&first, forwardMostMove);
11287 if (appData.debugMode) {
11288 fprintf(debugFP, "EditPosDone\n");
11291 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11292 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11293 gameMode = EditGame;
11295 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11296 ClearHighlights(); /* [AS] */
11299 /* Pause for `ms' milliseconds */
11300 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11310 } while (SubtractTimeMarks(&m2, &m1) < ms);
11313 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11315 SendMultiLineToICS(buf)
11318 char temp[MSG_SIZ+1], *p;
11325 strncpy(temp, buf, len);
11330 if (*p == '\n' || *p == '\r')
11335 strcat(temp, "\n");
11337 SendToPlayer(temp, strlen(temp));
11341 SetWhiteToPlayEvent()
11343 if (gameMode == EditPosition) {
11344 blackPlaysFirst = FALSE;
11345 DisplayBothClocks(); /* works because currentMove is 0 */
11346 } else if (gameMode == IcsExamining) {
11347 SendToICS(ics_prefix);
11348 SendToICS("tomove white\n");
11353 SetBlackToPlayEvent()
11355 if (gameMode == EditPosition) {
11356 blackPlaysFirst = TRUE;
11357 currentMove = 1; /* kludge */
11358 DisplayBothClocks();
11360 } else if (gameMode == IcsExamining) {
11361 SendToICS(ics_prefix);
11362 SendToICS("tomove black\n");
11367 EditPositionMenuEvent(selection, x, y)
11368 ChessSquare selection;
11372 ChessSquare piece = boards[0][y][x];
11374 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11376 switch (selection) {
11378 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11379 SendToICS(ics_prefix);
11380 SendToICS("bsetup clear\n");
11381 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11382 SendToICS(ics_prefix);
11383 SendToICS("clearboard\n");
11385 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11386 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11387 for (y = 0; y < BOARD_HEIGHT; y++) {
11388 if (gameMode == IcsExamining) {
11389 if (boards[currentMove][y][x] != EmptySquare) {
11390 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11395 boards[0][y][x] = p;
11400 if (gameMode == EditPosition) {
11401 DrawPosition(FALSE, boards[0]);
11406 SetWhiteToPlayEvent();
11410 SetBlackToPlayEvent();
11414 if (gameMode == IcsExamining) {
11415 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11418 boards[0][y][x] = EmptySquare;
11419 DrawPosition(FALSE, boards[0]);
11424 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11425 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11426 selection = (ChessSquare) (PROMOTED piece);
11427 } else if(piece == EmptySquare) selection = WhiteSilver;
11428 else selection = (ChessSquare)((int)piece - 1);
11432 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11433 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11434 selection = (ChessSquare) (DEMOTED piece);
11435 } else if(piece == EmptySquare) selection = BlackSilver;
11436 else selection = (ChessSquare)((int)piece + 1);
11441 if(gameInfo.variant == VariantShatranj ||
11442 gameInfo.variant == VariantXiangqi ||
11443 gameInfo.variant == VariantCourier )
11444 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11449 if(gameInfo.variant == VariantXiangqi)
11450 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11451 if(gameInfo.variant == VariantKnightmate)
11452 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11455 if (gameMode == IcsExamining) {
11456 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11457 PieceToChar(selection), AAA + x, ONE + y);
11460 boards[0][y][x] = selection;
11461 DrawPosition(FALSE, boards[0]);
11469 DropMenuEvent(selection, x, y)
11470 ChessSquare selection;
11473 ChessMove moveType;
11475 switch (gameMode) {
11476 case IcsPlayingWhite:
11477 case MachinePlaysBlack:
11478 if (!WhiteOnMove(currentMove)) {
11479 DisplayMoveError(_("It is Black's turn"));
11482 moveType = WhiteDrop;
11484 case IcsPlayingBlack:
11485 case MachinePlaysWhite:
11486 if (WhiteOnMove(currentMove)) {
11487 DisplayMoveError(_("It is White's turn"));
11490 moveType = BlackDrop;
11493 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11499 if (moveType == BlackDrop && selection < BlackPawn) {
11500 selection = (ChessSquare) ((int) selection
11501 + (int) BlackPawn - (int) WhitePawn);
11503 if (boards[currentMove][y][x] != EmptySquare) {
11504 DisplayMoveError(_("That square is occupied"));
11508 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11514 /* Accept a pending offer of any kind from opponent */
11516 if (appData.icsActive) {
11517 SendToICS(ics_prefix);
11518 SendToICS("accept\n");
11519 } else if (cmailMsgLoaded) {
11520 if (currentMove == cmailOldMove &&
11521 commentList[cmailOldMove] != NULL &&
11522 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11523 "Black offers a draw" : "White offers a draw")) {
11525 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11526 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11528 DisplayError(_("There is no pending offer on this move"), 0);
11529 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11532 /* Not used for offers from chess program */
11539 /* Decline a pending offer of any kind from opponent */
11541 if (appData.icsActive) {
11542 SendToICS(ics_prefix);
11543 SendToICS("decline\n");
11544 } else if (cmailMsgLoaded) {
11545 if (currentMove == cmailOldMove &&
11546 commentList[cmailOldMove] != NULL &&
11547 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11548 "Black offers a draw" : "White offers a draw")) {
11550 AppendComment(cmailOldMove, "Draw declined", TRUE);
11551 DisplayComment(cmailOldMove - 1, "Draw declined");
11554 DisplayError(_("There is no pending offer on this move"), 0);
11557 /* Not used for offers from chess program */
11564 /* Issue ICS rematch command */
11565 if (appData.icsActive) {
11566 SendToICS(ics_prefix);
11567 SendToICS("rematch\n");
11574 /* Call your opponent's flag (claim a win on time) */
11575 if (appData.icsActive) {
11576 SendToICS(ics_prefix);
11577 SendToICS("flag\n");
11579 switch (gameMode) {
11582 case MachinePlaysWhite:
11585 GameEnds(GameIsDrawn, "Both players ran out of time",
11588 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11590 DisplayError(_("Your opponent is not out of time"), 0);
11593 case MachinePlaysBlack:
11596 GameEnds(GameIsDrawn, "Both players ran out of time",
11599 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11601 DisplayError(_("Your opponent is not out of time"), 0);
11611 /* Offer draw or accept pending draw offer from opponent */
11613 if (appData.icsActive) {
11614 /* Note: tournament rules require draw offers to be
11615 made after you make your move but before you punch
11616 your clock. Currently ICS doesn't let you do that;
11617 instead, you immediately punch your clock after making
11618 a move, but you can offer a draw at any time. */
11620 SendToICS(ics_prefix);
11621 SendToICS("draw\n");
11622 } else if (cmailMsgLoaded) {
11623 if (currentMove == cmailOldMove &&
11624 commentList[cmailOldMove] != NULL &&
11625 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11626 "Black offers a draw" : "White offers a draw")) {
11627 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11628 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11629 } else if (currentMove == cmailOldMove + 1) {
11630 char *offer = WhiteOnMove(cmailOldMove) ?
11631 "White offers a draw" : "Black offers a draw";
11632 AppendComment(currentMove, offer, TRUE);
11633 DisplayComment(currentMove - 1, offer);
11634 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11636 DisplayError(_("You must make your move before offering a draw"), 0);
11637 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11639 } else if (first.offeredDraw) {
11640 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11642 if (first.sendDrawOffers) {
11643 SendToProgram("draw\n", &first);
11644 userOfferedDraw = TRUE;
11652 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11654 if (appData.icsActive) {
11655 SendToICS(ics_prefix);
11656 SendToICS("adjourn\n");
11658 /* Currently GNU Chess doesn't offer or accept Adjourns */
11666 /* Offer Abort or accept pending Abort offer from opponent */
11668 if (appData.icsActive) {
11669 SendToICS(ics_prefix);
11670 SendToICS("abort\n");
11672 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11679 /* Resign. You can do this even if it's not your turn. */
11681 if (appData.icsActive) {
11682 SendToICS(ics_prefix);
11683 SendToICS("resign\n");
11685 switch (gameMode) {
11686 case MachinePlaysWhite:
11687 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11689 case MachinePlaysBlack:
11690 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11693 if (cmailMsgLoaded) {
11695 if (WhiteOnMove(cmailOldMove)) {
11696 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11698 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11700 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11711 StopObservingEvent()
11713 /* Stop observing current games */
11714 SendToICS(ics_prefix);
11715 SendToICS("unobserve\n");
11719 StopExaminingEvent()
11721 /* Stop observing current game */
11722 SendToICS(ics_prefix);
11723 SendToICS("unexamine\n");
11727 ForwardInner(target)
11732 if (appData.debugMode)
11733 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11734 target, currentMove, forwardMostMove);
11736 if (gameMode == EditPosition)
11739 if (gameMode == PlayFromGameFile && !pausing)
11742 if (gameMode == IcsExamining && pausing)
11743 limit = pauseExamForwardMostMove;
11745 limit = forwardMostMove;
11747 if (target > limit) target = limit;
11749 if (target > 0 && moveList[target - 1][0]) {
11750 int fromX, fromY, toX, toY;
11751 toX = moveList[target - 1][2] - AAA;
11752 toY = moveList[target - 1][3] - ONE;
11753 if (moveList[target - 1][1] == '@') {
11754 if (appData.highlightLastMove) {
11755 SetHighlights(-1, -1, toX, toY);
11758 fromX = moveList[target - 1][0] - AAA;
11759 fromY = moveList[target - 1][1] - ONE;
11760 if (target == currentMove + 1) {
11761 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11763 if (appData.highlightLastMove) {
11764 SetHighlights(fromX, fromY, toX, toY);
11768 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11769 gameMode == Training || gameMode == PlayFromGameFile ||
11770 gameMode == AnalyzeFile) {
11771 while (currentMove < target) {
11772 SendMoveToProgram(currentMove++, &first);
11775 currentMove = target;
11778 if (gameMode == EditGame || gameMode == EndOfGame) {
11779 whiteTimeRemaining = timeRemaining[0][currentMove];
11780 blackTimeRemaining = timeRemaining[1][currentMove];
11782 DisplayBothClocks();
11783 DisplayMove(currentMove - 1);
11784 DrawPosition(FALSE, boards[currentMove]);
11785 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11786 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11787 DisplayComment(currentMove - 1, commentList[currentMove]);
11795 if (gameMode == IcsExamining && !pausing) {
11796 SendToICS(ics_prefix);
11797 SendToICS("forward\n");
11799 ForwardInner(currentMove + 1);
11806 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11807 /* to optimze, we temporarily turn off analysis mode while we feed
11808 * the remaining moves to the engine. Otherwise we get analysis output
11811 if (first.analysisSupport) {
11812 SendToProgram("exit\nforce\n", &first);
11813 first.analyzing = FALSE;
11817 if (gameMode == IcsExamining && !pausing) {
11818 SendToICS(ics_prefix);
11819 SendToICS("forward 999999\n");
11821 ForwardInner(forwardMostMove);
11824 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11825 /* we have fed all the moves, so reactivate analysis mode */
11826 SendToProgram("analyze\n", &first);
11827 first.analyzing = TRUE;
11828 /*first.maybeThinking = TRUE;*/
11829 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11834 BackwardInner(target)
11837 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11839 if (appData.debugMode)
11840 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11841 target, currentMove, forwardMostMove);
11843 if (gameMode == EditPosition) return;
11844 if (currentMove <= backwardMostMove) {
11846 DrawPosition(full_redraw, boards[currentMove]);
11849 if (gameMode == PlayFromGameFile && !pausing)
11852 if (moveList[target][0]) {
11853 int fromX, fromY, toX, toY;
11854 toX = moveList[target][2] - AAA;
11855 toY = moveList[target][3] - ONE;
11856 if (moveList[target][1] == '@') {
11857 if (appData.highlightLastMove) {
11858 SetHighlights(-1, -1, toX, toY);
11861 fromX = moveList[target][0] - AAA;
11862 fromY = moveList[target][1] - ONE;
11863 if (target == currentMove - 1) {
11864 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11866 if (appData.highlightLastMove) {
11867 SetHighlights(fromX, fromY, toX, toY);
11871 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11872 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11873 while (currentMove > target) {
11874 SendToProgram("undo\n", &first);
11878 currentMove = target;
11881 if (gameMode == EditGame || gameMode == EndOfGame) {
11882 whiteTimeRemaining = timeRemaining[0][currentMove];
11883 blackTimeRemaining = timeRemaining[1][currentMove];
11885 DisplayBothClocks();
11886 DisplayMove(currentMove - 1);
11887 DrawPosition(full_redraw, boards[currentMove]);
11888 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11889 // [HGM] PV info: routine tests if comment empty
11890 DisplayComment(currentMove - 1, commentList[currentMove]);
11896 if (gameMode == IcsExamining && !pausing) {
11897 SendToICS(ics_prefix);
11898 SendToICS("backward\n");
11900 BackwardInner(currentMove - 1);
11907 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11908 /* to optimize, we temporarily turn off analysis mode while we undo
11909 * all the moves. Otherwise we get analysis output after each undo.
11911 if (first.analysisSupport) {
11912 SendToProgram("exit\nforce\n", &first);
11913 first.analyzing = FALSE;
11917 if (gameMode == IcsExamining && !pausing) {
11918 SendToICS(ics_prefix);
11919 SendToICS("backward 999999\n");
11921 BackwardInner(backwardMostMove);
11924 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11925 /* we have fed all the moves, so reactivate analysis mode */
11926 SendToProgram("analyze\n", &first);
11927 first.analyzing = TRUE;
11928 /*first.maybeThinking = TRUE;*/
11929 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11936 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11937 if (to >= forwardMostMove) to = forwardMostMove;
11938 if (to <= backwardMostMove) to = backwardMostMove;
11939 if (to < currentMove) {
11949 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11952 if (gameMode != IcsExamining) {
11953 DisplayError(_("You are not examining a game"), 0);
11957 DisplayError(_("You can't revert while pausing"), 0);
11960 SendToICS(ics_prefix);
11961 SendToICS("revert\n");
11967 switch (gameMode) {
11968 case MachinePlaysWhite:
11969 case MachinePlaysBlack:
11970 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11971 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11974 if (forwardMostMove < 2) return;
11975 currentMove = forwardMostMove = forwardMostMove - 2;
11976 whiteTimeRemaining = timeRemaining[0][currentMove];
11977 blackTimeRemaining = timeRemaining[1][currentMove];
11978 DisplayBothClocks();
11979 DisplayMove(currentMove - 1);
11980 ClearHighlights();/*!! could figure this out*/
11981 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11982 SendToProgram("remove\n", &first);
11983 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11986 case BeginningOfGame:
11990 case IcsPlayingWhite:
11991 case IcsPlayingBlack:
11992 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11993 SendToICS(ics_prefix);
11994 SendToICS("takeback 2\n");
11996 SendToICS(ics_prefix);
11997 SendToICS("takeback 1\n");
12006 ChessProgramState *cps;
12008 switch (gameMode) {
12009 case MachinePlaysWhite:
12010 if (!WhiteOnMove(forwardMostMove)) {
12011 DisplayError(_("It is your turn"), 0);
12016 case MachinePlaysBlack:
12017 if (WhiteOnMove(forwardMostMove)) {
12018 DisplayError(_("It is your turn"), 0);
12023 case TwoMachinesPlay:
12024 if (WhiteOnMove(forwardMostMove) ==
12025 (first.twoMachinesColor[0] == 'w')) {
12031 case BeginningOfGame:
12035 SendToProgram("?\n", cps);
12039 TruncateGameEvent()
12042 if (gameMode != EditGame) return;
12049 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12050 if (forwardMostMove > currentMove) {
12051 if (gameInfo.resultDetails != NULL) {
12052 free(gameInfo.resultDetails);
12053 gameInfo.resultDetails = NULL;
12054 gameInfo.result = GameUnfinished;
12056 forwardMostMove = currentMove;
12057 HistorySet(parseList, backwardMostMove, forwardMostMove,
12065 if (appData.noChessProgram) return;
12066 switch (gameMode) {
12067 case MachinePlaysWhite:
12068 if (WhiteOnMove(forwardMostMove)) {
12069 DisplayError(_("Wait until your turn"), 0);
12073 case BeginningOfGame:
12074 case MachinePlaysBlack:
12075 if (!WhiteOnMove(forwardMostMove)) {
12076 DisplayError(_("Wait until your turn"), 0);
12081 DisplayError(_("No hint available"), 0);
12084 SendToProgram("hint\n", &first);
12085 hintRequested = TRUE;
12091 if (appData.noChessProgram) return;
12092 switch (gameMode) {
12093 case MachinePlaysWhite:
12094 if (WhiteOnMove(forwardMostMove)) {
12095 DisplayError(_("Wait until your turn"), 0);
12099 case BeginningOfGame:
12100 case MachinePlaysBlack:
12101 if (!WhiteOnMove(forwardMostMove)) {
12102 DisplayError(_("Wait until your turn"), 0);
12107 EditPositionDone(TRUE);
12109 case TwoMachinesPlay:
12114 SendToProgram("bk\n", &first);
12115 bookOutput[0] = NULLCHAR;
12116 bookRequested = TRUE;
12122 char *tags = PGNTags(&gameInfo);
12123 TagsPopUp(tags, CmailMsg());
12127 /* end button procedures */
12130 PrintPosition(fp, move)
12136 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12137 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12138 char c = PieceToChar(boards[move][i][j]);
12139 fputc(c == 'x' ? '.' : c, fp);
12140 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12143 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12144 fprintf(fp, "white to play\n");
12146 fprintf(fp, "black to play\n");
12153 if (gameInfo.white != NULL) {
12154 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12160 /* Find last component of program's own name, using some heuristics */
12162 TidyProgramName(prog, host, buf)
12163 char *prog, *host, buf[MSG_SIZ];
12166 int local = (strcmp(host, "localhost") == 0);
12167 while (!local && (p = strchr(prog, ';')) != NULL) {
12169 while (*p == ' ') p++;
12172 if (*prog == '"' || *prog == '\'') {
12173 q = strchr(prog + 1, *prog);
12175 q = strchr(prog, ' ');
12177 if (q == NULL) q = prog + strlen(prog);
12179 while (p >= prog && *p != '/' && *p != '\\') p--;
12181 if(p == prog && *p == '"') p++;
12182 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12183 memcpy(buf, p, q - p);
12184 buf[q - p] = NULLCHAR;
12192 TimeControlTagValue()
12195 if (!appData.clockMode) {
12197 } else if (movesPerSession > 0) {
12198 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12199 } else if (timeIncrement == 0) {
12200 sprintf(buf, "%ld", timeControl/1000);
12202 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12204 return StrSave(buf);
12210 /* This routine is used only for certain modes */
12211 VariantClass v = gameInfo.variant;
12212 ChessMove r = GameUnfinished;
12215 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12216 r = gameInfo.result;
12217 p = gameInfo.resultDetails;
12218 gameInfo.resultDetails = NULL;
12220 ClearGameInfo(&gameInfo);
12221 gameInfo.variant = v;
12223 switch (gameMode) {
12224 case MachinePlaysWhite:
12225 gameInfo.event = StrSave( appData.pgnEventHeader );
12226 gameInfo.site = StrSave(HostName());
12227 gameInfo.date = PGNDate();
12228 gameInfo.round = StrSave("-");
12229 gameInfo.white = StrSave(first.tidy);
12230 gameInfo.black = StrSave(UserName());
12231 gameInfo.timeControl = TimeControlTagValue();
12234 case MachinePlaysBlack:
12235 gameInfo.event = StrSave( appData.pgnEventHeader );
12236 gameInfo.site = StrSave(HostName());
12237 gameInfo.date = PGNDate();
12238 gameInfo.round = StrSave("-");
12239 gameInfo.white = StrSave(UserName());
12240 gameInfo.black = StrSave(first.tidy);
12241 gameInfo.timeControl = TimeControlTagValue();
12244 case TwoMachinesPlay:
12245 gameInfo.event = StrSave( appData.pgnEventHeader );
12246 gameInfo.site = StrSave(HostName());
12247 gameInfo.date = PGNDate();
12248 if (matchGame > 0) {
12250 sprintf(buf, "%d", matchGame);
12251 gameInfo.round = StrSave(buf);
12253 gameInfo.round = StrSave("-");
12255 if (first.twoMachinesColor[0] == 'w') {
12256 gameInfo.white = StrSave(first.tidy);
12257 gameInfo.black = StrSave(second.tidy);
12259 gameInfo.white = StrSave(second.tidy);
12260 gameInfo.black = StrSave(first.tidy);
12262 gameInfo.timeControl = TimeControlTagValue();
12266 gameInfo.event = StrSave("Edited game");
12267 gameInfo.site = StrSave(HostName());
12268 gameInfo.date = PGNDate();
12269 gameInfo.round = StrSave("-");
12270 gameInfo.white = StrSave("-");
12271 gameInfo.black = StrSave("-");
12272 gameInfo.result = r;
12273 gameInfo.resultDetails = p;
12277 gameInfo.event = StrSave("Edited position");
12278 gameInfo.site = StrSave(HostName());
12279 gameInfo.date = PGNDate();
12280 gameInfo.round = StrSave("-");
12281 gameInfo.white = StrSave("-");
12282 gameInfo.black = StrSave("-");
12285 case IcsPlayingWhite:
12286 case IcsPlayingBlack:
12291 case PlayFromGameFile:
12292 gameInfo.event = StrSave("Game from non-PGN file");
12293 gameInfo.site = StrSave(HostName());
12294 gameInfo.date = PGNDate();
12295 gameInfo.round = StrSave("-");
12296 gameInfo.white = StrSave("?");
12297 gameInfo.black = StrSave("?");
12306 ReplaceComment(index, text)
12312 while (*text == '\n') text++;
12313 len = strlen(text);
12314 while (len > 0 && text[len - 1] == '\n') len--;
12316 if (commentList[index] != NULL)
12317 free(commentList[index]);
12320 commentList[index] = NULL;
12323 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12324 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12325 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12326 commentList[index] = (char *) malloc(len + 2);
12327 strncpy(commentList[index], text, len);
12328 commentList[index][len] = '\n';
12329 commentList[index][len + 1] = NULLCHAR;
12331 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12333 commentList[index] = (char *) malloc(len + 6);
12334 strcpy(commentList[index], "{\n");
12335 strncpy(commentList[index]+2, text, len);
12336 commentList[index][len+2] = NULLCHAR;
12337 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12338 strcat(commentList[index], "\n}\n");
12352 if (ch == '\r') continue;
12354 } while (ch != '\0');
12358 AppendComment(index, text, addBraces)
12361 Boolean addBraces; // [HGM] braces: tells if we should add {}
12366 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12367 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12370 while (*text == '\n') text++;
12371 len = strlen(text);
12372 while (len > 0 && text[len - 1] == '\n') len--;
12374 if (len == 0) return;
12376 if (commentList[index] != NULL) {
12377 old = commentList[index];
12378 oldlen = strlen(old);
12379 while(commentList[index][oldlen-1] == '\n')
12380 commentList[index][--oldlen] = NULLCHAR;
12381 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12382 strcpy(commentList[index], old);
12384 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12385 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12386 if(addBraces) addBraces = FALSE; else { text++; len--; }
12387 while (*text == '\n') { text++; len--; }
12388 commentList[index][--oldlen] = NULLCHAR;
12390 if(addBraces) strcat(commentList[index], "\n{\n");
12391 else strcat(commentList[index], "\n");
12392 strcat(commentList[index], text);
12393 if(addBraces) strcat(commentList[index], "\n}\n");
12394 else strcat(commentList[index], "\n");
12396 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12398 strcpy(commentList[index], "{\n");
12399 else commentList[index][0] = NULLCHAR;
12400 strcat(commentList[index], text);
12401 strcat(commentList[index], "\n");
12402 if(addBraces) strcat(commentList[index], "}\n");
12406 static char * FindStr( char * text, char * sub_text )
12408 char * result = strstr( text, sub_text );
12410 if( result != NULL ) {
12411 result += strlen( sub_text );
12417 /* [AS] Try to extract PV info from PGN comment */
12418 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12419 char *GetInfoFromComment( int index, char * text )
12423 if( text != NULL && index > 0 ) {
12426 int time = -1, sec = 0, deci;
12427 char * s_eval = FindStr( text, "[%eval " );
12428 char * s_emt = FindStr( text, "[%emt " );
12430 if( s_eval != NULL || s_emt != NULL ) {
12434 if( s_eval != NULL ) {
12435 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12439 if( delim != ']' ) {
12444 if( s_emt != NULL ) {
12449 /* We expect something like: [+|-]nnn.nn/dd */
12452 if(*text != '{') return text; // [HGM] braces: must be normal comment
12454 sep = strchr( text, '/' );
12455 if( sep == NULL || sep < (text+4) ) {
12459 time = -1; sec = -1; deci = -1;
12460 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12461 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12462 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12463 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12467 if( score_lo < 0 || score_lo >= 100 ) {
12471 if(sec >= 0) time = 600*time + 10*sec; else
12472 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12474 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12476 /* [HGM] PV time: now locate end of PV info */
12477 while( *++sep >= '0' && *sep <= '9'); // strip depth
12479 while( *++sep >= '0' && *sep <= '9'); // strip time
12481 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12483 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12484 while(*sep == ' ') sep++;
12495 pvInfoList[index-1].depth = depth;
12496 pvInfoList[index-1].score = score;
12497 pvInfoList[index-1].time = 10*time; // centi-sec
12498 if(*sep == '}') *sep = 0; else *--sep = '{';
12504 SendToProgram(message, cps)
12506 ChessProgramState *cps;
12508 int count, outCount, error;
12511 if (cps->pr == NULL) return;
12514 if (appData.debugMode) {
12517 fprintf(debugFP, "%ld >%-6s: %s",
12518 SubtractTimeMarks(&now, &programStartTime),
12519 cps->which, message);
12522 count = strlen(message);
12523 outCount = OutputToProcess(cps->pr, message, count, &error);
12524 if (outCount < count && !exiting
12525 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12526 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12527 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12528 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12529 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12530 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12532 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12534 gameInfo.resultDetails = StrSave(buf);
12536 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12541 ReceiveFromProgram(isr, closure, message, count, error)
12542 InputSourceRef isr;
12550 ChessProgramState *cps = (ChessProgramState *)closure;
12552 if (isr != cps->isr) return; /* Killed intentionally */
12556 _("Error: %s chess program (%s) exited unexpectedly"),
12557 cps->which, cps->program);
12558 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12559 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12560 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12561 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12563 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12565 gameInfo.resultDetails = StrSave(buf);
12567 RemoveInputSource(cps->isr);
12568 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12571 _("Error reading from %s chess program (%s)"),
12572 cps->which, cps->program);
12573 RemoveInputSource(cps->isr);
12575 /* [AS] Program is misbehaving badly... kill it */
12576 if( count == -2 ) {
12577 DestroyChildProcess( cps->pr, 9 );
12581 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12586 if ((end_str = strchr(message, '\r')) != NULL)
12587 *end_str = NULLCHAR;
12588 if ((end_str = strchr(message, '\n')) != NULL)
12589 *end_str = NULLCHAR;
12591 if (appData.debugMode) {
12592 TimeMark now; int print = 1;
12593 char *quote = ""; char c; int i;
12595 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12596 char start = message[0];
12597 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12598 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12599 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12600 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12601 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12602 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12603 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12604 sscanf(message, "pong %c", &c)!=1 && start != '#')
12605 { quote = "# "; print = (appData.engineComments == 2); }
12606 message[0] = start; // restore original message
12610 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12611 SubtractTimeMarks(&now, &programStartTime), cps->which,
12617 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12618 if (appData.icsEngineAnalyze) {
12619 if (strstr(message, "whisper") != NULL ||
12620 strstr(message, "kibitz") != NULL ||
12621 strstr(message, "tellics") != NULL) return;
12624 HandleMachineMove(message, cps);
12629 SendTimeControl(cps, mps, tc, inc, sd, st)
12630 ChessProgramState *cps;
12631 int mps, inc, sd, st;
12637 if( timeControl_2 > 0 ) {
12638 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12639 tc = timeControl_2;
12642 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12643 inc /= cps->timeOdds;
12644 st /= cps->timeOdds;
12646 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12649 /* Set exact time per move, normally using st command */
12650 if (cps->stKludge) {
12651 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12653 if (seconds == 0) {
12654 sprintf(buf, "level 1 %d\n", st/60);
12656 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12659 sprintf(buf, "st %d\n", st);
12662 /* Set conventional or incremental time control, using level command */
12663 if (seconds == 0) {
12664 /* Note old gnuchess bug -- minutes:seconds used to not work.
12665 Fixed in later versions, but still avoid :seconds
12666 when seconds is 0. */
12667 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12669 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12670 seconds, inc/1000);
12673 SendToProgram(buf, cps);
12675 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12676 /* Orthogonally, limit search to given depth */
12678 if (cps->sdKludge) {
12679 sprintf(buf, "depth\n%d\n", sd);
12681 sprintf(buf, "sd %d\n", sd);
12683 SendToProgram(buf, cps);
12686 if(cps->nps > 0) { /* [HGM] nps */
12687 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12689 sprintf(buf, "nps %d\n", cps->nps);
12690 SendToProgram(buf, cps);
12695 ChessProgramState *WhitePlayer()
12696 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12698 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12699 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12705 SendTimeRemaining(cps, machineWhite)
12706 ChessProgramState *cps;
12707 int /*boolean*/ machineWhite;
12709 char message[MSG_SIZ];
12712 /* Note: this routine must be called when the clocks are stopped
12713 or when they have *just* been set or switched; otherwise
12714 it will be off by the time since the current tick started.
12716 if (machineWhite) {
12717 time = whiteTimeRemaining / 10;
12718 otime = blackTimeRemaining / 10;
12720 time = blackTimeRemaining / 10;
12721 otime = whiteTimeRemaining / 10;
12723 /* [HGM] translate opponent's time by time-odds factor */
12724 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12725 if (appData.debugMode) {
12726 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12729 if (time <= 0) time = 1;
12730 if (otime <= 0) otime = 1;
12732 sprintf(message, "time %ld\n", time);
12733 SendToProgram(message, cps);
12735 sprintf(message, "otim %ld\n", otime);
12736 SendToProgram(message, cps);
12740 BoolFeature(p, name, loc, cps)
12744 ChessProgramState *cps;
12747 int len = strlen(name);
12749 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12751 sscanf(*p, "%d", &val);
12753 while (**p && **p != ' ') (*p)++;
12754 sprintf(buf, "accepted %s\n", name);
12755 SendToProgram(buf, cps);
12762 IntFeature(p, name, loc, cps)
12766 ChessProgramState *cps;
12769 int len = strlen(name);
12770 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12772 sscanf(*p, "%d", loc);
12773 while (**p && **p != ' ') (*p)++;
12774 sprintf(buf, "accepted %s\n", name);
12775 SendToProgram(buf, cps);
12782 StringFeature(p, name, loc, cps)
12786 ChessProgramState *cps;
12789 int len = strlen(name);
12790 if (strncmp((*p), name, len) == 0
12791 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12793 sscanf(*p, "%[^\"]", loc);
12794 while (**p && **p != '\"') (*p)++;
12795 if (**p == '\"') (*p)++;
12796 sprintf(buf, "accepted %s\n", name);
12797 SendToProgram(buf, cps);
12804 ParseOption(Option *opt, ChessProgramState *cps)
12805 // [HGM] options: process the string that defines an engine option, and determine
12806 // name, type, default value, and allowed value range
12808 char *p, *q, buf[MSG_SIZ];
12809 int n, min = (-1)<<31, max = 1<<31, def;
12811 if(p = strstr(opt->name, " -spin ")) {
12812 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12813 if(max < min) max = min; // enforce consistency
12814 if(def < min) def = min;
12815 if(def > max) def = max;
12820 } else if((p = strstr(opt->name, " -slider "))) {
12821 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12822 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12823 if(max < min) max = min; // enforce consistency
12824 if(def < min) def = min;
12825 if(def > max) def = max;
12829 opt->type = Spin; // Slider;
12830 } else if((p = strstr(opt->name, " -string "))) {
12831 opt->textValue = p+9;
12832 opt->type = TextBox;
12833 } else if((p = strstr(opt->name, " -file "))) {
12834 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12835 opt->textValue = p+7;
12836 opt->type = TextBox; // FileName;
12837 } else if((p = strstr(opt->name, " -path "))) {
12838 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12839 opt->textValue = p+7;
12840 opt->type = TextBox; // PathName;
12841 } else if(p = strstr(opt->name, " -check ")) {
12842 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12843 opt->value = (def != 0);
12844 opt->type = CheckBox;
12845 } else if(p = strstr(opt->name, " -combo ")) {
12846 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12847 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12848 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12849 opt->value = n = 0;
12850 while(q = StrStr(q, " /// ")) {
12851 n++; *q = 0; // count choices, and null-terminate each of them
12853 if(*q == '*') { // remember default, which is marked with * prefix
12857 cps->comboList[cps->comboCnt++] = q;
12859 cps->comboList[cps->comboCnt++] = NULL;
12861 opt->type = ComboBox;
12862 } else if(p = strstr(opt->name, " -button")) {
12863 opt->type = Button;
12864 } else if(p = strstr(opt->name, " -save")) {
12865 opt->type = SaveButton;
12866 } else return FALSE;
12867 *p = 0; // terminate option name
12868 // now look if the command-line options define a setting for this engine option.
12869 if(cps->optionSettings && cps->optionSettings[0])
12870 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12871 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12872 sprintf(buf, "option %s", p);
12873 if(p = strstr(buf, ",")) *p = 0;
12875 SendToProgram(buf, cps);
12881 FeatureDone(cps, val)
12882 ChessProgramState* cps;
12885 DelayedEventCallback cb = GetDelayedEvent();
12886 if ((cb == InitBackEnd3 && cps == &first) ||
12887 (cb == TwoMachinesEventIfReady && cps == &second)) {
12888 CancelDelayedEvent();
12889 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12891 cps->initDone = val;
12894 /* Parse feature command from engine */
12896 ParseFeatures(args, cps)
12898 ChessProgramState *cps;
12906 while (*p == ' ') p++;
12907 if (*p == NULLCHAR) return;
12909 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12910 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12911 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12912 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12913 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12914 if (BoolFeature(&p, "reuse", &val, cps)) {
12915 /* Engine can disable reuse, but can't enable it if user said no */
12916 if (!val) cps->reuse = FALSE;
12919 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12920 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12921 if (gameMode == TwoMachinesPlay) {
12922 DisplayTwoMachinesTitle();
12928 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12929 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12930 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12931 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12932 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12933 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12934 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12935 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12936 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12937 if (IntFeature(&p, "done", &val, cps)) {
12938 FeatureDone(cps, val);
12941 /* Added by Tord: */
12942 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12943 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12944 /* End of additions by Tord */
12946 /* [HGM] added features: */
12947 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12948 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12949 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12950 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12951 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12952 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12953 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12954 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12955 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12956 SendToProgram(buf, cps);
12959 if(cps->nrOptions >= MAX_OPTIONS) {
12961 sprintf(buf, "%s engine has too many options\n", cps->which);
12962 DisplayError(buf, 0);
12966 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12967 /* End of additions by HGM */
12969 /* unknown feature: complain and skip */
12971 while (*q && *q != '=') q++;
12972 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12973 SendToProgram(buf, cps);
12979 while (*p && *p != '\"') p++;
12980 if (*p == '\"') p++;
12982 while (*p && *p != ' ') p++;
12990 PeriodicUpdatesEvent(newState)
12993 if (newState == appData.periodicUpdates)
12996 appData.periodicUpdates=newState;
12998 /* Display type changes, so update it now */
12999 // DisplayAnalysis();
13001 /* Get the ball rolling again... */
13003 AnalysisPeriodicEvent(1);
13004 StartAnalysisClock();
13009 PonderNextMoveEvent(newState)
13012 if (newState == appData.ponderNextMove) return;
13013 if (gameMode == EditPosition) EditPositionDone(TRUE);
13015 SendToProgram("hard\n", &first);
13016 if (gameMode == TwoMachinesPlay) {
13017 SendToProgram("hard\n", &second);
13020 SendToProgram("easy\n", &first);
13021 thinkOutput[0] = NULLCHAR;
13022 if (gameMode == TwoMachinesPlay) {
13023 SendToProgram("easy\n", &second);
13026 appData.ponderNextMove = newState;
13030 NewSettingEvent(option, command, value)
13036 if (gameMode == EditPosition) EditPositionDone(TRUE);
13037 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13038 SendToProgram(buf, &first);
13039 if (gameMode == TwoMachinesPlay) {
13040 SendToProgram(buf, &second);
13045 ShowThinkingEvent()
13046 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13048 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13049 int newState = appData.showThinking
13050 // [HGM] thinking: other features now need thinking output as well
13051 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13053 if (oldState == newState) return;
13054 oldState = newState;
13055 if (gameMode == EditPosition) EditPositionDone(TRUE);
13057 SendToProgram("post\n", &first);
13058 if (gameMode == TwoMachinesPlay) {
13059 SendToProgram("post\n", &second);
13062 SendToProgram("nopost\n", &first);
13063 thinkOutput[0] = NULLCHAR;
13064 if (gameMode == TwoMachinesPlay) {
13065 SendToProgram("nopost\n", &second);
13068 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13072 AskQuestionEvent(title, question, replyPrefix, which)
13073 char *title; char *question; char *replyPrefix; char *which;
13075 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13076 if (pr == NoProc) return;
13077 AskQuestion(title, question, replyPrefix, pr);
13081 DisplayMove(moveNumber)
13084 char message[MSG_SIZ];
13086 char cpThinkOutput[MSG_SIZ];
13088 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13090 if (moveNumber == forwardMostMove - 1 ||
13091 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13093 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13095 if (strchr(cpThinkOutput, '\n')) {
13096 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13099 *cpThinkOutput = NULLCHAR;
13102 /* [AS] Hide thinking from human user */
13103 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13104 *cpThinkOutput = NULLCHAR;
13105 if( thinkOutput[0] != NULLCHAR ) {
13108 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13109 cpThinkOutput[i] = '.';
13111 cpThinkOutput[i] = NULLCHAR;
13112 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13116 if (moveNumber == forwardMostMove - 1 &&
13117 gameInfo.resultDetails != NULL) {
13118 if (gameInfo.resultDetails[0] == NULLCHAR) {
13119 sprintf(res, " %s", PGNResult(gameInfo.result));
13121 sprintf(res, " {%s} %s",
13122 gameInfo.resultDetails, PGNResult(gameInfo.result));
13128 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13129 DisplayMessage(res, cpThinkOutput);
13131 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13132 WhiteOnMove(moveNumber) ? " " : ".. ",
13133 parseList[moveNumber], res);
13134 DisplayMessage(message, cpThinkOutput);
13139 DisplayComment(moveNumber, text)
13143 char title[MSG_SIZ];
13144 char buf[8000]; // comment can be long!
13147 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13148 strcpy(title, "Comment");
13150 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13151 WhiteOnMove(moveNumber) ? " " : ".. ",
13152 parseList[moveNumber]);
13154 // [HGM] PV info: display PV info together with (or as) comment
13155 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13156 if(text == NULL) text = "";
13157 score = pvInfoList[moveNumber].score;
13158 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13159 depth, (pvInfoList[moveNumber].time+50)/100, text);
13162 if (text != NULL && (appData.autoDisplayComment || commentUp))
13163 CommentPopUp(title, text);
13166 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13167 * might be busy thinking or pondering. It can be omitted if your
13168 * gnuchess is configured to stop thinking immediately on any user
13169 * input. However, that gnuchess feature depends on the FIONREAD
13170 * ioctl, which does not work properly on some flavors of Unix.
13174 ChessProgramState *cps;
13177 if (!cps->useSigint) return;
13178 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13179 switch (gameMode) {
13180 case MachinePlaysWhite:
13181 case MachinePlaysBlack:
13182 case TwoMachinesPlay:
13183 case IcsPlayingWhite:
13184 case IcsPlayingBlack:
13187 /* Skip if we know it isn't thinking */
13188 if (!cps->maybeThinking) return;
13189 if (appData.debugMode)
13190 fprintf(debugFP, "Interrupting %s\n", cps->which);
13191 InterruptChildProcess(cps->pr);
13192 cps->maybeThinking = FALSE;
13197 #endif /*ATTENTION*/
13203 if (whiteTimeRemaining <= 0) {
13206 if (appData.icsActive) {
13207 if (appData.autoCallFlag &&
13208 gameMode == IcsPlayingBlack && !blackFlag) {
13209 SendToICS(ics_prefix);
13210 SendToICS("flag\n");
13214 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13216 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13217 if (appData.autoCallFlag) {
13218 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13225 if (blackTimeRemaining <= 0) {
13228 if (appData.icsActive) {
13229 if (appData.autoCallFlag &&
13230 gameMode == IcsPlayingWhite && !whiteFlag) {
13231 SendToICS(ics_prefix);
13232 SendToICS("flag\n");
13236 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13238 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13239 if (appData.autoCallFlag) {
13240 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13253 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13254 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13257 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13259 if ( !WhiteOnMove(forwardMostMove) )
13260 /* White made time control */
13261 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13262 /* [HGM] time odds: correct new time quota for time odds! */
13263 / WhitePlayer()->timeOdds;
13265 /* Black made time control */
13266 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13267 / WhitePlayer()->other->timeOdds;
13271 DisplayBothClocks()
13273 int wom = gameMode == EditPosition ?
13274 !blackPlaysFirst : WhiteOnMove(currentMove);
13275 DisplayWhiteClock(whiteTimeRemaining, wom);
13276 DisplayBlackClock(blackTimeRemaining, !wom);
13280 /* Timekeeping seems to be a portability nightmare. I think everyone
13281 has ftime(), but I'm really not sure, so I'm including some ifdefs
13282 to use other calls if you don't. Clocks will be less accurate if
13283 you have neither ftime nor gettimeofday.
13286 /* VS 2008 requires the #include outside of the function */
13287 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13288 #include <sys/timeb.h>
13291 /* Get the current time as a TimeMark */
13296 #if HAVE_GETTIMEOFDAY
13298 struct timeval timeVal;
13299 struct timezone timeZone;
13301 gettimeofday(&timeVal, &timeZone);
13302 tm->sec = (long) timeVal.tv_sec;
13303 tm->ms = (int) (timeVal.tv_usec / 1000L);
13305 #else /*!HAVE_GETTIMEOFDAY*/
13308 // include <sys/timeb.h> / moved to just above start of function
13309 struct timeb timeB;
13312 tm->sec = (long) timeB.time;
13313 tm->ms = (int) timeB.millitm;
13315 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13316 tm->sec = (long) time(NULL);
13322 /* Return the difference in milliseconds between two
13323 time marks. We assume the difference will fit in a long!
13326 SubtractTimeMarks(tm2, tm1)
13327 TimeMark *tm2, *tm1;
13329 return 1000L*(tm2->sec - tm1->sec) +
13330 (long) (tm2->ms - tm1->ms);
13335 * Code to manage the game clocks.
13337 * In tournament play, black starts the clock and then white makes a move.
13338 * We give the human user a slight advantage if he is playing white---the
13339 * clocks don't run until he makes his first move, so it takes zero time.
13340 * Also, we don't account for network lag, so we could get out of sync
13341 * with GNU Chess's clock -- but then, referees are always right.
13344 static TimeMark tickStartTM;
13345 static long intendedTickLength;
13348 NextTickLength(timeRemaining)
13349 long timeRemaining;
13351 long nominalTickLength, nextTickLength;
13353 if (timeRemaining > 0L && timeRemaining <= 10000L)
13354 nominalTickLength = 100L;
13356 nominalTickLength = 1000L;
13357 nextTickLength = timeRemaining % nominalTickLength;
13358 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13360 return nextTickLength;
13363 /* Adjust clock one minute up or down */
13365 AdjustClock(Boolean which, int dir)
13367 if(which) blackTimeRemaining += 60000*dir;
13368 else whiteTimeRemaining += 60000*dir;
13369 DisplayBothClocks();
13372 /* Stop clocks and reset to a fresh time control */
13376 (void) StopClockTimer();
13377 if (appData.icsActive) {
13378 whiteTimeRemaining = blackTimeRemaining = 0;
13379 } else if (searchTime) {
13380 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13381 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13382 } else { /* [HGM] correct new time quote for time odds */
13383 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13384 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13386 if (whiteFlag || blackFlag) {
13388 whiteFlag = blackFlag = FALSE;
13390 DisplayBothClocks();
13393 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13395 /* Decrement running clock by amount of time that has passed */
13399 long timeRemaining;
13400 long lastTickLength, fudge;
13403 if (!appData.clockMode) return;
13404 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13408 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13410 /* Fudge if we woke up a little too soon */
13411 fudge = intendedTickLength - lastTickLength;
13412 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13414 if (WhiteOnMove(forwardMostMove)) {
13415 if(whiteNPS >= 0) lastTickLength = 0;
13416 timeRemaining = whiteTimeRemaining -= lastTickLength;
13417 DisplayWhiteClock(whiteTimeRemaining - fudge,
13418 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13420 if(blackNPS >= 0) lastTickLength = 0;
13421 timeRemaining = blackTimeRemaining -= lastTickLength;
13422 DisplayBlackClock(blackTimeRemaining - fudge,
13423 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13426 if (CheckFlags()) return;
13429 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13430 StartClockTimer(intendedTickLength);
13432 /* if the time remaining has fallen below the alarm threshold, sound the
13433 * alarm. if the alarm has sounded and (due to a takeback or time control
13434 * with increment) the time remaining has increased to a level above the
13435 * threshold, reset the alarm so it can sound again.
13438 if (appData.icsActive && appData.icsAlarm) {
13440 /* make sure we are dealing with the user's clock */
13441 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13442 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13445 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13446 alarmSounded = FALSE;
13447 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13449 alarmSounded = TRUE;
13455 /* A player has just moved, so stop the previously running
13456 clock and (if in clock mode) start the other one.
13457 We redisplay both clocks in case we're in ICS mode, because
13458 ICS gives us an update to both clocks after every move.
13459 Note that this routine is called *after* forwardMostMove
13460 is updated, so the last fractional tick must be subtracted
13461 from the color that is *not* on move now.
13466 long lastTickLength;
13468 int flagged = FALSE;
13472 if (StopClockTimer() && appData.clockMode) {
13473 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13474 if (WhiteOnMove(forwardMostMove)) {
13475 if(blackNPS >= 0) lastTickLength = 0;
13476 blackTimeRemaining -= lastTickLength;
13477 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13478 // if(pvInfoList[forwardMostMove-1].time == -1)
13479 pvInfoList[forwardMostMove-1].time = // use GUI time
13480 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13482 if(whiteNPS >= 0) lastTickLength = 0;
13483 whiteTimeRemaining -= lastTickLength;
13484 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13485 // if(pvInfoList[forwardMostMove-1].time == -1)
13486 pvInfoList[forwardMostMove-1].time =
13487 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13489 flagged = CheckFlags();
13491 CheckTimeControl();
13493 if (flagged || !appData.clockMode) return;
13495 switch (gameMode) {
13496 case MachinePlaysBlack:
13497 case MachinePlaysWhite:
13498 case BeginningOfGame:
13499 if (pausing) return;
13503 case PlayFromGameFile:
13511 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13512 if(WhiteOnMove(forwardMostMove))
13513 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13514 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13518 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13519 whiteTimeRemaining : blackTimeRemaining);
13520 StartClockTimer(intendedTickLength);
13524 /* Stop both clocks */
13528 long lastTickLength;
13531 if (!StopClockTimer()) return;
13532 if (!appData.clockMode) return;
13536 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13537 if (WhiteOnMove(forwardMostMove)) {
13538 if(whiteNPS >= 0) lastTickLength = 0;
13539 whiteTimeRemaining -= lastTickLength;
13540 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13542 if(blackNPS >= 0) lastTickLength = 0;
13543 blackTimeRemaining -= lastTickLength;
13544 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13549 /* Start clock of player on move. Time may have been reset, so
13550 if clock is already running, stop and restart it. */
13554 (void) StopClockTimer(); /* in case it was running already */
13555 DisplayBothClocks();
13556 if (CheckFlags()) return;
13558 if (!appData.clockMode) return;
13559 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13561 GetTimeMark(&tickStartTM);
13562 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13563 whiteTimeRemaining : blackTimeRemaining);
13565 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13566 whiteNPS = blackNPS = -1;
13567 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13568 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13569 whiteNPS = first.nps;
13570 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13571 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13572 blackNPS = first.nps;
13573 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13574 whiteNPS = second.nps;
13575 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13576 blackNPS = second.nps;
13577 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13579 StartClockTimer(intendedTickLength);
13586 long second, minute, hour, day;
13588 static char buf[32];
13590 if (ms > 0 && ms <= 9900) {
13591 /* convert milliseconds to tenths, rounding up */
13592 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13594 sprintf(buf, " %03.1f ", tenths/10.0);
13598 /* convert milliseconds to seconds, rounding up */
13599 /* use floating point to avoid strangeness of integer division
13600 with negative dividends on many machines */
13601 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13608 day = second / (60 * 60 * 24);
13609 second = second % (60 * 60 * 24);
13610 hour = second / (60 * 60);
13611 second = second % (60 * 60);
13612 minute = second / 60;
13613 second = second % 60;
13616 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13617 sign, day, hour, minute, second);
13619 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13621 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13628 * This is necessary because some C libraries aren't ANSI C compliant yet.
13631 StrStr(string, match)
13632 char *string, *match;
13636 length = strlen(match);
13638 for (i = strlen(string) - length; i >= 0; i--, string++)
13639 if (!strncmp(match, string, length))
13646 StrCaseStr(string, match)
13647 char *string, *match;
13651 length = strlen(match);
13653 for (i = strlen(string) - length; i >= 0; i--, string++) {
13654 for (j = 0; j < length; j++) {
13655 if (ToLower(match[j]) != ToLower(string[j]))
13658 if (j == length) return string;
13672 c1 = ToLower(*s1++);
13673 c2 = ToLower(*s2++);
13674 if (c1 > c2) return 1;
13675 if (c1 < c2) return -1;
13676 if (c1 == NULLCHAR) return 0;
13685 return isupper(c) ? tolower(c) : c;
13693 return islower(c) ? toupper(c) : c;
13695 #endif /* !_amigados */
13703 if ((ret = (char *) malloc(strlen(s) + 1))) {
13710 StrSavePtr(s, savePtr)
13711 char *s, **savePtr;
13716 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13717 strcpy(*savePtr, s);
13729 clock = time((time_t *)NULL);
13730 tm = localtime(&clock);
13731 sprintf(buf, "%04d.%02d.%02d",
13732 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13733 return StrSave(buf);
13738 PositionToFEN(move, overrideCastling)
13740 char *overrideCastling;
13742 int i, j, fromX, fromY, toX, toY;
13749 whiteToPlay = (gameMode == EditPosition) ?
13750 !blackPlaysFirst : (move % 2 == 0);
13753 /* Piece placement data */
13754 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13756 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13757 if (boards[move][i][j] == EmptySquare) {
13759 } else { ChessSquare piece = boards[move][i][j];
13760 if (emptycount > 0) {
13761 if(emptycount<10) /* [HGM] can be >= 10 */
13762 *p++ = '0' + emptycount;
13763 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13766 if(PieceToChar(piece) == '+') {
13767 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13769 piece = (ChessSquare)(DEMOTED piece);
13771 *p++ = PieceToChar(piece);
13773 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13774 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13779 if (emptycount > 0) {
13780 if(emptycount<10) /* [HGM] can be >= 10 */
13781 *p++ = '0' + emptycount;
13782 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13789 /* [HGM] print Crazyhouse or Shogi holdings */
13790 if( gameInfo.holdingsWidth ) {
13791 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13793 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13794 piece = boards[move][i][BOARD_WIDTH-1];
13795 if( piece != EmptySquare )
13796 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13797 *p++ = PieceToChar(piece);
13799 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13800 piece = boards[move][BOARD_HEIGHT-i-1][0];
13801 if( piece != EmptySquare )
13802 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13803 *p++ = PieceToChar(piece);
13806 if( q == p ) *p++ = '-';
13812 *p++ = whiteToPlay ? 'w' : 'b';
13815 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13816 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13818 if(nrCastlingRights) {
13820 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13821 /* [HGM] write directly from rights */
13822 if(boards[move][CASTLING][2] != NoRights &&
13823 boards[move][CASTLING][0] != NoRights )
13824 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13825 if(boards[move][CASTLING][2] != NoRights &&
13826 boards[move][CASTLING][1] != NoRights )
13827 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13828 if(boards[move][CASTLING][5] != NoRights &&
13829 boards[move][CASTLING][3] != NoRights )
13830 *p++ = boards[move][CASTLING][3] + AAA;
13831 if(boards[move][CASTLING][5] != NoRights &&
13832 boards[move][CASTLING][4] != NoRights )
13833 *p++ = boards[move][CASTLING][4] + AAA;
13836 /* [HGM] write true castling rights */
13837 if( nrCastlingRights == 6 ) {
13838 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13839 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13840 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13841 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13842 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13843 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13844 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13845 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13848 if (q == p) *p++ = '-'; /* No castling rights */
13852 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13853 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13854 /* En passant target square */
13855 if (move > backwardMostMove) {
13856 fromX = moveList[move - 1][0] - AAA;
13857 fromY = moveList[move - 1][1] - ONE;
13858 toX = moveList[move - 1][2] - AAA;
13859 toY = moveList[move - 1][3] - ONE;
13860 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13861 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13862 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13864 /* 2-square pawn move just happened */
13866 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13870 } else if(move == backwardMostMove) {
13871 // [HGM] perhaps we should always do it like this, and forget the above?
13872 if((signed char)boards[move][EP_STATUS] >= 0) {
13873 *p++ = boards[move][EP_STATUS] + AAA;
13874 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13885 /* [HGM] find reversible plies */
13886 { int i = 0, j=move;
13888 if (appData.debugMode) { int k;
13889 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13890 for(k=backwardMostMove; k<=forwardMostMove; k++)
13891 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13895 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13896 if( j == backwardMostMove ) i += initialRulePlies;
13897 sprintf(p, "%d ", i);
13898 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13900 /* Fullmove number */
13901 sprintf(p, "%d", (move / 2) + 1);
13903 return StrSave(buf);
13907 ParseFEN(board, blackPlaysFirst, fen)
13909 int *blackPlaysFirst;
13919 /* [HGM] by default clear Crazyhouse holdings, if present */
13920 if(gameInfo.holdingsWidth) {
13921 for(i=0; i<BOARD_HEIGHT; i++) {
13922 board[i][0] = EmptySquare; /* black holdings */
13923 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13924 board[i][1] = (ChessSquare) 0; /* black counts */
13925 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13929 /* Piece placement data */
13930 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13933 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13934 if (*p == '/') p++;
13935 emptycount = gameInfo.boardWidth - j;
13936 while (emptycount--)
13937 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13939 #if(BOARD_FILES >= 10)
13940 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13941 p++; emptycount=10;
13942 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13943 while (emptycount--)
13944 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13946 } else if (isdigit(*p)) {
13947 emptycount = *p++ - '0';
13948 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13949 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13950 while (emptycount--)
13951 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13952 } else if (*p == '+' || isalpha(*p)) {
13953 if (j >= gameInfo.boardWidth) return FALSE;
13955 piece = CharToPiece(*++p);
13956 if(piece == EmptySquare) return FALSE; /* unknown piece */
13957 piece = (ChessSquare) (PROMOTED piece ); p++;
13958 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13959 } else piece = CharToPiece(*p++);
13961 if(piece==EmptySquare) return FALSE; /* unknown piece */
13962 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13963 piece = (ChessSquare) (PROMOTED piece);
13964 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13967 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13973 while (*p == '/' || *p == ' ') p++;
13975 /* [HGM] look for Crazyhouse holdings here */
13976 while(*p==' ') p++;
13977 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13979 if(*p == '-' ) *p++; /* empty holdings */ else {
13980 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13981 /* if we would allow FEN reading to set board size, we would */
13982 /* have to add holdings and shift the board read so far here */
13983 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13985 if((int) piece >= (int) BlackPawn ) {
13986 i = (int)piece - (int)BlackPawn;
13987 i = PieceToNumber((ChessSquare)i);
13988 if( i >= gameInfo.holdingsSize ) return FALSE;
13989 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13990 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13992 i = (int)piece - (int)WhitePawn;
13993 i = PieceToNumber((ChessSquare)i);
13994 if( i >= gameInfo.holdingsSize ) return FALSE;
13995 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13996 board[i][BOARD_WIDTH-2]++; /* black holdings */
14000 if(*p == ']') *p++;
14003 while(*p == ' ') p++;
14008 *blackPlaysFirst = FALSE;
14011 *blackPlaysFirst = TRUE;
14017 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14018 /* return the extra info in global variiables */
14020 /* set defaults in case FEN is incomplete */
14021 board[EP_STATUS] = EP_UNKNOWN;
14022 for(i=0; i<nrCastlingRights; i++ ) {
14023 board[CASTLING][i] =
14024 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14025 } /* assume possible unless obviously impossible */
14026 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14027 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14028 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14029 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14030 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14031 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14034 while(*p==' ') p++;
14035 if(nrCastlingRights) {
14036 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14037 /* castling indicator present, so default becomes no castlings */
14038 for(i=0; i<nrCastlingRights; i++ ) {
14039 board[CASTLING][i] = NoRights;
14042 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14043 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14044 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14045 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14046 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14048 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14049 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14050 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14054 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14055 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14056 board[CASTLING][2] = whiteKingFile;
14059 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14060 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14061 board[CASTLING][2] = whiteKingFile;
14064 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14065 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14066 board[CASTLING][5] = blackKingFile;
14069 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14070 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14071 board[CASTLING][5] = blackKingFile;
14074 default: /* FRC castlings */
14075 if(c >= 'a') { /* black rights */
14076 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14077 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14078 if(i == BOARD_RGHT) break;
14079 board[CASTLING][5] = i;
14081 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14082 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14084 board[CASTLING][3] = c;
14086 board[CASTLING][4] = c;
14087 } else { /* white rights */
14088 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14089 if(board[0][i] == WhiteKing) break;
14090 if(i == BOARD_RGHT) break;
14091 board[CASTLING][2] = i;
14092 c -= AAA - 'a' + 'A';
14093 if(board[0][c] >= WhiteKing) break;
14095 board[CASTLING][0] = c;
14097 board[CASTLING][1] = c;
14101 if (appData.debugMode) {
14102 fprintf(debugFP, "FEN castling rights:");
14103 for(i=0; i<nrCastlingRights; i++)
14104 fprintf(debugFP, " %d", board[CASTLING][i]);
14105 fprintf(debugFP, "\n");
14108 while(*p==' ') p++;
14111 /* read e.p. field in games that know e.p. capture */
14112 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14113 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14115 p++; board[EP_STATUS] = EP_NONE;
14117 char c = *p++ - AAA;
14119 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14120 if(*p >= '0' && *p <='9') *p++;
14121 board[EP_STATUS] = c;
14126 if(sscanf(p, "%d", &i) == 1) {
14127 FENrulePlies = i; /* 50-move ply counter */
14128 /* (The move number is still ignored) */
14135 EditPositionPasteFEN(char *fen)
14138 Board initial_position;
14140 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14141 DisplayError(_("Bad FEN position in clipboard"), 0);
14144 int savedBlackPlaysFirst = blackPlaysFirst;
14145 EditPositionEvent();
14146 blackPlaysFirst = savedBlackPlaysFirst;
14147 CopyBoard(boards[0], initial_position);
14148 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14149 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14150 DisplayBothClocks();
14151 DrawPosition(FALSE, boards[currentMove]);
14156 static char cseq[12] = "\\ ";
14158 Boolean set_cont_sequence(char *new_seq)
14163 // handle bad attempts to set the sequence
14165 return 0; // acceptable error - no debug
14167 len = strlen(new_seq);
14168 ret = (len > 0) && (len < sizeof(cseq));
14170 strcpy(cseq, new_seq);
14171 else if (appData.debugMode)
14172 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14177 reformat a source message so words don't cross the width boundary. internal
14178 newlines are not removed. returns the wrapped size (no null character unless
14179 included in source message). If dest is NULL, only calculate the size required
14180 for the dest buffer. lp argument indicats line position upon entry, and it's
14181 passed back upon exit.
14183 int wrap(char *dest, char *src, int count, int width, int *lp)
14185 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14187 cseq_len = strlen(cseq);
14188 old_line = line = *lp;
14189 ansi = len = clen = 0;
14191 for (i=0; i < count; i++)
14193 if (src[i] == '\033')
14196 // if we hit the width, back up
14197 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14199 // store i & len in case the word is too long
14200 old_i = i, old_len = len;
14202 // find the end of the last word
14203 while (i && src[i] != ' ' && src[i] != '\n')
14209 // word too long? restore i & len before splitting it
14210 if ((old_i-i+clen) >= width)
14217 if (i && src[i-1] == ' ')
14220 if (src[i] != ' ' && src[i] != '\n')
14227 // now append the newline and continuation sequence
14232 strncpy(dest+len, cseq, cseq_len);
14240 dest[len] = src[i];
14244 if (src[i] == '\n')
14249 if (dest && appData.debugMode)
14251 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14252 count, width, line, len, *lp);
14253 show_bytes(debugFP, src, count);
14254 fprintf(debugFP, "\ndest: ");
14255 show_bytes(debugFP, dest, len);
14256 fprintf(debugFP, "\n");
14258 *lp = dest ? line : old_line;
14263 // [HGM] vari: routines for shelving variations
14266 PushTail(int firstMove, int lastMove)
14268 int i, j, nrMoves = lastMove - firstMove;
14270 if(appData.icsActive) { // only in local mode
14271 forwardMostMove = currentMove; // mimic old ICS behavior
14274 if(storedGames >= MAX_VARIATIONS-1) return;
14276 // push current tail of game on stack
14277 savedResult[storedGames] = gameInfo.result;
14278 savedDetails[storedGames] = gameInfo.resultDetails;
14279 gameInfo.resultDetails = NULL;
14280 savedFirst[storedGames] = firstMove;
14281 savedLast [storedGames] = lastMove;
14282 savedFramePtr[storedGames] = framePtr;
14283 framePtr -= nrMoves; // reserve space for the boards
14284 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14285 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14286 for(j=0; j<MOVE_LEN; j++)
14287 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14288 for(j=0; j<2*MOVE_LEN; j++)
14289 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14290 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14291 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14292 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14293 pvInfoList[firstMove+i-1].depth = 0;
14294 commentList[framePtr+i] = commentList[firstMove+i];
14295 commentList[firstMove+i] = NULL;
14299 forwardMostMove = currentMove; // truncte game so we can start variation
14300 if(storedGames == 1) GreyRevert(FALSE);
14304 PopTail(Boolean annotate)
14307 char buf[8000], moveBuf[20];
14309 if(appData.icsActive) return FALSE; // only in local mode
14310 if(!storedGames) return FALSE; // sanity
14313 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14314 nrMoves = savedLast[storedGames] - currentMove;
14317 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14318 else strcpy(buf, "(");
14319 for(i=currentMove; i<forwardMostMove; i++) {
14321 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14322 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14323 strcat(buf, moveBuf);
14324 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14328 for(i=1; i<nrMoves; i++) { // copy last variation back
14329 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14330 for(j=0; j<MOVE_LEN; j++)
14331 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14332 for(j=0; j<2*MOVE_LEN; j++)
14333 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14334 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14335 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14336 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14337 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14338 commentList[currentMove+i] = commentList[framePtr+i];
14339 commentList[framePtr+i] = NULL;
14341 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14342 framePtr = savedFramePtr[storedGames];
14343 gameInfo.result = savedResult[storedGames];
14344 if(gameInfo.resultDetails != NULL) {
14345 free(gameInfo.resultDetails);
14347 gameInfo.resultDetails = savedDetails[storedGames];
14348 forwardMostMove = currentMove + nrMoves;
14349 if(storedGames == 0) GreyRevert(TRUE);
14355 { // remove all shelved variations
14357 for(i=0; i<storedGames; i++) {
14358 if(savedDetails[i])
14359 free(savedDetails[i]);
14360 savedDetails[i] = NULL;
14362 for(i=framePtr; i<MAX_MOVES; i++) {
14363 if(commentList[i]) free(commentList[i]);
14364 commentList[i] = NULL;
14366 framePtr = MAX_MOVES-1;