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;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
254 /* States for ics_getting_history */
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
262 /* whosays values for GameEnds */
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
274 /* Different types of move when calling RegisterMove */
276 #define CMAIL_RESIGN 1
278 #define CMAIL_ACCEPT 3
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
285 /* Telnet protocol constants */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
298 assert( dst != NULL );
299 assert( src != NULL );
302 strncpy( dst, src, count );
303 dst[ count-1 ] = '\0';
307 /* Some compiler can't cast u64 to double
308 * This function do the job for us:
310 * We use the highest bit for cast, this only
311 * works if the highest bit is not
312 * in use (This should not happen)
314 * We used this for all compiler
317 u64ToDouble(u64 value)
320 u64 tmp = value & u64Const(0x7fffffffffffffff);
321 r = (double)(s64)tmp;
322 if (value & u64Const(0x8000000000000000))
323 r += 9.2233720368547758080e18; /* 2^63 */
327 /* Fake up flags for now, as we aren't keeping track of castling
328 availability yet. [HGM] Change of logic: the flag now only
329 indicates the type of castlings allowed by the rule of the game.
330 The actual rights themselves are maintained in the array
331 castlingRights, as part of the game history, and are not probed
337 int flags = F_ALL_CASTLE_OK;
338 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339 switch (gameInfo.variant) {
341 flags &= ~F_ALL_CASTLE_OK;
342 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343 flags |= F_IGNORE_CHECK;
345 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
348 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
350 case VariantKriegspiel:
351 flags |= F_KRIEGSPIEL_CAPTURE;
353 case VariantCapaRandom:
354 case VariantFischeRandom:
355 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356 case VariantNoCastle:
357 case VariantShatranj:
359 flags &= ~F_ALL_CASTLE_OK;
367 FILE *gameFileFP, *debugFP;
370 [AS] Note: sometimes, the sscanf() function is used to parse the input
371 into a fixed-size buffer. Because of this, we must be prepared to
372 receive strings as long as the size of the input buffer, which is currently
373 set to 4K for Windows and 8K for the rest.
374 So, we must either allocate sufficiently large buffers here, or
375 reduce the size of the input buffer in the input reading part.
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
382 ChessProgramState first, second;
384 /* premove variables */
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
434 /* animateTraining preserves the state of appData.animate
435 * when Training mode is activated. This allows the
436 * response to be animated when appData.animate == TRUE and
437 * appData.animateDragging == TRUE.
439 Boolean animateTraining;
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
448 signed char initialRights[BOARD_FILES];
449 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 // [HGM] vari: next 12 to save and restore variations
457 #define MAX_VARIATIONS 10
458 int framePtr = MAX_MOVES-1; // points to free stack entry
460 int savedFirst[MAX_VARIATIONS];
461 int savedLast[MAX_VARIATIONS];
462 int savedFramePtr[MAX_VARIATIONS];
463 char *savedDetails[MAX_VARIATIONS];
464 ChessMove savedResult[MAX_VARIATIONS];
466 void PushTail P((int firstMove, int lastMove));
467 Boolean PopTail P((Boolean annotate));
468 void CleanupTail P((void));
470 ChessSquare FIDEArray[2][BOARD_FILES] = {
471 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474 BlackKing, BlackBishop, BlackKnight, BlackRook }
477 ChessSquare twoKingsArray[2][BOARD_FILES] = {
478 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481 BlackKing, BlackKing, BlackKnight, BlackRook }
484 ChessSquare KnightmateArray[2][BOARD_FILES] = {
485 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487 { BlackRook, BlackMan, BlackBishop, BlackQueen,
488 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
491 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
492 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495 BlackKing, BlackBishop, BlackKnight, BlackRook }
498 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 #if (BOARD_FILES>=10)
507 ChessSquare ShogiArray[2][BOARD_FILES] = {
508 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
514 ChessSquare XiangqiArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
521 ChessSquare CapablancaArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
528 ChessSquare GreatArray[2][BOARD_FILES] = {
529 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
535 ChessSquare JanusArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 ChessSquare GothicArray[2][BOARD_FILES] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
550 #define GothicArray CapablancaArray
554 ChessSquare FalconArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
561 #define FalconArray CapablancaArray
564 #else // !(BOARD_FILES>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_FILES>=10)
571 #if (BOARD_FILES>=12)
572 ChessSquare CourierArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 #else // !(BOARD_FILES>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_FILES>=12)
583 Board initialPosition;
586 /* Convert str to a rating. Checks for special cases of "----",
588 "++++", etc. Also strips ()'s */
590 string_to_rating(str)
593 while(*str && !isdigit(*str)) ++str;
595 return 0; /* One of the special "no rating" cases */
603 /* Init programStats */
604 programStats.movelist[0] = 0;
605 programStats.depth = 0;
606 programStats.nr_moves = 0;
607 programStats.moves_left = 0;
608 programStats.nodes = 0;
609 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
610 programStats.score = 0;
611 programStats.got_only_move = 0;
612 programStats.got_fail = 0;
613 programStats.line_is_book = 0;
619 int matched, min, sec;
621 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623 GetTimeMark(&programStartTime);
624 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
627 programStats.ok_to_send = 1;
628 programStats.seen_stat = 0;
631 * Initialize game list
637 * Internet chess server status
639 if (appData.icsActive) {
640 appData.matchMode = FALSE;
641 appData.matchGames = 0;
643 appData.noChessProgram = !appData.zippyPlay;
645 appData.zippyPlay = FALSE;
646 appData.zippyTalk = FALSE;
647 appData.noChessProgram = TRUE;
649 if (*appData.icsHelper != NULLCHAR) {
650 appData.useTelnet = TRUE;
651 appData.telnetProgram = appData.icsHelper;
654 appData.zippyTalk = appData.zippyPlay = FALSE;
657 /* [AS] Initialize pv info list [HGM] and game state */
661 for( i=0; i<=framePtr; i++ ) {
662 pvInfoList[i].depth = -1;
663 boards[i][EP_STATUS] = EP_NONE;
664 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
669 * Parse timeControl resource
671 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672 appData.movesPerSession)) {
674 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675 DisplayFatalError(buf, 0, 2);
679 * Parse searchTime resource
681 if (*appData.searchTime != NULLCHAR) {
682 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684 searchTime = min * 60;
685 } else if (matched == 2) {
686 searchTime = min * 60 + sec;
689 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690 DisplayFatalError(buf, 0, 2);
694 /* [AS] Adjudication threshold */
695 adjudicateLossThreshold = appData.adjudicateLossThreshold;
697 first.which = "first";
698 second.which = "second";
699 first.maybeThinking = second.maybeThinking = FALSE;
700 first.pr = second.pr = NoProc;
701 first.isr = second.isr = NULL;
702 first.sendTime = second.sendTime = 2;
703 first.sendDrawOffers = 1;
704 if (appData.firstPlaysBlack) {
705 first.twoMachinesColor = "black\n";
706 second.twoMachinesColor = "white\n";
708 first.twoMachinesColor = "white\n";
709 second.twoMachinesColor = "black\n";
711 first.program = appData.firstChessProgram;
712 second.program = appData.secondChessProgram;
713 first.host = appData.firstHost;
714 second.host = appData.secondHost;
715 first.dir = appData.firstDirectory;
716 second.dir = appData.secondDirectory;
717 first.other = &second;
718 second.other = &first;
719 first.initString = appData.initString;
720 second.initString = appData.secondInitString;
721 first.computerString = appData.firstComputerString;
722 second.computerString = appData.secondComputerString;
723 first.useSigint = second.useSigint = TRUE;
724 first.useSigterm = second.useSigterm = TRUE;
725 first.reuse = appData.reuseFirst;
726 second.reuse = appData.reuseSecond;
727 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
728 second.nps = appData.secondNPS;
729 first.useSetboard = second.useSetboard = FALSE;
730 first.useSAN = second.useSAN = FALSE;
731 first.usePing = second.usePing = FALSE;
732 first.lastPing = second.lastPing = 0;
733 first.lastPong = second.lastPong = 0;
734 first.usePlayother = second.usePlayother = FALSE;
735 first.useColors = second.useColors = TRUE;
736 first.useUsermove = second.useUsermove = FALSE;
737 first.sendICS = second.sendICS = FALSE;
738 first.sendName = second.sendName = appData.icsActive;
739 first.sdKludge = second.sdKludge = FALSE;
740 first.stKludge = second.stKludge = FALSE;
741 TidyProgramName(first.program, first.host, first.tidy);
742 TidyProgramName(second.program, second.host, second.tidy);
743 first.matchWins = second.matchWins = 0;
744 strcpy(first.variants, appData.variant);
745 strcpy(second.variants, appData.variant);
746 first.analysisSupport = second.analysisSupport = 2; /* detect */
747 first.analyzing = second.analyzing = FALSE;
748 first.initDone = second.initDone = FALSE;
750 /* New features added by Tord: */
751 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753 /* End of new features added by Tord. */
754 first.fenOverride = appData.fenOverride1;
755 second.fenOverride = appData.fenOverride2;
757 /* [HGM] time odds: set factor for each machine */
758 first.timeOdds = appData.firstTimeOdds;
759 second.timeOdds = appData.secondTimeOdds;
761 if(appData.timeOddsMode) {
762 norm = first.timeOdds;
763 if(norm > second.timeOdds) norm = second.timeOdds;
765 first.timeOdds /= norm;
766 second.timeOdds /= norm;
769 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770 first.accumulateTC = appData.firstAccumulateTC;
771 second.accumulateTC = appData.secondAccumulateTC;
772 first.maxNrOfSessions = second.maxNrOfSessions = 1;
775 first.debug = second.debug = FALSE;
776 first.supportsNPS = second.supportsNPS = UNKNOWN;
779 first.optionSettings = appData.firstOptions;
780 second.optionSettings = appData.secondOptions;
782 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784 first.isUCI = appData.firstIsUCI; /* [AS] */
785 second.isUCI = appData.secondIsUCI; /* [AS] */
786 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789 if (appData.firstProtocolVersion > PROTOVER ||
790 appData.firstProtocolVersion < 1) {
792 sprintf(buf, _("protocol version %d not supported"),
793 appData.firstProtocolVersion);
794 DisplayFatalError(buf, 0, 2);
796 first.protocolVersion = appData.firstProtocolVersion;
799 if (appData.secondProtocolVersion > PROTOVER ||
800 appData.secondProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.secondProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 second.protocolVersion = appData.secondProtocolVersion;
809 if (appData.icsActive) {
810 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
811 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
813 appData.clockMode = FALSE;
814 first.sendTime = second.sendTime = 0;
818 /* Override some settings from environment variables, for backward
819 compatibility. Unfortunately it's not feasible to have the env
820 vars just set defaults, at least in xboard. Ugh.
822 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
827 if (appData.noChessProgram) {
828 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829 sprintf(programVersion, "%s", PACKAGE_STRING);
831 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
832 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
833 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
836 if (!appData.icsActive) {
838 /* Check for variants that are supported only in ICS mode,
839 or not at all. Some that are accepted here nevertheless
840 have bugs; see comments below.
842 VariantClass variant = StringToVariant(appData.variant);
844 case VariantBughouse: /* need four players and two boards */
845 case VariantKriegspiel: /* need to hide pieces and move details */
846 /* case VariantFischeRandom: (Fabien: moved below) */
847 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
852 case VariantLoadable:
862 sprintf(buf, _("Unknown variant name %s"), appData.variant);
863 DisplayFatalError(buf, 0, 2);
866 case VariantXiangqi: /* [HGM] repetition rules not implemented */
867 case VariantFairy: /* [HGM] TestLegality definitely off! */
868 case VariantGothic: /* [HGM] should work */
869 case VariantCapablanca: /* [HGM] should work */
870 case VariantCourier: /* [HGM] initial forced moves not implemented */
871 case VariantShogi: /* [HGM] drops not tested for legality */
872 case VariantKnightmate: /* [HGM] should work */
873 case VariantCylinder: /* [HGM] untested */
874 case VariantFalcon: /* [HGM] untested */
875 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
876 offboard interposition not understood */
877 case VariantNormal: /* definitely works! */
878 case VariantWildCastle: /* pieces not automatically shuffled */
879 case VariantNoCastle: /* pieces not automatically shuffled */
880 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
881 case VariantLosers: /* should work except for win condition,
882 and doesn't know captures are mandatory */
883 case VariantSuicide: /* should work except for win condition,
884 and doesn't know captures are mandatory */
885 case VariantGiveaway: /* should work except for win condition,
886 and doesn't know captures are mandatory */
887 case VariantTwoKings: /* should work */
888 case VariantAtomic: /* should work except for win condition */
889 case Variant3Check: /* should work except for win condition */
890 case VariantShatranj: /* should work except for all win conditions */
891 case VariantBerolina: /* might work if TestLegality is off */
892 case VariantCapaRandom: /* should work */
893 case VariantJanus: /* should work */
894 case VariantSuper: /* experimental */
895 case VariantGreat: /* experimental, requires legality testing to be off */
900 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
901 InitEngineUCI( installDir, &second );
904 int NextIntegerFromString( char ** str, long * value )
909 while( *s == ' ' || *s == '\t' ) {
915 if( *s >= '0' && *s <= '9' ) {
916 while( *s >= '0' && *s <= '9' ) {
917 *value = *value * 10 + (*s - '0');
929 int NextTimeControlFromString( char ** str, long * value )
932 int result = NextIntegerFromString( str, &temp );
935 *value = temp * 60; /* Minutes */
938 result = NextIntegerFromString( str, &temp );
939 *value += temp; /* Seconds */
946 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
947 { /* [HGM] routine added to read '+moves/time' for secondary time control */
948 int result = -1; long temp, temp2;
950 if(**str != '+') return -1; // old params remain in force!
952 if( NextTimeControlFromString( str, &temp ) ) return -1;
955 /* time only: incremental or sudden-death time control */
956 if(**str == '+') { /* increment follows; read it */
958 if(result = NextIntegerFromString( str, &temp2)) return -1;
961 *moves = 0; *tc = temp * 1000;
963 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
965 (*str)++; /* classical time control */
966 result = NextTimeControlFromString( str, &temp2);
975 int GetTimeQuota(int movenr)
976 { /* [HGM] get time to add from the multi-session time-control string */
977 int moves=1; /* kludge to force reading of first session */
978 long time, increment;
979 char *s = fullTimeControlString;
981 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
983 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
984 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
985 if(movenr == -1) return time; /* last move before new session */
986 if(!moves) return increment; /* current session is incremental */
987 if(movenr >= 0) movenr -= moves; /* we already finished this session */
988 } while(movenr >= -1); /* try again for next session */
990 return 0; // no new time quota on this move
994 ParseTimeControl(tc, ti, mps)
1003 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1006 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1007 else sprintf(buf, "+%s+%d", tc, ti);
1010 sprintf(buf, "+%d/%s", mps, tc);
1011 else sprintf(buf, "+%s", tc);
1013 fullTimeControlString = StrSave(buf);
1015 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1020 /* Parse second time control */
1023 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1031 timeControl_2 = tc2 * 1000;
1041 timeControl = tc1 * 1000;
1044 timeIncrement = ti * 1000; /* convert to ms */
1045 movesPerSession = 0;
1048 movesPerSession = mps;
1056 if (appData.debugMode) {
1057 fprintf(debugFP, "%s\n", programVersion);
1060 set_cont_sequence(appData.wrapContSeq);
1061 if (appData.matchGames > 0) {
1062 appData.matchMode = TRUE;
1063 } else if (appData.matchMode) {
1064 appData.matchGames = 1;
1066 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1067 appData.matchGames = appData.sameColorGames;
1068 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1069 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1070 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1073 if (appData.noChessProgram || first.protocolVersion == 1) {
1076 /* kludge: allow timeout for initial "feature" commands */
1078 DisplayMessage("", _("Starting chess program"));
1079 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1084 InitBackEnd3 P((void))
1086 GameMode initialMode;
1090 InitChessProgram(&first, startedFromSetupPosition);
1093 if (appData.icsActive) {
1095 /* [DM] Make a console window if needed [HGM] merged ifs */
1100 if (*appData.icsCommPort != NULLCHAR) {
1101 sprintf(buf, _("Could not open comm port %s"),
1102 appData.icsCommPort);
1104 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1105 appData.icsHost, appData.icsPort);
1107 DisplayFatalError(buf, err, 1);
1112 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1114 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1115 } else if (appData.noChessProgram) {
1121 if (*appData.cmailGameName != NULLCHAR) {
1123 OpenLoopback(&cmailPR);
1125 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1129 DisplayMessage("", "");
1130 if (StrCaseCmp(appData.initialMode, "") == 0) {
1131 initialMode = BeginningOfGame;
1132 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1133 initialMode = TwoMachinesPlay;
1134 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1135 initialMode = AnalyzeFile;
1136 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1137 initialMode = AnalyzeMode;
1138 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1139 initialMode = MachinePlaysWhite;
1140 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1141 initialMode = MachinePlaysBlack;
1142 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1143 initialMode = EditGame;
1144 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1145 initialMode = EditPosition;
1146 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1147 initialMode = Training;
1149 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1150 DisplayFatalError(buf, 0, 2);
1154 if (appData.matchMode) {
1155 /* Set up machine vs. machine match */
1156 if (appData.noChessProgram) {
1157 DisplayFatalError(_("Can't have a match with no chess programs"),
1163 if (*appData.loadGameFile != NULLCHAR) {
1164 int index = appData.loadGameIndex; // [HGM] autoinc
1165 if(index<0) lastIndex = index = 1;
1166 if (!LoadGameFromFile(appData.loadGameFile,
1168 appData.loadGameFile, FALSE)) {
1169 DisplayFatalError(_("Bad game file"), 0, 1);
1172 } else if (*appData.loadPositionFile != NULLCHAR) {
1173 int index = appData.loadPositionIndex; // [HGM] autoinc
1174 if(index<0) lastIndex = index = 1;
1175 if (!LoadPositionFromFile(appData.loadPositionFile,
1177 appData.loadPositionFile)) {
1178 DisplayFatalError(_("Bad position file"), 0, 1);
1183 } else if (*appData.cmailGameName != NULLCHAR) {
1184 /* Set up cmail mode */
1185 ReloadCmailMsgEvent(TRUE);
1187 /* Set up other modes */
1188 if (initialMode == AnalyzeFile) {
1189 if (*appData.loadGameFile == NULLCHAR) {
1190 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1194 if (*appData.loadGameFile != NULLCHAR) {
1195 (void) LoadGameFromFile(appData.loadGameFile,
1196 appData.loadGameIndex,
1197 appData.loadGameFile, TRUE);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 (void) LoadPositionFromFile(appData.loadPositionFile,
1200 appData.loadPositionIndex,
1201 appData.loadPositionFile);
1202 /* [HGM] try to make self-starting even after FEN load */
1203 /* to allow automatic setup of fairy variants with wtm */
1204 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1205 gameMode = BeginningOfGame;
1206 setboardSpoiledMachineBlack = 1;
1208 /* [HGM] loadPos: make that every new game uses the setup */
1209 /* from file as long as we do not switch variant */
1210 if(!blackPlaysFirst) {
1211 startedFromPositionFile = TRUE;
1212 CopyBoard(filePosition, boards[0]);
1215 if (initialMode == AnalyzeMode) {
1216 if (appData.noChessProgram) {
1217 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1220 if (appData.icsActive) {
1221 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1225 } else if (initialMode == AnalyzeFile) {
1226 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1227 ShowThinkingEvent();
1229 AnalysisPeriodicEvent(1);
1230 } else if (initialMode == MachinePlaysWhite) {
1231 if (appData.noChessProgram) {
1232 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1236 if (appData.icsActive) {
1237 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1241 MachineWhiteEvent();
1242 } else if (initialMode == MachinePlaysBlack) {
1243 if (appData.noChessProgram) {
1244 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1248 if (appData.icsActive) {
1249 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1253 MachineBlackEvent();
1254 } else if (initialMode == TwoMachinesPlay) {
1255 if (appData.noChessProgram) {
1256 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1260 if (appData.icsActive) {
1261 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1266 } else if (initialMode == EditGame) {
1268 } else if (initialMode == EditPosition) {
1269 EditPositionEvent();
1270 } else if (initialMode == Training) {
1271 if (*appData.loadGameFile == NULLCHAR) {
1272 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1281 * Establish will establish a contact to a remote host.port.
1282 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1283 * used to talk to the host.
1284 * Returns 0 if okay, error code if not.
1291 if (*appData.icsCommPort != NULLCHAR) {
1292 /* Talk to the host through a serial comm port */
1293 return OpenCommPort(appData.icsCommPort, &icsPR);
1295 } else if (*appData.gateway != NULLCHAR) {
1296 if (*appData.remoteShell == NULLCHAR) {
1297 /* Use the rcmd protocol to run telnet program on a gateway host */
1298 snprintf(buf, sizeof(buf), "%s %s %s",
1299 appData.telnetProgram, appData.icsHost, appData.icsPort);
1300 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1303 /* Use the rsh program to run telnet program on a gateway host */
1304 if (*appData.remoteUser == NULLCHAR) {
1305 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1306 appData.gateway, appData.telnetProgram,
1307 appData.icsHost, appData.icsPort);
1309 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1310 appData.remoteShell, appData.gateway,
1311 appData.remoteUser, appData.telnetProgram,
1312 appData.icsHost, appData.icsPort);
1314 return StartChildProcess(buf, "", &icsPR);
1317 } else if (appData.useTelnet) {
1318 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1321 /* TCP socket interface differs somewhat between
1322 Unix and NT; handle details in the front end.
1324 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1329 show_bytes(fp, buf, count)
1335 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1336 fprintf(fp, "\\%03o", *buf & 0xff);
1345 /* Returns an errno value */
1347 OutputMaybeTelnet(pr, message, count, outError)
1353 char buf[8192], *p, *q, *buflim;
1354 int left, newcount, outcount;
1356 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1357 *appData.gateway != NULLCHAR) {
1358 if (appData.debugMode) {
1359 fprintf(debugFP, ">ICS: ");
1360 show_bytes(debugFP, message, count);
1361 fprintf(debugFP, "\n");
1363 return OutputToProcess(pr, message, count, outError);
1366 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1373 if (appData.debugMode) {
1374 fprintf(debugFP, ">ICS: ");
1375 show_bytes(debugFP, buf, newcount);
1376 fprintf(debugFP, "\n");
1378 outcount = OutputToProcess(pr, buf, newcount, outError);
1379 if (outcount < newcount) return -1; /* to be sure */
1386 } else if (((unsigned char) *p) == TN_IAC) {
1387 *q++ = (char) TN_IAC;
1394 if (appData.debugMode) {
1395 fprintf(debugFP, ">ICS: ");
1396 show_bytes(debugFP, buf, newcount);
1397 fprintf(debugFP, "\n");
1399 outcount = OutputToProcess(pr, buf, newcount, outError);
1400 if (outcount < newcount) return -1; /* to be sure */
1405 read_from_player(isr, closure, message, count, error)
1412 int outError, outCount;
1413 static int gotEof = 0;
1415 /* Pass data read from player on to ICS */
1418 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1419 if (outCount < count) {
1420 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1422 } else if (count < 0) {
1423 RemoveInputSource(isr);
1424 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1425 } else if (gotEof++ > 0) {
1426 RemoveInputSource(isr);
1427 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1433 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1434 SendToICS("date\n");
1435 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1438 /* added routine for printf style output to ics */
1439 void ics_printf(char *format, ...)
1441 char buffer[MSG_SIZ];
1444 va_start(args, format);
1445 vsnprintf(buffer, sizeof(buffer), format, args);
1446 buffer[sizeof(buffer)-1] = '\0';
1455 int count, outCount, outError;
1457 if (icsPR == NULL) return;
1460 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1461 if (outCount < count) {
1462 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466 /* This is used for sending logon scripts to the ICS. Sending
1467 without a delay causes problems when using timestamp on ICC
1468 (at least on my machine). */
1470 SendToICSDelayed(s,msdelay)
1474 int count, outCount, outError;
1476 if (icsPR == NULL) return;
1479 if (appData.debugMode) {
1480 fprintf(debugFP, ">ICS: ");
1481 show_bytes(debugFP, s, count);
1482 fprintf(debugFP, "\n");
1484 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1486 if (outCount < count) {
1487 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1492 /* Remove all highlighting escape sequences in s
1493 Also deletes any suffix starting with '('
1496 StripHighlightAndTitle(s)
1499 static char retbuf[MSG_SIZ];
1502 while (*s != NULLCHAR) {
1503 while (*s == '\033') {
1504 while (*s != NULLCHAR && !isalpha(*s)) s++;
1505 if (*s != NULLCHAR) s++;
1507 while (*s != NULLCHAR && *s != '\033') {
1508 if (*s == '(' || *s == '[') {
1519 /* Remove all highlighting escape sequences in s */
1524 static char retbuf[MSG_SIZ];
1527 while (*s != NULLCHAR) {
1528 while (*s == '\033') {
1529 while (*s != NULLCHAR && !isalpha(*s)) s++;
1530 if (*s != NULLCHAR) s++;
1532 while (*s != NULLCHAR && *s != '\033') {
1540 char *variantNames[] = VARIANT_NAMES;
1545 return variantNames[v];
1549 /* Identify a variant from the strings the chess servers use or the
1550 PGN Variant tag names we use. */
1557 VariantClass v = VariantNormal;
1558 int i, found = FALSE;
1563 /* [HGM] skip over optional board-size prefixes */
1564 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1565 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1566 while( *e++ != '_');
1569 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574 if (StrCaseStr(e, variantNames[i])) {
1575 v = (VariantClass) i;
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583 || StrCaseStr(e, "wild/fr")
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585 v = VariantFischeRandom;
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587 (i = 1, p = StrCaseStr(e, "w"))) {
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1596 case 0: /* FICS only, actually */
1598 /* Castling legal even if K starts on d-file */
1599 v = VariantWildCastle;
1604 /* Castling illegal even if K & R happen to start in
1605 normal positions. */
1606 v = VariantNoCastle;
1619 /* Castling legal iff K & R start in normal positions */
1625 /* Special wilds for position setup; unclear what to do here */
1626 v = VariantLoadable;
1629 /* Bizarre ICC game */
1630 v = VariantTwoKings;
1633 v = VariantKriegspiel;
1639 v = VariantFischeRandom;
1642 v = VariantCrazyhouse;
1645 v = VariantBughouse;
1651 /* Not quite the same as FICS suicide! */
1652 v = VariantGiveaway;
1658 v = VariantShatranj;
1661 /* Temporary names for future ICC types. The name *will* change in
1662 the next xboard/WinBoard release after ICC defines it. */
1700 v = VariantCapablanca;
1703 v = VariantKnightmate;
1709 v = VariantCylinder;
1715 v = VariantCapaRandom;
1718 v = VariantBerolina;
1730 /* Found "wild" or "w" in the string but no number;
1731 must assume it's normal chess. */
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
1736 DisplayError(buf, 0);
1742 if (appData.debugMode) {
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744 e, wnum, VariantName(v));
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753 advance *index beyond it, and set leftover_start to the new value of
1754 *index; else return FALSE. If pattern contains the character '*', it
1755 matches any sequence of characters not containing '\r', '\n', or the
1756 character following the '*' (if any), and the matched sequence(s) are
1757 copied into star_match.
1760 looking_at(buf, index, pattern)
1765 char *bufp = &buf[*index], *patternp = pattern;
1767 char *matchp = star_match[0];
1770 if (*patternp == NULLCHAR) {
1771 *index = leftover_start = bufp - buf;
1775 if (*bufp == NULLCHAR) return FALSE;
1776 if (*patternp == '*') {
1777 if (*bufp == *(patternp + 1)) {
1779 matchp = star_match[++star_count];
1783 } else if (*bufp == '\n' || *bufp == '\r') {
1785 if (*patternp == NULLCHAR)
1790 *matchp++ = *bufp++;
1794 if (*patternp != *bufp) return FALSE;
1801 SendToPlayer(data, length)
1805 int error, outCount;
1806 outCount = OutputToProcess(NoProc, data, length, &error);
1807 if (outCount < length) {
1808 DisplayFatalError(_("Error writing to display"), error, 1);
1813 PackHolding(packed, holding)
1825 switch (runlength) {
1836 sprintf(q, "%d", runlength);
1848 /* Telnet protocol requests from the front end */
1850 TelnetRequest(ddww, option)
1851 unsigned char ddww, option;
1853 unsigned char msg[3];
1854 int outCount, outError;
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858 if (appData.debugMode) {
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1875 sprintf(buf1, "%d", ddww);
1884 sprintf(buf2, "%d", option);
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901 if (!appData.icsActive) return;
1902 TelnetRequest(TN_DO, TN_ECHO);
1908 if (!appData.icsActive) return;
1909 TelnetRequest(TN_DONT, TN_ECHO);
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 /* put the holdings sent to us by the server on the board holdings area */
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1920 if(gameInfo.holdingsWidth < 2) return;
1921 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1922 return; // prevent overwriting by pre-board holdings
1924 if( (int)lowestPiece >= BlackPawn ) {
1927 holdingsStartRow = BOARD_HEIGHT-1;
1930 holdingsColumn = BOARD_WIDTH-1;
1931 countsColumn = BOARD_WIDTH-2;
1932 holdingsStartRow = 0;
1936 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1937 board[i][holdingsColumn] = EmptySquare;
1938 board[i][countsColumn] = (ChessSquare) 0;
1940 while( (p=*holdings++) != NULLCHAR ) {
1941 piece = CharToPiece( ToUpper(p) );
1942 if(piece == EmptySquare) continue;
1943 /*j = (int) piece - (int) WhitePawn;*/
1944 j = PieceToNumber(piece);
1945 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1946 if(j < 0) continue; /* should not happen */
1947 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1948 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1949 board[holdingsStartRow+j*direction][countsColumn]++;
1955 VariantSwitch(Board board, VariantClass newVariant)
1957 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1960 startedFromPositionFile = FALSE;
1961 if(gameInfo.variant == newVariant) return;
1963 /* [HGM] This routine is called each time an assignment is made to
1964 * gameInfo.variant during a game, to make sure the board sizes
1965 * are set to match the new variant. If that means adding or deleting
1966 * holdings, we shift the playing board accordingly
1967 * This kludge is needed because in ICS observe mode, we get boards
1968 * of an ongoing game without knowing the variant, and learn about the
1969 * latter only later. This can be because of the move list we requested,
1970 * in which case the game history is refilled from the beginning anyway,
1971 * but also when receiving holdings of a crazyhouse game. In the latter
1972 * case we want to add those holdings to the already received position.
1976 if (appData.debugMode) {
1977 fprintf(debugFP, "Switch board from %s to %s\n",
1978 VariantName(gameInfo.variant), VariantName(newVariant));
1979 setbuf(debugFP, NULL);
1981 shuffleOpenings = 0; /* [HGM] shuffle */
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1986 newWidth = 9; newHeight = 9;
1987 gameInfo.holdingsSize = 7;
1988 case VariantBughouse:
1989 case VariantCrazyhouse:
1990 newHoldingsWidth = 2; break;
1994 newHoldingsWidth = 2;
1995 gameInfo.holdingsSize = 8;
1998 case VariantCapablanca:
1999 case VariantCapaRandom:
2002 newHoldingsWidth = gameInfo.holdingsSize = 0;
2005 if(newWidth != gameInfo.boardWidth ||
2006 newHeight != gameInfo.boardHeight ||
2007 newHoldingsWidth != gameInfo.holdingsWidth ) {
2009 /* shift position to new playing area, if needed */
2010 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2011 for(i=0; i<BOARD_HEIGHT; i++)
2012 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2013 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015 for(i=0; i<newHeight; i++) {
2016 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2017 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2019 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2020 for(i=0; i<BOARD_HEIGHT; i++)
2021 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2022 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2025 gameInfo.boardWidth = newWidth;
2026 gameInfo.boardHeight = newHeight;
2027 gameInfo.holdingsWidth = newHoldingsWidth;
2028 gameInfo.variant = newVariant;
2029 InitDrawingSizes(-2, 0);
2030 } else gameInfo.variant = newVariant;
2031 CopyBoard(oldBoard, board); // remember correctly formatted board
2032 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2033 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2036 static int loggedOn = FALSE;
2038 /*-- Game start info cache: --*/
2040 char gs_kind[MSG_SIZ];
2041 static char player1Name[128] = "";
2042 static char player2Name[128] = "";
2043 static char cont_seq[] = "\n\\ ";
2044 static int player1Rating = -1;
2045 static int player2Rating = -1;
2046 /*----------------------------*/
2048 ColorClass curColor = ColorNormal;
2049 int suppressKibitz = 0;
2052 read_from_ics(isr, closure, data, count, error)
2059 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2060 #define STARTED_NONE 0
2061 #define STARTED_MOVES 1
2062 #define STARTED_BOARD 2
2063 #define STARTED_OBSERVE 3
2064 #define STARTED_HOLDINGS 4
2065 #define STARTED_CHATTER 5
2066 #define STARTED_COMMENT 6
2067 #define STARTED_MOVES_NOHIDE 7
2069 static int started = STARTED_NONE;
2070 static char parse[20000];
2071 static int parse_pos = 0;
2072 static char buf[BUF_SIZE + 1];
2073 static int firstTime = TRUE, intfSet = FALSE;
2074 static ColorClass prevColor = ColorNormal;
2075 static int savingComment = FALSE;
2076 static int cmatch = 0; // continuation sequence match
2083 int backup; /* [DM] For zippy color lines */
2085 char talker[MSG_SIZ]; // [HGM] chat
2088 if (appData.debugMode) {
2090 fprintf(debugFP, "<ICS: ");
2091 show_bytes(debugFP, data, count);
2092 fprintf(debugFP, "\n");
2096 if (appData.debugMode) { int f = forwardMostMove;
2097 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2098 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2099 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2102 /* If last read ended with a partial line that we couldn't parse,
2103 prepend it to the new read and try again. */
2104 if (leftover_len > 0) {
2105 for (i=0; i<leftover_len; i++)
2106 buf[i] = buf[leftover_start + i];
2109 /* copy new characters into the buffer */
2110 bp = buf + leftover_len;
2111 buf_len=leftover_len;
2112 for (i=0; i<count; i++)
2115 if (data[i] == '\r')
2118 // join lines split by ICS?
2119 if (!appData.noJoin)
2122 Joining just consists of finding matches against the
2123 continuation sequence, and discarding that sequence
2124 if found instead of copying it. So, until a match
2125 fails, there's nothing to do since it might be the
2126 complete sequence, and thus, something we don't want
2129 if (data[i] == cont_seq[cmatch])
2132 if (cmatch == strlen(cont_seq))
2134 cmatch = 0; // complete match. just reset the counter
2137 it's possible for the ICS to not include the space
2138 at the end of the last word, making our [correct]
2139 join operation fuse two separate words. the server
2140 does this when the space occurs at the width setting.
2142 if (!buf_len || buf[buf_len-1] != ' ')
2153 match failed, so we have to copy what matched before
2154 falling through and copying this character. In reality,
2155 this will only ever be just the newline character, but
2156 it doesn't hurt to be precise.
2158 strncpy(bp, cont_seq, cmatch);
2170 buf[buf_len] = NULLCHAR;
2171 next_out = leftover_len;
2175 while (i < buf_len) {
2176 /* Deal with part of the TELNET option negotiation
2177 protocol. We refuse to do anything beyond the
2178 defaults, except that we allow the WILL ECHO option,
2179 which ICS uses to turn off password echoing when we are
2180 directly connected to it. We reject this option
2181 if localLineEditing mode is on (always on in xboard)
2182 and we are talking to port 23, which might be a real
2183 telnet server that will try to keep WILL ECHO on permanently.
2185 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2186 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2187 unsigned char option;
2189 switch ((unsigned char) buf[++i]) {
2191 if (appData.debugMode)
2192 fprintf(debugFP, "\n<WILL ");
2193 switch (option = (unsigned char) buf[++i]) {
2195 if (appData.debugMode)
2196 fprintf(debugFP, "ECHO ");
2197 /* Reply only if this is a change, according
2198 to the protocol rules. */
2199 if (remoteEchoOption) break;
2200 if (appData.localLineEditing &&
2201 atoi(appData.icsPort) == TN_PORT) {
2202 TelnetRequest(TN_DONT, TN_ECHO);
2205 TelnetRequest(TN_DO, TN_ECHO);
2206 remoteEchoOption = TRUE;
2210 if (appData.debugMode)
2211 fprintf(debugFP, "%d ", option);
2212 /* Whatever this is, we don't want it. */
2213 TelnetRequest(TN_DONT, option);
2218 if (appData.debugMode)
2219 fprintf(debugFP, "\n<WONT ");
2220 switch (option = (unsigned char) buf[++i]) {
2222 if (appData.debugMode)
2223 fprintf(debugFP, "ECHO ");
2224 /* Reply only if this is a change, according
2225 to the protocol rules. */
2226 if (!remoteEchoOption) break;
2228 TelnetRequest(TN_DONT, TN_ECHO);
2229 remoteEchoOption = FALSE;
2232 if (appData.debugMode)
2233 fprintf(debugFP, "%d ", (unsigned char) option);
2234 /* Whatever this is, it must already be turned
2235 off, because we never agree to turn on
2236 anything non-default, so according to the
2237 protocol rules, we don't reply. */
2242 if (appData.debugMode)
2243 fprintf(debugFP, "\n<DO ");
2244 switch (option = (unsigned char) buf[++i]) {
2246 /* Whatever this is, we refuse to do it. */
2247 if (appData.debugMode)
2248 fprintf(debugFP, "%d ", option);
2249 TelnetRequest(TN_WONT, option);
2254 if (appData.debugMode)
2255 fprintf(debugFP, "\n<DONT ");
2256 switch (option = (unsigned char) buf[++i]) {
2258 if (appData.debugMode)
2259 fprintf(debugFP, "%d ", option);
2260 /* Whatever this is, we are already not doing
2261 it, because we never agree to do anything
2262 non-default, so according to the protocol
2263 rules, we don't reply. */
2268 if (appData.debugMode)
2269 fprintf(debugFP, "\n<IAC ");
2270 /* Doubled IAC; pass it through */
2274 if (appData.debugMode)
2275 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2276 /* Drop all other telnet commands on the floor */
2279 if (oldi > next_out)
2280 SendToPlayer(&buf[next_out], oldi - next_out);
2286 /* OK, this at least will *usually* work */
2287 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2291 if (loggedOn && !intfSet) {
2292 if (ics_type == ICS_ICC) {
2294 "/set-quietly interface %s\n/set-quietly style 12\n",
2296 } else if (ics_type == ICS_CHESSNET) {
2297 sprintf(str, "/style 12\n");
2299 strcpy(str, "alias $ @\n$set interface ");
2300 strcat(str, programVersion);
2301 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2303 strcat(str, "$iset nohighlight 1\n");
2305 strcat(str, "$iset lock 1\n$style 12\n");
2308 NotifyFrontendLogin();
2312 if (started == STARTED_COMMENT) {
2313 /* Accumulate characters in comment */
2314 parse[parse_pos++] = buf[i];
2315 if (buf[i] == '\n') {
2316 parse[parse_pos] = NULLCHAR;
2317 if(chattingPartner>=0) {
2319 sprintf(mess, "%s%s", talker, parse);
2320 OutputChatMessage(chattingPartner, mess);
2321 chattingPartner = -1;
2323 if(!suppressKibitz) // [HGM] kibitz
2324 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2325 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2326 int nrDigit = 0, nrAlph = 0, i;
2327 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2328 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2329 parse[parse_pos] = NULLCHAR;
2330 // try to be smart: if it does not look like search info, it should go to
2331 // ICS interaction window after all, not to engine-output window.
2332 for(i=0; i<parse_pos; i++) { // count letters and digits
2333 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2334 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2335 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2337 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2338 int depth=0; float score;
2339 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2340 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2341 pvInfoList[forwardMostMove-1].depth = depth;
2342 pvInfoList[forwardMostMove-1].score = 100*score;
2344 OutputKibitz(suppressKibitz, parse);
2347 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2348 SendToPlayer(tmp, strlen(tmp));
2351 started = STARTED_NONE;
2353 /* Don't match patterns against characters in chatter */
2358 if (started == STARTED_CHATTER) {
2359 if (buf[i] != '\n') {
2360 /* Don't match patterns against characters in chatter */
2364 started = STARTED_NONE;
2367 /* Kludge to deal with rcmd protocol */
2368 if (firstTime && looking_at(buf, &i, "\001*")) {
2369 DisplayFatalError(&buf[1], 0, 1);
2375 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2378 if (appData.debugMode)
2379 fprintf(debugFP, "ics_type %d\n", ics_type);
2382 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2383 ics_type = ICS_FICS;
2385 if (appData.debugMode)
2386 fprintf(debugFP, "ics_type %d\n", ics_type);
2389 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2390 ics_type = ICS_CHESSNET;
2392 if (appData.debugMode)
2393 fprintf(debugFP, "ics_type %d\n", ics_type);
2398 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2399 looking_at(buf, &i, "Logging you in as \"*\"") ||
2400 looking_at(buf, &i, "will be \"*\""))) {
2401 strcpy(ics_handle, star_match[0]);
2405 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2407 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2408 DisplayIcsInteractionTitle(buf);
2409 have_set_title = TRUE;
2412 /* skip finger notes */
2413 if (started == STARTED_NONE &&
2414 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2415 (buf[i] == '1' && buf[i+1] == '0')) &&
2416 buf[i+2] == ':' && buf[i+3] == ' ') {
2417 started = STARTED_CHATTER;
2422 /* skip formula vars */
2423 if (started == STARTED_NONE &&
2424 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2425 started = STARTED_CHATTER;
2431 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2432 if (appData.autoKibitz && started == STARTED_NONE &&
2433 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2434 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2435 if(looking_at(buf, &i, "* kibitzes: ") &&
2436 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2437 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2438 suppressKibitz = TRUE;
2439 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2440 && (gameMode == IcsPlayingWhite)) ||
2441 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2442 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2443 started = STARTED_CHATTER; // own kibitz we simply discard
2445 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2446 parse_pos = 0; parse[0] = NULLCHAR;
2447 savingComment = TRUE;
2448 suppressKibitz = gameMode != IcsObserving ? 2 :
2449 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2453 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2454 started = STARTED_CHATTER;
2455 suppressKibitz = TRUE;
2457 } // [HGM] kibitz: end of patch
2459 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2461 // [HGM] chat: intercept tells by users for which we have an open chat window
2463 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2464 looking_at(buf, &i, "* whispers:") ||
2465 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2466 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2468 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2469 chattingPartner = -1;
2471 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2472 for(p=0; p<MAX_CHAT; p++) {
2473 if(channel == atoi(chatPartner[p])) {
2474 talker[0] = '['; strcat(talker, "]");
2475 chattingPartner = p; break;
2478 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2479 for(p=0; p<MAX_CHAT; p++) {
2480 if(!strcmp("WHISPER", chatPartner[p])) {
2481 talker[0] = '['; strcat(talker, "]");
2482 chattingPartner = p; break;
2485 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2486 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2488 chattingPartner = p; break;
2490 if(chattingPartner<0) i = oldi; else {
2491 started = STARTED_COMMENT;
2492 parse_pos = 0; parse[0] = NULLCHAR;
2493 savingComment = TRUE;
2494 suppressKibitz = TRUE;
2496 } // [HGM] chat: end of patch
2498 if (appData.zippyTalk || appData.zippyPlay) {
2499 /* [DM] Backup address for color zippy lines */
2503 if (loggedOn == TRUE)
2504 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2505 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2507 if (ZippyControl(buf, &i) ||
2508 ZippyConverse(buf, &i) ||
2509 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2511 if (!appData.colorize) continue;
2515 } // [DM] 'else { ' deleted
2517 /* Regular tells and says */
2518 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2519 looking_at(buf, &i, "* (your partner) tells you: ") ||
2520 looking_at(buf, &i, "* says: ") ||
2521 /* Don't color "message" or "messages" output */
2522 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2523 looking_at(buf, &i, "*. * at *:*: ") ||
2524 looking_at(buf, &i, "--* (*:*): ") ||
2525 /* Message notifications (same color as tells) */
2526 looking_at(buf, &i, "* has left a message ") ||
2527 looking_at(buf, &i, "* just sent you a message:\n") ||
2528 /* Whispers and kibitzes */
2529 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2530 looking_at(buf, &i, "* kibitzes: ") ||
2532 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2534 if (tkind == 1 && strchr(star_match[0], ':')) {
2535 /* Avoid "tells you:" spoofs in channels */
2538 if (star_match[0][0] == NULLCHAR ||
2539 strchr(star_match[0], ' ') ||
2540 (tkind == 3 && strchr(star_match[1], ' '))) {
2541 /* Reject bogus matches */
2544 if (appData.colorize) {
2545 if (oldi > next_out) {
2546 SendToPlayer(&buf[next_out], oldi - next_out);
2551 Colorize(ColorTell, FALSE);
2552 curColor = ColorTell;
2555 Colorize(ColorKibitz, FALSE);
2556 curColor = ColorKibitz;
2559 p = strrchr(star_match[1], '(');
2566 Colorize(ColorChannel1, FALSE);
2567 curColor = ColorChannel1;
2569 Colorize(ColorChannel, FALSE);
2570 curColor = ColorChannel;
2574 curColor = ColorNormal;
2578 if (started == STARTED_NONE && appData.autoComment &&
2579 (gameMode == IcsObserving ||
2580 gameMode == IcsPlayingWhite ||
2581 gameMode == IcsPlayingBlack)) {
2582 parse_pos = i - oldi;
2583 memcpy(parse, &buf[oldi], parse_pos);
2584 parse[parse_pos] = NULLCHAR;
2585 started = STARTED_COMMENT;
2586 savingComment = TRUE;
2588 started = STARTED_CHATTER;
2589 savingComment = FALSE;
2596 if (looking_at(buf, &i, "* s-shouts: ") ||
2597 looking_at(buf, &i, "* c-shouts: ")) {
2598 if (appData.colorize) {
2599 if (oldi > next_out) {
2600 SendToPlayer(&buf[next_out], oldi - next_out);
2603 Colorize(ColorSShout, FALSE);
2604 curColor = ColorSShout;
2607 started = STARTED_CHATTER;
2611 if (looking_at(buf, &i, "--->")) {
2616 if (looking_at(buf, &i, "* shouts: ") ||
2617 looking_at(buf, &i, "--> ")) {
2618 if (appData.colorize) {
2619 if (oldi > next_out) {
2620 SendToPlayer(&buf[next_out], oldi - next_out);
2623 Colorize(ColorShout, FALSE);
2624 curColor = ColorShout;
2627 started = STARTED_CHATTER;
2631 if (looking_at( buf, &i, "Challenge:")) {
2632 if (appData.colorize) {
2633 if (oldi > next_out) {
2634 SendToPlayer(&buf[next_out], oldi - next_out);
2637 Colorize(ColorChallenge, FALSE);
2638 curColor = ColorChallenge;
2644 if (looking_at(buf, &i, "* offers you") ||
2645 looking_at(buf, &i, "* offers to be") ||
2646 looking_at(buf, &i, "* would like to") ||
2647 looking_at(buf, &i, "* requests to") ||
2648 looking_at(buf, &i, "Your opponent offers") ||
2649 looking_at(buf, &i, "Your opponent requests")) {
2651 if (appData.colorize) {
2652 if (oldi > next_out) {
2653 SendToPlayer(&buf[next_out], oldi - next_out);
2656 Colorize(ColorRequest, FALSE);
2657 curColor = ColorRequest;
2662 if (looking_at(buf, &i, "* (*) seeking")) {
2663 if (appData.colorize) {
2664 if (oldi > next_out) {
2665 SendToPlayer(&buf[next_out], oldi - next_out);
2668 Colorize(ColorSeek, FALSE);
2669 curColor = ColorSeek;
2674 if (looking_at(buf, &i, "\\ ")) {
2675 if (prevColor != ColorNormal) {
2676 if (oldi > next_out) {
2677 SendToPlayer(&buf[next_out], oldi - next_out);
2680 Colorize(prevColor, TRUE);
2681 curColor = prevColor;
2683 if (savingComment) {
2684 parse_pos = i - oldi;
2685 memcpy(parse, &buf[oldi], parse_pos);
2686 parse[parse_pos] = NULLCHAR;
2687 started = STARTED_COMMENT;
2689 started = STARTED_CHATTER;
2694 if (looking_at(buf, &i, "Black Strength :") ||
2695 looking_at(buf, &i, "<<< style 10 board >>>") ||
2696 looking_at(buf, &i, "<10>") ||
2697 looking_at(buf, &i, "#@#")) {
2698 /* Wrong board style */
2700 SendToICS(ics_prefix);
2701 SendToICS("set style 12\n");
2702 SendToICS(ics_prefix);
2703 SendToICS("refresh\n");
2707 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2709 have_sent_ICS_logon = 1;
2713 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2714 (looking_at(buf, &i, "\n<12> ") ||
2715 looking_at(buf, &i, "<12> "))) {
2717 if (oldi > next_out) {
2718 SendToPlayer(&buf[next_out], oldi - next_out);
2721 started = STARTED_BOARD;
2726 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2727 looking_at(buf, &i, "<b1> ")) {
2728 if (oldi > next_out) {
2729 SendToPlayer(&buf[next_out], oldi - next_out);
2732 started = STARTED_HOLDINGS;
2737 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2739 /* Header for a move list -- first line */
2741 switch (ics_getting_history) {
2745 case BeginningOfGame:
2746 /* User typed "moves" or "oldmoves" while we
2747 were idle. Pretend we asked for these
2748 moves and soak them up so user can step
2749 through them and/or save them.
2752 gameMode = IcsObserving;
2755 ics_getting_history = H_GOT_UNREQ_HEADER;
2757 case EditGame: /*?*/
2758 case EditPosition: /*?*/
2759 /* Should above feature work in these modes too? */
2760 /* For now it doesn't */
2761 ics_getting_history = H_GOT_UNWANTED_HEADER;
2764 ics_getting_history = H_GOT_UNWANTED_HEADER;
2769 /* Is this the right one? */
2770 if (gameInfo.white && gameInfo.black &&
2771 strcmp(gameInfo.white, star_match[0]) == 0 &&
2772 strcmp(gameInfo.black, star_match[2]) == 0) {
2774 ics_getting_history = H_GOT_REQ_HEADER;
2777 case H_GOT_REQ_HEADER:
2778 case H_GOT_UNREQ_HEADER:
2779 case H_GOT_UNWANTED_HEADER:
2780 case H_GETTING_MOVES:
2781 /* Should not happen */
2782 DisplayError(_("Error gathering move list: two headers"), 0);
2783 ics_getting_history = H_FALSE;
2787 /* Save player ratings into gameInfo if needed */
2788 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2789 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2790 (gameInfo.whiteRating == -1 ||
2791 gameInfo.blackRating == -1)) {
2793 gameInfo.whiteRating = string_to_rating(star_match[1]);
2794 gameInfo.blackRating = string_to_rating(star_match[3]);
2795 if (appData.debugMode)
2796 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2797 gameInfo.whiteRating, gameInfo.blackRating);
2802 if (looking_at(buf, &i,
2803 "* * match, initial time: * minute*, increment: * second")) {
2804 /* Header for a move list -- second line */
2805 /* Initial board will follow if this is a wild game */
2806 if (gameInfo.event != NULL) free(gameInfo.event);
2807 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2808 gameInfo.event = StrSave(str);
2809 /* [HGM] we switched variant. Translate boards if needed. */
2810 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2814 if (looking_at(buf, &i, "Move ")) {
2815 /* Beginning of a move list */
2816 switch (ics_getting_history) {
2818 /* Normally should not happen */
2819 /* Maybe user hit reset while we were parsing */
2822 /* Happens if we are ignoring a move list that is not
2823 * the one we just requested. Common if the user
2824 * tries to observe two games without turning off
2827 case H_GETTING_MOVES:
2828 /* Should not happen */
2829 DisplayError(_("Error gathering move list: nested"), 0);
2830 ics_getting_history = H_FALSE;
2832 case H_GOT_REQ_HEADER:
2833 ics_getting_history = H_GETTING_MOVES;
2834 started = STARTED_MOVES;
2836 if (oldi > next_out) {
2837 SendToPlayer(&buf[next_out], oldi - next_out);
2840 case H_GOT_UNREQ_HEADER:
2841 ics_getting_history = H_GETTING_MOVES;
2842 started = STARTED_MOVES_NOHIDE;
2845 case H_GOT_UNWANTED_HEADER:
2846 ics_getting_history = H_FALSE;
2852 if (looking_at(buf, &i, "% ") ||
2853 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2854 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2855 savingComment = FALSE;
2858 case STARTED_MOVES_NOHIDE:
2859 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2860 parse[parse_pos + i - oldi] = NULLCHAR;
2861 ParseGameHistory(parse);
2863 if (appData.zippyPlay && first.initDone) {
2864 FeedMovesToProgram(&first, forwardMostMove);
2865 if (gameMode == IcsPlayingWhite) {
2866 if (WhiteOnMove(forwardMostMove)) {
2867 if (first.sendTime) {
2868 if (first.useColors) {
2869 SendToProgram("black\n", &first);
2871 SendTimeRemaining(&first, TRUE);
2873 if (first.useColors) {
2874 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2876 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2877 first.maybeThinking = TRUE;
2879 if (first.usePlayother) {
2880 if (first.sendTime) {
2881 SendTimeRemaining(&first, TRUE);
2883 SendToProgram("playother\n", &first);
2889 } else if (gameMode == IcsPlayingBlack) {
2890 if (!WhiteOnMove(forwardMostMove)) {
2891 if (first.sendTime) {
2892 if (first.useColors) {
2893 SendToProgram("white\n", &first);
2895 SendTimeRemaining(&first, FALSE);
2897 if (first.useColors) {
2898 SendToProgram("black\n", &first);
2900 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2901 first.maybeThinking = TRUE;
2903 if (first.usePlayother) {
2904 if (first.sendTime) {
2905 SendTimeRemaining(&first, FALSE);
2907 SendToProgram("playother\n", &first);
2916 if (gameMode == IcsObserving && ics_gamenum == -1) {
2917 /* Moves came from oldmoves or moves command
2918 while we weren't doing anything else.
2920 currentMove = forwardMostMove;
2921 ClearHighlights();/*!!could figure this out*/
2922 flipView = appData.flipView;
2923 DrawPosition(TRUE, boards[currentMove]);
2924 DisplayBothClocks();
2925 sprintf(str, "%s vs. %s",
2926 gameInfo.white, gameInfo.black);
2930 /* Moves were history of an active game */
2931 if (gameInfo.resultDetails != NULL) {
2932 free(gameInfo.resultDetails);
2933 gameInfo.resultDetails = NULL;
2936 HistorySet(parseList, backwardMostMove,
2937 forwardMostMove, currentMove-1);
2938 DisplayMove(currentMove - 1);
2939 if (started == STARTED_MOVES) next_out = i;
2940 started = STARTED_NONE;
2941 ics_getting_history = H_FALSE;
2944 case STARTED_OBSERVE:
2945 started = STARTED_NONE;
2946 SendToICS(ics_prefix);
2947 SendToICS("refresh\n");
2953 if(bookHit) { // [HGM] book: simulate book reply
2954 static char bookMove[MSG_SIZ]; // a bit generous?
2956 programStats.nodes = programStats.depth = programStats.time =
2957 programStats.score = programStats.got_only_move = 0;
2958 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2960 strcpy(bookMove, "move ");
2961 strcat(bookMove, bookHit);
2962 HandleMachineMove(bookMove, &first);
2967 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2968 started == STARTED_HOLDINGS ||
2969 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2970 /* Accumulate characters in move list or board */
2971 parse[parse_pos++] = buf[i];
2974 /* Start of game messages. Mostly we detect start of game
2975 when the first board image arrives. On some versions
2976 of the ICS, though, we need to do a "refresh" after starting
2977 to observe in order to get the current board right away. */
2978 if (looking_at(buf, &i, "Adding game * to observation list")) {
2979 started = STARTED_OBSERVE;
2983 /* Handle auto-observe */
2984 if (appData.autoObserve &&
2985 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2986 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2988 /* Choose the player that was highlighted, if any. */
2989 if (star_match[0][0] == '\033' ||
2990 star_match[1][0] != '\033') {
2991 player = star_match[0];
2993 player = star_match[2];
2995 sprintf(str, "%sobserve %s\n",
2996 ics_prefix, StripHighlightAndTitle(player));
2999 /* Save ratings from notify string */
3000 strcpy(player1Name, star_match[0]);
3001 player1Rating = string_to_rating(star_match[1]);
3002 strcpy(player2Name, star_match[2]);
3003 player2Rating = string_to_rating(star_match[3]);
3005 if (appData.debugMode)
3007 "Ratings from 'Game notification:' %s %d, %s %d\n",
3008 player1Name, player1Rating,
3009 player2Name, player2Rating);
3014 /* Deal with automatic examine mode after a game,
3015 and with IcsObserving -> IcsExamining transition */
3016 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3017 looking_at(buf, &i, "has made you an examiner of game *")) {
3019 int gamenum = atoi(star_match[0]);
3020 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3021 gamenum == ics_gamenum) {
3022 /* We were already playing or observing this game;
3023 no need to refetch history */
3024 gameMode = IcsExamining;
3026 pauseExamForwardMostMove = forwardMostMove;
3027 } else if (currentMove < forwardMostMove) {
3028 ForwardInner(forwardMostMove);
3031 /* I don't think this case really can happen */
3032 SendToICS(ics_prefix);
3033 SendToICS("refresh\n");
3038 /* Error messages */
3039 // if (ics_user_moved) {
3040 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3041 if (looking_at(buf, &i, "Illegal move") ||
3042 looking_at(buf, &i, "Not a legal move") ||
3043 looking_at(buf, &i, "Your king is in check") ||
3044 looking_at(buf, &i, "It isn't your turn") ||
3045 looking_at(buf, &i, "It is not your move")) {
3047 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3048 currentMove = --forwardMostMove;
3049 DisplayMove(currentMove - 1); /* before DMError */
3050 DrawPosition(FALSE, boards[currentMove]);
3052 DisplayBothClocks();
3054 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3060 if (looking_at(buf, &i, "still have time") ||
3061 looking_at(buf, &i, "not out of time") ||
3062 looking_at(buf, &i, "either player is out of time") ||
3063 looking_at(buf, &i, "has timeseal; checking")) {
3064 /* We must have called his flag a little too soon */
3065 whiteFlag = blackFlag = FALSE;
3069 if (looking_at(buf, &i, "added * seconds to") ||
3070 looking_at(buf, &i, "seconds were added to")) {
3071 /* Update the clocks */
3072 SendToICS(ics_prefix);
3073 SendToICS("refresh\n");
3077 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3078 ics_clock_paused = TRUE;
3083 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3084 ics_clock_paused = FALSE;
3089 /* Grab player ratings from the Creating: message.
3090 Note we have to check for the special case when
3091 the ICS inserts things like [white] or [black]. */
3092 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3093 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3095 0 player 1 name (not necessarily white)
3097 2 empty, white, or black (IGNORED)
3098 3 player 2 name (not necessarily black)
3101 The names/ratings are sorted out when the game
3102 actually starts (below).
3104 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3105 player1Rating = string_to_rating(star_match[1]);
3106 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3107 player2Rating = string_to_rating(star_match[4]);
3109 if (appData.debugMode)
3111 "Ratings from 'Creating:' %s %d, %s %d\n",
3112 player1Name, player1Rating,
3113 player2Name, player2Rating);
3118 /* Improved generic start/end-of-game messages */
3119 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3120 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3121 /* If tkind == 0: */
3122 /* star_match[0] is the game number */
3123 /* [1] is the white player's name */
3124 /* [2] is the black player's name */
3125 /* For end-of-game: */
3126 /* [3] is the reason for the game end */
3127 /* [4] is a PGN end game-token, preceded by " " */
3128 /* For start-of-game: */
3129 /* [3] begins with "Creating" or "Continuing" */
3130 /* [4] is " *" or empty (don't care). */
3131 int gamenum = atoi(star_match[0]);
3132 char *whitename, *blackname, *why, *endtoken;
3133 ChessMove endtype = (ChessMove) 0;
3136 whitename = star_match[1];
3137 blackname = star_match[2];
3138 why = star_match[3];
3139 endtoken = star_match[4];
3141 whitename = star_match[1];
3142 blackname = star_match[3];
3143 why = star_match[5];
3144 endtoken = star_match[6];
3147 /* Game start messages */
3148 if (strncmp(why, "Creating ", 9) == 0 ||
3149 strncmp(why, "Continuing ", 11) == 0) {
3150 gs_gamenum = gamenum;
3151 strcpy(gs_kind, strchr(why, ' ') + 1);
3153 if (appData.zippyPlay) {
3154 ZippyGameStart(whitename, blackname);
3160 /* Game end messages */
3161 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3162 ics_gamenum != gamenum) {
3165 while (endtoken[0] == ' ') endtoken++;
3166 switch (endtoken[0]) {
3169 endtype = GameUnfinished;
3172 endtype = BlackWins;
3175 if (endtoken[1] == '/')
3176 endtype = GameIsDrawn;
3178 endtype = WhiteWins;
3181 GameEnds(endtype, why, GE_ICS);
3183 if (appData.zippyPlay && first.initDone) {
3184 ZippyGameEnd(endtype, why);
3185 if (first.pr == NULL) {
3186 /* Start the next process early so that we'll
3187 be ready for the next challenge */
3188 StartChessProgram(&first);
3190 /* Send "new" early, in case this command takes
3191 a long time to finish, so that we'll be ready
3192 for the next challenge. */
3193 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3200 if (looking_at(buf, &i, "Removing game * from observation") ||
3201 looking_at(buf, &i, "no longer observing game *") ||
3202 looking_at(buf, &i, "Game * (*) has no examiners")) {
3203 if (gameMode == IcsObserving &&
3204 atoi(star_match[0]) == ics_gamenum)
3206 /* icsEngineAnalyze */
3207 if (appData.icsEngineAnalyze) {
3214 ics_user_moved = FALSE;
3219 if (looking_at(buf, &i, "no longer examining game *")) {
3220 if (gameMode == IcsExamining &&
3221 atoi(star_match[0]) == ics_gamenum)
3225 ics_user_moved = FALSE;
3230 /* Advance leftover_start past any newlines we find,
3231 so only partial lines can get reparsed */
3232 if (looking_at(buf, &i, "\n")) {
3233 prevColor = curColor;
3234 if (curColor != ColorNormal) {
3235 if (oldi > next_out) {
3236 SendToPlayer(&buf[next_out], oldi - next_out);
3239 Colorize(ColorNormal, FALSE);
3240 curColor = ColorNormal;
3242 if (started == STARTED_BOARD) {
3243 started = STARTED_NONE;
3244 parse[parse_pos] = NULLCHAR;
3245 ParseBoard12(parse);
3248 /* Send premove here */
3249 if (appData.premove) {
3251 if (currentMove == 0 &&
3252 gameMode == IcsPlayingWhite &&
3253 appData.premoveWhite) {
3254 sprintf(str, "%s\n", appData.premoveWhiteText);
3255 if (appData.debugMode)
3256 fprintf(debugFP, "Sending premove:\n");
3258 } else if (currentMove == 1 &&
3259 gameMode == IcsPlayingBlack &&
3260 appData.premoveBlack) {
3261 sprintf(str, "%s\n", appData.premoveBlackText);
3262 if (appData.debugMode)
3263 fprintf(debugFP, "Sending premove:\n");
3265 } else if (gotPremove) {
3267 ClearPremoveHighlights();
3268 if (appData.debugMode)
3269 fprintf(debugFP, "Sending premove:\n");
3270 UserMoveEvent(premoveFromX, premoveFromY,
3271 premoveToX, premoveToY,
3276 /* Usually suppress following prompt */
3277 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3278 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3279 if (looking_at(buf, &i, "*% ")) {
3280 savingComment = FALSE;
3284 } else if (started == STARTED_HOLDINGS) {
3286 char new_piece[MSG_SIZ];
3287 started = STARTED_NONE;
3288 parse[parse_pos] = NULLCHAR;
3289 if (appData.debugMode)
3290 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3291 parse, currentMove);
3292 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3293 gamenum == ics_gamenum) {
3294 if (gameInfo.variant == VariantNormal) {
3295 /* [HGM] We seem to switch variant during a game!
3296 * Presumably no holdings were displayed, so we have
3297 * to move the position two files to the right to
3298 * create room for them!
3300 VariantClass newVariant;
3301 switch(gameInfo.boardWidth) { // base guess on board width
3302 case 9: newVariant = VariantShogi; break;
3303 case 10: newVariant = VariantGreat; break;
3304 default: newVariant = VariantCrazyhouse; break;
3306 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3307 /* Get a move list just to see the header, which
3308 will tell us whether this is really bug or zh */
3309 if (ics_getting_history == H_FALSE) {
3310 ics_getting_history = H_REQUESTED;
3311 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3315 new_piece[0] = NULLCHAR;
3316 sscanf(parse, "game %d white [%s black [%s <- %s",
3317 &gamenum, white_holding, black_holding,
3319 white_holding[strlen(white_holding)-1] = NULLCHAR;
3320 black_holding[strlen(black_holding)-1] = NULLCHAR;
3321 /* [HGM] copy holdings to board holdings area */
3322 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3323 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3324 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3326 if (appData.zippyPlay && first.initDone) {
3327 ZippyHoldings(white_holding, black_holding,
3331 if (tinyLayout || smallLayout) {
3332 char wh[16], bh[16];
3333 PackHolding(wh, white_holding);
3334 PackHolding(bh, black_holding);
3335 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3336 gameInfo.white, gameInfo.black);
3338 sprintf(str, "%s [%s] vs. %s [%s]",
3339 gameInfo.white, white_holding,
3340 gameInfo.black, black_holding);
3343 DrawPosition(FALSE, boards[currentMove]);
3346 /* Suppress following prompt */
3347 if (looking_at(buf, &i, "*% ")) {
3348 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3349 savingComment = FALSE;
3356 i++; /* skip unparsed character and loop back */
3359 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3360 started != STARTED_HOLDINGS && i > next_out) {
3361 SendToPlayer(&buf[next_out], i - next_out);
3364 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3366 leftover_len = buf_len - leftover_start;
3367 /* if buffer ends with something we couldn't parse,
3368 reparse it after appending the next read */
3370 } else if (count == 0) {
3371 RemoveInputSource(isr);
3372 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3374 DisplayFatalError(_("Error reading from ICS"), error, 1);
3379 /* Board style 12 looks like this:
3381 <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
3383 * The "<12> " is stripped before it gets to this routine. The two
3384 * trailing 0's (flip state and clock ticking) are later addition, and
3385 * some chess servers may not have them, or may have only the first.
3386 * Additional trailing fields may be added in the future.
3389 #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"
3391 #define RELATION_OBSERVING_PLAYED 0
3392 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3393 #define RELATION_PLAYING_MYMOVE 1
3394 #define RELATION_PLAYING_NOTMYMOVE -1
3395 #define RELATION_EXAMINING 2
3396 #define RELATION_ISOLATED_BOARD -3
3397 #define RELATION_STARTING_POSITION -4 /* FICS only */
3400 ParseBoard12(string)
3403 GameMode newGameMode;
3404 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3405 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3406 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3407 char to_play, board_chars[200];
3408 char move_str[500], str[500], elapsed_time[500];
3409 char black[32], white[32];
3411 int prevMove = currentMove;
3414 int fromX, fromY, toX, toY;
3416 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3417 char *bookHit = NULL; // [HGM] book
3418 Boolean weird = FALSE, reqFlag = FALSE;
3420 fromX = fromY = toX = toY = -1;
3424 if (appData.debugMode)
3425 fprintf(debugFP, _("Parsing board: %s\n"), string);
3427 move_str[0] = NULLCHAR;
3428 elapsed_time[0] = NULLCHAR;
3429 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3431 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3432 if(string[i] == ' ') { ranks++; files = 0; }
3434 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3437 for(j = 0; j <i; j++) board_chars[j] = string[j];
3438 board_chars[i] = '\0';
3441 n = sscanf(string, PATTERN, &to_play, &double_push,
3442 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3443 &gamenum, white, black, &relation, &basetime, &increment,
3444 &white_stren, &black_stren, &white_time, &black_time,
3445 &moveNum, str, elapsed_time, move_str, &ics_flip,
3449 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3450 DisplayError(str, 0);
3454 /* Convert the move number to internal form */
3455 moveNum = (moveNum - 1) * 2;
3456 if (to_play == 'B') moveNum++;
3457 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3458 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3464 case RELATION_OBSERVING_PLAYED:
3465 case RELATION_OBSERVING_STATIC:
3466 if (gamenum == -1) {
3467 /* Old ICC buglet */
3468 relation = RELATION_OBSERVING_STATIC;
3470 newGameMode = IcsObserving;
3472 case RELATION_PLAYING_MYMOVE:
3473 case RELATION_PLAYING_NOTMYMOVE:
3475 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3476 IcsPlayingWhite : IcsPlayingBlack;
3478 case RELATION_EXAMINING:
3479 newGameMode = IcsExamining;
3481 case RELATION_ISOLATED_BOARD:
3483 /* Just display this board. If user was doing something else,
3484 we will forget about it until the next board comes. */
3485 newGameMode = IcsIdle;
3487 case RELATION_STARTING_POSITION:
3488 newGameMode = gameMode;
3492 /* Modify behavior for initial board display on move listing
3495 switch (ics_getting_history) {
3499 case H_GOT_REQ_HEADER:
3500 case H_GOT_UNREQ_HEADER:
3501 /* This is the initial position of the current game */
3502 gamenum = ics_gamenum;
3503 moveNum = 0; /* old ICS bug workaround */
3504 if (to_play == 'B') {
3505 startedFromSetupPosition = TRUE;
3506 blackPlaysFirst = TRUE;
3508 if (forwardMostMove == 0) forwardMostMove = 1;
3509 if (backwardMostMove == 0) backwardMostMove = 1;
3510 if (currentMove == 0) currentMove = 1;
3512 newGameMode = gameMode;
3513 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3515 case H_GOT_UNWANTED_HEADER:
3516 /* This is an initial board that we don't want */
3518 case H_GETTING_MOVES:
3519 /* Should not happen */
3520 DisplayError(_("Error gathering move list: extra board"), 0);
3521 ics_getting_history = H_FALSE;
3525 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3526 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3527 /* [HGM] We seem to have switched variant unexpectedly
3528 * Try to guess new variant from board size
3530 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3531 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3532 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3533 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3534 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3535 if(!weird) newVariant = VariantNormal;
3536 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3537 /* Get a move list just to see the header, which
3538 will tell us whether this is really bug or zh */
3539 if (ics_getting_history == H_FALSE) {
3540 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3541 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3546 /* Take action if this is the first board of a new game, or of a
3547 different game than is currently being displayed. */
3548 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3549 relation == RELATION_ISOLATED_BOARD) {
3551 /* Forget the old game and get the history (if any) of the new one */
3552 if (gameMode != BeginningOfGame) {
3556 if (appData.autoRaiseBoard) BoardToTop();
3558 if (gamenum == -1) {
3559 newGameMode = IcsIdle;
3560 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3561 appData.getMoveList && !reqFlag) {
3562 /* Need to get game history */
3563 ics_getting_history = H_REQUESTED;
3564 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3568 /* Initially flip the board to have black on the bottom if playing
3569 black or if the ICS flip flag is set, but let the user change
3570 it with the Flip View button. */
3571 flipView = appData.autoFlipView ?
3572 (newGameMode == IcsPlayingBlack) || ics_flip :
3575 /* Done with values from previous mode; copy in new ones */
3576 gameMode = newGameMode;
3578 ics_gamenum = gamenum;
3579 if (gamenum == gs_gamenum) {
3580 int klen = strlen(gs_kind);
3581 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3582 sprintf(str, "ICS %s", gs_kind);
3583 gameInfo.event = StrSave(str);
3585 gameInfo.event = StrSave("ICS game");
3587 gameInfo.site = StrSave(appData.icsHost);
3588 gameInfo.date = PGNDate();
3589 gameInfo.round = StrSave("-");
3590 gameInfo.white = StrSave(white);
3591 gameInfo.black = StrSave(black);
3592 timeControl = basetime * 60 * 1000;
3594 timeIncrement = increment * 1000;
3595 movesPerSession = 0;
3596 gameInfo.timeControl = TimeControlTagValue();
3597 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3598 if (appData.debugMode) {
3599 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3600 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3601 setbuf(debugFP, NULL);
3604 gameInfo.outOfBook = NULL;
3606 /* Do we have the ratings? */
3607 if (strcmp(player1Name, white) == 0 &&
3608 strcmp(player2Name, black) == 0) {
3609 if (appData.debugMode)
3610 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3611 player1Rating, player2Rating);
3612 gameInfo.whiteRating = player1Rating;
3613 gameInfo.blackRating = player2Rating;
3614 } else if (strcmp(player2Name, white) == 0 &&
3615 strcmp(player1Name, black) == 0) {
3616 if (appData.debugMode)
3617 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3618 player2Rating, player1Rating);
3619 gameInfo.whiteRating = player2Rating;
3620 gameInfo.blackRating = player1Rating;
3622 player1Name[0] = player2Name[0] = NULLCHAR;
3624 /* Silence shouts if requested */
3625 if (appData.quietPlay &&
3626 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3627 SendToICS(ics_prefix);
3628 SendToICS("set shout 0\n");
3632 /* Deal with midgame name changes */
3634 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3635 if (gameInfo.white) free(gameInfo.white);
3636 gameInfo.white = StrSave(white);
3638 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3639 if (gameInfo.black) free(gameInfo.black);
3640 gameInfo.black = StrSave(black);
3644 /* Throw away game result if anything actually changes in examine mode */
3645 if (gameMode == IcsExamining && !newGame) {
3646 gameInfo.result = GameUnfinished;
3647 if (gameInfo.resultDetails != NULL) {
3648 free(gameInfo.resultDetails);
3649 gameInfo.resultDetails = NULL;
3653 /* In pausing && IcsExamining mode, we ignore boards coming
3654 in if they are in a different variation than we are. */
3655 if (pauseExamInvalid) return;
3656 if (pausing && gameMode == IcsExamining) {
3657 if (moveNum <= pauseExamForwardMostMove) {
3658 pauseExamInvalid = TRUE;
3659 forwardMostMove = pauseExamForwardMostMove;
3664 if (appData.debugMode) {
3665 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3667 /* Parse the board */
3668 for (k = 0; k < ranks; k++) {
3669 for (j = 0; j < files; j++)
3670 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3671 if(gameInfo.holdingsWidth > 1) {
3672 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3673 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3676 CopyBoard(boards[moveNum], board);
3677 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3679 startedFromSetupPosition =
3680 !CompareBoards(board, initialPosition);
3681 if(startedFromSetupPosition)
3682 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3685 /* [HGM] Set castling rights. Take the outermost Rooks,
3686 to make it also work for FRC opening positions. Note that board12
3687 is really defective for later FRC positions, as it has no way to
3688 indicate which Rook can castle if they are on the same side of King.
3689 For the initial position we grant rights to the outermost Rooks,
3690 and remember thos rights, and we then copy them on positions
3691 later in an FRC game. This means WB might not recognize castlings with
3692 Rooks that have moved back to their original position as illegal,
3693 but in ICS mode that is not its job anyway.
3695 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3696 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3698 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3699 if(board[0][i] == WhiteRook) j = i;
3700 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3702 if(board[0][i] == WhiteRook) j = i;
3703 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3705 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3708 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3709 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3711 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3712 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3713 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3714 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3715 if(board[BOARD_HEIGHT-1][k] == bKing)
3716 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3718 r = boards[moveNum][CASTLING][0] = initialRights[0];
3719 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3720 r = boards[moveNum][CASTLING][1] = initialRights[1];
3721 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3722 r = boards[moveNum][CASTLING][3] = initialRights[3];
3723 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3724 r = boards[moveNum][CASTLING][4] = initialRights[4];
3725 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3726 /* wildcastle kludge: always assume King has rights */
3727 r = boards[moveNum][CASTLING][2] = initialRights[2];
3728 r = boards[moveNum][CASTLING][5] = initialRights[5];
3730 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3731 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3734 if (ics_getting_history == H_GOT_REQ_HEADER ||
3735 ics_getting_history == H_GOT_UNREQ_HEADER) {
3736 /* This was an initial position from a move list, not
3737 the current position */
3741 /* Update currentMove and known move number limits */
3742 newMove = newGame || moveNum > forwardMostMove;
3745 forwardMostMove = backwardMostMove = currentMove = moveNum;
3746 if (gameMode == IcsExamining && moveNum == 0) {
3747 /* Workaround for ICS limitation: we are not told the wild
3748 type when starting to examine a game. But if we ask for
3749 the move list, the move list header will tell us */
3750 ics_getting_history = H_REQUESTED;
3751 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3754 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3755 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3757 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3758 /* [HGM] applied this also to an engine that is silently watching */
3759 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3760 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3761 gameInfo.variant == currentlyInitializedVariant) {
3762 takeback = forwardMostMove - moveNum;
3763 for (i = 0; i < takeback; i++) {
3764 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3765 SendToProgram("undo\n", &first);
3770 forwardMostMove = moveNum;
3771 if (!pausing || currentMove > forwardMostMove)
3772 currentMove = forwardMostMove;
3774 /* New part of history that is not contiguous with old part */
3775 if (pausing && gameMode == IcsExamining) {
3776 pauseExamInvalid = TRUE;
3777 forwardMostMove = pauseExamForwardMostMove;
3780 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3782 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3783 // [HGM] when we will receive the move list we now request, it will be
3784 // fed to the engine from the first move on. So if the engine is not
3785 // in the initial position now, bring it there.
3786 InitChessProgram(&first, 0);
3789 ics_getting_history = H_REQUESTED;
3790 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3793 forwardMostMove = backwardMostMove = currentMove = moveNum;
3796 /* Update the clocks */
3797 if (strchr(elapsed_time, '.')) {
3799 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3800 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3802 /* Time is in seconds */
3803 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3804 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3809 if (appData.zippyPlay && newGame &&
3810 gameMode != IcsObserving && gameMode != IcsIdle &&
3811 gameMode != IcsExamining)
3812 ZippyFirstBoard(moveNum, basetime, increment);
3815 /* Put the move on the move list, first converting
3816 to canonical algebraic form. */
3818 if (appData.debugMode) {
3819 if (appData.debugMode) { int f = forwardMostMove;
3820 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3821 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3822 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3824 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3825 fprintf(debugFP, "moveNum = %d\n", moveNum);
3826 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3827 setbuf(debugFP, NULL);
3829 if (moveNum <= backwardMostMove) {
3830 /* We don't know what the board looked like before
3832 strcpy(parseList[moveNum - 1], move_str);
3833 strcat(parseList[moveNum - 1], " ");
3834 strcat(parseList[moveNum - 1], elapsed_time);
3835 moveList[moveNum - 1][0] = NULLCHAR;
3836 } else if (strcmp(move_str, "none") == 0) {
3837 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3838 /* Again, we don't know what the board looked like;
3839 this is really the start of the game. */
3840 parseList[moveNum - 1][0] = NULLCHAR;
3841 moveList[moveNum - 1][0] = NULLCHAR;
3842 backwardMostMove = moveNum;
3843 startedFromSetupPosition = TRUE;
3844 fromX = fromY = toX = toY = -1;
3846 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3847 // So we parse the long-algebraic move string in stead of the SAN move
3848 int valid; char buf[MSG_SIZ], *prom;
3850 // str looks something like "Q/a1-a2"; kill the slash
3852 sprintf(buf, "%c%s", str[0], str+2);
3853 else strcpy(buf, str); // might be castling
3854 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3855 strcat(buf, prom); // long move lacks promo specification!
3856 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3857 if(appData.debugMode)
3858 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3859 strcpy(move_str, buf);
3861 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3862 &fromX, &fromY, &toX, &toY, &promoChar)
3863 || ParseOneMove(buf, moveNum - 1, &moveType,
3864 &fromX, &fromY, &toX, &toY, &promoChar);
3865 // end of long SAN patch
3867 (void) CoordsToAlgebraic(boards[moveNum - 1],
3868 PosFlags(moveNum - 1),
3869 fromY, fromX, toY, toX, promoChar,
3870 parseList[moveNum-1]);
3871 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3877 if(gameInfo.variant != VariantShogi)
3878 strcat(parseList[moveNum - 1], "+");
3881 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3882 strcat(parseList[moveNum - 1], "#");
3885 strcat(parseList[moveNum - 1], " ");
3886 strcat(parseList[moveNum - 1], elapsed_time);
3887 /* currentMoveString is set as a side-effect of ParseOneMove */
3888 strcpy(moveList[moveNum - 1], currentMoveString);
3889 strcat(moveList[moveNum - 1], "\n");
3891 /* Move from ICS was illegal!? Punt. */
3892 if (appData.debugMode) {
3893 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3894 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3896 strcpy(parseList[moveNum - 1], move_str);
3897 strcat(parseList[moveNum - 1], " ");
3898 strcat(parseList[moveNum - 1], elapsed_time);
3899 moveList[moveNum - 1][0] = NULLCHAR;
3900 fromX = fromY = toX = toY = -1;
3903 if (appData.debugMode) {
3904 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3905 setbuf(debugFP, NULL);
3909 /* Send move to chess program (BEFORE animating it). */
3910 if (appData.zippyPlay && !newGame && newMove &&
3911 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3913 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3914 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3915 if (moveList[moveNum - 1][0] == NULLCHAR) {
3916 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3918 DisplayError(str, 0);
3920 if (first.sendTime) {
3921 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3923 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3924 if (firstMove && !bookHit) {
3926 if (first.useColors) {
3927 SendToProgram(gameMode == IcsPlayingWhite ?
3929 "black\ngo\n", &first);
3931 SendToProgram("go\n", &first);
3933 first.maybeThinking = TRUE;
3936 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3937 if (moveList[moveNum - 1][0] == NULLCHAR) {
3938 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3939 DisplayError(str, 0);
3941 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3942 SendMoveToProgram(moveNum - 1, &first);
3949 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3950 /* If move comes from a remote source, animate it. If it
3951 isn't remote, it will have already been animated. */
3952 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3953 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3955 if (!pausing && appData.highlightLastMove) {
3956 SetHighlights(fromX, fromY, toX, toY);
3960 /* Start the clocks */
3961 whiteFlag = blackFlag = FALSE;
3962 appData.clockMode = !(basetime == 0 && increment == 0);
3964 ics_clock_paused = TRUE;
3966 } else if (ticking == 1) {
3967 ics_clock_paused = FALSE;
3969 if (gameMode == IcsIdle ||
3970 relation == RELATION_OBSERVING_STATIC ||
3971 relation == RELATION_EXAMINING ||
3973 DisplayBothClocks();
3977 /* Display opponents and material strengths */
3978 if (gameInfo.variant != VariantBughouse &&
3979 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3980 if (tinyLayout || smallLayout) {
3981 if(gameInfo.variant == VariantNormal)
3982 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3983 gameInfo.white, white_stren, gameInfo.black, black_stren,
3984 basetime, increment);
3986 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3987 gameInfo.white, white_stren, gameInfo.black, black_stren,
3988 basetime, increment, (int) gameInfo.variant);
3990 if(gameInfo.variant == VariantNormal)
3991 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3992 gameInfo.white, white_stren, gameInfo.black, black_stren,
3993 basetime, increment);
3995 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3996 gameInfo.white, white_stren, gameInfo.black, black_stren,
3997 basetime, increment, VariantName(gameInfo.variant));
4000 if (appData.debugMode) {
4001 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4006 /* Display the board */
4007 if (!pausing && !appData.noGUI) {
4009 if (appData.premove)
4011 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4012 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4013 ClearPremoveHighlights();
4015 DrawPosition(FALSE, boards[currentMove]);
4016 DisplayMove(moveNum - 1);
4017 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4018 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4019 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4020 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4024 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4026 if(bookHit) { // [HGM] book: simulate book reply
4027 static char bookMove[MSG_SIZ]; // a bit generous?
4029 programStats.nodes = programStats.depth = programStats.time =
4030 programStats.score = programStats.got_only_move = 0;
4031 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4033 strcpy(bookMove, "move ");
4034 strcat(bookMove, bookHit);
4035 HandleMachineMove(bookMove, &first);
4044 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4045 ics_getting_history = H_REQUESTED;
4046 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4052 AnalysisPeriodicEvent(force)
4055 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4056 && !force) || !appData.periodicUpdates)
4059 /* Send . command to Crafty to collect stats */
4060 SendToProgram(".\n", &first);
4062 /* Don't send another until we get a response (this makes
4063 us stop sending to old Crafty's which don't understand
4064 the "." command (sending illegal cmds resets node count & time,
4065 which looks bad)) */
4066 programStats.ok_to_send = 0;
4069 void ics_update_width(new_width)
4072 ics_printf("set width %d\n", new_width);
4076 SendMoveToProgram(moveNum, cps)
4078 ChessProgramState *cps;
4082 if (cps->useUsermove) {
4083 SendToProgram("usermove ", cps);
4087 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4088 int len = space - parseList[moveNum];
4089 memcpy(buf, parseList[moveNum], len);
4091 buf[len] = NULLCHAR;
4093 sprintf(buf, "%s\n", parseList[moveNum]);
4095 SendToProgram(buf, cps);
4097 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4098 AlphaRank(moveList[moveNum], 4);
4099 SendToProgram(moveList[moveNum], cps);
4100 AlphaRank(moveList[moveNum], 4); // and back
4102 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4103 * the engine. It would be nice to have a better way to identify castle
4105 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4106 && cps->useOOCastle) {
4107 int fromX = moveList[moveNum][0] - AAA;
4108 int fromY = moveList[moveNum][1] - ONE;
4109 int toX = moveList[moveNum][2] - AAA;
4110 int toY = moveList[moveNum][3] - ONE;
4111 if((boards[moveNum][fromY][fromX] == WhiteKing
4112 && boards[moveNum][toY][toX] == WhiteRook)
4113 || (boards[moveNum][fromY][fromX] == BlackKing
4114 && boards[moveNum][toY][toX] == BlackRook)) {
4115 if(toX > fromX) SendToProgram("O-O\n", cps);
4116 else SendToProgram("O-O-O\n", cps);
4118 else SendToProgram(moveList[moveNum], cps);
4120 else SendToProgram(moveList[moveNum], cps);
4121 /* End of additions by Tord */
4124 /* [HGM] setting up the opening has brought engine in force mode! */
4125 /* Send 'go' if we are in a mode where machine should play. */
4126 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4127 (gameMode == TwoMachinesPlay ||
4129 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4131 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4132 SendToProgram("go\n", cps);
4133 if (appData.debugMode) {
4134 fprintf(debugFP, "(extra)\n");
4137 setboardSpoiledMachineBlack = 0;
4141 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4143 int fromX, fromY, toX, toY;
4145 char user_move[MSG_SIZ];
4149 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4150 (int)moveType, fromX, fromY, toX, toY);
4151 DisplayError(user_move + strlen("say "), 0);
4153 case WhiteKingSideCastle:
4154 case BlackKingSideCastle:
4155 case WhiteQueenSideCastleWild:
4156 case BlackQueenSideCastleWild:
4158 case WhiteHSideCastleFR:
4159 case BlackHSideCastleFR:
4161 sprintf(user_move, "o-o\n");
4163 case WhiteQueenSideCastle:
4164 case BlackQueenSideCastle:
4165 case WhiteKingSideCastleWild:
4166 case BlackKingSideCastleWild:
4168 case WhiteASideCastleFR:
4169 case BlackASideCastleFR:
4171 sprintf(user_move, "o-o-o\n");
4173 case WhitePromotionQueen:
4174 case BlackPromotionQueen:
4175 case WhitePromotionRook:
4176 case BlackPromotionRook:
4177 case WhitePromotionBishop:
4178 case BlackPromotionBishop:
4179 case WhitePromotionKnight:
4180 case BlackPromotionKnight:
4181 case WhitePromotionKing:
4182 case BlackPromotionKing:
4183 case WhitePromotionChancellor:
4184 case BlackPromotionChancellor:
4185 case WhitePromotionArchbishop:
4186 case BlackPromotionArchbishop:
4187 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4188 sprintf(user_move, "%c%c%c%c=%c\n",
4189 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4190 PieceToChar(WhiteFerz));
4191 else if(gameInfo.variant == VariantGreat)
4192 sprintf(user_move, "%c%c%c%c=%c\n",
4193 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4194 PieceToChar(WhiteMan));
4196 sprintf(user_move, "%c%c%c%c=%c\n",
4197 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4198 PieceToChar(PromoPiece(moveType)));
4202 sprintf(user_move, "%c@%c%c\n",
4203 ToUpper(PieceToChar((ChessSquare) fromX)),
4204 AAA + toX, ONE + toY);
4207 case WhiteCapturesEnPassant:
4208 case BlackCapturesEnPassant:
4209 case IllegalMove: /* could be a variant we don't quite understand */
4210 sprintf(user_move, "%c%c%c%c\n",
4211 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4214 SendToICS(user_move);
4215 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4216 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4220 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4225 if (rf == DROP_RANK) {
4226 sprintf(move, "%c@%c%c\n",
4227 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4229 if (promoChar == 'x' || promoChar == NULLCHAR) {
4230 sprintf(move, "%c%c%c%c\n",
4231 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4233 sprintf(move, "%c%c%c%c%c\n",
4234 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4240 ProcessICSInitScript(f)
4245 while (fgets(buf, MSG_SIZ, f)) {
4246 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4253 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4255 AlphaRank(char *move, int n)
4257 // char *p = move, c; int x, y;
4259 if (appData.debugMode) {
4260 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4264 move[2]>='0' && move[2]<='9' &&
4265 move[3]>='a' && move[3]<='x' ) {
4267 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4268 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4270 if(move[0]>='0' && move[0]<='9' &&
4271 move[1]>='a' && move[1]<='x' &&
4272 move[2]>='0' && move[2]<='9' &&
4273 move[3]>='a' && move[3]<='x' ) {
4274 /* input move, Shogi -> normal */
4275 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4276 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4277 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4278 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4281 move[3]>='0' && move[3]<='9' &&
4282 move[2]>='a' && move[2]<='x' ) {
4284 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4285 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4288 move[0]>='a' && move[0]<='x' &&
4289 move[3]>='0' && move[3]<='9' &&
4290 move[2]>='a' && move[2]<='x' ) {
4291 /* output move, normal -> Shogi */
4292 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4293 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4294 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4295 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4296 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4298 if (appData.debugMode) {
4299 fprintf(debugFP, " out = '%s'\n", move);
4303 /* Parser for moves from gnuchess, ICS, or user typein box */
4305 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4308 ChessMove *moveType;
4309 int *fromX, *fromY, *toX, *toY;
4312 if (appData.debugMode) {
4313 fprintf(debugFP, "move to parse: %s\n", move);
4315 *moveType = yylexstr(moveNum, move);
4317 switch (*moveType) {
4318 case WhitePromotionChancellor:
4319 case BlackPromotionChancellor:
4320 case WhitePromotionArchbishop:
4321 case BlackPromotionArchbishop:
4322 case WhitePromotionQueen:
4323 case BlackPromotionQueen:
4324 case WhitePromotionRook:
4325 case BlackPromotionRook:
4326 case WhitePromotionBishop:
4327 case BlackPromotionBishop:
4328 case WhitePromotionKnight:
4329 case BlackPromotionKnight:
4330 case WhitePromotionKing:
4331 case BlackPromotionKing:
4333 case WhiteCapturesEnPassant:
4334 case BlackCapturesEnPassant:
4335 case WhiteKingSideCastle:
4336 case WhiteQueenSideCastle:
4337 case BlackKingSideCastle:
4338 case BlackQueenSideCastle:
4339 case WhiteKingSideCastleWild:
4340 case WhiteQueenSideCastleWild:
4341 case BlackKingSideCastleWild:
4342 case BlackQueenSideCastleWild:
4343 /* Code added by Tord: */
4344 case WhiteHSideCastleFR:
4345 case WhiteASideCastleFR:
4346 case BlackHSideCastleFR:
4347 case BlackASideCastleFR:
4348 /* End of code added by Tord */
4349 case IllegalMove: /* bug or odd chess variant */
4350 *fromX = currentMoveString[0] - AAA;
4351 *fromY = currentMoveString[1] - ONE;
4352 *toX = currentMoveString[2] - AAA;
4353 *toY = currentMoveString[3] - ONE;
4354 *promoChar = currentMoveString[4];
4355 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4356 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4357 if (appData.debugMode) {
4358 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4360 *fromX = *fromY = *toX = *toY = 0;
4363 if (appData.testLegality) {
4364 return (*moveType != IllegalMove);
4366 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4367 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4372 *fromX = *moveType == WhiteDrop ?
4373 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4374 (int) CharToPiece(ToLower(currentMoveString[0]));
4376 *toX = currentMoveString[2] - AAA;
4377 *toY = currentMoveString[3] - ONE;
4378 *promoChar = NULLCHAR;
4382 case ImpossibleMove:
4383 case (ChessMove) 0: /* end of file */
4392 if (appData.debugMode) {
4393 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4396 *fromX = *fromY = *toX = *toY = 0;
4397 *promoChar = NULLCHAR;
4405 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4406 int fromX, fromY, toX, toY; char promoChar;
4411 endPV = forwardMostMove;
4413 while(*pv == ' ') pv++;
4414 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4415 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4416 if(appData.debugMode){
4417 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4419 if(!valid && nr == 0 &&
4420 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4421 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4423 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4424 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4426 if(endPV+1 > framePtr) break; // no space, truncate
4429 CopyBoard(boards[endPV], boards[endPV-1]);
4430 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4431 moveList[endPV-1][0] = fromX + AAA;
4432 moveList[endPV-1][1] = fromY + ONE;
4433 moveList[endPV-1][2] = toX + AAA;
4434 moveList[endPV-1][3] = toY + ONE;
4435 parseList[endPV-1][0] = NULLCHAR;
4437 currentMove = endPV;
4438 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4439 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4440 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4441 DrawPosition(TRUE, boards[currentMove]);
4444 static int lastX, lastY;
4447 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4451 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4452 lastX = x; lastY = y;
4453 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4455 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4457 while(buf[index] && buf[index] != '\n') index++;
4459 ParsePV(buf+startPV);
4460 *start = startPV; *end = index-1;
4465 LoadPV(int x, int y)
4466 { // called on right mouse click to load PV
4467 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4468 lastX = x; lastY = y;
4469 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4476 if(endPV < 0) return;
4478 currentMove = forwardMostMove;
4479 ClearPremoveHighlights();
4480 DrawPosition(TRUE, boards[currentMove]);
4484 MovePV(int x, int y, int h)
4485 { // step through PV based on mouse coordinates (called on mouse move)
4486 int margin = h>>3, step = 0;
4488 if(endPV < 0) return;
4489 // we must somehow check if right button is still down (might be released off board!)
4490 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4491 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4492 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4494 lastX = x; lastY = y;
4495 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4496 currentMove += step;
4497 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4498 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4499 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4500 DrawPosition(FALSE, boards[currentMove]);
4504 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4505 // All positions will have equal probability, but the current method will not provide a unique
4506 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4512 int piecesLeft[(int)BlackPawn];
4513 int seed, nrOfShuffles;
4515 void GetPositionNumber()
4516 { // sets global variable seed
4519 seed = appData.defaultFrcPosition;
4520 if(seed < 0) { // randomize based on time for negative FRC position numbers
4521 for(i=0; i<50; i++) seed += random();
4522 seed = random() ^ random() >> 8 ^ random() << 8;
4523 if(seed<0) seed = -seed;
4527 int put(Board board, int pieceType, int rank, int n, int shade)
4528 // put the piece on the (n-1)-th empty squares of the given shade
4532 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4533 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4534 board[rank][i] = (ChessSquare) pieceType;
4535 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4537 piecesLeft[pieceType]--;
4545 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4546 // calculate where the next piece goes, (any empty square), and put it there
4550 i = seed % squaresLeft[shade];
4551 nrOfShuffles *= squaresLeft[shade];
4552 seed /= squaresLeft[shade];
4553 put(board, pieceType, rank, i, shade);
4556 void AddTwoPieces(Board board, int pieceType, int rank)
4557 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4559 int i, n=squaresLeft[ANY], j=n-1, k;
4561 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4562 i = seed % k; // pick one
4565 while(i >= j) i -= j--;
4566 j = n - 1 - j; i += j;
4567 put(board, pieceType, rank, j, ANY);
4568 put(board, pieceType, rank, i, ANY);
4571 void SetUpShuffle(Board board, int number)
4575 GetPositionNumber(); nrOfShuffles = 1;
4577 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4578 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4579 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4581 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4583 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4584 p = (int) board[0][i];
4585 if(p < (int) BlackPawn) piecesLeft[p] ++;
4586 board[0][i] = EmptySquare;
4589 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4590 // shuffles restricted to allow normal castling put KRR first
4591 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4592 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4593 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4594 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4595 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4596 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4597 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4598 put(board, WhiteRook, 0, 0, ANY);
4599 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4602 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4603 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4604 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4605 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4606 while(piecesLeft[p] >= 2) {
4607 AddOnePiece(board, p, 0, LITE);
4608 AddOnePiece(board, p, 0, DARK);
4610 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4613 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4614 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4615 // but we leave King and Rooks for last, to possibly obey FRC restriction
4616 if(p == (int)WhiteRook) continue;
4617 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4618 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4621 // now everything is placed, except perhaps King (Unicorn) and Rooks
4623 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4624 // Last King gets castling rights
4625 while(piecesLeft[(int)WhiteUnicorn]) {
4626 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4627 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4630 while(piecesLeft[(int)WhiteKing]) {
4631 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4632 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4637 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4638 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4641 // Only Rooks can be left; simply place them all
4642 while(piecesLeft[(int)WhiteRook]) {
4643 i = put(board, WhiteRook, 0, 0, ANY);
4644 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4647 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4649 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4652 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4653 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4656 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4659 int SetCharTable( char *table, const char * map )
4660 /* [HGM] moved here from winboard.c because of its general usefulness */
4661 /* Basically a safe strcpy that uses the last character as King */
4663 int result = FALSE; int NrPieces;
4665 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4666 && NrPieces >= 12 && !(NrPieces&1)) {
4667 int i; /* [HGM] Accept even length from 12 to 34 */
4669 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4670 for( i=0; i<NrPieces/2-1; i++ ) {
4672 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4674 table[(int) WhiteKing] = map[NrPieces/2-1];
4675 table[(int) BlackKing] = map[NrPieces-1];
4683 void Prelude(Board board)
4684 { // [HGM] superchess: random selection of exo-pieces
4685 int i, j, k; ChessSquare p;
4686 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4688 GetPositionNumber(); // use FRC position number
4690 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4691 SetCharTable(pieceToChar, appData.pieceToCharTable);
4692 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4693 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4696 j = seed%4; seed /= 4;
4697 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4698 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4699 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4700 j = seed%3 + (seed%3 >= j); seed /= 3;
4701 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4702 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4703 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4704 j = seed%3; seed /= 3;
4705 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4706 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4707 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4708 j = seed%2 + (seed%2 >= j); seed /= 2;
4709 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4710 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4711 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4712 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4713 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4714 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4715 put(board, exoPieces[0], 0, 0, ANY);
4716 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4720 InitPosition(redraw)
4723 ChessSquare (* pieces)[BOARD_FILES];
4724 int i, j, pawnRow, overrule,
4725 oldx = gameInfo.boardWidth,
4726 oldy = gameInfo.boardHeight,
4727 oldh = gameInfo.holdingsWidth,
4728 oldv = gameInfo.variant;
4730 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4732 /* [AS] Initialize pv info list [HGM] and game status */
4734 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4735 pvInfoList[i].depth = 0;
4736 boards[i][EP_STATUS] = EP_NONE;
4737 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4740 initialRulePlies = 0; /* 50-move counter start */
4742 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4743 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4747 /* [HGM] logic here is completely changed. In stead of full positions */
4748 /* the initialized data only consist of the two backranks. The switch */
4749 /* selects which one we will use, which is than copied to the Board */
4750 /* initialPosition, which for the rest is initialized by Pawns and */
4751 /* empty squares. This initial position is then copied to boards[0], */
4752 /* possibly after shuffling, so that it remains available. */
4754 gameInfo.holdingsWidth = 0; /* default board sizes */
4755 gameInfo.boardWidth = 8;
4756 gameInfo.boardHeight = 8;
4757 gameInfo.holdingsSize = 0;
4758 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4759 for(i=0; i<BOARD_FILES-2; i++)
4760 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4761 initialPosition[EP_STATUS] = EP_NONE;
4762 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4764 switch (gameInfo.variant) {
4765 case VariantFischeRandom:
4766 shuffleOpenings = TRUE;
4770 case VariantShatranj:
4771 pieces = ShatranjArray;
4772 nrCastlingRights = 0;
4773 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4775 case VariantTwoKings:
4776 pieces = twoKingsArray;
4778 case VariantCapaRandom:
4779 shuffleOpenings = TRUE;
4780 case VariantCapablanca:
4781 pieces = CapablancaArray;
4782 gameInfo.boardWidth = 10;
4783 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4786 pieces = GothicArray;
4787 gameInfo.boardWidth = 10;
4788 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4791 pieces = JanusArray;
4792 gameInfo.boardWidth = 10;
4793 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4794 nrCastlingRights = 6;
4795 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4796 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4797 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4798 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4799 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4800 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4803 pieces = FalconArray;
4804 gameInfo.boardWidth = 10;
4805 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4807 case VariantXiangqi:
4808 pieces = XiangqiArray;
4809 gameInfo.boardWidth = 9;
4810 gameInfo.boardHeight = 10;
4811 nrCastlingRights = 0;
4812 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4815 pieces = ShogiArray;
4816 gameInfo.boardWidth = 9;
4817 gameInfo.boardHeight = 9;
4818 gameInfo.holdingsSize = 7;
4819 nrCastlingRights = 0;
4820 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4822 case VariantCourier:
4823 pieces = CourierArray;
4824 gameInfo.boardWidth = 12;
4825 nrCastlingRights = 0;
4826 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4828 case VariantKnightmate:
4829 pieces = KnightmateArray;
4830 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4833 pieces = fairyArray;
4834 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4837 pieces = GreatArray;
4838 gameInfo.boardWidth = 10;
4839 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4840 gameInfo.holdingsSize = 8;
4844 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4845 gameInfo.holdingsSize = 8;
4846 startedFromSetupPosition = TRUE;
4848 case VariantCrazyhouse:
4849 case VariantBughouse:
4851 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4852 gameInfo.holdingsSize = 5;
4854 case VariantWildCastle:
4856 /* !!?shuffle with kings guaranteed to be on d or e file */
4857 shuffleOpenings = 1;
4859 case VariantNoCastle:
4861 nrCastlingRights = 0;
4862 /* !!?unconstrained back-rank shuffle */
4863 shuffleOpenings = 1;
4868 if(appData.NrFiles >= 0) {
4869 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4870 gameInfo.boardWidth = appData.NrFiles;
4872 if(appData.NrRanks >= 0) {
4873 gameInfo.boardHeight = appData.NrRanks;
4875 if(appData.holdingsSize >= 0) {
4876 i = appData.holdingsSize;
4877 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4878 gameInfo.holdingsSize = i;
4880 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4881 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4882 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4884 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4885 if(pawnRow < 1) pawnRow = 1;
4887 /* User pieceToChar list overrules defaults */
4888 if(appData.pieceToCharTable != NULL)
4889 SetCharTable(pieceToChar, appData.pieceToCharTable);
4891 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4893 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4894 s = (ChessSquare) 0; /* account holding counts in guard band */
4895 for( i=0; i<BOARD_HEIGHT; i++ )
4896 initialPosition[i][j] = s;
4898 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4899 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4900 initialPosition[pawnRow][j] = WhitePawn;
4901 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4902 if(gameInfo.variant == VariantXiangqi) {
4904 initialPosition[pawnRow][j] =
4905 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4906 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4907 initialPosition[2][j] = WhiteCannon;
4908 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4912 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4914 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4917 initialPosition[1][j] = WhiteBishop;
4918 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4920 initialPosition[1][j] = WhiteRook;
4921 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4924 if( nrCastlingRights == -1) {
4925 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4926 /* This sets default castling rights from none to normal corners */
4927 /* Variants with other castling rights must set them themselves above */
4928 nrCastlingRights = 6;
4930 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4931 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4932 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4933 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4934 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4935 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4938 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4939 if(gameInfo.variant == VariantGreat) { // promotion commoners
4940 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4941 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4942 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4943 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4945 if (appData.debugMode) {
4946 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4948 if(shuffleOpenings) {
4949 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4950 startedFromSetupPosition = TRUE;
4952 if(startedFromPositionFile) {
4953 /* [HGM] loadPos: use PositionFile for every new game */
4954 CopyBoard(initialPosition, filePosition);
4955 for(i=0; i<nrCastlingRights; i++)
4956 initialRights[i] = filePosition[CASTLING][i];
4957 startedFromSetupPosition = TRUE;
4960 CopyBoard(boards[0], initialPosition);
4962 if(oldx != gameInfo.boardWidth ||
4963 oldy != gameInfo.boardHeight ||
4964 oldh != gameInfo.holdingsWidth
4966 || oldv == VariantGothic || // For licensing popups
4967 gameInfo.variant == VariantGothic
4970 || oldv == VariantFalcon ||
4971 gameInfo.variant == VariantFalcon
4974 InitDrawingSizes(-2 ,0);
4977 DrawPosition(TRUE, boards[currentMove]);
4981 SendBoard(cps, moveNum)
4982 ChessProgramState *cps;
4985 char message[MSG_SIZ];
4987 if (cps->useSetboard) {
4988 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4989 sprintf(message, "setboard %s\n", fen);
4990 SendToProgram(message, cps);
4996 /* Kludge to set black to move, avoiding the troublesome and now
4997 * deprecated "black" command.
4999 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5001 SendToProgram("edit\n", cps);
5002 SendToProgram("#\n", cps);
5003 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5004 bp = &boards[moveNum][i][BOARD_LEFT];
5005 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5006 if ((int) *bp < (int) BlackPawn) {
5007 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5009 if(message[0] == '+' || message[0] == '~') {
5010 sprintf(message, "%c%c%c+\n",
5011 PieceToChar((ChessSquare)(DEMOTED *bp)),
5014 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5015 message[1] = BOARD_RGHT - 1 - j + '1';
5016 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5018 SendToProgram(message, cps);
5023 SendToProgram("c\n", cps);
5024 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5025 bp = &boards[moveNum][i][BOARD_LEFT];
5026 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5027 if (((int) *bp != (int) EmptySquare)
5028 && ((int) *bp >= (int) BlackPawn)) {
5029 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5031 if(message[0] == '+' || message[0] == '~') {
5032 sprintf(message, "%c%c%c+\n",
5033 PieceToChar((ChessSquare)(DEMOTED *bp)),
5036 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5037 message[1] = BOARD_RGHT - 1 - j + '1';
5038 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5040 SendToProgram(message, cps);
5045 SendToProgram(".\n", cps);
5047 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5051 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5053 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5054 /* [HGM] add Shogi promotions */
5055 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5060 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5061 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5063 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5064 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5067 piece = boards[currentMove][fromY][fromX];
5068 if(gameInfo.variant == VariantShogi) {
5069 promotionZoneSize = 3;
5070 highestPromotingPiece = (int)WhiteFerz;
5073 // next weed out all moves that do not touch the promotion zone at all
5074 if((int)piece >= BlackPawn) {
5075 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5077 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5079 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5080 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5083 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5085 // weed out mandatory Shogi promotions
5086 if(gameInfo.variant == VariantShogi) {
5087 if(piece >= BlackPawn) {
5088 if(toY == 0 && piece == BlackPawn ||
5089 toY == 0 && piece == BlackQueen ||
5090 toY <= 1 && piece == BlackKnight) {
5095 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5096 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5097 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5104 // weed out obviously illegal Pawn moves
5105 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5106 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5107 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5108 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5109 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5110 // note we are not allowed to test for valid (non-)capture, due to premove
5113 // we either have a choice what to promote to, or (in Shogi) whether to promote
5114 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5115 *promoChoice = PieceToChar(BlackFerz); // no choice
5118 if(appData.alwaysPromoteToQueen) { // predetermined
5119 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5120 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5121 else *promoChoice = PieceToChar(BlackQueen);
5125 // suppress promotion popup on illegal moves that are not premoves
5126 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5127 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5128 if(appData.testLegality && !premove) {
5129 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5130 fromY, fromX, toY, toX, NULLCHAR);
5131 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5132 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5140 InPalace(row, column)
5142 { /* [HGM] for Xiangqi */
5143 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5144 column < (BOARD_WIDTH + 4)/2 &&
5145 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5150 PieceForSquare (x, y)
5154 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5157 return boards[currentMove][y][x];
5161 OKToStartUserMove(x, y)
5164 ChessSquare from_piece;
5167 if (matchMode) return FALSE;
5168 if (gameMode == EditPosition) return TRUE;
5170 if (x >= 0 && y >= 0)
5171 from_piece = boards[currentMove][y][x];
5173 from_piece = EmptySquare;
5175 if (from_piece == EmptySquare) return FALSE;
5177 white_piece = (int)from_piece >= (int)WhitePawn &&
5178 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5181 case PlayFromGameFile:
5183 case TwoMachinesPlay:
5191 case MachinePlaysWhite:
5192 case IcsPlayingBlack:
5193 if (appData.zippyPlay) return FALSE;
5195 DisplayMoveError(_("You are playing Black"));
5200 case MachinePlaysBlack:
5201 case IcsPlayingWhite:
5202 if (appData.zippyPlay) return FALSE;
5204 DisplayMoveError(_("You are playing White"));
5210 if (!white_piece && WhiteOnMove(currentMove)) {
5211 DisplayMoveError(_("It is White's turn"));
5214 if (white_piece && !WhiteOnMove(currentMove)) {
5215 DisplayMoveError(_("It is Black's turn"));
5218 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5219 /* Editing correspondence game history */
5220 /* Could disallow this or prompt for confirmation */
5225 case BeginningOfGame:
5226 if (appData.icsActive) return FALSE;
5227 if (!appData.noChessProgram) {
5229 DisplayMoveError(_("You are playing White"));
5236 if (!white_piece && WhiteOnMove(currentMove)) {
5237 DisplayMoveError(_("It is White's turn"));
5240 if (white_piece && !WhiteOnMove(currentMove)) {
5241 DisplayMoveError(_("It is Black's turn"));
5250 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5251 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5252 && gameMode != AnalyzeFile && gameMode != Training) {
5253 DisplayMoveError(_("Displayed position is not current"));
5259 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5260 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5261 int lastLoadGameUseList = FALSE;
5262 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5263 ChessMove lastLoadGameStart = (ChessMove) 0;
5266 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5267 int fromX, fromY, toX, toY;
5272 ChessSquare pdown, pup;
5274 /* Check if the user is playing in turn. This is complicated because we
5275 let the user "pick up" a piece before it is his turn. So the piece he
5276 tried to pick up may have been captured by the time he puts it down!
5277 Therefore we use the color the user is supposed to be playing in this
5278 test, not the color of the piece that is currently on the starting
5279 square---except in EditGame mode, where the user is playing both
5280 sides; fortunately there the capture race can't happen. (It can
5281 now happen in IcsExamining mode, but that's just too bad. The user
5282 will get a somewhat confusing message in that case.)
5286 case PlayFromGameFile:
5288 case TwoMachinesPlay:
5292 /* We switched into a game mode where moves are not accepted,
5293 perhaps while the mouse button was down. */
5294 return ImpossibleMove;
5296 case MachinePlaysWhite:
5297 /* User is moving for Black */
5298 if (WhiteOnMove(currentMove)) {
5299 DisplayMoveError(_("It is White's turn"));
5300 return ImpossibleMove;
5304 case MachinePlaysBlack:
5305 /* User is moving for White */
5306 if (!WhiteOnMove(currentMove)) {
5307 DisplayMoveError(_("It is Black's turn"));
5308 return ImpossibleMove;
5314 case BeginningOfGame:
5317 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5318 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5319 /* User is moving for Black */
5320 if (WhiteOnMove(currentMove)) {
5321 DisplayMoveError(_("It is White's turn"));
5322 return ImpossibleMove;
5325 /* User is moving for White */
5326 if (!WhiteOnMove(currentMove)) {
5327 DisplayMoveError(_("It is Black's turn"));
5328 return ImpossibleMove;
5333 case IcsPlayingBlack:
5334 /* User is moving for Black */
5335 if (WhiteOnMove(currentMove)) {
5336 if (!appData.premove) {
5337 DisplayMoveError(_("It is White's turn"));
5338 } else if (toX >= 0 && toY >= 0) {
5341 premoveFromX = fromX;
5342 premoveFromY = fromY;
5343 premovePromoChar = promoChar;
5345 if (appData.debugMode)
5346 fprintf(debugFP, "Got premove: fromX %d,"
5347 "fromY %d, toX %d, toY %d\n",
5348 fromX, fromY, toX, toY);
5350 return ImpossibleMove;
5354 case IcsPlayingWhite:
5355 /* User is moving for White */
5356 if (!WhiteOnMove(currentMove)) {
5357 if (!appData.premove) {
5358 DisplayMoveError(_("It is Black's turn"));
5359 } else if (toX >= 0 && toY >= 0) {
5362 premoveFromX = fromX;
5363 premoveFromY = fromY;
5364 premovePromoChar = promoChar;
5366 if (appData.debugMode)
5367 fprintf(debugFP, "Got premove: fromX %d,"
5368 "fromY %d, toX %d, toY %d\n",
5369 fromX, fromY, toX, toY);
5371 return ImpossibleMove;
5379 /* EditPosition, empty square, or different color piece;
5380 click-click move is possible */
5381 if (toX == -2 || toY == -2) {
5382 boards[0][fromY][fromX] = EmptySquare;
5383 return AmbiguousMove;
5384 } else if (toX >= 0 && toY >= 0) {
5385 boards[0][toY][toX] = boards[0][fromY][fromX];
5386 boards[0][fromY][fromX] = EmptySquare;
5387 return AmbiguousMove;
5389 return ImpossibleMove;
5392 if(toX < 0 || toY < 0) return ImpossibleMove;
5393 pdown = boards[currentMove][fromY][fromX];
5394 pup = boards[currentMove][toY][toX];
5396 /* [HGM] If move started in holdings, it means a drop */
5397 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5398 if( pup != EmptySquare ) return ImpossibleMove;
5399 if(appData.testLegality) {
5400 /* it would be more logical if LegalityTest() also figured out
5401 * which drops are legal. For now we forbid pawns on back rank.
5402 * Shogi is on its own here...
5404 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5405 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5406 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5408 return WhiteDrop; /* Not needed to specify white or black yet */
5411 userOfferedDraw = FALSE;
5413 /* [HGM] always test for legality, to get promotion info */
5414 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5415 fromY, fromX, toY, toX, promoChar);
5416 /* [HGM] but possibly ignore an IllegalMove result */
5417 if (appData.testLegality) {
5418 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5419 DisplayMoveError(_("Illegal move"));
5420 return ImpossibleMove;
5425 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5426 function is made into one that returns an OK move type if FinishMove
5427 should be called. This to give the calling driver routine the
5428 opportunity to finish the userMove input with a promotion popup,
5429 without bothering the user with this for invalid or illegal moves */
5431 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5434 /* Common tail of UserMoveEvent and DropMenuEvent */
5436 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5438 int fromX, fromY, toX, toY;
5439 /*char*/int promoChar;
5443 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5444 // [HGM] superchess: suppress promotions to non-available piece
5445 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5446 if(WhiteOnMove(currentMove)) {
5447 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5449 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5453 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5454 move type in caller when we know the move is a legal promotion */
5455 if(moveType == NormalMove && promoChar)
5456 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5458 /* [HGM] convert drag-and-drop piece drops to standard form */
5459 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5460 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5461 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5462 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5463 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5464 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5465 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5466 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5470 /* [HGM] <popupFix> The following if has been moved here from
5471 UserMoveEvent(). Because it seemed to belong here (why not allow
5472 piece drops in training games?), and because it can only be
5473 performed after it is known to what we promote. */
5474 if (gameMode == Training) {
5475 /* compare the move played on the board to the next move in the
5476 * game. If they match, display the move and the opponent's response.
5477 * If they don't match, display an error message.
5481 CopyBoard(testBoard, boards[currentMove]);
5482 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5484 if (CompareBoards(testBoard, boards[currentMove+1])) {
5485 ForwardInner(currentMove+1);
5487 /* Autoplay the opponent's response.
5488 * if appData.animate was TRUE when Training mode was entered,
5489 * the response will be animated.
5491 saveAnimate = appData.animate;
5492 appData.animate = animateTraining;
5493 ForwardInner(currentMove+1);
5494 appData.animate = saveAnimate;
5496 /* check for the end of the game */
5497 if (currentMove >= forwardMostMove) {
5498 gameMode = PlayFromGameFile;
5500 SetTrainingModeOff();
5501 DisplayInformation(_("End of game"));
5504 DisplayError(_("Incorrect move"), 0);
5509 /* Ok, now we know that the move is good, so we can kill
5510 the previous line in Analysis Mode */
5511 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5512 && currentMove < forwardMostMove) {
5513 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5516 /* If we need the chess program but it's dead, restart it */
5517 ResurrectChessProgram();
5519 /* A user move restarts a paused game*/
5523 thinkOutput[0] = NULLCHAR;
5525 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5527 if (gameMode == BeginningOfGame) {
5528 if (appData.noChessProgram) {
5529 gameMode = EditGame;
5533 gameMode = MachinePlaysBlack;
5536 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5538 if (first.sendName) {
5539 sprintf(buf, "name %s\n", gameInfo.white);
5540 SendToProgram(buf, &first);
5547 /* Relay move to ICS or chess engine */
5548 if (appData.icsActive) {
5549 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5550 gameMode == IcsExamining) {
5551 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5555 if (first.sendTime && (gameMode == BeginningOfGame ||
5556 gameMode == MachinePlaysWhite ||
5557 gameMode == MachinePlaysBlack)) {
5558 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5560 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5561 // [HGM] book: if program might be playing, let it use book
5562 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5563 first.maybeThinking = TRUE;
5564 } else SendMoveToProgram(forwardMostMove-1, &first);
5565 if (currentMove == cmailOldMove + 1) {
5566 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5570 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5574 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5580 if (WhiteOnMove(currentMove)) {
5581 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5583 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5587 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5592 case MachinePlaysBlack:
5593 case MachinePlaysWhite:
5594 /* disable certain menu options while machine is thinking */
5595 SetMachineThinkingEnables();
5602 if(bookHit) { // [HGM] book: simulate book reply
5603 static char bookMove[MSG_SIZ]; // a bit generous?
5605 programStats.nodes = programStats.depth = programStats.time =
5606 programStats.score = programStats.got_only_move = 0;
5607 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5609 strcpy(bookMove, "move ");
5610 strcat(bookMove, bookHit);
5611 HandleMachineMove(bookMove, &first);
5617 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5618 int fromX, fromY, toX, toY;
5621 /* [HGM] This routine was added to allow calling of its two logical
5622 parts from other modules in the old way. Before, UserMoveEvent()
5623 automatically called FinishMove() if the move was OK, and returned
5624 otherwise. I separated the two, in order to make it possible to
5625 slip a promotion popup in between. But that it always needs two
5626 calls, to the first part, (now called UserMoveTest() ), and to
5627 FinishMove if the first part succeeded. Calls that do not need
5628 to do anything in between, can call this routine the old way.
5630 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5631 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5632 if(moveType == AmbiguousMove)
5633 DrawPosition(FALSE, boards[currentMove]);
5634 else if(moveType != ImpossibleMove && moveType != Comment)
5635 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5639 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5646 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5647 Markers *m = (Markers *) closure;
5648 if(rf == fromY && ff == fromX)
5649 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5650 || kind == WhiteCapturesEnPassant
5651 || kind == BlackCapturesEnPassant);
5652 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5656 MarkTargetSquares(int clear)
5659 if(!appData.markers || !appData.highlightDragging ||
5660 !appData.testLegality || gameMode == EditPosition) return;
5662 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5665 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5666 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5667 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5669 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5672 DrawPosition(TRUE, NULL);
5675 void LeftClick(ClickType clickType, int xPix, int yPix)
5678 Boolean saveAnimate;
5679 static int second = 0, promotionChoice = 0;
5680 char promoChoice = NULLCHAR;
5682 if (clickType == Press) ErrorPopDown();
5683 MarkTargetSquares(1);
5685 x = EventToSquare(xPix, BOARD_WIDTH);
5686 y = EventToSquare(yPix, BOARD_HEIGHT);
5687 if (!flipView && y >= 0) {
5688 y = BOARD_HEIGHT - 1 - y;
5690 if (flipView && x >= 0) {
5691 x = BOARD_WIDTH - 1 - x;
5694 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5695 if(clickType == Release) return; // ignore upclick of click-click destination
5696 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5697 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5698 if(gameInfo.holdingsWidth &&
5699 (WhiteOnMove(currentMove)
5700 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5701 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5702 // click in right holdings, for determining promotion piece
5703 ChessSquare p = boards[currentMove][y][x];
5704 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5705 if(p != EmptySquare) {
5706 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5711 DrawPosition(FALSE, boards[currentMove]);
5715 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5716 if(clickType == Press
5717 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5718 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5719 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5723 if (clickType == Press) {
5725 if (OKToStartUserMove(x, y)) {
5729 MarkTargetSquares(0);
5730 DragPieceBegin(xPix, yPix);
5731 if (appData.highlightDragging) {
5732 SetHighlights(x, y, -1, -1);
5740 if (clickType == Press && gameMode != EditPosition) {
5745 // ignore off-board to clicks
5746 if(y < 0 || x < 0) return;
5748 /* Check if clicking again on the same color piece */
5749 fromP = boards[currentMove][fromY][fromX];
5750 toP = boards[currentMove][y][x];
5751 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5752 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5753 WhitePawn <= toP && toP <= WhiteKing &&
5754 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5755 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5756 (BlackPawn <= fromP && fromP <= BlackKing &&
5757 BlackPawn <= toP && toP <= BlackKing &&
5758 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5759 !(fromP == BlackKing && toP == BlackRook && frc))) {
5760 /* Clicked again on same color piece -- changed his mind */
5761 second = (x == fromX && y == fromY);
5762 if (appData.highlightDragging) {
5763 SetHighlights(x, y, -1, -1);
5767 if (OKToStartUserMove(x, y)) {
5770 MarkTargetSquares(0);
5771 DragPieceBegin(xPix, yPix);
5775 // ignore clicks on holdings
5776 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5779 if (clickType == Release && x == fromX && y == fromY) {
5780 DragPieceEnd(xPix, yPix);
5781 if (appData.animateDragging) {
5782 /* Undo animation damage if any */
5783 DrawPosition(FALSE, NULL);
5786 /* Second up/down in same square; just abort move */
5791 ClearPremoveHighlights();
5793 /* First upclick in same square; start click-click mode */
5794 SetHighlights(x, y, -1, -1);
5799 /* we now have a different from- and (possibly off-board) to-square */
5800 /* Completed move */
5803 saveAnimate = appData.animate;
5804 if (clickType == Press) {
5805 /* Finish clickclick move */
5806 if (appData.animate || appData.highlightLastMove) {
5807 SetHighlights(fromX, fromY, toX, toY);
5812 /* Finish drag move */
5813 if (appData.highlightLastMove) {
5814 SetHighlights(fromX, fromY, toX, toY);
5818 DragPieceEnd(xPix, yPix);
5819 /* Don't animate move and drag both */
5820 appData.animate = FALSE;
5823 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5824 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5827 DrawPosition(TRUE, NULL);
5831 // off-board moves should not be highlighted
5832 if(x < 0 || x < 0) ClearHighlights();
5834 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5835 SetHighlights(fromX, fromY, toX, toY);
5836 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5837 // [HGM] super: promotion to captured piece selected from holdings
5838 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5839 promotionChoice = TRUE;
5840 // kludge follows to temporarily execute move on display, without promoting yet
5841 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5842 boards[currentMove][toY][toX] = p;
5843 DrawPosition(FALSE, boards[currentMove]);
5844 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5845 boards[currentMove][toY][toX] = q;
5846 DisplayMessage("Click in holdings to choose piece", "");
5851 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5852 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5853 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5856 appData.animate = saveAnimate;
5857 if (appData.animate || appData.animateDragging) {
5858 /* Undo animation damage if needed */
5859 DrawPosition(FALSE, NULL);
5863 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5865 // char * hint = lastHint;
5866 FrontEndProgramStats stats;
5868 stats.which = cps == &first ? 0 : 1;
5869 stats.depth = cpstats->depth;
5870 stats.nodes = cpstats->nodes;
5871 stats.score = cpstats->score;
5872 stats.time = cpstats->time;
5873 stats.pv = cpstats->movelist;
5874 stats.hint = lastHint;
5875 stats.an_move_index = 0;
5876 stats.an_move_count = 0;
5878 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5879 stats.hint = cpstats->move_name;
5880 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5881 stats.an_move_count = cpstats->nr_moves;
5884 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5886 SetProgramStats( &stats );
5889 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5890 { // [HGM] book: this routine intercepts moves to simulate book replies
5891 char *bookHit = NULL;
5893 //first determine if the incoming move brings opponent into his book
5894 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5895 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5896 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5897 if(bookHit != NULL && !cps->bookSuspend) {
5898 // make sure opponent is not going to reply after receiving move to book position
5899 SendToProgram("force\n", cps);
5900 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5902 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5903 // now arrange restart after book miss
5905 // after a book hit we never send 'go', and the code after the call to this routine
5906 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5908 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5909 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5910 SendToProgram(buf, cps);
5911 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5912 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5913 SendToProgram("go\n", cps);
5914 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5915 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5916 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5917 SendToProgram("go\n", cps);
5918 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5920 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5924 ChessProgramState *savedState;
5925 void DeferredBookMove(void)
5927 if(savedState->lastPing != savedState->lastPong)
5928 ScheduleDelayedEvent(DeferredBookMove, 10);
5930 HandleMachineMove(savedMessage, savedState);
5934 HandleMachineMove(message, cps)
5936 ChessProgramState *cps;
5938 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5939 char realname[MSG_SIZ];
5940 int fromX, fromY, toX, toY;
5949 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5951 * Kludge to ignore BEL characters
5953 while (*message == '\007') message++;
5956 * [HGM] engine debug message: ignore lines starting with '#' character
5958 if(cps->debug && *message == '#') return;
5961 * Look for book output
5963 if (cps == &first && bookRequested) {
5964 if (message[0] == '\t' || message[0] == ' ') {
5965 /* Part of the book output is here; append it */
5966 strcat(bookOutput, message);
5967 strcat(bookOutput, " \n");
5969 } else if (bookOutput[0] != NULLCHAR) {
5970 /* All of book output has arrived; display it */
5971 char *p = bookOutput;
5972 while (*p != NULLCHAR) {
5973 if (*p == '\t') *p = ' ';
5976 DisplayInformation(bookOutput);
5977 bookRequested = FALSE;
5978 /* Fall through to parse the current output */
5983 * Look for machine move.
5985 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5986 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5988 /* This method is only useful on engines that support ping */
5989 if (cps->lastPing != cps->lastPong) {
5990 if (gameMode == BeginningOfGame) {
5991 /* Extra move from before last new; ignore */
5992 if (appData.debugMode) {
5993 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5996 if (appData.debugMode) {
5997 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5998 cps->which, gameMode);
6001 SendToProgram("undo\n", cps);
6007 case BeginningOfGame:
6008 /* Extra move from before last reset; ignore */
6009 if (appData.debugMode) {
6010 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6017 /* Extra move after we tried to stop. The mode test is
6018 not a reliable way of detecting this problem, but it's
6019 the best we can do on engines that don't support ping.
6021 if (appData.debugMode) {
6022 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6023 cps->which, gameMode);
6025 SendToProgram("undo\n", cps);
6028 case MachinePlaysWhite:
6029 case IcsPlayingWhite:
6030 machineWhite = TRUE;
6033 case MachinePlaysBlack:
6034 case IcsPlayingBlack:
6035 machineWhite = FALSE;
6038 case TwoMachinesPlay:
6039 machineWhite = (cps->twoMachinesColor[0] == 'w');
6042 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6043 if (appData.debugMode) {
6045 "Ignoring move out of turn by %s, gameMode %d"
6046 ", forwardMost %d\n",
6047 cps->which, gameMode, forwardMostMove);
6052 if (appData.debugMode) { int f = forwardMostMove;
6053 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6054 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6055 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6057 if(cps->alphaRank) AlphaRank(machineMove, 4);
6058 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6059 &fromX, &fromY, &toX, &toY, &promoChar)) {
6060 /* Machine move could not be parsed; ignore it. */
6061 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6062 machineMove, cps->which);
6063 DisplayError(buf1, 0);
6064 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6065 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6066 if (gameMode == TwoMachinesPlay) {
6067 GameEnds(machineWhite ? BlackWins : WhiteWins,
6073 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6074 /* So we have to redo legality test with true e.p. status here, */
6075 /* to make sure an illegal e.p. capture does not slip through, */
6076 /* to cause a forfeit on a justified illegal-move complaint */
6077 /* of the opponent. */
6078 if( gameMode==TwoMachinesPlay && appData.testLegality
6079 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6082 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6083 fromY, fromX, toY, toX, promoChar);
6084 if (appData.debugMode) {
6086 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6087 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6088 fprintf(debugFP, "castling rights\n");
6090 if(moveType == IllegalMove) {
6091 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6092 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6093 GameEnds(machineWhite ? BlackWins : WhiteWins,
6096 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6097 /* [HGM] Kludge to handle engines that send FRC-style castling
6098 when they shouldn't (like TSCP-Gothic) */
6100 case WhiteASideCastleFR:
6101 case BlackASideCastleFR:
6103 currentMoveString[2]++;
6105 case WhiteHSideCastleFR:
6106 case BlackHSideCastleFR:
6108 currentMoveString[2]--;
6110 default: ; // nothing to do, but suppresses warning of pedantic compilers
6113 hintRequested = FALSE;
6114 lastHint[0] = NULLCHAR;
6115 bookRequested = FALSE;
6116 /* Program may be pondering now */
6117 cps->maybeThinking = TRUE;
6118 if (cps->sendTime == 2) cps->sendTime = 1;
6119 if (cps->offeredDraw) cps->offeredDraw--;
6122 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6124 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6126 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6127 char buf[3*MSG_SIZ];
6129 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6130 programStats.score / 100.,
6132 programStats.time / 100.,
6133 (unsigned int)programStats.nodes,
6134 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6135 programStats.movelist);
6137 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6141 /* currentMoveString is set as a side-effect of ParseOneMove */
6142 strcpy(machineMove, currentMoveString);
6143 strcat(machineMove, "\n");
6144 strcpy(moveList[forwardMostMove], machineMove);
6146 /* [AS] Save move info and clear stats for next move */
6147 pvInfoList[ forwardMostMove ].score = programStats.score;
6148 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6149 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6150 ClearProgramStats();
6151 thinkOutput[0] = NULLCHAR;
6152 hiddenThinkOutputState = 0;
6154 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6156 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6157 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6160 while( count < adjudicateLossPlies ) {
6161 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6164 score = -score; /* Flip score for winning side */
6167 if( score > adjudicateLossThreshold ) {
6174 if( count >= adjudicateLossPlies ) {
6175 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6177 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6178 "Xboard adjudication",
6185 if( gameMode == TwoMachinesPlay ) {
6186 // [HGM] some adjudications useful with buggy engines
6187 int k, count = 0; static int bare = 1;
6188 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6191 if( appData.testLegality )
6192 { /* [HGM] Some more adjudications for obstinate engines */
6193 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6194 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6195 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6196 static int moveCount = 6;
6198 char *reason = NULL;
6200 /* Count what is on board. */
6201 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6202 { ChessSquare p = boards[forwardMostMove][i][j];
6206 { /* count B,N,R and other of each side */
6209 NrK++; break; // [HGM] atomic: count Kings
6213 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6214 bishopsColor |= 1 << ((i^j)&1);
6219 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6220 bishopsColor |= 1 << ((i^j)&1);
6235 PawnAdvance += m; NrPawns++;
6237 NrPieces += (p != EmptySquare);
6238 NrW += ((int)p < (int)BlackPawn);
6239 if(gameInfo.variant == VariantXiangqi &&
6240 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6241 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6242 NrW -= ((int)p < (int)BlackPawn);
6246 /* Some material-based adjudications that have to be made before stalemate test */
6247 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6248 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6249 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6250 if(appData.checkMates) {
6251 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6252 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6253 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6254 "Xboard adjudication: King destroyed", GE_XBOARD );
6259 /* Bare King in Shatranj (loses) or Losers (wins) */
6260 if( NrW == 1 || NrPieces - NrW == 1) {
6261 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6262 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6263 if(appData.checkMates) {
6264 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6265 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6266 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6267 "Xboard adjudication: Bare king", GE_XBOARD );
6271 if( gameInfo.variant == VariantShatranj && --bare < 0)
6273 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6274 if(appData.checkMates) {
6275 /* but only adjudicate if adjudication enabled */
6276 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6277 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6278 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6279 "Xboard adjudication: Bare king", GE_XBOARD );
6286 // don't wait for engine to announce game end if we can judge ourselves
6287 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6289 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6290 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6291 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6292 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6295 reason = "Xboard adjudication: 3rd check";
6296 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6306 reason = "Xboard adjudication: Stalemate";
6307 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6308 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6309 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6310 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6311 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6312 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6313 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6314 EP_CHECKMATE : EP_WINS);
6315 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6316 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6320 reason = "Xboard adjudication: Checkmate";
6321 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6325 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6327 result = GameIsDrawn; break;
6329 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6331 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6333 result = (ChessMove) 0;
6335 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6336 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6337 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6338 GameEnds( result, reason, GE_XBOARD );
6342 /* Next absolutely insufficient mating material. */
6343 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6344 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6345 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6346 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6347 { /* KBK, KNK, KK of KBKB with like Bishops */
6349 /* always flag draws, for judging claims */
6350 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6352 if(appData.materialDraws) {
6353 /* but only adjudicate them if adjudication enabled */
6354 SendToProgram("force\n", cps->other); // suppress reply
6355 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6356 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6357 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6362 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6364 ( NrWR == 1 && NrBR == 1 /* KRKR */
6365 || NrWQ==1 && NrBQ==1 /* KQKQ */
6366 || NrWN==2 || NrBN==2 /* KNNK */
6367 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6369 if(--moveCount < 0 && appData.trivialDraws)
6370 { /* if the first 3 moves do not show a tactical win, declare draw */
6371 SendToProgram("force\n", cps->other); // suppress reply
6372 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6373 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6374 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6377 } else moveCount = 6;
6381 if (appData.debugMode) { int i;
6382 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6383 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6384 appData.drawRepeats);
6385 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6386 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6390 /* Check for rep-draws */
6392 for(k = forwardMostMove-2;
6393 k>=backwardMostMove && k>=forwardMostMove-100 &&
6394 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6395 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6398 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6399 /* compare castling rights */
6400 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6401 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6402 rights++; /* King lost rights, while rook still had them */
6403 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6404 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6405 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6406 rights++; /* but at least one rook lost them */
6408 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6409 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6411 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6412 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6413 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6416 if( rights == 0 && ++count > appData.drawRepeats-2
6417 && appData.drawRepeats > 1) {
6418 /* adjudicate after user-specified nr of repeats */
6419 SendToProgram("force\n", cps->other); // suppress reply
6420 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6421 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6422 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6423 // [HGM] xiangqi: check for forbidden perpetuals
6424 int m, ourPerpetual = 1, hisPerpetual = 1;
6425 for(m=forwardMostMove; m>k; m-=2) {
6426 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6427 ourPerpetual = 0; // the current mover did not always check
6428 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6429 hisPerpetual = 0; // the opponent did not always check
6431 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6432 ourPerpetual, hisPerpetual);
6433 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6434 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6435 "Xboard adjudication: perpetual checking", GE_XBOARD );
6438 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6439 break; // (or we would have caught him before). Abort repetition-checking loop.
6440 // Now check for perpetual chases
6441 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6442 hisPerpetual = PerpetualChase(k, forwardMostMove);
6443 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6444 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6445 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6446 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6449 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6450 break; // Abort repetition-checking loop.
6452 // if neither of us is checking or chasing all the time, or both are, it is draw
6454 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6457 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6458 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6462 /* Now we test for 50-move draws. Determine ply count */
6463 count = forwardMostMove;
6464 /* look for last irreversble move */
6465 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6467 /* if we hit starting position, add initial plies */
6468 if( count == backwardMostMove )
6469 count -= initialRulePlies;
6470 count = forwardMostMove - count;
6472 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6473 /* this is used to judge if draw claims are legal */
6474 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6475 SendToProgram("force\n", cps->other); // suppress reply
6476 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6477 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6478 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6482 /* if draw offer is pending, treat it as a draw claim
6483 * when draw condition present, to allow engines a way to
6484 * claim draws before making their move to avoid a race
6485 * condition occurring after their move
6487 if( cps->other->offeredDraw || cps->offeredDraw ) {
6489 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6490 p = "Draw claim: 50-move rule";
6491 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6492 p = "Draw claim: 3-fold repetition";
6493 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6494 p = "Draw claim: insufficient mating material";
6496 SendToProgram("force\n", cps->other); // suppress reply
6497 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6498 GameEnds( GameIsDrawn, p, GE_XBOARD );
6499 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6505 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6506 SendToProgram("force\n", cps->other); // suppress reply
6507 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6508 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6510 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6517 if (gameMode == TwoMachinesPlay) {
6518 /* [HGM] relaying draw offers moved to after reception of move */
6519 /* and interpreting offer as claim if it brings draw condition */
6520 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6521 SendToProgram("draw\n", cps->other);
6523 if (cps->other->sendTime) {
6524 SendTimeRemaining(cps->other,
6525 cps->other->twoMachinesColor[0] == 'w');
6527 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6528 if (firstMove && !bookHit) {
6530 if (cps->other->useColors) {
6531 SendToProgram(cps->other->twoMachinesColor, cps->other);
6533 SendToProgram("go\n", cps->other);
6535 cps->other->maybeThinking = TRUE;
6538 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6540 if (!pausing && appData.ringBellAfterMoves) {
6545 * Reenable menu items that were disabled while
6546 * machine was thinking
6548 if (gameMode != TwoMachinesPlay)
6549 SetUserThinkingEnables();
6551 // [HGM] book: after book hit opponent has received move and is now in force mode
6552 // force the book reply into it, and then fake that it outputted this move by jumping
6553 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6555 static char bookMove[MSG_SIZ]; // a bit generous?
6557 strcpy(bookMove, "move ");
6558 strcat(bookMove, bookHit);
6561 programStats.nodes = programStats.depth = programStats.time =
6562 programStats.score = programStats.got_only_move = 0;
6563 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6565 if(cps->lastPing != cps->lastPong) {
6566 savedMessage = message; // args for deferred call
6568 ScheduleDelayedEvent(DeferredBookMove, 10);
6577 /* Set special modes for chess engines. Later something general
6578 * could be added here; for now there is just one kludge feature,
6579 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6580 * when "xboard" is given as an interactive command.
6582 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6583 cps->useSigint = FALSE;
6584 cps->useSigterm = FALSE;
6586 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6587 ParseFeatures(message+8, cps);
6588 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6591 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6592 * want this, I was asked to put it in, and obliged.
6594 if (!strncmp(message, "setboard ", 9)) {
6595 Board initial_position;
6597 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6599 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6600 DisplayError(_("Bad FEN received from engine"), 0);
6604 CopyBoard(boards[0], initial_position);
6605 initialRulePlies = FENrulePlies;
6606 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6607 else gameMode = MachinePlaysBlack;
6608 DrawPosition(FALSE, boards[currentMove]);
6614 * Look for communication commands
6616 if (!strncmp(message, "telluser ", 9)) {
6617 DisplayNote(message + 9);
6620 if (!strncmp(message, "tellusererror ", 14)) {
6622 DisplayError(message + 14, 0);
6625 if (!strncmp(message, "tellopponent ", 13)) {
6626 if (appData.icsActive) {
6628 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6632 DisplayNote(message + 13);
6636 if (!strncmp(message, "tellothers ", 11)) {
6637 if (appData.icsActive) {
6639 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6645 if (!strncmp(message, "tellall ", 8)) {
6646 if (appData.icsActive) {
6648 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6652 DisplayNote(message + 8);
6656 if (strncmp(message, "warning", 7) == 0) {
6657 /* Undocumented feature, use tellusererror in new code */
6658 DisplayError(message, 0);
6661 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6662 strcpy(realname, cps->tidy);
6663 strcat(realname, " query");
6664 AskQuestion(realname, buf2, buf1, cps->pr);
6667 /* Commands from the engine directly to ICS. We don't allow these to be
6668 * sent until we are logged on. Crafty kibitzes have been known to
6669 * interfere with the login process.
6672 if (!strncmp(message, "tellics ", 8)) {
6673 SendToICS(message + 8);
6677 if (!strncmp(message, "tellicsnoalias ", 15)) {
6678 SendToICS(ics_prefix);
6679 SendToICS(message + 15);
6683 /* The following are for backward compatibility only */
6684 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6685 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6686 SendToICS(ics_prefix);
6692 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6696 * If the move is illegal, cancel it and redraw the board.
6697 * Also deal with other error cases. Matching is rather loose
6698 * here to accommodate engines written before the spec.
6700 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6701 strncmp(message, "Error", 5) == 0) {
6702 if (StrStr(message, "name") ||
6703 StrStr(message, "rating") || StrStr(message, "?") ||
6704 StrStr(message, "result") || StrStr(message, "board") ||
6705 StrStr(message, "bk") || StrStr(message, "computer") ||
6706 StrStr(message, "variant") || StrStr(message, "hint") ||
6707 StrStr(message, "random") || StrStr(message, "depth") ||
6708 StrStr(message, "accepted")) {
6711 if (StrStr(message, "protover")) {
6712 /* Program is responding to input, so it's apparently done
6713 initializing, and this error message indicates it is
6714 protocol version 1. So we don't need to wait any longer
6715 for it to initialize and send feature commands. */
6716 FeatureDone(cps, 1);
6717 cps->protocolVersion = 1;
6720 cps->maybeThinking = FALSE;
6722 if (StrStr(message, "draw")) {
6723 /* Program doesn't have "draw" command */
6724 cps->sendDrawOffers = 0;
6727 if (cps->sendTime != 1 &&
6728 (StrStr(message, "time") || StrStr(message, "otim"))) {
6729 /* Program apparently doesn't have "time" or "otim" command */
6733 if (StrStr(message, "analyze")) {
6734 cps->analysisSupport = FALSE;
6735 cps->analyzing = FALSE;
6737 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6738 DisplayError(buf2, 0);
6741 if (StrStr(message, "(no matching move)st")) {
6742 /* Special kludge for GNU Chess 4 only */
6743 cps->stKludge = TRUE;
6744 SendTimeControl(cps, movesPerSession, timeControl,
6745 timeIncrement, appData.searchDepth,
6749 if (StrStr(message, "(no matching move)sd")) {
6750 /* Special kludge for GNU Chess 4 only */
6751 cps->sdKludge = TRUE;
6752 SendTimeControl(cps, movesPerSession, timeControl,
6753 timeIncrement, appData.searchDepth,
6757 if (!StrStr(message, "llegal")) {
6760 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6761 gameMode == IcsIdle) return;
6762 if (forwardMostMove <= backwardMostMove) return;
6763 if (pausing) PauseEvent();
6764 if(appData.forceIllegal) {
6765 // [HGM] illegal: machine refused move; force position after move into it
6766 SendToProgram("force\n", cps);
6767 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6768 // we have a real problem now, as SendBoard will use the a2a3 kludge
6769 // when black is to move, while there might be nothing on a2 or black
6770 // might already have the move. So send the board as if white has the move.
6771 // But first we must change the stm of the engine, as it refused the last move
6772 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6773 if(WhiteOnMove(forwardMostMove)) {
6774 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6775 SendBoard(cps, forwardMostMove); // kludgeless board
6777 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6778 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6779 SendBoard(cps, forwardMostMove+1); // kludgeless board
6781 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6782 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6783 gameMode == TwoMachinesPlay)
6784 SendToProgram("go\n", cps);
6787 if (gameMode == PlayFromGameFile) {
6788 /* Stop reading this game file */
6789 gameMode = EditGame;
6792 currentMove = --forwardMostMove;
6793 DisplayMove(currentMove-1); /* before DisplayMoveError */
6795 DisplayBothClocks();
6796 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6797 parseList[currentMove], cps->which);
6798 DisplayMoveError(buf1);
6799 DrawPosition(FALSE, boards[currentMove]);
6801 /* [HGM] illegal-move claim should forfeit game when Xboard */
6802 /* only passes fully legal moves */
6803 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6804 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6805 "False illegal-move claim", GE_XBOARD );
6809 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6810 /* Program has a broken "time" command that
6811 outputs a string not ending in newline.
6817 * If chess program startup fails, exit with an error message.
6818 * Attempts to recover here are futile.
6820 if ((StrStr(message, "unknown host") != NULL)
6821 || (StrStr(message, "No remote directory") != NULL)
6822 || (StrStr(message, "not found") != NULL)
6823 || (StrStr(message, "No such file") != NULL)
6824 || (StrStr(message, "can't alloc") != NULL)
6825 || (StrStr(message, "Permission denied") != NULL)) {
6827 cps->maybeThinking = FALSE;
6828 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6829 cps->which, cps->program, cps->host, message);
6830 RemoveInputSource(cps->isr);
6831 DisplayFatalError(buf1, 0, 1);
6836 * Look for hint output
6838 if (sscanf(message, "Hint: %s", buf1) == 1) {
6839 if (cps == &first && hintRequested) {
6840 hintRequested = FALSE;
6841 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6842 &fromX, &fromY, &toX, &toY, &promoChar)) {
6843 (void) CoordsToAlgebraic(boards[forwardMostMove],
6844 PosFlags(forwardMostMove),
6845 fromY, fromX, toY, toX, promoChar, buf1);
6846 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6847 DisplayInformation(buf2);
6849 /* Hint move could not be parsed!? */
6850 snprintf(buf2, sizeof(buf2),
6851 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6853 DisplayError(buf2, 0);
6856 strcpy(lastHint, buf1);
6862 * Ignore other messages if game is not in progress
6864 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6865 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6868 * look for win, lose, draw, or draw offer
6870 if (strncmp(message, "1-0", 3) == 0) {
6871 char *p, *q, *r = "";
6872 p = strchr(message, '{');
6880 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6882 } else if (strncmp(message, "0-1", 3) == 0) {
6883 char *p, *q, *r = "";
6884 p = strchr(message, '{');
6892 /* Kludge for Arasan 4.1 bug */
6893 if (strcmp(r, "Black resigns") == 0) {
6894 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6897 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6899 } else if (strncmp(message, "1/2", 3) == 0) {
6900 char *p, *q, *r = "";
6901 p = strchr(message, '{');
6910 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6913 } else if (strncmp(message, "White resign", 12) == 0) {
6914 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6916 } else if (strncmp(message, "Black resign", 12) == 0) {
6917 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6919 } else if (strncmp(message, "White matches", 13) == 0 ||
6920 strncmp(message, "Black matches", 13) == 0 ) {
6921 /* [HGM] ignore GNUShogi noises */
6923 } else if (strncmp(message, "White", 5) == 0 &&
6924 message[5] != '(' &&
6925 StrStr(message, "Black") == NULL) {
6926 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6928 } else if (strncmp(message, "Black", 5) == 0 &&
6929 message[5] != '(') {
6930 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6932 } else if (strcmp(message, "resign") == 0 ||
6933 strcmp(message, "computer resigns") == 0) {
6935 case MachinePlaysBlack:
6936 case IcsPlayingBlack:
6937 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6939 case MachinePlaysWhite:
6940 case IcsPlayingWhite:
6941 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6943 case TwoMachinesPlay:
6944 if (cps->twoMachinesColor[0] == 'w')
6945 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6947 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6954 } else if (strncmp(message, "opponent mates", 14) == 0) {
6956 case MachinePlaysBlack:
6957 case IcsPlayingBlack:
6958 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6960 case MachinePlaysWhite:
6961 case IcsPlayingWhite:
6962 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6964 case TwoMachinesPlay:
6965 if (cps->twoMachinesColor[0] == 'w')
6966 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6968 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6975 } else if (strncmp(message, "computer mates", 14) == 0) {
6977 case MachinePlaysBlack:
6978 case IcsPlayingBlack:
6979 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6981 case MachinePlaysWhite:
6982 case IcsPlayingWhite:
6983 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6985 case TwoMachinesPlay:
6986 if (cps->twoMachinesColor[0] == 'w')
6987 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6989 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6996 } else if (strncmp(message, "checkmate", 9) == 0) {
6997 if (WhiteOnMove(forwardMostMove)) {
6998 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7000 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7003 } else if (strstr(message, "Draw") != NULL ||
7004 strstr(message, "game is a draw") != NULL) {
7005 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7007 } else if (strstr(message, "offer") != NULL &&
7008 strstr(message, "draw") != NULL) {
7010 if (appData.zippyPlay && first.initDone) {
7011 /* Relay offer to ICS */
7012 SendToICS(ics_prefix);
7013 SendToICS("draw\n");
7016 cps->offeredDraw = 2; /* valid until this engine moves twice */
7017 if (gameMode == TwoMachinesPlay) {
7018 if (cps->other->offeredDraw) {
7019 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7020 /* [HGM] in two-machine mode we delay relaying draw offer */
7021 /* until after we also have move, to see if it is really claim */
7023 } else if (gameMode == MachinePlaysWhite ||
7024 gameMode == MachinePlaysBlack) {
7025 if (userOfferedDraw) {
7026 DisplayInformation(_("Machine accepts your draw offer"));
7027 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7029 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7036 * Look for thinking output
7038 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7039 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7041 int plylev, mvleft, mvtot, curscore, time;
7042 char mvname[MOVE_LEN];
7046 int prefixHint = FALSE;
7047 mvname[0] = NULLCHAR;
7050 case MachinePlaysBlack:
7051 case IcsPlayingBlack:
7052 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7054 case MachinePlaysWhite:
7055 case IcsPlayingWhite:
7056 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7061 case IcsObserving: /* [DM] icsEngineAnalyze */
7062 if (!appData.icsEngineAnalyze) ignore = TRUE;
7064 case TwoMachinesPlay:
7065 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7076 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7077 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7079 if (plyext != ' ' && plyext != '\t') {
7083 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7084 if( cps->scoreIsAbsolute &&
7085 ( gameMode == MachinePlaysBlack ||
7086 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7087 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7088 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7089 !WhiteOnMove(currentMove)
7092 curscore = -curscore;
7096 programStats.depth = plylev;
7097 programStats.nodes = nodes;
7098 programStats.time = time;
7099 programStats.score = curscore;
7100 programStats.got_only_move = 0;
7102 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7105 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7106 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7107 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7108 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7109 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7110 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7111 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7112 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7115 /* Buffer overflow protection */
7116 if (buf1[0] != NULLCHAR) {
7117 if (strlen(buf1) >= sizeof(programStats.movelist)
7118 && appData.debugMode) {
7120 "PV is too long; using the first %u bytes.\n",
7121 (unsigned) sizeof(programStats.movelist) - 1);
7124 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7126 sprintf(programStats.movelist, " no PV\n");
7129 if (programStats.seen_stat) {
7130 programStats.ok_to_send = 1;
7133 if (strchr(programStats.movelist, '(') != NULL) {
7134 programStats.line_is_book = 1;
7135 programStats.nr_moves = 0;
7136 programStats.moves_left = 0;
7138 programStats.line_is_book = 0;
7141 SendProgramStatsToFrontend( cps, &programStats );
7144 [AS] Protect the thinkOutput buffer from overflow... this
7145 is only useful if buf1 hasn't overflowed first!
7147 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7149 (gameMode == TwoMachinesPlay ?
7150 ToUpper(cps->twoMachinesColor[0]) : ' '),
7151 ((double) curscore) / 100.0,
7152 prefixHint ? lastHint : "",
7153 prefixHint ? " " : "" );
7155 if( buf1[0] != NULLCHAR ) {
7156 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7158 if( strlen(buf1) > max_len ) {
7159 if( appData.debugMode) {
7160 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7162 buf1[max_len+1] = '\0';
7165 strcat( thinkOutput, buf1 );
7168 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7169 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7170 DisplayMove(currentMove - 1);
7174 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7175 /* crafty (9.25+) says "(only move) <move>"
7176 * if there is only 1 legal move
7178 sscanf(p, "(only move) %s", buf1);
7179 sprintf(thinkOutput, "%s (only move)", buf1);
7180 sprintf(programStats.movelist, "%s (only move)", buf1);
7181 programStats.depth = 1;
7182 programStats.nr_moves = 1;
7183 programStats.moves_left = 1;
7184 programStats.nodes = 1;
7185 programStats.time = 1;
7186 programStats.got_only_move = 1;
7188 /* Not really, but we also use this member to
7189 mean "line isn't going to change" (Crafty
7190 isn't searching, so stats won't change) */
7191 programStats.line_is_book = 1;
7193 SendProgramStatsToFrontend( cps, &programStats );
7195 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7196 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7197 DisplayMove(currentMove - 1);
7200 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7201 &time, &nodes, &plylev, &mvleft,
7202 &mvtot, mvname) >= 5) {
7203 /* The stat01: line is from Crafty (9.29+) in response
7204 to the "." command */
7205 programStats.seen_stat = 1;
7206 cps->maybeThinking = TRUE;
7208 if (programStats.got_only_move || !appData.periodicUpdates)
7211 programStats.depth = plylev;
7212 programStats.time = time;
7213 programStats.nodes = nodes;
7214 programStats.moves_left = mvleft;
7215 programStats.nr_moves = mvtot;
7216 strcpy(programStats.move_name, mvname);
7217 programStats.ok_to_send = 1;
7218 programStats.movelist[0] = '\0';
7220 SendProgramStatsToFrontend( cps, &programStats );
7224 } else if (strncmp(message,"++",2) == 0) {
7225 /* Crafty 9.29+ outputs this */
7226 programStats.got_fail = 2;
7229 } else if (strncmp(message,"--",2) == 0) {
7230 /* Crafty 9.29+ outputs this */
7231 programStats.got_fail = 1;
7234 } else if (thinkOutput[0] != NULLCHAR &&
7235 strncmp(message, " ", 4) == 0) {
7236 unsigned message_len;
7239 while (*p && *p == ' ') p++;
7241 message_len = strlen( p );
7243 /* [AS] Avoid buffer overflow */
7244 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7245 strcat(thinkOutput, " ");
7246 strcat(thinkOutput, p);
7249 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7250 strcat(programStats.movelist, " ");
7251 strcat(programStats.movelist, p);
7254 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7255 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7256 DisplayMove(currentMove - 1);
7264 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7265 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7267 ChessProgramStats cpstats;
7269 if (plyext != ' ' && plyext != '\t') {
7273 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7274 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7275 curscore = -curscore;
7278 cpstats.depth = plylev;
7279 cpstats.nodes = nodes;
7280 cpstats.time = time;
7281 cpstats.score = curscore;
7282 cpstats.got_only_move = 0;
7283 cpstats.movelist[0] = '\0';
7285 if (buf1[0] != NULLCHAR) {
7286 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7289 cpstats.ok_to_send = 0;
7290 cpstats.line_is_book = 0;
7291 cpstats.nr_moves = 0;
7292 cpstats.moves_left = 0;
7294 SendProgramStatsToFrontend( cps, &cpstats );
7301 /* Parse a game score from the character string "game", and
7302 record it as the history of the current game. The game
7303 score is NOT assumed to start from the standard position.
7304 The display is not updated in any way.
7307 ParseGameHistory(game)
7311 int fromX, fromY, toX, toY, boardIndex;
7316 if (appData.debugMode)
7317 fprintf(debugFP, "Parsing game history: %s\n", game);
7319 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7320 gameInfo.site = StrSave(appData.icsHost);
7321 gameInfo.date = PGNDate();
7322 gameInfo.round = StrSave("-");
7324 /* Parse out names of players */
7325 while (*game == ' ') game++;
7327 while (*game != ' ') *p++ = *game++;
7329 gameInfo.white = StrSave(buf);
7330 while (*game == ' ') game++;
7332 while (*game != ' ' && *game != '\n') *p++ = *game++;
7334 gameInfo.black = StrSave(buf);
7337 boardIndex = blackPlaysFirst ? 1 : 0;
7340 yyboardindex = boardIndex;
7341 moveType = (ChessMove) yylex();
7343 case IllegalMove: /* maybe suicide chess, etc. */
7344 if (appData.debugMode) {
7345 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7346 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7347 setbuf(debugFP, NULL);
7349 case WhitePromotionChancellor:
7350 case BlackPromotionChancellor:
7351 case WhitePromotionArchbishop:
7352 case BlackPromotionArchbishop:
7353 case WhitePromotionQueen:
7354 case BlackPromotionQueen:
7355 case WhitePromotionRook:
7356 case BlackPromotionRook:
7357 case WhitePromotionBishop:
7358 case BlackPromotionBishop:
7359 case WhitePromotionKnight:
7360 case BlackPromotionKnight:
7361 case WhitePromotionKing:
7362 case BlackPromotionKing:
7364 case WhiteCapturesEnPassant:
7365 case BlackCapturesEnPassant:
7366 case WhiteKingSideCastle:
7367 case WhiteQueenSideCastle:
7368 case BlackKingSideCastle:
7369 case BlackQueenSideCastle:
7370 case WhiteKingSideCastleWild:
7371 case WhiteQueenSideCastleWild:
7372 case BlackKingSideCastleWild:
7373 case BlackQueenSideCastleWild:
7375 case WhiteHSideCastleFR:
7376 case WhiteASideCastleFR:
7377 case BlackHSideCastleFR:
7378 case BlackASideCastleFR:
7380 fromX = currentMoveString[0] - AAA;
7381 fromY = currentMoveString[1] - ONE;
7382 toX = currentMoveString[2] - AAA;
7383 toY = currentMoveString[3] - ONE;
7384 promoChar = currentMoveString[4];
7388 fromX = moveType == WhiteDrop ?
7389 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7390 (int) CharToPiece(ToLower(currentMoveString[0]));
7392 toX = currentMoveString[2] - AAA;
7393 toY = currentMoveString[3] - ONE;
7394 promoChar = NULLCHAR;
7398 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7399 if (appData.debugMode) {
7400 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7401 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7402 setbuf(debugFP, NULL);
7404 DisplayError(buf, 0);
7406 case ImpossibleMove:
7408 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7409 if (appData.debugMode) {
7410 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7411 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7412 setbuf(debugFP, NULL);
7414 DisplayError(buf, 0);
7416 case (ChessMove) 0: /* end of file */
7417 if (boardIndex < backwardMostMove) {
7418 /* Oops, gap. How did that happen? */
7419 DisplayError(_("Gap in move list"), 0);
7422 backwardMostMove = blackPlaysFirst ? 1 : 0;
7423 if (boardIndex > forwardMostMove) {
7424 forwardMostMove = boardIndex;
7428 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7429 strcat(parseList[boardIndex-1], " ");
7430 strcat(parseList[boardIndex-1], yy_text);
7442 case GameUnfinished:
7443 if (gameMode == IcsExamining) {
7444 if (boardIndex < backwardMostMove) {
7445 /* Oops, gap. How did that happen? */
7448 backwardMostMove = blackPlaysFirst ? 1 : 0;
7451 gameInfo.result = moveType;
7452 p = strchr(yy_text, '{');
7453 if (p == NULL) p = strchr(yy_text, '(');
7456 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7458 q = strchr(p, *p == '{' ? '}' : ')');
7459 if (q != NULL) *q = NULLCHAR;
7462 gameInfo.resultDetails = StrSave(p);
7465 if (boardIndex >= forwardMostMove &&
7466 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7467 backwardMostMove = blackPlaysFirst ? 1 : 0;
7470 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7471 fromY, fromX, toY, toX, promoChar,
7472 parseList[boardIndex]);
7473 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7474 /* currentMoveString is set as a side-effect of yylex */
7475 strcpy(moveList[boardIndex], currentMoveString);
7476 strcat(moveList[boardIndex], "\n");
7478 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7479 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7485 if(gameInfo.variant != VariantShogi)
7486 strcat(parseList[boardIndex - 1], "+");
7490 strcat(parseList[boardIndex - 1], "#");
7497 /* Apply a move to the given board */
7499 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7500 int fromX, fromY, toX, toY;
7504 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7506 /* [HGM] compute & store e.p. status and castling rights for new position */
7507 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7510 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7511 oldEP = (signed char)board[EP_STATUS];
7512 board[EP_STATUS] = EP_NONE;
7514 if( board[toY][toX] != EmptySquare )
7515 board[EP_STATUS] = EP_CAPTURE;
7517 if( board[fromY][fromX] == WhitePawn ) {
7518 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7519 board[EP_STATUS] = EP_PAWN_MOVE;
7521 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7522 gameInfo.variant != VariantBerolina || toX < fromX)
7523 board[EP_STATUS] = toX | berolina;
7524 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7525 gameInfo.variant != VariantBerolina || toX > fromX)
7526 board[EP_STATUS] = toX;
7529 if( board[fromY][fromX] == BlackPawn ) {
7530 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7531 board[EP_STATUS] = EP_PAWN_MOVE;
7532 if( toY-fromY== -2) {
7533 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7534 gameInfo.variant != VariantBerolina || toX < fromX)
7535 board[EP_STATUS] = toX | berolina;
7536 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7537 gameInfo.variant != VariantBerolina || toX > fromX)
7538 board[EP_STATUS] = toX;
7542 for(i=0; i<nrCastlingRights; i++) {
7543 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7544 board[CASTLING][i] == toX && castlingRank[i] == toY
7545 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7550 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7551 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7552 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7554 if (fromX == toX && fromY == toY) return;
7556 if (fromY == DROP_RANK) {
7558 piece = board[toY][toX] = (ChessSquare) fromX;
7560 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7561 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7562 if(gameInfo.variant == VariantKnightmate)
7563 king += (int) WhiteUnicorn - (int) WhiteKing;
7565 /* Code added by Tord: */
7566 /* FRC castling assumed when king captures friendly rook. */
7567 if (board[fromY][fromX] == WhiteKing &&
7568 board[toY][toX] == WhiteRook) {
7569 board[fromY][fromX] = EmptySquare;
7570 board[toY][toX] = EmptySquare;
7572 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7574 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7576 } else if (board[fromY][fromX] == BlackKing &&
7577 board[toY][toX] == BlackRook) {
7578 board[fromY][fromX] = EmptySquare;
7579 board[toY][toX] = EmptySquare;
7581 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7583 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7585 /* End of code added by Tord */
7587 } else if (board[fromY][fromX] == king
7588 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7589 && toY == fromY && toX > fromX+1) {
7590 board[fromY][fromX] = EmptySquare;
7591 board[toY][toX] = king;
7592 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7593 board[fromY][BOARD_RGHT-1] = EmptySquare;
7594 } else if (board[fromY][fromX] == king
7595 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7596 && toY == fromY && toX < fromX-1) {
7597 board[fromY][fromX] = EmptySquare;
7598 board[toY][toX] = king;
7599 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7600 board[fromY][BOARD_LEFT] = EmptySquare;
7601 } else if (board[fromY][fromX] == WhitePawn
7602 && toY == BOARD_HEIGHT-1
7603 && gameInfo.variant != VariantXiangqi
7605 /* white pawn promotion */
7606 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7607 if (board[toY][toX] == EmptySquare) {
7608 board[toY][toX] = WhiteQueen;
7610 if(gameInfo.variant==VariantBughouse ||
7611 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7612 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7613 board[fromY][fromX] = EmptySquare;
7614 } else if ((fromY == BOARD_HEIGHT-4)
7616 && gameInfo.variant != VariantXiangqi
7617 && gameInfo.variant != VariantBerolina
7618 && (board[fromY][fromX] == WhitePawn)
7619 && (board[toY][toX] == EmptySquare)) {
7620 board[fromY][fromX] = EmptySquare;
7621 board[toY][toX] = WhitePawn;
7622 captured = board[toY - 1][toX];
7623 board[toY - 1][toX] = EmptySquare;
7624 } else if ((fromY == BOARD_HEIGHT-4)
7626 && gameInfo.variant == VariantBerolina
7627 && (board[fromY][fromX] == WhitePawn)
7628 && (board[toY][toX] == EmptySquare)) {
7629 board[fromY][fromX] = EmptySquare;
7630 board[toY][toX] = WhitePawn;
7631 if(oldEP & EP_BEROLIN_A) {
7632 captured = board[fromY][fromX-1];
7633 board[fromY][fromX-1] = EmptySquare;
7634 }else{ captured = board[fromY][fromX+1];
7635 board[fromY][fromX+1] = EmptySquare;
7637 } else if (board[fromY][fromX] == king
7638 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7639 && toY == fromY && toX > fromX+1) {
7640 board[fromY][fromX] = EmptySquare;
7641 board[toY][toX] = king;
7642 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7643 board[fromY][BOARD_RGHT-1] = EmptySquare;
7644 } else if (board[fromY][fromX] == king
7645 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7646 && toY == fromY && toX < fromX-1) {
7647 board[fromY][fromX] = EmptySquare;
7648 board[toY][toX] = king;
7649 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7650 board[fromY][BOARD_LEFT] = EmptySquare;
7651 } else if (fromY == 7 && fromX == 3
7652 && board[fromY][fromX] == BlackKing
7653 && toY == 7 && toX == 5) {
7654 board[fromY][fromX] = EmptySquare;
7655 board[toY][toX] = BlackKing;
7656 board[fromY][7] = EmptySquare;
7657 board[toY][4] = BlackRook;
7658 } else if (fromY == 7 && fromX == 3
7659 && board[fromY][fromX] == BlackKing
7660 && toY == 7 && toX == 1) {
7661 board[fromY][fromX] = EmptySquare;
7662 board[toY][toX] = BlackKing;
7663 board[fromY][0] = EmptySquare;
7664 board[toY][2] = BlackRook;
7665 } else if (board[fromY][fromX] == BlackPawn
7667 && gameInfo.variant != VariantXiangqi
7669 /* black pawn promotion */
7670 board[0][toX] = CharToPiece(ToLower(promoChar));
7671 if (board[0][toX] == EmptySquare) {
7672 board[0][toX] = BlackQueen;
7674 if(gameInfo.variant==VariantBughouse ||
7675 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7676 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7677 board[fromY][fromX] = EmptySquare;
7678 } else if ((fromY == 3)
7680 && gameInfo.variant != VariantXiangqi
7681 && gameInfo.variant != VariantBerolina
7682 && (board[fromY][fromX] == BlackPawn)
7683 && (board[toY][toX] == EmptySquare)) {
7684 board[fromY][fromX] = EmptySquare;
7685 board[toY][toX] = BlackPawn;
7686 captured = board[toY + 1][toX];
7687 board[toY + 1][toX] = EmptySquare;
7688 } else if ((fromY == 3)
7690 && gameInfo.variant == VariantBerolina
7691 && (board[fromY][fromX] == BlackPawn)
7692 && (board[toY][toX] == EmptySquare)) {
7693 board[fromY][fromX] = EmptySquare;
7694 board[toY][toX] = BlackPawn;
7695 if(oldEP & EP_BEROLIN_A) {
7696 captured = board[fromY][fromX-1];
7697 board[fromY][fromX-1] = EmptySquare;
7698 }else{ captured = board[fromY][fromX+1];
7699 board[fromY][fromX+1] = EmptySquare;
7702 board[toY][toX] = board[fromY][fromX];
7703 board[fromY][fromX] = EmptySquare;
7706 /* [HGM] now we promote for Shogi, if needed */
7707 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7708 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7711 if (gameInfo.holdingsWidth != 0) {
7713 /* !!A lot more code needs to be written to support holdings */
7714 /* [HGM] OK, so I have written it. Holdings are stored in the */
7715 /* penultimate board files, so they are automaticlly stored */
7716 /* in the game history. */
7717 if (fromY == DROP_RANK) {
7718 /* Delete from holdings, by decreasing count */
7719 /* and erasing image if necessary */
7721 if(p < (int) BlackPawn) { /* white drop */
7722 p -= (int)WhitePawn;
7723 p = PieceToNumber((ChessSquare)p);
7724 if(p >= gameInfo.holdingsSize) p = 0;
7725 if(--board[p][BOARD_WIDTH-2] <= 0)
7726 board[p][BOARD_WIDTH-1] = EmptySquare;
7727 if((int)board[p][BOARD_WIDTH-2] < 0)
7728 board[p][BOARD_WIDTH-2] = 0;
7729 } else { /* black drop */
7730 p -= (int)BlackPawn;
7731 p = PieceToNumber((ChessSquare)p);
7732 if(p >= gameInfo.holdingsSize) p = 0;
7733 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7734 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7735 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7736 board[BOARD_HEIGHT-1-p][1] = 0;
7739 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7740 && gameInfo.variant != VariantBughouse ) {
7741 /* [HGM] holdings: Add to holdings, if holdings exist */
7742 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7743 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7744 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7747 if (p >= (int) BlackPawn) {
7748 p -= (int)BlackPawn;
7749 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7750 /* in Shogi restore piece to its original first */
7751 captured = (ChessSquare) (DEMOTED captured);
7754 p = PieceToNumber((ChessSquare)p);
7755 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7756 board[p][BOARD_WIDTH-2]++;
7757 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7759 p -= (int)WhitePawn;
7760 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7761 captured = (ChessSquare) (DEMOTED captured);
7764 p = PieceToNumber((ChessSquare)p);
7765 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7766 board[BOARD_HEIGHT-1-p][1]++;
7767 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7770 } else if (gameInfo.variant == VariantAtomic) {
7771 if (captured != EmptySquare) {
7773 for (y = toY-1; y <= toY+1; y++) {
7774 for (x = toX-1; x <= toX+1; x++) {
7775 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7776 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7777 board[y][x] = EmptySquare;
7781 board[toY][toX] = EmptySquare;
7784 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7785 /* [HGM] Shogi promotions */
7786 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7789 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7790 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7791 // [HGM] superchess: take promotion piece out of holdings
7792 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7793 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7794 if(!--board[k][BOARD_WIDTH-2])
7795 board[k][BOARD_WIDTH-1] = EmptySquare;
7797 if(!--board[BOARD_HEIGHT-1-k][1])
7798 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7804 /* Updates forwardMostMove */
7806 MakeMove(fromX, fromY, toX, toY, promoChar)
7807 int fromX, fromY, toX, toY;
7810 // forwardMostMove++; // [HGM] bare: moved downstream
7812 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7813 int timeLeft; static int lastLoadFlag=0; int king, piece;
7814 piece = boards[forwardMostMove][fromY][fromX];
7815 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7816 if(gameInfo.variant == VariantKnightmate)
7817 king += (int) WhiteUnicorn - (int) WhiteKing;
7818 if(forwardMostMove == 0) {
7820 fprintf(serverMoves, "%s;", second.tidy);
7821 fprintf(serverMoves, "%s;", first.tidy);
7822 if(!blackPlaysFirst)
7823 fprintf(serverMoves, "%s;", second.tidy);
7824 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7825 lastLoadFlag = loadFlag;
7827 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7828 // print castling suffix
7829 if( toY == fromY && piece == king ) {
7831 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7833 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7836 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7837 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7838 boards[forwardMostMove][toY][toX] == EmptySquare
7840 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7842 if(promoChar != NULLCHAR)
7843 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7845 fprintf(serverMoves, "/%d/%d",
7846 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7847 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7848 else timeLeft = blackTimeRemaining/1000;
7849 fprintf(serverMoves, "/%d", timeLeft);
7851 fflush(serverMoves);
7854 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7855 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7859 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7860 if (commentList[forwardMostMove+1] != NULL) {
7861 free(commentList[forwardMostMove+1]);
7862 commentList[forwardMostMove+1] = NULL;
7864 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7865 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7866 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7867 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7868 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7869 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7870 gameInfo.result = GameUnfinished;
7871 if (gameInfo.resultDetails != NULL) {
7872 free(gameInfo.resultDetails);
7873 gameInfo.resultDetails = NULL;
7875 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7876 moveList[forwardMostMove - 1]);
7877 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7878 PosFlags(forwardMostMove - 1),
7879 fromY, fromX, toY, toX, promoChar,
7880 parseList[forwardMostMove - 1]);
7881 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7887 if(gameInfo.variant != VariantShogi)
7888 strcat(parseList[forwardMostMove - 1], "+");
7892 strcat(parseList[forwardMostMove - 1], "#");
7895 if (appData.debugMode) {
7896 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7901 /* Updates currentMove if not pausing */
7903 ShowMove(fromX, fromY, toX, toY)
7905 int instant = (gameMode == PlayFromGameFile) ?
7906 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7907 if(appData.noGUI) return;
7908 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7910 if (forwardMostMove == currentMove + 1) {
7911 AnimateMove(boards[forwardMostMove - 1],
7912 fromX, fromY, toX, toY);
7914 if (appData.highlightLastMove) {
7915 SetHighlights(fromX, fromY, toX, toY);
7918 currentMove = forwardMostMove;
7921 if (instant) return;
7923 DisplayMove(currentMove - 1);
7924 DrawPosition(FALSE, boards[currentMove]);
7925 DisplayBothClocks();
7926 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7929 void SendEgtPath(ChessProgramState *cps)
7930 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7931 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7933 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7936 char c, *q = name+1, *r, *s;
7938 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7939 while(*p && *p != ',') *q++ = *p++;
7941 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7942 strcmp(name, ",nalimov:") == 0 ) {
7943 // take nalimov path from the menu-changeable option first, if it is defined
7944 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7945 SendToProgram(buf,cps); // send egtbpath command for nalimov
7947 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7948 (s = StrStr(appData.egtFormats, name)) != NULL) {
7949 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7950 s = r = StrStr(s, ":") + 1; // beginning of path info
7951 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7952 c = *r; *r = 0; // temporarily null-terminate path info
7953 *--q = 0; // strip of trailig ':' from name
7954 sprintf(buf, "egtpath %s %s\n", name+1, s);
7956 SendToProgram(buf,cps); // send egtbpath command for this format
7958 if(*p == ',') p++; // read away comma to position for next format name
7963 InitChessProgram(cps, setup)
7964 ChessProgramState *cps;
7965 int setup; /* [HGM] needed to setup FRC opening position */
7967 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7968 if (appData.noChessProgram) return;
7969 hintRequested = FALSE;
7970 bookRequested = FALSE;
7972 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7973 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7974 if(cps->memSize) { /* [HGM] memory */
7975 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7976 SendToProgram(buf, cps);
7978 SendEgtPath(cps); /* [HGM] EGT */
7979 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7980 sprintf(buf, "cores %d\n", appData.smpCores);
7981 SendToProgram(buf, cps);
7984 SendToProgram(cps->initString, cps);
7985 if (gameInfo.variant != VariantNormal &&
7986 gameInfo.variant != VariantLoadable
7987 /* [HGM] also send variant if board size non-standard */
7988 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7990 char *v = VariantName(gameInfo.variant);
7991 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7992 /* [HGM] in protocol 1 we have to assume all variants valid */
7993 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7994 DisplayFatalError(buf, 0, 1);
7998 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7999 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8000 if( gameInfo.variant == VariantXiangqi )
8001 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8002 if( gameInfo.variant == VariantShogi )
8003 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8004 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8005 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8006 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8007 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8008 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8009 if( gameInfo.variant == VariantCourier )
8010 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8011 if( gameInfo.variant == VariantSuper )
8012 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8013 if( gameInfo.variant == VariantGreat )
8014 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8017 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8018 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8019 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8020 if(StrStr(cps->variants, b) == NULL) {
8021 // specific sized variant not known, check if general sizing allowed
8022 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8023 if(StrStr(cps->variants, "boardsize") == NULL) {
8024 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8025 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8026 DisplayFatalError(buf, 0, 1);
8029 /* [HGM] here we really should compare with the maximum supported board size */
8032 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8033 sprintf(buf, "variant %s\n", b);
8034 SendToProgram(buf, cps);
8036 currentlyInitializedVariant = gameInfo.variant;
8038 /* [HGM] send opening position in FRC to first engine */
8040 SendToProgram("force\n", cps);
8042 /* engine is now in force mode! Set flag to wake it up after first move. */
8043 setboardSpoiledMachineBlack = 1;
8047 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8048 SendToProgram(buf, cps);
8050 cps->maybeThinking = FALSE;
8051 cps->offeredDraw = 0;
8052 if (!appData.icsActive) {
8053 SendTimeControl(cps, movesPerSession, timeControl,
8054 timeIncrement, appData.searchDepth,
8057 if (appData.showThinking
8058 // [HGM] thinking: four options require thinking output to be sent
8059 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8061 SendToProgram("post\n", cps);
8063 SendToProgram("hard\n", cps);
8064 if (!appData.ponderNextMove) {
8065 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8066 it without being sure what state we are in first. "hard"
8067 is not a toggle, so that one is OK.
8069 SendToProgram("easy\n", cps);
8072 sprintf(buf, "ping %d\n", ++cps->lastPing);
8073 SendToProgram(buf, cps);
8075 cps->initDone = TRUE;
8080 StartChessProgram(cps)
8081 ChessProgramState *cps;
8086 if (appData.noChessProgram) return;
8087 cps->initDone = FALSE;
8089 if (strcmp(cps->host, "localhost") == 0) {
8090 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8091 } else if (*appData.remoteShell == NULLCHAR) {
8092 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8094 if (*appData.remoteUser == NULLCHAR) {
8095 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8098 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8099 cps->host, appData.remoteUser, cps->program);
8101 err = StartChildProcess(buf, "", &cps->pr);
8105 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8106 DisplayFatalError(buf, err, 1);
8112 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8113 if (cps->protocolVersion > 1) {
8114 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8115 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8116 cps->comboCnt = 0; // and values of combo boxes
8117 SendToProgram(buf, cps);
8119 SendToProgram("xboard\n", cps);
8125 TwoMachinesEventIfReady P((void))
8127 if (first.lastPing != first.lastPong) {
8128 DisplayMessage("", _("Waiting for first chess program"));
8129 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8132 if (second.lastPing != second.lastPong) {
8133 DisplayMessage("", _("Waiting for second chess program"));
8134 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8142 NextMatchGame P((void))
8144 int index; /* [HGM] autoinc: step load index during match */
8146 if (*appData.loadGameFile != NULLCHAR) {
8147 index = appData.loadGameIndex;
8148 if(index < 0) { // [HGM] autoinc
8149 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8150 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8152 LoadGameFromFile(appData.loadGameFile,
8154 appData.loadGameFile, FALSE);
8155 } else if (*appData.loadPositionFile != NULLCHAR) {
8156 index = appData.loadPositionIndex;
8157 if(index < 0) { // [HGM] autoinc
8158 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8159 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8161 LoadPositionFromFile(appData.loadPositionFile,
8163 appData.loadPositionFile);
8165 TwoMachinesEventIfReady();
8168 void UserAdjudicationEvent( int result )
8170 ChessMove gameResult = GameIsDrawn;
8173 gameResult = WhiteWins;
8175 else if( result < 0 ) {
8176 gameResult = BlackWins;
8179 if( gameMode == TwoMachinesPlay ) {
8180 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8185 // [HGM] save: calculate checksum of game to make games easily identifiable
8186 int StringCheckSum(char *s)
8189 if(s==NULL) return 0;
8190 while(*s) i = i*259 + *s++;
8197 for(i=backwardMostMove; i<forwardMostMove; i++) {
8198 sum += pvInfoList[i].depth;
8199 sum += StringCheckSum(parseList[i]);
8200 sum += StringCheckSum(commentList[i]);
8203 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8204 return sum + StringCheckSum(commentList[i]);
8205 } // end of save patch
8208 GameEnds(result, resultDetails, whosays)
8210 char *resultDetails;
8213 GameMode nextGameMode;
8217 if(endingGame) return; /* [HGM] crash: forbid recursion */
8220 if (appData.debugMode) {
8221 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8222 result, resultDetails ? resultDetails : "(null)", whosays);
8225 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8226 /* If we are playing on ICS, the server decides when the
8227 game is over, but the engine can offer to draw, claim
8231 if (appData.zippyPlay && first.initDone) {
8232 if (result == GameIsDrawn) {
8233 /* In case draw still needs to be claimed */
8234 SendToICS(ics_prefix);
8235 SendToICS("draw\n");
8236 } else if (StrCaseStr(resultDetails, "resign")) {
8237 SendToICS(ics_prefix);
8238 SendToICS("resign\n");
8242 endingGame = 0; /* [HGM] crash */
8246 /* If we're loading the game from a file, stop */
8247 if (whosays == GE_FILE) {
8248 (void) StopLoadGameTimer();
8252 /* Cancel draw offers */
8253 first.offeredDraw = second.offeredDraw = 0;
8255 /* If this is an ICS game, only ICS can really say it's done;
8256 if not, anyone can. */
8257 isIcsGame = (gameMode == IcsPlayingWhite ||
8258 gameMode == IcsPlayingBlack ||
8259 gameMode == IcsObserving ||
8260 gameMode == IcsExamining);
8262 if (!isIcsGame || whosays == GE_ICS) {
8263 /* OK -- not an ICS game, or ICS said it was done */
8265 if (!isIcsGame && !appData.noChessProgram)
8266 SetUserThinkingEnables();
8268 /* [HGM] if a machine claims the game end we verify this claim */
8269 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8270 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8272 ChessMove trueResult = (ChessMove) -1;
8274 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8275 first.twoMachinesColor[0] :
8276 second.twoMachinesColor[0] ;
8278 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8279 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8280 /* [HGM] verify: engine mate claims accepted if they were flagged */
8281 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8283 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8284 /* [HGM] verify: engine mate claims accepted if they were flagged */
8285 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8287 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8288 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8291 // now verify win claims, but not in drop games, as we don't understand those yet
8292 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8293 || gameInfo.variant == VariantGreat) &&
8294 (result == WhiteWins && claimer == 'w' ||
8295 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8296 if (appData.debugMode) {
8297 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8298 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8300 if(result != trueResult) {
8301 sprintf(buf, "False win claim: '%s'", resultDetails);
8302 result = claimer == 'w' ? BlackWins : WhiteWins;
8303 resultDetails = buf;
8306 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8307 && (forwardMostMove <= backwardMostMove ||
8308 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8309 (claimer=='b')==(forwardMostMove&1))
8311 /* [HGM] verify: draws that were not flagged are false claims */
8312 sprintf(buf, "False draw claim: '%s'", resultDetails);
8313 result = claimer == 'w' ? BlackWins : WhiteWins;
8314 resultDetails = buf;
8316 /* (Claiming a loss is accepted no questions asked!) */
8318 /* [HGM] bare: don't allow bare King to win */
8319 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8320 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8321 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8322 && result != GameIsDrawn)
8323 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8324 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8325 int p = (signed char)boards[forwardMostMove][i][j] - color;
8326 if(p >= 0 && p <= (int)WhiteKing) k++;
8328 if (appData.debugMode) {
8329 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8330 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8333 result = GameIsDrawn;
8334 sprintf(buf, "%s but bare king", resultDetails);
8335 resultDetails = buf;
8341 if(serverMoves != NULL && !loadFlag) { char c = '=';
8342 if(result==WhiteWins) c = '+';
8343 if(result==BlackWins) c = '-';
8344 if(resultDetails != NULL)
8345 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8347 if (resultDetails != NULL) {
8348 gameInfo.result = result;
8349 gameInfo.resultDetails = StrSave(resultDetails);
8351 /* display last move only if game was not loaded from file */
8352 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8353 DisplayMove(currentMove - 1);
8355 if (forwardMostMove != 0) {
8356 if (gameMode != PlayFromGameFile && gameMode != EditGame
8357 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8359 if (*appData.saveGameFile != NULLCHAR) {
8360 SaveGameToFile(appData.saveGameFile, TRUE);
8361 } else if (appData.autoSaveGames) {
8364 if (*appData.savePositionFile != NULLCHAR) {
8365 SavePositionToFile(appData.savePositionFile);
8370 /* Tell program how game ended in case it is learning */
8371 /* [HGM] Moved this to after saving the PGN, just in case */
8372 /* engine died and we got here through time loss. In that */
8373 /* case we will get a fatal error writing the pipe, which */
8374 /* would otherwise lose us the PGN. */
8375 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8376 /* output during GameEnds should never be fatal anymore */
8377 if (gameMode == MachinePlaysWhite ||
8378 gameMode == MachinePlaysBlack ||
8379 gameMode == TwoMachinesPlay ||
8380 gameMode == IcsPlayingWhite ||
8381 gameMode == IcsPlayingBlack ||
8382 gameMode == BeginningOfGame) {
8384 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8386 if (first.pr != NoProc) {
8387 SendToProgram(buf, &first);
8389 if (second.pr != NoProc &&
8390 gameMode == TwoMachinesPlay) {
8391 SendToProgram(buf, &second);
8396 if (appData.icsActive) {
8397 if (appData.quietPlay &&
8398 (gameMode == IcsPlayingWhite ||
8399 gameMode == IcsPlayingBlack)) {
8400 SendToICS(ics_prefix);
8401 SendToICS("set shout 1\n");
8403 nextGameMode = IcsIdle;
8404 ics_user_moved = FALSE;
8405 /* clean up premove. It's ugly when the game has ended and the
8406 * premove highlights are still on the board.
8410 ClearPremoveHighlights();
8411 DrawPosition(FALSE, boards[currentMove]);
8413 if (whosays == GE_ICS) {
8416 if (gameMode == IcsPlayingWhite)
8418 else if(gameMode == IcsPlayingBlack)
8422 if (gameMode == IcsPlayingBlack)
8424 else if(gameMode == IcsPlayingWhite)
8431 PlayIcsUnfinishedSound();
8434 } else if (gameMode == EditGame ||
8435 gameMode == PlayFromGameFile ||
8436 gameMode == AnalyzeMode ||
8437 gameMode == AnalyzeFile) {
8438 nextGameMode = gameMode;
8440 nextGameMode = EndOfGame;
8445 nextGameMode = gameMode;
8448 if (appData.noChessProgram) {
8449 gameMode = nextGameMode;
8451 endingGame = 0; /* [HGM] crash */
8456 /* Put first chess program into idle state */
8457 if (first.pr != NoProc &&
8458 (gameMode == MachinePlaysWhite ||
8459 gameMode == MachinePlaysBlack ||
8460 gameMode == TwoMachinesPlay ||
8461 gameMode == IcsPlayingWhite ||
8462 gameMode == IcsPlayingBlack ||
8463 gameMode == BeginningOfGame)) {
8464 SendToProgram("force\n", &first);
8465 if (first.usePing) {
8467 sprintf(buf, "ping %d\n", ++first.lastPing);
8468 SendToProgram(buf, &first);
8471 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8472 /* Kill off first chess program */
8473 if (first.isr != NULL)
8474 RemoveInputSource(first.isr);
8477 if (first.pr != NoProc) {
8479 DoSleep( appData.delayBeforeQuit );
8480 SendToProgram("quit\n", &first);
8481 DoSleep( appData.delayAfterQuit );
8482 DestroyChildProcess(first.pr, first.useSigterm);
8487 /* Put second chess program into idle state */
8488 if (second.pr != NoProc &&
8489 gameMode == TwoMachinesPlay) {
8490 SendToProgram("force\n", &second);
8491 if (second.usePing) {
8493 sprintf(buf, "ping %d\n", ++second.lastPing);
8494 SendToProgram(buf, &second);
8497 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8498 /* Kill off second chess program */
8499 if (second.isr != NULL)
8500 RemoveInputSource(second.isr);
8503 if (second.pr != NoProc) {
8504 DoSleep( appData.delayBeforeQuit );
8505 SendToProgram("quit\n", &second);
8506 DoSleep( appData.delayAfterQuit );
8507 DestroyChildProcess(second.pr, second.useSigterm);
8512 if (matchMode && gameMode == TwoMachinesPlay) {
8515 if (first.twoMachinesColor[0] == 'w') {
8522 if (first.twoMachinesColor[0] == 'b') {
8531 if (matchGame < appData.matchGames) {
8533 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8534 tmp = first.twoMachinesColor;
8535 first.twoMachinesColor = second.twoMachinesColor;
8536 second.twoMachinesColor = tmp;
8538 gameMode = nextGameMode;
8540 if(appData.matchPause>10000 || appData.matchPause<10)
8541 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8542 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8543 endingGame = 0; /* [HGM] crash */
8547 gameMode = nextGameMode;
8548 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8549 first.tidy, second.tidy,
8550 first.matchWins, second.matchWins,
8551 appData.matchGames - (first.matchWins + second.matchWins));
8552 DisplayFatalError(buf, 0, 0);
8555 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8556 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8558 gameMode = nextGameMode;
8560 endingGame = 0; /* [HGM] crash */
8563 /* Assumes program was just initialized (initString sent).
8564 Leaves program in force mode. */
8566 FeedMovesToProgram(cps, upto)
8567 ChessProgramState *cps;
8572 if (appData.debugMode)
8573 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8574 startedFromSetupPosition ? "position and " : "",
8575 backwardMostMove, upto, cps->which);
8576 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8577 // [HGM] variantswitch: make engine aware of new variant
8578 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8579 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8580 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8581 SendToProgram(buf, cps);
8582 currentlyInitializedVariant = gameInfo.variant;
8584 SendToProgram("force\n", cps);
8585 if (startedFromSetupPosition) {
8586 SendBoard(cps, backwardMostMove);
8587 if (appData.debugMode) {
8588 fprintf(debugFP, "feedMoves\n");
8591 for (i = backwardMostMove; i < upto; i++) {
8592 SendMoveToProgram(i, cps);
8598 ResurrectChessProgram()
8600 /* The chess program may have exited.
8601 If so, restart it and feed it all the moves made so far. */
8603 if (appData.noChessProgram || first.pr != NoProc) return;
8605 StartChessProgram(&first);
8606 InitChessProgram(&first, FALSE);
8607 FeedMovesToProgram(&first, currentMove);
8609 if (!first.sendTime) {
8610 /* can't tell gnuchess what its clock should read,
8611 so we bow to its notion. */
8613 timeRemaining[0][currentMove] = whiteTimeRemaining;
8614 timeRemaining[1][currentMove] = blackTimeRemaining;
8617 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8618 appData.icsEngineAnalyze) && first.analysisSupport) {
8619 SendToProgram("analyze\n", &first);
8620 first.analyzing = TRUE;
8633 if (appData.debugMode) {
8634 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8635 redraw, init, gameMode);
8637 CleanupTail(); // [HGM] vari: delete any stored variations
8638 pausing = pauseExamInvalid = FALSE;
8639 startedFromSetupPosition = blackPlaysFirst = FALSE;
8641 whiteFlag = blackFlag = FALSE;
8642 userOfferedDraw = FALSE;
8643 hintRequested = bookRequested = FALSE;
8644 first.maybeThinking = FALSE;
8645 second.maybeThinking = FALSE;
8646 first.bookSuspend = FALSE; // [HGM] book
8647 second.bookSuspend = FALSE;
8648 thinkOutput[0] = NULLCHAR;
8649 lastHint[0] = NULLCHAR;
8650 ClearGameInfo(&gameInfo);
8651 gameInfo.variant = StringToVariant(appData.variant);
8652 ics_user_moved = ics_clock_paused = FALSE;
8653 ics_getting_history = H_FALSE;
8655 white_holding[0] = black_holding[0] = NULLCHAR;
8656 ClearProgramStats();
8657 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8661 flipView = appData.flipView;
8662 ClearPremoveHighlights();
8664 alarmSounded = FALSE;
8666 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8667 if(appData.serverMovesName != NULL) {
8668 /* [HGM] prepare to make moves file for broadcasting */
8669 clock_t t = clock();
8670 if(serverMoves != NULL) fclose(serverMoves);
8671 serverMoves = fopen(appData.serverMovesName, "r");
8672 if(serverMoves != NULL) {
8673 fclose(serverMoves);
8674 /* delay 15 sec before overwriting, so all clients can see end */
8675 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8677 serverMoves = fopen(appData.serverMovesName, "w");
8681 gameMode = BeginningOfGame;
8683 if(appData.icsActive) gameInfo.variant = VariantNormal;
8684 currentMove = forwardMostMove = backwardMostMove = 0;
8685 InitPosition(redraw);
8686 for (i = 0; i < MAX_MOVES; i++) {
8687 if (commentList[i] != NULL) {
8688 free(commentList[i]);
8689 commentList[i] = NULL;
8693 timeRemaining[0][0] = whiteTimeRemaining;
8694 timeRemaining[1][0] = blackTimeRemaining;
8695 if (first.pr == NULL) {
8696 StartChessProgram(&first);
8699 InitChessProgram(&first, startedFromSetupPosition);
8702 DisplayMessage("", "");
8703 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8704 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8711 if (!AutoPlayOneMove())
8713 if (matchMode || appData.timeDelay == 0)
8715 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8717 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8726 int fromX, fromY, toX, toY;
8728 if (appData.debugMode) {
8729 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8732 if (gameMode != PlayFromGameFile)
8735 if (currentMove >= forwardMostMove) {
8736 gameMode = EditGame;
8739 /* [AS] Clear current move marker at the end of a game */
8740 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8745 toX = moveList[currentMove][2] - AAA;
8746 toY = moveList[currentMove][3] - ONE;
8748 if (moveList[currentMove][1] == '@') {
8749 if (appData.highlightLastMove) {
8750 SetHighlights(-1, -1, toX, toY);
8753 fromX = moveList[currentMove][0] - AAA;
8754 fromY = moveList[currentMove][1] - ONE;
8756 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8758 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8760 if (appData.highlightLastMove) {
8761 SetHighlights(fromX, fromY, toX, toY);
8764 DisplayMove(currentMove);
8765 SendMoveToProgram(currentMove++, &first);
8766 DisplayBothClocks();
8767 DrawPosition(FALSE, boards[currentMove]);
8768 // [HGM] PV info: always display, routine tests if empty
8769 DisplayComment(currentMove - 1, commentList[currentMove]);
8775 LoadGameOneMove(readAhead)
8776 ChessMove readAhead;
8778 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8779 char promoChar = NULLCHAR;
8784 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8785 gameMode != AnalyzeMode && gameMode != Training) {
8790 yyboardindex = forwardMostMove;
8791 if (readAhead != (ChessMove)0) {
8792 moveType = readAhead;
8794 if (gameFileFP == NULL)
8796 moveType = (ChessMove) yylex();
8802 if (appData.debugMode)
8803 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8806 /* append the comment but don't display it */
8807 AppendComment(currentMove, p, FALSE);
8810 case WhiteCapturesEnPassant:
8811 case BlackCapturesEnPassant:
8812 case WhitePromotionChancellor:
8813 case BlackPromotionChancellor:
8814 case WhitePromotionArchbishop:
8815 case BlackPromotionArchbishop:
8816 case WhitePromotionCentaur:
8817 case BlackPromotionCentaur:
8818 case WhitePromotionQueen:
8819 case BlackPromotionQueen:
8820 case WhitePromotionRook:
8821 case BlackPromotionRook:
8822 case WhitePromotionBishop:
8823 case BlackPromotionBishop:
8824 case WhitePromotionKnight:
8825 case BlackPromotionKnight:
8826 case WhitePromotionKing:
8827 case BlackPromotionKing:
8829 case WhiteKingSideCastle:
8830 case WhiteQueenSideCastle:
8831 case BlackKingSideCastle:
8832 case BlackQueenSideCastle:
8833 case WhiteKingSideCastleWild:
8834 case WhiteQueenSideCastleWild:
8835 case BlackKingSideCastleWild:
8836 case BlackQueenSideCastleWild:
8838 case WhiteHSideCastleFR:
8839 case WhiteASideCastleFR:
8840 case BlackHSideCastleFR:
8841 case BlackASideCastleFR:
8843 if (appData.debugMode)
8844 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8845 fromX = currentMoveString[0] - AAA;
8846 fromY = currentMoveString[1] - ONE;
8847 toX = currentMoveString[2] - AAA;
8848 toY = currentMoveString[3] - ONE;
8849 promoChar = currentMoveString[4];
8854 if (appData.debugMode)
8855 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8856 fromX = moveType == WhiteDrop ?
8857 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8858 (int) CharToPiece(ToLower(currentMoveString[0]));
8860 toX = currentMoveString[2] - AAA;
8861 toY = currentMoveString[3] - ONE;
8867 case GameUnfinished:
8868 if (appData.debugMode)
8869 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8870 p = strchr(yy_text, '{');
8871 if (p == NULL) p = strchr(yy_text, '(');
8874 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8876 q = strchr(p, *p == '{' ? '}' : ')');
8877 if (q != NULL) *q = NULLCHAR;
8880 GameEnds(moveType, p, GE_FILE);
8882 if (cmailMsgLoaded) {
8884 flipView = WhiteOnMove(currentMove);
8885 if (moveType == GameUnfinished) flipView = !flipView;
8886 if (appData.debugMode)
8887 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8891 case (ChessMove) 0: /* end of file */
8892 if (appData.debugMode)
8893 fprintf(debugFP, "Parser hit end of file\n");
8894 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8900 if (WhiteOnMove(currentMove)) {
8901 GameEnds(BlackWins, "Black mates", GE_FILE);
8903 GameEnds(WhiteWins, "White mates", GE_FILE);
8907 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8914 if (lastLoadGameStart == GNUChessGame) {
8915 /* GNUChessGames have numbers, but they aren't move numbers */
8916 if (appData.debugMode)
8917 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8918 yy_text, (int) moveType);
8919 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8921 /* else fall thru */
8926 /* Reached start of next game in file */
8927 if (appData.debugMode)
8928 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8929 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8935 if (WhiteOnMove(currentMove)) {
8936 GameEnds(BlackWins, "Black mates", GE_FILE);
8938 GameEnds(WhiteWins, "White mates", GE_FILE);
8942 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8948 case PositionDiagram: /* should not happen; ignore */
8949 case ElapsedTime: /* ignore */
8950 case NAG: /* ignore */
8951 if (appData.debugMode)
8952 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8953 yy_text, (int) moveType);
8954 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8957 if (appData.testLegality) {
8958 if (appData.debugMode)
8959 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8960 sprintf(move, _("Illegal move: %d.%s%s"),
8961 (forwardMostMove / 2) + 1,
8962 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8963 DisplayError(move, 0);
8966 if (appData.debugMode)
8967 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8968 yy_text, currentMoveString);
8969 fromX = currentMoveString[0] - AAA;
8970 fromY = currentMoveString[1] - ONE;
8971 toX = currentMoveString[2] - AAA;
8972 toY = currentMoveString[3] - ONE;
8973 promoChar = currentMoveString[4];
8978 if (appData.debugMode)
8979 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8980 sprintf(move, _("Ambiguous move: %d.%s%s"),
8981 (forwardMostMove / 2) + 1,
8982 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8983 DisplayError(move, 0);
8988 case ImpossibleMove:
8989 if (appData.debugMode)
8990 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8991 sprintf(move, _("Illegal move: %d.%s%s"),
8992 (forwardMostMove / 2) + 1,
8993 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8994 DisplayError(move, 0);
9000 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9001 DrawPosition(FALSE, boards[currentMove]);
9002 DisplayBothClocks();
9003 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9004 DisplayComment(currentMove - 1, commentList[currentMove]);
9006 (void) StopLoadGameTimer();
9008 cmailOldMove = forwardMostMove;
9011 /* currentMoveString is set as a side-effect of yylex */
9012 strcat(currentMoveString, "\n");
9013 strcpy(moveList[forwardMostMove], currentMoveString);
9015 thinkOutput[0] = NULLCHAR;
9016 MakeMove(fromX, fromY, toX, toY, promoChar);
9017 currentMove = forwardMostMove;
9022 /* Load the nth game from the given file */
9024 LoadGameFromFile(filename, n, title, useList)
9028 /*Boolean*/ int useList;
9033 if (strcmp(filename, "-") == 0) {
9037 f = fopen(filename, "rb");
9039 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9040 DisplayError(buf, errno);
9044 if (fseek(f, 0, 0) == -1) {
9045 /* f is not seekable; probably a pipe */
9048 if (useList && n == 0) {
9049 int error = GameListBuild(f);
9051 DisplayError(_("Cannot build game list"), error);
9052 } else if (!ListEmpty(&gameList) &&
9053 ((ListGame *) gameList.tailPred)->number > 1) {
9054 GameListPopUp(f, title);
9061 return LoadGame(f, n, title, FALSE);
9066 MakeRegisteredMove()
9068 int fromX, fromY, toX, toY;
9070 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9071 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9074 if (appData.debugMode)
9075 fprintf(debugFP, "Restoring %s for game %d\n",
9076 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9078 thinkOutput[0] = NULLCHAR;
9079 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9080 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9081 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9082 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9083 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9084 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9085 MakeMove(fromX, fromY, toX, toY, promoChar);
9086 ShowMove(fromX, fromY, toX, toY);
9088 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9095 if (WhiteOnMove(currentMove)) {
9096 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9098 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9103 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9110 if (WhiteOnMove(currentMove)) {
9111 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9113 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9118 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9129 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9131 CmailLoadGame(f, gameNumber, title, useList)
9139 if (gameNumber > nCmailGames) {
9140 DisplayError(_("No more games in this message"), 0);
9143 if (f == lastLoadGameFP) {
9144 int offset = gameNumber - lastLoadGameNumber;
9146 cmailMsg[0] = NULLCHAR;
9147 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9148 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9149 nCmailMovesRegistered--;
9151 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9152 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9153 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9156 if (! RegisterMove()) return FALSE;
9160 retVal = LoadGame(f, gameNumber, title, useList);
9162 /* Make move registered during previous look at this game, if any */
9163 MakeRegisteredMove();
9165 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9166 commentList[currentMove]
9167 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9168 DisplayComment(currentMove - 1, commentList[currentMove]);
9174 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9179 int gameNumber = lastLoadGameNumber + offset;
9180 if (lastLoadGameFP == NULL) {
9181 DisplayError(_("No game has been loaded yet"), 0);
9184 if (gameNumber <= 0) {
9185 DisplayError(_("Can't back up any further"), 0);
9188 if (cmailMsgLoaded) {
9189 return CmailLoadGame(lastLoadGameFP, gameNumber,
9190 lastLoadGameTitle, lastLoadGameUseList);
9192 return LoadGame(lastLoadGameFP, gameNumber,
9193 lastLoadGameTitle, lastLoadGameUseList);
9199 /* Load the nth game from open file f */
9201 LoadGame(f, gameNumber, title, useList)
9209 int gn = gameNumber;
9210 ListGame *lg = NULL;
9213 GameMode oldGameMode;
9214 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9216 if (appData.debugMode)
9217 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9219 if (gameMode == Training )
9220 SetTrainingModeOff();
9222 oldGameMode = gameMode;
9223 if (gameMode != BeginningOfGame) {
9228 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9229 fclose(lastLoadGameFP);
9233 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9236 fseek(f, lg->offset, 0);
9237 GameListHighlight(gameNumber);
9241 DisplayError(_("Game number out of range"), 0);
9246 if (fseek(f, 0, 0) == -1) {
9247 if (f == lastLoadGameFP ?
9248 gameNumber == lastLoadGameNumber + 1 :
9252 DisplayError(_("Can't seek on game file"), 0);
9258 lastLoadGameNumber = gameNumber;
9259 strcpy(lastLoadGameTitle, title);
9260 lastLoadGameUseList = useList;
9264 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9265 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9266 lg->gameInfo.black);
9268 } else if (*title != NULLCHAR) {
9269 if (gameNumber > 1) {
9270 sprintf(buf, "%s %d", title, gameNumber);
9273 DisplayTitle(title);
9277 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9278 gameMode = PlayFromGameFile;
9282 currentMove = forwardMostMove = backwardMostMove = 0;
9283 CopyBoard(boards[0], initialPosition);
9287 * Skip the first gn-1 games in the file.
9288 * Also skip over anything that precedes an identifiable
9289 * start of game marker, to avoid being confused by
9290 * garbage at the start of the file. Currently
9291 * recognized start of game markers are the move number "1",
9292 * the pattern "gnuchess .* game", the pattern
9293 * "^[#;%] [^ ]* game file", and a PGN tag block.
9294 * A game that starts with one of the latter two patterns
9295 * will also have a move number 1, possibly
9296 * following a position diagram.
9297 * 5-4-02: Let's try being more lenient and allowing a game to
9298 * start with an unnumbered move. Does that break anything?
9300 cm = lastLoadGameStart = (ChessMove) 0;
9302 yyboardindex = forwardMostMove;
9303 cm = (ChessMove) yylex();
9306 if (cmailMsgLoaded) {
9307 nCmailGames = CMAIL_MAX_GAMES - gn;
9310 DisplayError(_("Game not found in file"), 0);
9317 lastLoadGameStart = cm;
9321 switch (lastLoadGameStart) {
9328 gn--; /* count this game */
9329 lastLoadGameStart = cm;
9338 switch (lastLoadGameStart) {
9343 gn--; /* count this game */
9344 lastLoadGameStart = cm;
9347 lastLoadGameStart = cm; /* game counted already */
9355 yyboardindex = forwardMostMove;
9356 cm = (ChessMove) yylex();
9357 } while (cm == PGNTag || cm == Comment);
9364 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9365 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9366 != CMAIL_OLD_RESULT) {
9368 cmailResult[ CMAIL_MAX_GAMES
9369 - gn - 1] = CMAIL_OLD_RESULT;
9375 /* Only a NormalMove can be at the start of a game
9376 * without a position diagram. */
9377 if (lastLoadGameStart == (ChessMove) 0) {
9379 lastLoadGameStart = MoveNumberOne;
9388 if (appData.debugMode)
9389 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9391 if (cm == XBoardGame) {
9392 /* Skip any header junk before position diagram and/or move 1 */
9394 yyboardindex = forwardMostMove;
9395 cm = (ChessMove) yylex();
9397 if (cm == (ChessMove) 0 ||
9398 cm == GNUChessGame || cm == XBoardGame) {
9399 /* Empty game; pretend end-of-file and handle later */
9404 if (cm == MoveNumberOne || cm == PositionDiagram ||
9405 cm == PGNTag || cm == Comment)
9408 } else if (cm == GNUChessGame) {
9409 if (gameInfo.event != NULL) {
9410 free(gameInfo.event);
9412 gameInfo.event = StrSave(yy_text);
9415 startedFromSetupPosition = FALSE;
9416 while (cm == PGNTag) {
9417 if (appData.debugMode)
9418 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9419 err = ParsePGNTag(yy_text, &gameInfo);
9420 if (!err) numPGNTags++;
9422 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9423 if(gameInfo.variant != oldVariant) {
9424 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9426 oldVariant = gameInfo.variant;
9427 if (appData.debugMode)
9428 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9432 if (gameInfo.fen != NULL) {
9433 Board initial_position;
9434 startedFromSetupPosition = TRUE;
9435 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9437 DisplayError(_("Bad FEN position in file"), 0);
9440 CopyBoard(boards[0], initial_position);
9441 if (blackPlaysFirst) {
9442 currentMove = forwardMostMove = backwardMostMove = 1;
9443 CopyBoard(boards[1], initial_position);
9444 strcpy(moveList[0], "");
9445 strcpy(parseList[0], "");
9446 timeRemaining[0][1] = whiteTimeRemaining;
9447 timeRemaining[1][1] = blackTimeRemaining;
9448 if (commentList[0] != NULL) {
9449 commentList[1] = commentList[0];
9450 commentList[0] = NULL;
9453 currentMove = forwardMostMove = backwardMostMove = 0;
9455 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9457 initialRulePlies = FENrulePlies;
9458 for( i=0; i< nrCastlingRights; i++ )
9459 initialRights[i] = initial_position[CASTLING][i];
9461 yyboardindex = forwardMostMove;
9463 gameInfo.fen = NULL;
9466 yyboardindex = forwardMostMove;
9467 cm = (ChessMove) yylex();
9469 /* Handle comments interspersed among the tags */
9470 while (cm == Comment) {
9472 if (appData.debugMode)
9473 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9475 AppendComment(currentMove, p, FALSE);
9476 yyboardindex = forwardMostMove;
9477 cm = (ChessMove) yylex();
9481 /* don't rely on existence of Event tag since if game was
9482 * pasted from clipboard the Event tag may not exist
9484 if (numPGNTags > 0){
9486 if (gameInfo.variant == VariantNormal) {
9487 gameInfo.variant = StringToVariant(gameInfo.event);
9490 if( appData.autoDisplayTags ) {
9491 tags = PGNTags(&gameInfo);
9492 TagsPopUp(tags, CmailMsg());
9497 /* Make something up, but don't display it now */
9502 if (cm == PositionDiagram) {
9505 Board initial_position;
9507 if (appData.debugMode)
9508 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9510 if (!startedFromSetupPosition) {
9512 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9513 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9523 initial_position[i][j++] = CharToPiece(*p);
9526 while (*p == ' ' || *p == '\t' ||
9527 *p == '\n' || *p == '\r') p++;
9529 if (strncmp(p, "black", strlen("black"))==0)
9530 blackPlaysFirst = TRUE;
9532 blackPlaysFirst = FALSE;
9533 startedFromSetupPosition = TRUE;
9535 CopyBoard(boards[0], initial_position);
9536 if (blackPlaysFirst) {
9537 currentMove = forwardMostMove = backwardMostMove = 1;
9538 CopyBoard(boards[1], initial_position);
9539 strcpy(moveList[0], "");
9540 strcpy(parseList[0], "");
9541 timeRemaining[0][1] = whiteTimeRemaining;
9542 timeRemaining[1][1] = blackTimeRemaining;
9543 if (commentList[0] != NULL) {
9544 commentList[1] = commentList[0];
9545 commentList[0] = NULL;
9548 currentMove = forwardMostMove = backwardMostMove = 0;
9551 yyboardindex = forwardMostMove;
9552 cm = (ChessMove) yylex();
9555 if (first.pr == NoProc) {
9556 StartChessProgram(&first);
9558 InitChessProgram(&first, FALSE);
9559 SendToProgram("force\n", &first);
9560 if (startedFromSetupPosition) {
9561 SendBoard(&first, forwardMostMove);
9562 if (appData.debugMode) {
9563 fprintf(debugFP, "Load Game\n");
9565 DisplayBothClocks();
9568 /* [HGM] server: flag to write setup moves in broadcast file as one */
9569 loadFlag = appData.suppressLoadMoves;
9571 while (cm == Comment) {
9573 if (appData.debugMode)
9574 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9576 AppendComment(currentMove, p, FALSE);
9577 yyboardindex = forwardMostMove;
9578 cm = (ChessMove) yylex();
9581 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9582 cm == WhiteWins || cm == BlackWins ||
9583 cm == GameIsDrawn || cm == GameUnfinished) {
9584 DisplayMessage("", _("No moves in game"));
9585 if (cmailMsgLoaded) {
9586 if (appData.debugMode)
9587 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9591 DrawPosition(FALSE, boards[currentMove]);
9592 DisplayBothClocks();
9593 gameMode = EditGame;
9600 // [HGM] PV info: routine tests if comment empty
9601 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9602 DisplayComment(currentMove - 1, commentList[currentMove]);
9604 if (!matchMode && appData.timeDelay != 0)
9605 DrawPosition(FALSE, boards[currentMove]);
9607 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9608 programStats.ok_to_send = 1;
9611 /* if the first token after the PGN tags is a move
9612 * and not move number 1, retrieve it from the parser
9614 if (cm != MoveNumberOne)
9615 LoadGameOneMove(cm);
9617 /* load the remaining moves from the file */
9618 while (LoadGameOneMove((ChessMove)0)) {
9619 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9620 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9623 /* rewind to the start of the game */
9624 currentMove = backwardMostMove;
9626 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9628 if (oldGameMode == AnalyzeFile ||
9629 oldGameMode == AnalyzeMode) {
9633 if (matchMode || appData.timeDelay == 0) {
9635 gameMode = EditGame;
9637 } else if (appData.timeDelay > 0) {
9641 if (appData.debugMode)
9642 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9644 loadFlag = 0; /* [HGM] true game starts */
9648 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9650 ReloadPosition(offset)
9653 int positionNumber = lastLoadPositionNumber + offset;
9654 if (lastLoadPositionFP == NULL) {
9655 DisplayError(_("No position has been loaded yet"), 0);
9658 if (positionNumber <= 0) {
9659 DisplayError(_("Can't back up any further"), 0);
9662 return LoadPosition(lastLoadPositionFP, positionNumber,
9663 lastLoadPositionTitle);
9666 /* Load the nth position from the given file */
9668 LoadPositionFromFile(filename, n, title)
9676 if (strcmp(filename, "-") == 0) {
9677 return LoadPosition(stdin, n, "stdin");
9679 f = fopen(filename, "rb");
9681 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9682 DisplayError(buf, errno);
9685 return LoadPosition(f, n, title);
9690 /* Load the nth position from the given open file, and close it */
9692 LoadPosition(f, positionNumber, title)
9697 char *p, line[MSG_SIZ];
9698 Board initial_position;
9699 int i, j, fenMode, pn;
9701 if (gameMode == Training )
9702 SetTrainingModeOff();
9704 if (gameMode != BeginningOfGame) {
9707 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9708 fclose(lastLoadPositionFP);
9710 if (positionNumber == 0) positionNumber = 1;
9711 lastLoadPositionFP = f;
9712 lastLoadPositionNumber = positionNumber;
9713 strcpy(lastLoadPositionTitle, title);
9714 if (first.pr == NoProc) {
9715 StartChessProgram(&first);
9716 InitChessProgram(&first, FALSE);
9718 pn = positionNumber;
9719 if (positionNumber < 0) {
9720 /* Negative position number means to seek to that byte offset */
9721 if (fseek(f, -positionNumber, 0) == -1) {
9722 DisplayError(_("Can't seek on position file"), 0);
9727 if (fseek(f, 0, 0) == -1) {
9728 if (f == lastLoadPositionFP ?
9729 positionNumber == lastLoadPositionNumber + 1 :
9730 positionNumber == 1) {
9733 DisplayError(_("Can't seek on position file"), 0);
9738 /* See if this file is FEN or old-style xboard */
9739 if (fgets(line, MSG_SIZ, f) == NULL) {
9740 DisplayError(_("Position not found in file"), 0);
9743 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9744 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9747 if (fenMode || line[0] == '#') pn--;
9749 /* skip positions before number pn */
9750 if (fgets(line, MSG_SIZ, f) == NULL) {
9752 DisplayError(_("Position not found in file"), 0);
9755 if (fenMode || line[0] == '#') pn--;
9760 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9761 DisplayError(_("Bad FEN position in file"), 0);
9765 (void) fgets(line, MSG_SIZ, f);
9766 (void) fgets(line, MSG_SIZ, f);
9768 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9769 (void) fgets(line, MSG_SIZ, f);
9770 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9773 initial_position[i][j++] = CharToPiece(*p);
9777 blackPlaysFirst = FALSE;
9779 (void) fgets(line, MSG_SIZ, f);
9780 if (strncmp(line, "black", strlen("black"))==0)
9781 blackPlaysFirst = TRUE;
9784 startedFromSetupPosition = TRUE;
9786 SendToProgram("force\n", &first);
9787 CopyBoard(boards[0], initial_position);
9788 if (blackPlaysFirst) {
9789 currentMove = forwardMostMove = backwardMostMove = 1;
9790 strcpy(moveList[0], "");
9791 strcpy(parseList[0], "");
9792 CopyBoard(boards[1], initial_position);
9793 DisplayMessage("", _("Black to play"));
9795 currentMove = forwardMostMove = backwardMostMove = 0;
9796 DisplayMessage("", _("White to play"));
9798 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9799 SendBoard(&first, forwardMostMove);
9800 if (appData.debugMode) {
9802 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9803 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9804 fprintf(debugFP, "Load Position\n");
9807 if (positionNumber > 1) {
9808 sprintf(line, "%s %d", title, positionNumber);
9811 DisplayTitle(title);
9813 gameMode = EditGame;
9816 timeRemaining[0][1] = whiteTimeRemaining;
9817 timeRemaining[1][1] = blackTimeRemaining;
9818 DrawPosition(FALSE, boards[currentMove]);
9825 CopyPlayerNameIntoFileName(dest, src)
9828 while (*src != NULLCHAR && *src != ',') {
9833 *(*dest)++ = *src++;
9838 char *DefaultFileName(ext)
9841 static char def[MSG_SIZ];
9844 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9846 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9848 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9857 /* Save the current game to the given file */
9859 SaveGameToFile(filename, append)
9866 if (strcmp(filename, "-") == 0) {
9867 return SaveGame(stdout, 0, NULL);
9869 f = fopen(filename, append ? "a" : "w");
9871 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9872 DisplayError(buf, errno);
9875 return SaveGame(f, 0, NULL);
9884 static char buf[MSG_SIZ];
9887 p = strchr(str, ' ');
9888 if (p == NULL) return str;
9889 strncpy(buf, str, p - str);
9890 buf[p - str] = NULLCHAR;
9894 #define PGN_MAX_LINE 75
9896 #define PGN_SIDE_WHITE 0
9897 #define PGN_SIDE_BLACK 1
9900 static int FindFirstMoveOutOfBook( int side )
9904 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9905 int index = backwardMostMove;
9906 int has_book_hit = 0;
9908 if( (index % 2) != side ) {
9912 while( index < forwardMostMove ) {
9913 /* Check to see if engine is in book */
9914 int depth = pvInfoList[index].depth;
9915 int score = pvInfoList[index].score;
9921 else if( score == 0 && depth == 63 ) {
9922 in_book = 1; /* Zappa */
9924 else if( score == 2 && depth == 99 ) {
9925 in_book = 1; /* Abrok */
9928 has_book_hit += in_book;
9944 void GetOutOfBookInfo( char * buf )
9948 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9950 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9951 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9955 if( oob[0] >= 0 || oob[1] >= 0 ) {
9956 for( i=0; i<2; i++ ) {
9960 if( i > 0 && oob[0] >= 0 ) {
9964 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9965 sprintf( buf+strlen(buf), "%s%.2f",
9966 pvInfoList[idx].score >= 0 ? "+" : "",
9967 pvInfoList[idx].score / 100.0 );
9973 /* Save game in PGN style and close the file */
9978 int i, offset, linelen, newblock;
9982 int movelen, numlen, blank;
9983 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9985 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9987 tm = time((time_t *) NULL);
9989 PrintPGNTags(f, &gameInfo);
9991 if (backwardMostMove > 0 || startedFromSetupPosition) {
9992 char *fen = PositionToFEN(backwardMostMove, NULL);
9993 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9994 fprintf(f, "\n{--------------\n");
9995 PrintPosition(f, backwardMostMove);
9996 fprintf(f, "--------------}\n");
10000 /* [AS] Out of book annotation */
10001 if( appData.saveOutOfBookInfo ) {
10004 GetOutOfBookInfo( buf );
10006 if( buf[0] != '\0' ) {
10007 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10014 i = backwardMostMove;
10018 while (i < forwardMostMove) {
10019 /* Print comments preceding this move */
10020 if (commentList[i] != NULL) {
10021 if (linelen > 0) fprintf(f, "\n");
10022 fprintf(f, "%s", commentList[i]);
10027 /* Format move number */
10028 if ((i % 2) == 0) {
10029 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10032 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10034 numtext[0] = NULLCHAR;
10037 numlen = strlen(numtext);
10040 /* Print move number */
10041 blank = linelen > 0 && numlen > 0;
10042 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10051 fprintf(f, "%s", numtext);
10055 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10056 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10059 blank = linelen > 0 && movelen > 0;
10060 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10069 fprintf(f, "%s", move_buffer);
10070 linelen += movelen;
10072 /* [AS] Add PV info if present */
10073 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10074 /* [HGM] add time */
10075 char buf[MSG_SIZ]; int seconds;
10077 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10079 if( seconds <= 0) buf[0] = 0; else
10080 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10081 seconds = (seconds + 4)/10; // round to full seconds
10082 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10083 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10086 sprintf( move_buffer, "{%s%.2f/%d%s}",
10087 pvInfoList[i].score >= 0 ? "+" : "",
10088 pvInfoList[i].score / 100.0,
10089 pvInfoList[i].depth,
10092 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10094 /* Print score/depth */
10095 blank = linelen > 0 && movelen > 0;
10096 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10105 fprintf(f, "%s", move_buffer);
10106 linelen += movelen;
10112 /* Start a new line */
10113 if (linelen > 0) fprintf(f, "\n");
10115 /* Print comments after last move */
10116 if (commentList[i] != NULL) {
10117 fprintf(f, "%s\n", commentList[i]);
10121 if (gameInfo.resultDetails != NULL &&
10122 gameInfo.resultDetails[0] != NULLCHAR) {
10123 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10124 PGNResult(gameInfo.result));
10126 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10130 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10134 /* Save game in old style and close the file */
10136 SaveGameOldStyle(f)
10142 tm = time((time_t *) NULL);
10144 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10147 if (backwardMostMove > 0 || startedFromSetupPosition) {
10148 fprintf(f, "\n[--------------\n");
10149 PrintPosition(f, backwardMostMove);
10150 fprintf(f, "--------------]\n");
10155 i = backwardMostMove;
10156 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10158 while (i < forwardMostMove) {
10159 if (commentList[i] != NULL) {
10160 fprintf(f, "[%s]\n", commentList[i]);
10163 if ((i % 2) == 1) {
10164 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10167 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10169 if (commentList[i] != NULL) {
10173 if (i >= forwardMostMove) {
10177 fprintf(f, "%s\n", parseList[i]);
10182 if (commentList[i] != NULL) {
10183 fprintf(f, "[%s]\n", commentList[i]);
10186 /* This isn't really the old style, but it's close enough */
10187 if (gameInfo.resultDetails != NULL &&
10188 gameInfo.resultDetails[0] != NULLCHAR) {
10189 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10190 gameInfo.resultDetails);
10192 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10199 /* Save the current game to open file f and close the file */
10201 SaveGame(f, dummy, dummy2)
10206 if (gameMode == EditPosition) EditPositionDone(TRUE);
10207 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10208 if (appData.oldSaveStyle)
10209 return SaveGameOldStyle(f);
10211 return SaveGamePGN(f);
10214 /* Save the current position to the given file */
10216 SavePositionToFile(filename)
10222 if (strcmp(filename, "-") == 0) {
10223 return SavePosition(stdout, 0, NULL);
10225 f = fopen(filename, "a");
10227 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10228 DisplayError(buf, errno);
10231 SavePosition(f, 0, NULL);
10237 /* Save the current position to the given open file and close the file */
10239 SavePosition(f, dummy, dummy2)
10247 if (gameMode == EditPosition) EditPositionDone(TRUE);
10248 if (appData.oldSaveStyle) {
10249 tm = time((time_t *) NULL);
10251 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10253 fprintf(f, "[--------------\n");
10254 PrintPosition(f, currentMove);
10255 fprintf(f, "--------------]\n");
10257 fen = PositionToFEN(currentMove, NULL);
10258 fprintf(f, "%s\n", fen);
10266 ReloadCmailMsgEvent(unregister)
10270 static char *inFilename = NULL;
10271 static char *outFilename;
10273 struct stat inbuf, outbuf;
10276 /* Any registered moves are unregistered if unregister is set, */
10277 /* i.e. invoked by the signal handler */
10279 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10280 cmailMoveRegistered[i] = FALSE;
10281 if (cmailCommentList[i] != NULL) {
10282 free(cmailCommentList[i]);
10283 cmailCommentList[i] = NULL;
10286 nCmailMovesRegistered = 0;
10289 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10290 cmailResult[i] = CMAIL_NOT_RESULT;
10294 if (inFilename == NULL) {
10295 /* Because the filenames are static they only get malloced once */
10296 /* and they never get freed */
10297 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10298 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10300 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10301 sprintf(outFilename, "%s.out", appData.cmailGameName);
10304 status = stat(outFilename, &outbuf);
10306 cmailMailedMove = FALSE;
10308 status = stat(inFilename, &inbuf);
10309 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10312 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10313 counts the games, notes how each one terminated, etc.
10315 It would be nice to remove this kludge and instead gather all
10316 the information while building the game list. (And to keep it
10317 in the game list nodes instead of having a bunch of fixed-size
10318 parallel arrays.) Note this will require getting each game's
10319 termination from the PGN tags, as the game list builder does
10320 not process the game moves. --mann
10322 cmailMsgLoaded = TRUE;
10323 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10325 /* Load first game in the file or popup game menu */
10326 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10328 #endif /* !WIN32 */
10336 char string[MSG_SIZ];
10338 if ( cmailMailedMove
10339 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10340 return TRUE; /* Allow free viewing */
10343 /* Unregister move to ensure that we don't leave RegisterMove */
10344 /* with the move registered when the conditions for registering no */
10346 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10347 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10348 nCmailMovesRegistered --;
10350 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10352 free(cmailCommentList[lastLoadGameNumber - 1]);
10353 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10357 if (cmailOldMove == -1) {
10358 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10362 if (currentMove > cmailOldMove + 1) {
10363 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10367 if (currentMove < cmailOldMove) {
10368 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10372 if (forwardMostMove > currentMove) {
10373 /* Silently truncate extra moves */
10377 if ( (currentMove == cmailOldMove + 1)
10378 || ( (currentMove == cmailOldMove)
10379 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10380 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10381 if (gameInfo.result != GameUnfinished) {
10382 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10385 if (commentList[currentMove] != NULL) {
10386 cmailCommentList[lastLoadGameNumber - 1]
10387 = StrSave(commentList[currentMove]);
10389 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10391 if (appData.debugMode)
10392 fprintf(debugFP, "Saving %s for game %d\n",
10393 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10396 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10398 f = fopen(string, "w");
10399 if (appData.oldSaveStyle) {
10400 SaveGameOldStyle(f); /* also closes the file */
10402 sprintf(string, "%s.pos.out", appData.cmailGameName);
10403 f = fopen(string, "w");
10404 SavePosition(f, 0, NULL); /* also closes the file */
10406 fprintf(f, "{--------------\n");
10407 PrintPosition(f, currentMove);
10408 fprintf(f, "--------------}\n\n");
10410 SaveGame(f, 0, NULL); /* also closes the file*/
10413 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10414 nCmailMovesRegistered ++;
10415 } else if (nCmailGames == 1) {
10416 DisplayError(_("You have not made a move yet"), 0);
10427 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10428 FILE *commandOutput;
10429 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10430 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10436 if (! cmailMsgLoaded) {
10437 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10441 if (nCmailGames == nCmailResults) {
10442 DisplayError(_("No unfinished games"), 0);
10446 #if CMAIL_PROHIBIT_REMAIL
10447 if (cmailMailedMove) {
10448 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);
10449 DisplayError(msg, 0);
10454 if (! (cmailMailedMove || RegisterMove())) return;
10456 if ( cmailMailedMove
10457 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10458 sprintf(string, partCommandString,
10459 appData.debugMode ? " -v" : "", appData.cmailGameName);
10460 commandOutput = popen(string, "r");
10462 if (commandOutput == NULL) {
10463 DisplayError(_("Failed to invoke cmail"), 0);
10465 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10466 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10468 if (nBuffers > 1) {
10469 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10470 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10471 nBytes = MSG_SIZ - 1;
10473 (void) memcpy(msg, buffer, nBytes);
10475 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10477 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10478 cmailMailedMove = TRUE; /* Prevent >1 moves */
10481 for (i = 0; i < nCmailGames; i ++) {
10482 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10487 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10489 sprintf(buffer, "%s/%s.%s.archive",
10491 appData.cmailGameName,
10493 LoadGameFromFile(buffer, 1, buffer, FALSE);
10494 cmailMsgLoaded = FALSE;
10498 DisplayInformation(msg);
10499 pclose(commandOutput);
10502 if ((*cmailMsg) != '\0') {
10503 DisplayInformation(cmailMsg);
10508 #endif /* !WIN32 */
10517 int prependComma = 0;
10519 char string[MSG_SIZ]; /* Space for game-list */
10522 if (!cmailMsgLoaded) return "";
10524 if (cmailMailedMove) {
10525 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10527 /* Create a list of games left */
10528 sprintf(string, "[");
10529 for (i = 0; i < nCmailGames; i ++) {
10530 if (! ( cmailMoveRegistered[i]
10531 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10532 if (prependComma) {
10533 sprintf(number, ",%d", i + 1);
10535 sprintf(number, "%d", i + 1);
10539 strcat(string, number);
10542 strcat(string, "]");
10544 if (nCmailMovesRegistered + nCmailResults == 0) {
10545 switch (nCmailGames) {
10548 _("Still need to make move for game\n"));
10553 _("Still need to make moves for both games\n"));
10558 _("Still need to make moves for all %d games\n"),
10563 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10566 _("Still need to make a move for game %s\n"),
10571 if (nCmailResults == nCmailGames) {
10572 sprintf(cmailMsg, _("No unfinished games\n"));
10574 sprintf(cmailMsg, _("Ready to send mail\n"));
10580 _("Still need to make moves for games %s\n"),
10592 if (gameMode == Training)
10593 SetTrainingModeOff();
10596 cmailMsgLoaded = FALSE;
10597 if (appData.icsActive) {
10598 SendToICS(ics_prefix);
10599 SendToICS("refresh\n");
10609 /* Give up on clean exit */
10613 /* Keep trying for clean exit */
10617 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10619 if (telnetISR != NULL) {
10620 RemoveInputSource(telnetISR);
10622 if (icsPR != NoProc) {
10623 DestroyChildProcess(icsPR, TRUE);
10626 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10627 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10629 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10630 /* make sure this other one finishes before killing it! */
10631 if(endingGame) { int count = 0;
10632 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10633 while(endingGame && count++ < 10) DoSleep(1);
10634 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10637 /* Kill off chess programs */
10638 if (first.pr != NoProc) {
10641 DoSleep( appData.delayBeforeQuit );
10642 SendToProgram("quit\n", &first);
10643 DoSleep( appData.delayAfterQuit );
10644 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10646 if (second.pr != NoProc) {
10647 DoSleep( appData.delayBeforeQuit );
10648 SendToProgram("quit\n", &second);
10649 DoSleep( appData.delayAfterQuit );
10650 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10652 if (first.isr != NULL) {
10653 RemoveInputSource(first.isr);
10655 if (second.isr != NULL) {
10656 RemoveInputSource(second.isr);
10659 ShutDownFrontEnd();
10666 if (appData.debugMode)
10667 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10671 if (gameMode == MachinePlaysWhite ||
10672 gameMode == MachinePlaysBlack) {
10675 DisplayBothClocks();
10677 if (gameMode == PlayFromGameFile) {
10678 if (appData.timeDelay >= 0)
10679 AutoPlayGameLoop();
10680 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10681 Reset(FALSE, TRUE);
10682 SendToICS(ics_prefix);
10683 SendToICS("refresh\n");
10684 } else if (currentMove < forwardMostMove) {
10685 ForwardInner(forwardMostMove);
10687 pauseExamInvalid = FALSE;
10689 switch (gameMode) {
10693 pauseExamForwardMostMove = forwardMostMove;
10694 pauseExamInvalid = FALSE;
10697 case IcsPlayingWhite:
10698 case IcsPlayingBlack:
10702 case PlayFromGameFile:
10703 (void) StopLoadGameTimer();
10707 case BeginningOfGame:
10708 if (appData.icsActive) return;
10709 /* else fall through */
10710 case MachinePlaysWhite:
10711 case MachinePlaysBlack:
10712 case TwoMachinesPlay:
10713 if (forwardMostMove == 0)
10714 return; /* don't pause if no one has moved */
10715 if ((gameMode == MachinePlaysWhite &&
10716 !WhiteOnMove(forwardMostMove)) ||
10717 (gameMode == MachinePlaysBlack &&
10718 WhiteOnMove(forwardMostMove))) {
10731 char title[MSG_SIZ];
10733 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10734 strcpy(title, _("Edit comment"));
10736 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10737 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10738 parseList[currentMove - 1]);
10741 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10748 char *tags = PGNTags(&gameInfo);
10749 EditTagsPopUp(tags);
10756 if (appData.noChessProgram || gameMode == AnalyzeMode)
10759 if (gameMode != AnalyzeFile) {
10760 if (!appData.icsEngineAnalyze) {
10762 if (gameMode != EditGame) return;
10764 ResurrectChessProgram();
10765 SendToProgram("analyze\n", &first);
10766 first.analyzing = TRUE;
10767 /*first.maybeThinking = TRUE;*/
10768 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10769 EngineOutputPopUp();
10771 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10776 StartAnalysisClock();
10777 GetTimeMark(&lastNodeCountTime);
10784 if (appData.noChessProgram || gameMode == AnalyzeFile)
10787 if (gameMode != AnalyzeMode) {
10789 if (gameMode != EditGame) return;
10790 ResurrectChessProgram();
10791 SendToProgram("analyze\n", &first);
10792 first.analyzing = TRUE;
10793 /*first.maybeThinking = TRUE;*/
10794 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10795 EngineOutputPopUp();
10797 gameMode = AnalyzeFile;
10802 StartAnalysisClock();
10803 GetTimeMark(&lastNodeCountTime);
10808 MachineWhiteEvent()
10811 char *bookHit = NULL;
10813 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10817 if (gameMode == PlayFromGameFile ||
10818 gameMode == TwoMachinesPlay ||
10819 gameMode == Training ||
10820 gameMode == AnalyzeMode ||
10821 gameMode == EndOfGame)
10824 if (gameMode == EditPosition)
10825 EditPositionDone(TRUE);
10827 if (!WhiteOnMove(currentMove)) {
10828 DisplayError(_("It is not White's turn"), 0);
10832 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10835 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10836 gameMode == AnalyzeFile)
10839 ResurrectChessProgram(); /* in case it isn't running */
10840 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10841 gameMode = MachinePlaysWhite;
10844 gameMode = MachinePlaysWhite;
10848 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10850 if (first.sendName) {
10851 sprintf(buf, "name %s\n", gameInfo.black);
10852 SendToProgram(buf, &first);
10854 if (first.sendTime) {
10855 if (first.useColors) {
10856 SendToProgram("black\n", &first); /*gnu kludge*/
10858 SendTimeRemaining(&first, TRUE);
10860 if (first.useColors) {
10861 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10863 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10864 SetMachineThinkingEnables();
10865 first.maybeThinking = TRUE;
10869 if (appData.autoFlipView && !flipView) {
10870 flipView = !flipView;
10871 DrawPosition(FALSE, NULL);
10872 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10875 if(bookHit) { // [HGM] book: simulate book reply
10876 static char bookMove[MSG_SIZ]; // a bit generous?
10878 programStats.nodes = programStats.depth = programStats.time =
10879 programStats.score = programStats.got_only_move = 0;
10880 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10882 strcpy(bookMove, "move ");
10883 strcat(bookMove, bookHit);
10884 HandleMachineMove(bookMove, &first);
10889 MachineBlackEvent()
10892 char *bookHit = NULL;
10894 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10898 if (gameMode == PlayFromGameFile ||
10899 gameMode == TwoMachinesPlay ||
10900 gameMode == Training ||
10901 gameMode == AnalyzeMode ||
10902 gameMode == EndOfGame)
10905 if (gameMode == EditPosition)
10906 EditPositionDone(TRUE);
10908 if (WhiteOnMove(currentMove)) {
10909 DisplayError(_("It is not Black's turn"), 0);
10913 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10916 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10917 gameMode == AnalyzeFile)
10920 ResurrectChessProgram(); /* in case it isn't running */
10921 gameMode = MachinePlaysBlack;
10925 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10927 if (first.sendName) {
10928 sprintf(buf, "name %s\n", gameInfo.white);
10929 SendToProgram(buf, &first);
10931 if (first.sendTime) {
10932 if (first.useColors) {
10933 SendToProgram("white\n", &first); /*gnu kludge*/
10935 SendTimeRemaining(&first, FALSE);
10937 if (first.useColors) {
10938 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10940 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10941 SetMachineThinkingEnables();
10942 first.maybeThinking = TRUE;
10945 if (appData.autoFlipView && flipView) {
10946 flipView = !flipView;
10947 DrawPosition(FALSE, NULL);
10948 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10950 if(bookHit) { // [HGM] book: simulate book reply
10951 static char bookMove[MSG_SIZ]; // a bit generous?
10953 programStats.nodes = programStats.depth = programStats.time =
10954 programStats.score = programStats.got_only_move = 0;
10955 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10957 strcpy(bookMove, "move ");
10958 strcat(bookMove, bookHit);
10959 HandleMachineMove(bookMove, &first);
10965 DisplayTwoMachinesTitle()
10968 if (appData.matchGames > 0) {
10969 if (first.twoMachinesColor[0] == 'w') {
10970 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10971 gameInfo.white, gameInfo.black,
10972 first.matchWins, second.matchWins,
10973 matchGame - 1 - (first.matchWins + second.matchWins));
10975 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10976 gameInfo.white, gameInfo.black,
10977 second.matchWins, first.matchWins,
10978 matchGame - 1 - (first.matchWins + second.matchWins));
10981 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10987 TwoMachinesEvent P((void))
10991 ChessProgramState *onmove;
10992 char *bookHit = NULL;
10994 if (appData.noChessProgram) return;
10996 switch (gameMode) {
10997 case TwoMachinesPlay:
10999 case MachinePlaysWhite:
11000 case MachinePlaysBlack:
11001 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11002 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11006 case BeginningOfGame:
11007 case PlayFromGameFile:
11010 if (gameMode != EditGame) return;
11013 EditPositionDone(TRUE);
11024 // forwardMostMove = currentMove;
11025 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11026 ResurrectChessProgram(); /* in case first program isn't running */
11028 if (second.pr == NULL) {
11029 StartChessProgram(&second);
11030 if (second.protocolVersion == 1) {
11031 TwoMachinesEventIfReady();
11033 /* kludge: allow timeout for initial "feature" command */
11035 DisplayMessage("", _("Starting second chess program"));
11036 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11040 DisplayMessage("", "");
11041 InitChessProgram(&second, FALSE);
11042 SendToProgram("force\n", &second);
11043 if (startedFromSetupPosition) {
11044 SendBoard(&second, backwardMostMove);
11045 if (appData.debugMode) {
11046 fprintf(debugFP, "Two Machines\n");
11049 for (i = backwardMostMove; i < forwardMostMove; i++) {
11050 SendMoveToProgram(i, &second);
11053 gameMode = TwoMachinesPlay;
11057 DisplayTwoMachinesTitle();
11059 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11065 SendToProgram(first.computerString, &first);
11066 if (first.sendName) {
11067 sprintf(buf, "name %s\n", second.tidy);
11068 SendToProgram(buf, &first);
11070 SendToProgram(second.computerString, &second);
11071 if (second.sendName) {
11072 sprintf(buf, "name %s\n", first.tidy);
11073 SendToProgram(buf, &second);
11077 if (!first.sendTime || !second.sendTime) {
11078 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11079 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11081 if (onmove->sendTime) {
11082 if (onmove->useColors) {
11083 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11085 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11087 if (onmove->useColors) {
11088 SendToProgram(onmove->twoMachinesColor, onmove);
11090 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11091 // SendToProgram("go\n", onmove);
11092 onmove->maybeThinking = TRUE;
11093 SetMachineThinkingEnables();
11097 if(bookHit) { // [HGM] book: simulate book reply
11098 static char bookMove[MSG_SIZ]; // a bit generous?
11100 programStats.nodes = programStats.depth = programStats.time =
11101 programStats.score = programStats.got_only_move = 0;
11102 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11104 strcpy(bookMove, "move ");
11105 strcat(bookMove, bookHit);
11106 savedMessage = bookMove; // args for deferred call
11107 savedState = onmove;
11108 ScheduleDelayedEvent(DeferredBookMove, 1);
11115 if (gameMode == Training) {
11116 SetTrainingModeOff();
11117 gameMode = PlayFromGameFile;
11118 DisplayMessage("", _("Training mode off"));
11120 gameMode = Training;
11121 animateTraining = appData.animate;
11123 /* make sure we are not already at the end of the game */
11124 if (currentMove < forwardMostMove) {
11125 SetTrainingModeOn();
11126 DisplayMessage("", _("Training mode on"));
11128 gameMode = PlayFromGameFile;
11129 DisplayError(_("Already at end of game"), 0);
11138 if (!appData.icsActive) return;
11139 switch (gameMode) {
11140 case IcsPlayingWhite:
11141 case IcsPlayingBlack:
11144 case BeginningOfGame:
11152 EditPositionDone(TRUE);
11165 gameMode = IcsIdle;
11176 switch (gameMode) {
11178 SetTrainingModeOff();
11180 case MachinePlaysWhite:
11181 case MachinePlaysBlack:
11182 case BeginningOfGame:
11183 SendToProgram("force\n", &first);
11184 SetUserThinkingEnables();
11186 case PlayFromGameFile:
11187 (void) StopLoadGameTimer();
11188 if (gameFileFP != NULL) {
11193 EditPositionDone(TRUE);
11198 SendToProgram("force\n", &first);
11200 case TwoMachinesPlay:
11201 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11202 ResurrectChessProgram();
11203 SetUserThinkingEnables();
11206 ResurrectChessProgram();
11208 case IcsPlayingBlack:
11209 case IcsPlayingWhite:
11210 DisplayError(_("Warning: You are still playing a game"), 0);
11213 DisplayError(_("Warning: You are still observing a game"), 0);
11216 DisplayError(_("Warning: You are still examining a game"), 0);
11227 first.offeredDraw = second.offeredDraw = 0;
11229 if (gameMode == PlayFromGameFile) {
11230 whiteTimeRemaining = timeRemaining[0][currentMove];
11231 blackTimeRemaining = timeRemaining[1][currentMove];
11235 if (gameMode == MachinePlaysWhite ||
11236 gameMode == MachinePlaysBlack ||
11237 gameMode == TwoMachinesPlay ||
11238 gameMode == EndOfGame) {
11239 i = forwardMostMove;
11240 while (i > currentMove) {
11241 SendToProgram("undo\n", &first);
11244 whiteTimeRemaining = timeRemaining[0][currentMove];
11245 blackTimeRemaining = timeRemaining[1][currentMove];
11246 DisplayBothClocks();
11247 if (whiteFlag || blackFlag) {
11248 whiteFlag = blackFlag = 0;
11253 gameMode = EditGame;
11260 EditPositionEvent()
11262 if (gameMode == EditPosition) {
11268 if (gameMode != EditGame) return;
11270 gameMode = EditPosition;
11273 if (currentMove > 0)
11274 CopyBoard(boards[0], boards[currentMove]);
11276 blackPlaysFirst = !WhiteOnMove(currentMove);
11278 currentMove = forwardMostMove = backwardMostMove = 0;
11279 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11286 /* [DM] icsEngineAnalyze - possible call from other functions */
11287 if (appData.icsEngineAnalyze) {
11288 appData.icsEngineAnalyze = FALSE;
11290 DisplayMessage("",_("Close ICS engine analyze..."));
11292 if (first.analysisSupport && first.analyzing) {
11293 SendToProgram("exit\n", &first);
11294 first.analyzing = FALSE;
11296 thinkOutput[0] = NULLCHAR;
11300 EditPositionDone(Boolean fakeRights)
11302 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11304 startedFromSetupPosition = TRUE;
11305 InitChessProgram(&first, FALSE);
11306 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11307 boards[0][EP_STATUS] = EP_NONE;
11308 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11309 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11310 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11311 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11312 } else boards[0][CASTLING][2] = NoRights;
11313 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11314 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11315 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11316 } else boards[0][CASTLING][5] = NoRights;
11318 SendToProgram("force\n", &first);
11319 if (blackPlaysFirst) {
11320 strcpy(moveList[0], "");
11321 strcpy(parseList[0], "");
11322 currentMove = forwardMostMove = backwardMostMove = 1;
11323 CopyBoard(boards[1], boards[0]);
11325 currentMove = forwardMostMove = backwardMostMove = 0;
11327 SendBoard(&first, forwardMostMove);
11328 if (appData.debugMode) {
11329 fprintf(debugFP, "EditPosDone\n");
11332 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11333 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11334 gameMode = EditGame;
11336 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11337 ClearHighlights(); /* [AS] */
11340 /* Pause for `ms' milliseconds */
11341 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11351 } while (SubtractTimeMarks(&m2, &m1) < ms);
11354 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11356 SendMultiLineToICS(buf)
11359 char temp[MSG_SIZ+1], *p;
11366 strncpy(temp, buf, len);
11371 if (*p == '\n' || *p == '\r')
11376 strcat(temp, "\n");
11378 SendToPlayer(temp, strlen(temp));
11382 SetWhiteToPlayEvent()
11384 if (gameMode == EditPosition) {
11385 blackPlaysFirst = FALSE;
11386 DisplayBothClocks(); /* works because currentMove is 0 */
11387 } else if (gameMode == IcsExamining) {
11388 SendToICS(ics_prefix);
11389 SendToICS("tomove white\n");
11394 SetBlackToPlayEvent()
11396 if (gameMode == EditPosition) {
11397 blackPlaysFirst = TRUE;
11398 currentMove = 1; /* kludge */
11399 DisplayBothClocks();
11401 } else if (gameMode == IcsExamining) {
11402 SendToICS(ics_prefix);
11403 SendToICS("tomove black\n");
11408 EditPositionMenuEvent(selection, x, y)
11409 ChessSquare selection;
11413 ChessSquare piece = boards[0][y][x];
11415 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11417 switch (selection) {
11419 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11420 SendToICS(ics_prefix);
11421 SendToICS("bsetup clear\n");
11422 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11423 SendToICS(ics_prefix);
11424 SendToICS("clearboard\n");
11426 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11427 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11428 for (y = 0; y < BOARD_HEIGHT; y++) {
11429 if (gameMode == IcsExamining) {
11430 if (boards[currentMove][y][x] != EmptySquare) {
11431 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11436 boards[0][y][x] = p;
11441 if (gameMode == EditPosition) {
11442 DrawPosition(FALSE, boards[0]);
11447 SetWhiteToPlayEvent();
11451 SetBlackToPlayEvent();
11455 if (gameMode == IcsExamining) {
11456 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11459 boards[0][y][x] = EmptySquare;
11460 DrawPosition(FALSE, boards[0]);
11465 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11466 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11467 selection = (ChessSquare) (PROMOTED piece);
11468 } else if(piece == EmptySquare) selection = WhiteSilver;
11469 else selection = (ChessSquare)((int)piece - 1);
11473 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11474 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11475 selection = (ChessSquare) (DEMOTED piece);
11476 } else if(piece == EmptySquare) selection = BlackSilver;
11477 else selection = (ChessSquare)((int)piece + 1);
11482 if(gameInfo.variant == VariantShatranj ||
11483 gameInfo.variant == VariantXiangqi ||
11484 gameInfo.variant == VariantCourier )
11485 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11490 if(gameInfo.variant == VariantXiangqi)
11491 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11492 if(gameInfo.variant == VariantKnightmate)
11493 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11496 if (gameMode == IcsExamining) {
11497 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11498 PieceToChar(selection), AAA + x, ONE + y);
11501 boards[0][y][x] = selection;
11502 DrawPosition(FALSE, boards[0]);
11510 DropMenuEvent(selection, x, y)
11511 ChessSquare selection;
11514 ChessMove moveType;
11516 switch (gameMode) {
11517 case IcsPlayingWhite:
11518 case MachinePlaysBlack:
11519 if (!WhiteOnMove(currentMove)) {
11520 DisplayMoveError(_("It is Black's turn"));
11523 moveType = WhiteDrop;
11525 case IcsPlayingBlack:
11526 case MachinePlaysWhite:
11527 if (WhiteOnMove(currentMove)) {
11528 DisplayMoveError(_("It is White's turn"));
11531 moveType = BlackDrop;
11534 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11540 if (moveType == BlackDrop && selection < BlackPawn) {
11541 selection = (ChessSquare) ((int) selection
11542 + (int) BlackPawn - (int) WhitePawn);
11544 if (boards[currentMove][y][x] != EmptySquare) {
11545 DisplayMoveError(_("That square is occupied"));
11549 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11555 /* Accept a pending offer of any kind from opponent */
11557 if (appData.icsActive) {
11558 SendToICS(ics_prefix);
11559 SendToICS("accept\n");
11560 } else if (cmailMsgLoaded) {
11561 if (currentMove == cmailOldMove &&
11562 commentList[cmailOldMove] != NULL &&
11563 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11564 "Black offers a draw" : "White offers a draw")) {
11566 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11567 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11569 DisplayError(_("There is no pending offer on this move"), 0);
11570 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11573 /* Not used for offers from chess program */
11580 /* Decline a pending offer of any kind from opponent */
11582 if (appData.icsActive) {
11583 SendToICS(ics_prefix);
11584 SendToICS("decline\n");
11585 } else if (cmailMsgLoaded) {
11586 if (currentMove == cmailOldMove &&
11587 commentList[cmailOldMove] != NULL &&
11588 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11589 "Black offers a draw" : "White offers a draw")) {
11591 AppendComment(cmailOldMove, "Draw declined", TRUE);
11592 DisplayComment(cmailOldMove - 1, "Draw declined");
11595 DisplayError(_("There is no pending offer on this move"), 0);
11598 /* Not used for offers from chess program */
11605 /* Issue ICS rematch command */
11606 if (appData.icsActive) {
11607 SendToICS(ics_prefix);
11608 SendToICS("rematch\n");
11615 /* Call your opponent's flag (claim a win on time) */
11616 if (appData.icsActive) {
11617 SendToICS(ics_prefix);
11618 SendToICS("flag\n");
11620 switch (gameMode) {
11623 case MachinePlaysWhite:
11626 GameEnds(GameIsDrawn, "Both players ran out of time",
11629 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11631 DisplayError(_("Your opponent is not out of time"), 0);
11634 case MachinePlaysBlack:
11637 GameEnds(GameIsDrawn, "Both players ran out of time",
11640 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11642 DisplayError(_("Your opponent is not out of time"), 0);
11652 /* Offer draw or accept pending draw offer from opponent */
11654 if (appData.icsActive) {
11655 /* Note: tournament rules require draw offers to be
11656 made after you make your move but before you punch
11657 your clock. Currently ICS doesn't let you do that;
11658 instead, you immediately punch your clock after making
11659 a move, but you can offer a draw at any time. */
11661 SendToICS(ics_prefix);
11662 SendToICS("draw\n");
11663 } else if (cmailMsgLoaded) {
11664 if (currentMove == cmailOldMove &&
11665 commentList[cmailOldMove] != NULL &&
11666 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11667 "Black offers a draw" : "White offers a draw")) {
11668 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11669 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11670 } else if (currentMove == cmailOldMove + 1) {
11671 char *offer = WhiteOnMove(cmailOldMove) ?
11672 "White offers a draw" : "Black offers a draw";
11673 AppendComment(currentMove, offer, TRUE);
11674 DisplayComment(currentMove - 1, offer);
11675 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11677 DisplayError(_("You must make your move before offering a draw"), 0);
11678 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11680 } else if (first.offeredDraw) {
11681 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11683 if (first.sendDrawOffers) {
11684 SendToProgram("draw\n", &first);
11685 userOfferedDraw = TRUE;
11693 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11695 if (appData.icsActive) {
11696 SendToICS(ics_prefix);
11697 SendToICS("adjourn\n");
11699 /* Currently GNU Chess doesn't offer or accept Adjourns */
11707 /* Offer Abort or accept pending Abort offer from opponent */
11709 if (appData.icsActive) {
11710 SendToICS(ics_prefix);
11711 SendToICS("abort\n");
11713 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11720 /* Resign. You can do this even if it's not your turn. */
11722 if (appData.icsActive) {
11723 SendToICS(ics_prefix);
11724 SendToICS("resign\n");
11726 switch (gameMode) {
11727 case MachinePlaysWhite:
11728 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11730 case MachinePlaysBlack:
11731 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11734 if (cmailMsgLoaded) {
11736 if (WhiteOnMove(cmailOldMove)) {
11737 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11739 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11741 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11752 StopObservingEvent()
11754 /* Stop observing current games */
11755 SendToICS(ics_prefix);
11756 SendToICS("unobserve\n");
11760 StopExaminingEvent()
11762 /* Stop observing current game */
11763 SendToICS(ics_prefix);
11764 SendToICS("unexamine\n");
11768 ForwardInner(target)
11773 if (appData.debugMode)
11774 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11775 target, currentMove, forwardMostMove);
11777 if (gameMode == EditPosition)
11780 if (gameMode == PlayFromGameFile && !pausing)
11783 if (gameMode == IcsExamining && pausing)
11784 limit = pauseExamForwardMostMove;
11786 limit = forwardMostMove;
11788 if (target > limit) target = limit;
11790 if (target > 0 && moveList[target - 1][0]) {
11791 int fromX, fromY, toX, toY;
11792 toX = moveList[target - 1][2] - AAA;
11793 toY = moveList[target - 1][3] - ONE;
11794 if (moveList[target - 1][1] == '@') {
11795 if (appData.highlightLastMove) {
11796 SetHighlights(-1, -1, toX, toY);
11799 fromX = moveList[target - 1][0] - AAA;
11800 fromY = moveList[target - 1][1] - ONE;
11801 if (target == currentMove + 1) {
11802 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11804 if (appData.highlightLastMove) {
11805 SetHighlights(fromX, fromY, toX, toY);
11809 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11810 gameMode == Training || gameMode == PlayFromGameFile ||
11811 gameMode == AnalyzeFile) {
11812 while (currentMove < target) {
11813 SendMoveToProgram(currentMove++, &first);
11816 currentMove = target;
11819 if (gameMode == EditGame || gameMode == EndOfGame) {
11820 whiteTimeRemaining = timeRemaining[0][currentMove];
11821 blackTimeRemaining = timeRemaining[1][currentMove];
11823 DisplayBothClocks();
11824 DisplayMove(currentMove - 1);
11825 DrawPosition(FALSE, boards[currentMove]);
11826 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11827 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11828 DisplayComment(currentMove - 1, commentList[currentMove]);
11836 if (gameMode == IcsExamining && !pausing) {
11837 SendToICS(ics_prefix);
11838 SendToICS("forward\n");
11840 ForwardInner(currentMove + 1);
11847 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11848 /* to optimze, we temporarily turn off analysis mode while we feed
11849 * the remaining moves to the engine. Otherwise we get analysis output
11852 if (first.analysisSupport) {
11853 SendToProgram("exit\nforce\n", &first);
11854 first.analyzing = FALSE;
11858 if (gameMode == IcsExamining && !pausing) {
11859 SendToICS(ics_prefix);
11860 SendToICS("forward 999999\n");
11862 ForwardInner(forwardMostMove);
11865 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11866 /* we have fed all the moves, so reactivate analysis mode */
11867 SendToProgram("analyze\n", &first);
11868 first.analyzing = TRUE;
11869 /*first.maybeThinking = TRUE;*/
11870 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11875 BackwardInner(target)
11878 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11880 if (appData.debugMode)
11881 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11882 target, currentMove, forwardMostMove);
11884 if (gameMode == EditPosition) return;
11885 if (currentMove <= backwardMostMove) {
11887 DrawPosition(full_redraw, boards[currentMove]);
11890 if (gameMode == PlayFromGameFile && !pausing)
11893 if (moveList[target][0]) {
11894 int fromX, fromY, toX, toY;
11895 toX = moveList[target][2] - AAA;
11896 toY = moveList[target][3] - ONE;
11897 if (moveList[target][1] == '@') {
11898 if (appData.highlightLastMove) {
11899 SetHighlights(-1, -1, toX, toY);
11902 fromX = moveList[target][0] - AAA;
11903 fromY = moveList[target][1] - ONE;
11904 if (target == currentMove - 1) {
11905 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11907 if (appData.highlightLastMove) {
11908 SetHighlights(fromX, fromY, toX, toY);
11912 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11913 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11914 while (currentMove > target) {
11915 SendToProgram("undo\n", &first);
11919 currentMove = target;
11922 if (gameMode == EditGame || gameMode == EndOfGame) {
11923 whiteTimeRemaining = timeRemaining[0][currentMove];
11924 blackTimeRemaining = timeRemaining[1][currentMove];
11926 DisplayBothClocks();
11927 DisplayMove(currentMove - 1);
11928 DrawPosition(full_redraw, boards[currentMove]);
11929 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11930 // [HGM] PV info: routine tests if comment empty
11931 DisplayComment(currentMove - 1, commentList[currentMove]);
11937 if (gameMode == IcsExamining && !pausing) {
11938 SendToICS(ics_prefix);
11939 SendToICS("backward\n");
11941 BackwardInner(currentMove - 1);
11948 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11949 /* to optimize, we temporarily turn off analysis mode while we undo
11950 * all the moves. Otherwise we get analysis output after each undo.
11952 if (first.analysisSupport) {
11953 SendToProgram("exit\nforce\n", &first);
11954 first.analyzing = FALSE;
11958 if (gameMode == IcsExamining && !pausing) {
11959 SendToICS(ics_prefix);
11960 SendToICS("backward 999999\n");
11962 BackwardInner(backwardMostMove);
11965 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11966 /* we have fed all the moves, so reactivate analysis mode */
11967 SendToProgram("analyze\n", &first);
11968 first.analyzing = TRUE;
11969 /*first.maybeThinking = TRUE;*/
11970 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11977 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11978 if (to >= forwardMostMove) to = forwardMostMove;
11979 if (to <= backwardMostMove) to = backwardMostMove;
11980 if (to < currentMove) {
11990 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11993 if (gameMode != IcsExamining) {
11994 DisplayError(_("You are not examining a game"), 0);
11998 DisplayError(_("You can't revert while pausing"), 0);
12001 SendToICS(ics_prefix);
12002 SendToICS("revert\n");
12008 switch (gameMode) {
12009 case MachinePlaysWhite:
12010 case MachinePlaysBlack:
12011 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12012 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12015 if (forwardMostMove < 2) return;
12016 currentMove = forwardMostMove = forwardMostMove - 2;
12017 whiteTimeRemaining = timeRemaining[0][currentMove];
12018 blackTimeRemaining = timeRemaining[1][currentMove];
12019 DisplayBothClocks();
12020 DisplayMove(currentMove - 1);
12021 ClearHighlights();/*!! could figure this out*/
12022 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12023 SendToProgram("remove\n", &first);
12024 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12027 case BeginningOfGame:
12031 case IcsPlayingWhite:
12032 case IcsPlayingBlack:
12033 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12034 SendToICS(ics_prefix);
12035 SendToICS("takeback 2\n");
12037 SendToICS(ics_prefix);
12038 SendToICS("takeback 1\n");
12047 ChessProgramState *cps;
12049 switch (gameMode) {
12050 case MachinePlaysWhite:
12051 if (!WhiteOnMove(forwardMostMove)) {
12052 DisplayError(_("It is your turn"), 0);
12057 case MachinePlaysBlack:
12058 if (WhiteOnMove(forwardMostMove)) {
12059 DisplayError(_("It is your turn"), 0);
12064 case TwoMachinesPlay:
12065 if (WhiteOnMove(forwardMostMove) ==
12066 (first.twoMachinesColor[0] == 'w')) {
12072 case BeginningOfGame:
12076 SendToProgram("?\n", cps);
12080 TruncateGameEvent()
12083 if (gameMode != EditGame) return;
12090 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12091 if (forwardMostMove > currentMove) {
12092 if (gameInfo.resultDetails != NULL) {
12093 free(gameInfo.resultDetails);
12094 gameInfo.resultDetails = NULL;
12095 gameInfo.result = GameUnfinished;
12097 forwardMostMove = currentMove;
12098 HistorySet(parseList, backwardMostMove, forwardMostMove,
12106 if (appData.noChessProgram) return;
12107 switch (gameMode) {
12108 case MachinePlaysWhite:
12109 if (WhiteOnMove(forwardMostMove)) {
12110 DisplayError(_("Wait until your turn"), 0);
12114 case BeginningOfGame:
12115 case MachinePlaysBlack:
12116 if (!WhiteOnMove(forwardMostMove)) {
12117 DisplayError(_("Wait until your turn"), 0);
12122 DisplayError(_("No hint available"), 0);
12125 SendToProgram("hint\n", &first);
12126 hintRequested = TRUE;
12132 if (appData.noChessProgram) return;
12133 switch (gameMode) {
12134 case MachinePlaysWhite:
12135 if (WhiteOnMove(forwardMostMove)) {
12136 DisplayError(_("Wait until your turn"), 0);
12140 case BeginningOfGame:
12141 case MachinePlaysBlack:
12142 if (!WhiteOnMove(forwardMostMove)) {
12143 DisplayError(_("Wait until your turn"), 0);
12148 EditPositionDone(TRUE);
12150 case TwoMachinesPlay:
12155 SendToProgram("bk\n", &first);
12156 bookOutput[0] = NULLCHAR;
12157 bookRequested = TRUE;
12163 char *tags = PGNTags(&gameInfo);
12164 TagsPopUp(tags, CmailMsg());
12168 /* end button procedures */
12171 PrintPosition(fp, move)
12177 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12178 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12179 char c = PieceToChar(boards[move][i][j]);
12180 fputc(c == 'x' ? '.' : c, fp);
12181 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12184 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12185 fprintf(fp, "white to play\n");
12187 fprintf(fp, "black to play\n");
12194 if (gameInfo.white != NULL) {
12195 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12201 /* Find last component of program's own name, using some heuristics */
12203 TidyProgramName(prog, host, buf)
12204 char *prog, *host, buf[MSG_SIZ];
12207 int local = (strcmp(host, "localhost") == 0);
12208 while (!local && (p = strchr(prog, ';')) != NULL) {
12210 while (*p == ' ') p++;
12213 if (*prog == '"' || *prog == '\'') {
12214 q = strchr(prog + 1, *prog);
12216 q = strchr(prog, ' ');
12218 if (q == NULL) q = prog + strlen(prog);
12220 while (p >= prog && *p != '/' && *p != '\\') p--;
12222 if(p == prog && *p == '"') p++;
12223 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12224 memcpy(buf, p, q - p);
12225 buf[q - p] = NULLCHAR;
12233 TimeControlTagValue()
12236 if (!appData.clockMode) {
12238 } else if (movesPerSession > 0) {
12239 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12240 } else if (timeIncrement == 0) {
12241 sprintf(buf, "%ld", timeControl/1000);
12243 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12245 return StrSave(buf);
12251 /* This routine is used only for certain modes */
12252 VariantClass v = gameInfo.variant;
12253 ChessMove r = GameUnfinished;
12256 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12257 r = gameInfo.result;
12258 p = gameInfo.resultDetails;
12259 gameInfo.resultDetails = NULL;
12261 ClearGameInfo(&gameInfo);
12262 gameInfo.variant = v;
12264 switch (gameMode) {
12265 case MachinePlaysWhite:
12266 gameInfo.event = StrSave( appData.pgnEventHeader );
12267 gameInfo.site = StrSave(HostName());
12268 gameInfo.date = PGNDate();
12269 gameInfo.round = StrSave("-");
12270 gameInfo.white = StrSave(first.tidy);
12271 gameInfo.black = StrSave(UserName());
12272 gameInfo.timeControl = TimeControlTagValue();
12275 case MachinePlaysBlack:
12276 gameInfo.event = StrSave( appData.pgnEventHeader );
12277 gameInfo.site = StrSave(HostName());
12278 gameInfo.date = PGNDate();
12279 gameInfo.round = StrSave("-");
12280 gameInfo.white = StrSave(UserName());
12281 gameInfo.black = StrSave(first.tidy);
12282 gameInfo.timeControl = TimeControlTagValue();
12285 case TwoMachinesPlay:
12286 gameInfo.event = StrSave( appData.pgnEventHeader );
12287 gameInfo.site = StrSave(HostName());
12288 gameInfo.date = PGNDate();
12289 if (matchGame > 0) {
12291 sprintf(buf, "%d", matchGame);
12292 gameInfo.round = StrSave(buf);
12294 gameInfo.round = StrSave("-");
12296 if (first.twoMachinesColor[0] == 'w') {
12297 gameInfo.white = StrSave(first.tidy);
12298 gameInfo.black = StrSave(second.tidy);
12300 gameInfo.white = StrSave(second.tidy);
12301 gameInfo.black = StrSave(first.tidy);
12303 gameInfo.timeControl = TimeControlTagValue();
12307 gameInfo.event = StrSave("Edited game");
12308 gameInfo.site = StrSave(HostName());
12309 gameInfo.date = PGNDate();
12310 gameInfo.round = StrSave("-");
12311 gameInfo.white = StrSave("-");
12312 gameInfo.black = StrSave("-");
12313 gameInfo.result = r;
12314 gameInfo.resultDetails = p;
12318 gameInfo.event = StrSave("Edited position");
12319 gameInfo.site = StrSave(HostName());
12320 gameInfo.date = PGNDate();
12321 gameInfo.round = StrSave("-");
12322 gameInfo.white = StrSave("-");
12323 gameInfo.black = StrSave("-");
12326 case IcsPlayingWhite:
12327 case IcsPlayingBlack:
12332 case PlayFromGameFile:
12333 gameInfo.event = StrSave("Game from non-PGN file");
12334 gameInfo.site = StrSave(HostName());
12335 gameInfo.date = PGNDate();
12336 gameInfo.round = StrSave("-");
12337 gameInfo.white = StrSave("?");
12338 gameInfo.black = StrSave("?");
12347 ReplaceComment(index, text)
12353 while (*text == '\n') text++;
12354 len = strlen(text);
12355 while (len > 0 && text[len - 1] == '\n') len--;
12357 if (commentList[index] != NULL)
12358 free(commentList[index]);
12361 commentList[index] = NULL;
12364 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12365 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12366 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12367 commentList[index] = (char *) malloc(len + 2);
12368 strncpy(commentList[index], text, len);
12369 commentList[index][len] = '\n';
12370 commentList[index][len + 1] = NULLCHAR;
12372 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12374 commentList[index] = (char *) malloc(len + 6);
12375 strcpy(commentList[index], "{\n");
12376 strncpy(commentList[index]+2, text, len);
12377 commentList[index][len+2] = NULLCHAR;
12378 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12379 strcat(commentList[index], "\n}\n");
12393 if (ch == '\r') continue;
12395 } while (ch != '\0');
12399 AppendComment(index, text, addBraces)
12402 Boolean addBraces; // [HGM] braces: tells if we should add {}
12407 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12408 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12411 while (*text == '\n') text++;
12412 len = strlen(text);
12413 while (len > 0 && text[len - 1] == '\n') len--;
12415 if (len == 0) return;
12417 if (commentList[index] != NULL) {
12418 old = commentList[index];
12419 oldlen = strlen(old);
12420 while(commentList[index][oldlen-1] == '\n')
12421 commentList[index][--oldlen] = NULLCHAR;
12422 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12423 strcpy(commentList[index], old);
12425 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12426 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12427 if(addBraces) addBraces = FALSE; else { text++; len--; }
12428 while (*text == '\n') { text++; len--; }
12429 commentList[index][--oldlen] = NULLCHAR;
12431 if(addBraces) strcat(commentList[index], "\n{\n");
12432 else strcat(commentList[index], "\n");
12433 strcat(commentList[index], text);
12434 if(addBraces) strcat(commentList[index], "\n}\n");
12435 else strcat(commentList[index], "\n");
12437 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12439 strcpy(commentList[index], "{\n");
12440 else commentList[index][0] = NULLCHAR;
12441 strcat(commentList[index], text);
12442 strcat(commentList[index], "\n");
12443 if(addBraces) strcat(commentList[index], "}\n");
12447 static char * FindStr( char * text, char * sub_text )
12449 char * result = strstr( text, sub_text );
12451 if( result != NULL ) {
12452 result += strlen( sub_text );
12458 /* [AS] Try to extract PV info from PGN comment */
12459 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12460 char *GetInfoFromComment( int index, char * text )
12464 if( text != NULL && index > 0 ) {
12467 int time = -1, sec = 0, deci;
12468 char * s_eval = FindStr( text, "[%eval " );
12469 char * s_emt = FindStr( text, "[%emt " );
12471 if( s_eval != NULL || s_emt != NULL ) {
12475 if( s_eval != NULL ) {
12476 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12480 if( delim != ']' ) {
12485 if( s_emt != NULL ) {
12490 /* We expect something like: [+|-]nnn.nn/dd */
12493 if(*text != '{') return text; // [HGM] braces: must be normal comment
12495 sep = strchr( text, '/' );
12496 if( sep == NULL || sep < (text+4) ) {
12500 time = -1; sec = -1; deci = -1;
12501 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12502 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12503 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12504 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12508 if( score_lo < 0 || score_lo >= 100 ) {
12512 if(sec >= 0) time = 600*time + 10*sec; else
12513 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12515 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12517 /* [HGM] PV time: now locate end of PV info */
12518 while( *++sep >= '0' && *sep <= '9'); // strip depth
12520 while( *++sep >= '0' && *sep <= '9'); // strip time
12522 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12524 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12525 while(*sep == ' ') sep++;
12536 pvInfoList[index-1].depth = depth;
12537 pvInfoList[index-1].score = score;
12538 pvInfoList[index-1].time = 10*time; // centi-sec
12539 if(*sep == '}') *sep = 0; else *--sep = '{';
12545 SendToProgram(message, cps)
12547 ChessProgramState *cps;
12549 int count, outCount, error;
12552 if (cps->pr == NULL) return;
12555 if (appData.debugMode) {
12558 fprintf(debugFP, "%ld >%-6s: %s",
12559 SubtractTimeMarks(&now, &programStartTime),
12560 cps->which, message);
12563 count = strlen(message);
12564 outCount = OutputToProcess(cps->pr, message, count, &error);
12565 if (outCount < count && !exiting
12566 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12567 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12568 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12569 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12570 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12571 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12573 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12575 gameInfo.resultDetails = StrSave(buf);
12577 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12582 ReceiveFromProgram(isr, closure, message, count, error)
12583 InputSourceRef isr;
12591 ChessProgramState *cps = (ChessProgramState *)closure;
12593 if (isr != cps->isr) return; /* Killed intentionally */
12597 _("Error: %s chess program (%s) exited unexpectedly"),
12598 cps->which, cps->program);
12599 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12600 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12601 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12602 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12604 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12606 gameInfo.resultDetails = StrSave(buf);
12608 RemoveInputSource(cps->isr);
12609 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12612 _("Error reading from %s chess program (%s)"),
12613 cps->which, cps->program);
12614 RemoveInputSource(cps->isr);
12616 /* [AS] Program is misbehaving badly... kill it */
12617 if( count == -2 ) {
12618 DestroyChildProcess( cps->pr, 9 );
12622 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12627 if ((end_str = strchr(message, '\r')) != NULL)
12628 *end_str = NULLCHAR;
12629 if ((end_str = strchr(message, '\n')) != NULL)
12630 *end_str = NULLCHAR;
12632 if (appData.debugMode) {
12633 TimeMark now; int print = 1;
12634 char *quote = ""; char c; int i;
12636 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12637 char start = message[0];
12638 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12639 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12640 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12641 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12642 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12643 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12644 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12645 sscanf(message, "pong %c", &c)!=1 && start != '#')
12646 { quote = "# "; print = (appData.engineComments == 2); }
12647 message[0] = start; // restore original message
12651 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12652 SubtractTimeMarks(&now, &programStartTime), cps->which,
12658 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12659 if (appData.icsEngineAnalyze) {
12660 if (strstr(message, "whisper") != NULL ||
12661 strstr(message, "kibitz") != NULL ||
12662 strstr(message, "tellics") != NULL) return;
12665 HandleMachineMove(message, cps);
12670 SendTimeControl(cps, mps, tc, inc, sd, st)
12671 ChessProgramState *cps;
12672 int mps, inc, sd, st;
12678 if( timeControl_2 > 0 ) {
12679 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12680 tc = timeControl_2;
12683 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12684 inc /= cps->timeOdds;
12685 st /= cps->timeOdds;
12687 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12690 /* Set exact time per move, normally using st command */
12691 if (cps->stKludge) {
12692 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12694 if (seconds == 0) {
12695 sprintf(buf, "level 1 %d\n", st/60);
12697 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12700 sprintf(buf, "st %d\n", st);
12703 /* Set conventional or incremental time control, using level command */
12704 if (seconds == 0) {
12705 /* Note old gnuchess bug -- minutes:seconds used to not work.
12706 Fixed in later versions, but still avoid :seconds
12707 when seconds is 0. */
12708 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12710 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12711 seconds, inc/1000);
12714 SendToProgram(buf, cps);
12716 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12717 /* Orthogonally, limit search to given depth */
12719 if (cps->sdKludge) {
12720 sprintf(buf, "depth\n%d\n", sd);
12722 sprintf(buf, "sd %d\n", sd);
12724 SendToProgram(buf, cps);
12727 if(cps->nps > 0) { /* [HGM] nps */
12728 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12730 sprintf(buf, "nps %d\n", cps->nps);
12731 SendToProgram(buf, cps);
12736 ChessProgramState *WhitePlayer()
12737 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12739 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12740 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12746 SendTimeRemaining(cps, machineWhite)
12747 ChessProgramState *cps;
12748 int /*boolean*/ machineWhite;
12750 char message[MSG_SIZ];
12753 /* Note: this routine must be called when the clocks are stopped
12754 or when they have *just* been set or switched; otherwise
12755 it will be off by the time since the current tick started.
12757 if (machineWhite) {
12758 time = whiteTimeRemaining / 10;
12759 otime = blackTimeRemaining / 10;
12761 time = blackTimeRemaining / 10;
12762 otime = whiteTimeRemaining / 10;
12764 /* [HGM] translate opponent's time by time-odds factor */
12765 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12766 if (appData.debugMode) {
12767 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12770 if (time <= 0) time = 1;
12771 if (otime <= 0) otime = 1;
12773 sprintf(message, "time %ld\n", time);
12774 SendToProgram(message, cps);
12776 sprintf(message, "otim %ld\n", otime);
12777 SendToProgram(message, cps);
12781 BoolFeature(p, name, loc, cps)
12785 ChessProgramState *cps;
12788 int len = strlen(name);
12790 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12792 sscanf(*p, "%d", &val);
12794 while (**p && **p != ' ') (*p)++;
12795 sprintf(buf, "accepted %s\n", name);
12796 SendToProgram(buf, cps);
12803 IntFeature(p, name, loc, cps)
12807 ChessProgramState *cps;
12810 int len = strlen(name);
12811 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12813 sscanf(*p, "%d", loc);
12814 while (**p && **p != ' ') (*p)++;
12815 sprintf(buf, "accepted %s\n", name);
12816 SendToProgram(buf, cps);
12823 StringFeature(p, name, loc, cps)
12827 ChessProgramState *cps;
12830 int len = strlen(name);
12831 if (strncmp((*p), name, len) == 0
12832 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12834 sscanf(*p, "%[^\"]", loc);
12835 while (**p && **p != '\"') (*p)++;
12836 if (**p == '\"') (*p)++;
12837 sprintf(buf, "accepted %s\n", name);
12838 SendToProgram(buf, cps);
12845 ParseOption(Option *opt, ChessProgramState *cps)
12846 // [HGM] options: process the string that defines an engine option, and determine
12847 // name, type, default value, and allowed value range
12849 char *p, *q, buf[MSG_SIZ];
12850 int n, min = (-1)<<31, max = 1<<31, def;
12852 if(p = strstr(opt->name, " -spin ")) {
12853 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12854 if(max < min) max = min; // enforce consistency
12855 if(def < min) def = min;
12856 if(def > max) def = max;
12861 } else if((p = strstr(opt->name, " -slider "))) {
12862 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12863 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12864 if(max < min) max = min; // enforce consistency
12865 if(def < min) def = min;
12866 if(def > max) def = max;
12870 opt->type = Spin; // Slider;
12871 } else if((p = strstr(opt->name, " -string "))) {
12872 opt->textValue = p+9;
12873 opt->type = TextBox;
12874 } else if((p = strstr(opt->name, " -file "))) {
12875 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12876 opt->textValue = p+7;
12877 opt->type = TextBox; // FileName;
12878 } else if((p = strstr(opt->name, " -path "))) {
12879 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12880 opt->textValue = p+7;
12881 opt->type = TextBox; // PathName;
12882 } else if(p = strstr(opt->name, " -check ")) {
12883 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12884 opt->value = (def != 0);
12885 opt->type = CheckBox;
12886 } else if(p = strstr(opt->name, " -combo ")) {
12887 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12888 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12889 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12890 opt->value = n = 0;
12891 while(q = StrStr(q, " /// ")) {
12892 n++; *q = 0; // count choices, and null-terminate each of them
12894 if(*q == '*') { // remember default, which is marked with * prefix
12898 cps->comboList[cps->comboCnt++] = q;
12900 cps->comboList[cps->comboCnt++] = NULL;
12902 opt->type = ComboBox;
12903 } else if(p = strstr(opt->name, " -button")) {
12904 opt->type = Button;
12905 } else if(p = strstr(opt->name, " -save")) {
12906 opt->type = SaveButton;
12907 } else return FALSE;
12908 *p = 0; // terminate option name
12909 // now look if the command-line options define a setting for this engine option.
12910 if(cps->optionSettings && cps->optionSettings[0])
12911 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12912 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12913 sprintf(buf, "option %s", p);
12914 if(p = strstr(buf, ",")) *p = 0;
12916 SendToProgram(buf, cps);
12922 FeatureDone(cps, val)
12923 ChessProgramState* cps;
12926 DelayedEventCallback cb = GetDelayedEvent();
12927 if ((cb == InitBackEnd3 && cps == &first) ||
12928 (cb == TwoMachinesEventIfReady && cps == &second)) {
12929 CancelDelayedEvent();
12930 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12932 cps->initDone = val;
12935 /* Parse feature command from engine */
12937 ParseFeatures(args, cps)
12939 ChessProgramState *cps;
12947 while (*p == ' ') p++;
12948 if (*p == NULLCHAR) return;
12950 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12951 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12952 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12953 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12954 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12955 if (BoolFeature(&p, "reuse", &val, cps)) {
12956 /* Engine can disable reuse, but can't enable it if user said no */
12957 if (!val) cps->reuse = FALSE;
12960 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12961 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12962 if (gameMode == TwoMachinesPlay) {
12963 DisplayTwoMachinesTitle();
12969 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12970 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12971 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12972 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12973 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12974 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12975 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12976 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12977 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12978 if (IntFeature(&p, "done", &val, cps)) {
12979 FeatureDone(cps, val);
12982 /* Added by Tord: */
12983 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12984 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12985 /* End of additions by Tord */
12987 /* [HGM] added features: */
12988 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12989 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12990 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12991 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12992 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12993 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12994 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12995 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12996 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12997 SendToProgram(buf, cps);
13000 if(cps->nrOptions >= MAX_OPTIONS) {
13002 sprintf(buf, "%s engine has too many options\n", cps->which);
13003 DisplayError(buf, 0);
13007 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13008 /* End of additions by HGM */
13010 /* unknown feature: complain and skip */
13012 while (*q && *q != '=') q++;
13013 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13014 SendToProgram(buf, cps);
13020 while (*p && *p != '\"') p++;
13021 if (*p == '\"') p++;
13023 while (*p && *p != ' ') p++;
13031 PeriodicUpdatesEvent(newState)
13034 if (newState == appData.periodicUpdates)
13037 appData.periodicUpdates=newState;
13039 /* Display type changes, so update it now */
13040 // DisplayAnalysis();
13042 /* Get the ball rolling again... */
13044 AnalysisPeriodicEvent(1);
13045 StartAnalysisClock();
13050 PonderNextMoveEvent(newState)
13053 if (newState == appData.ponderNextMove) return;
13054 if (gameMode == EditPosition) EditPositionDone(TRUE);
13056 SendToProgram("hard\n", &first);
13057 if (gameMode == TwoMachinesPlay) {
13058 SendToProgram("hard\n", &second);
13061 SendToProgram("easy\n", &first);
13062 thinkOutput[0] = NULLCHAR;
13063 if (gameMode == TwoMachinesPlay) {
13064 SendToProgram("easy\n", &second);
13067 appData.ponderNextMove = newState;
13071 NewSettingEvent(option, command, value)
13077 if (gameMode == EditPosition) EditPositionDone(TRUE);
13078 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13079 SendToProgram(buf, &first);
13080 if (gameMode == TwoMachinesPlay) {
13081 SendToProgram(buf, &second);
13086 ShowThinkingEvent()
13087 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13089 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13090 int newState = appData.showThinking
13091 // [HGM] thinking: other features now need thinking output as well
13092 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13094 if (oldState == newState) return;
13095 oldState = newState;
13096 if (gameMode == EditPosition) EditPositionDone(TRUE);
13098 SendToProgram("post\n", &first);
13099 if (gameMode == TwoMachinesPlay) {
13100 SendToProgram("post\n", &second);
13103 SendToProgram("nopost\n", &first);
13104 thinkOutput[0] = NULLCHAR;
13105 if (gameMode == TwoMachinesPlay) {
13106 SendToProgram("nopost\n", &second);
13109 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13113 AskQuestionEvent(title, question, replyPrefix, which)
13114 char *title; char *question; char *replyPrefix; char *which;
13116 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13117 if (pr == NoProc) return;
13118 AskQuestion(title, question, replyPrefix, pr);
13122 DisplayMove(moveNumber)
13125 char message[MSG_SIZ];
13127 char cpThinkOutput[MSG_SIZ];
13129 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13131 if (moveNumber == forwardMostMove - 1 ||
13132 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13134 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13136 if (strchr(cpThinkOutput, '\n')) {
13137 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13140 *cpThinkOutput = NULLCHAR;
13143 /* [AS] Hide thinking from human user */
13144 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13145 *cpThinkOutput = NULLCHAR;
13146 if( thinkOutput[0] != NULLCHAR ) {
13149 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13150 cpThinkOutput[i] = '.';
13152 cpThinkOutput[i] = NULLCHAR;
13153 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13157 if (moveNumber == forwardMostMove - 1 &&
13158 gameInfo.resultDetails != NULL) {
13159 if (gameInfo.resultDetails[0] == NULLCHAR) {
13160 sprintf(res, " %s", PGNResult(gameInfo.result));
13162 sprintf(res, " {%s} %s",
13163 gameInfo.resultDetails, PGNResult(gameInfo.result));
13169 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13170 DisplayMessage(res, cpThinkOutput);
13172 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13173 WhiteOnMove(moveNumber) ? " " : ".. ",
13174 parseList[moveNumber], res);
13175 DisplayMessage(message, cpThinkOutput);
13180 DisplayComment(moveNumber, text)
13184 char title[MSG_SIZ];
13185 char buf[8000]; // comment can be long!
13188 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13189 strcpy(title, "Comment");
13191 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13192 WhiteOnMove(moveNumber) ? " " : ".. ",
13193 parseList[moveNumber]);
13195 // [HGM] PV info: display PV info together with (or as) comment
13196 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13197 if(text == NULL) text = "";
13198 score = pvInfoList[moveNumber].score;
13199 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13200 depth, (pvInfoList[moveNumber].time+50)/100, text);
13203 if (text != NULL && (appData.autoDisplayComment || commentUp))
13204 CommentPopUp(title, text);
13207 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13208 * might be busy thinking or pondering. It can be omitted if your
13209 * gnuchess is configured to stop thinking immediately on any user
13210 * input. However, that gnuchess feature depends on the FIONREAD
13211 * ioctl, which does not work properly on some flavors of Unix.
13215 ChessProgramState *cps;
13218 if (!cps->useSigint) return;
13219 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13220 switch (gameMode) {
13221 case MachinePlaysWhite:
13222 case MachinePlaysBlack:
13223 case TwoMachinesPlay:
13224 case IcsPlayingWhite:
13225 case IcsPlayingBlack:
13228 /* Skip if we know it isn't thinking */
13229 if (!cps->maybeThinking) return;
13230 if (appData.debugMode)
13231 fprintf(debugFP, "Interrupting %s\n", cps->which);
13232 InterruptChildProcess(cps->pr);
13233 cps->maybeThinking = FALSE;
13238 #endif /*ATTENTION*/
13244 if (whiteTimeRemaining <= 0) {
13247 if (appData.icsActive) {
13248 if (appData.autoCallFlag &&
13249 gameMode == IcsPlayingBlack && !blackFlag) {
13250 SendToICS(ics_prefix);
13251 SendToICS("flag\n");
13255 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13257 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13258 if (appData.autoCallFlag) {
13259 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13266 if (blackTimeRemaining <= 0) {
13269 if (appData.icsActive) {
13270 if (appData.autoCallFlag &&
13271 gameMode == IcsPlayingWhite && !whiteFlag) {
13272 SendToICS(ics_prefix);
13273 SendToICS("flag\n");
13277 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13279 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13280 if (appData.autoCallFlag) {
13281 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13294 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13295 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13298 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13300 if ( !WhiteOnMove(forwardMostMove) )
13301 /* White made time control */
13302 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13303 /* [HGM] time odds: correct new time quota for time odds! */
13304 / WhitePlayer()->timeOdds;
13306 /* Black made time control */
13307 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13308 / WhitePlayer()->other->timeOdds;
13312 DisplayBothClocks()
13314 int wom = gameMode == EditPosition ?
13315 !blackPlaysFirst : WhiteOnMove(currentMove);
13316 DisplayWhiteClock(whiteTimeRemaining, wom);
13317 DisplayBlackClock(blackTimeRemaining, !wom);
13321 /* Timekeeping seems to be a portability nightmare. I think everyone
13322 has ftime(), but I'm really not sure, so I'm including some ifdefs
13323 to use other calls if you don't. Clocks will be less accurate if
13324 you have neither ftime nor gettimeofday.
13327 /* VS 2008 requires the #include outside of the function */
13328 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13329 #include <sys/timeb.h>
13332 /* Get the current time as a TimeMark */
13337 #if HAVE_GETTIMEOFDAY
13339 struct timeval timeVal;
13340 struct timezone timeZone;
13342 gettimeofday(&timeVal, &timeZone);
13343 tm->sec = (long) timeVal.tv_sec;
13344 tm->ms = (int) (timeVal.tv_usec / 1000L);
13346 #else /*!HAVE_GETTIMEOFDAY*/
13349 // include <sys/timeb.h> / moved to just above start of function
13350 struct timeb timeB;
13353 tm->sec = (long) timeB.time;
13354 tm->ms = (int) timeB.millitm;
13356 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13357 tm->sec = (long) time(NULL);
13363 /* Return the difference in milliseconds between two
13364 time marks. We assume the difference will fit in a long!
13367 SubtractTimeMarks(tm2, tm1)
13368 TimeMark *tm2, *tm1;
13370 return 1000L*(tm2->sec - tm1->sec) +
13371 (long) (tm2->ms - tm1->ms);
13376 * Code to manage the game clocks.
13378 * In tournament play, black starts the clock and then white makes a move.
13379 * We give the human user a slight advantage if he is playing white---the
13380 * clocks don't run until he makes his first move, so it takes zero time.
13381 * Also, we don't account for network lag, so we could get out of sync
13382 * with GNU Chess's clock -- but then, referees are always right.
13385 static TimeMark tickStartTM;
13386 static long intendedTickLength;
13389 NextTickLength(timeRemaining)
13390 long timeRemaining;
13392 long nominalTickLength, nextTickLength;
13394 if (timeRemaining > 0L && timeRemaining <= 10000L)
13395 nominalTickLength = 100L;
13397 nominalTickLength = 1000L;
13398 nextTickLength = timeRemaining % nominalTickLength;
13399 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13401 return nextTickLength;
13404 /* Adjust clock one minute up or down */
13406 AdjustClock(Boolean which, int dir)
13408 if(which) blackTimeRemaining += 60000*dir;
13409 else whiteTimeRemaining += 60000*dir;
13410 DisplayBothClocks();
13413 /* Stop clocks and reset to a fresh time control */
13417 (void) StopClockTimer();
13418 if (appData.icsActive) {
13419 whiteTimeRemaining = blackTimeRemaining = 0;
13420 } else if (searchTime) {
13421 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13422 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13423 } else { /* [HGM] correct new time quote for time odds */
13424 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13425 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13427 if (whiteFlag || blackFlag) {
13429 whiteFlag = blackFlag = FALSE;
13431 DisplayBothClocks();
13434 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13436 /* Decrement running clock by amount of time that has passed */
13440 long timeRemaining;
13441 long lastTickLength, fudge;
13444 if (!appData.clockMode) return;
13445 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13449 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13451 /* Fudge if we woke up a little too soon */
13452 fudge = intendedTickLength - lastTickLength;
13453 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13455 if (WhiteOnMove(forwardMostMove)) {
13456 if(whiteNPS >= 0) lastTickLength = 0;
13457 timeRemaining = whiteTimeRemaining -= lastTickLength;
13458 DisplayWhiteClock(whiteTimeRemaining - fudge,
13459 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13461 if(blackNPS >= 0) lastTickLength = 0;
13462 timeRemaining = blackTimeRemaining -= lastTickLength;
13463 DisplayBlackClock(blackTimeRemaining - fudge,
13464 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13467 if (CheckFlags()) return;
13470 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13471 StartClockTimer(intendedTickLength);
13473 /* if the time remaining has fallen below the alarm threshold, sound the
13474 * alarm. if the alarm has sounded and (due to a takeback or time control
13475 * with increment) the time remaining has increased to a level above the
13476 * threshold, reset the alarm so it can sound again.
13479 if (appData.icsActive && appData.icsAlarm) {
13481 /* make sure we are dealing with the user's clock */
13482 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13483 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13486 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13487 alarmSounded = FALSE;
13488 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13490 alarmSounded = TRUE;
13496 /* A player has just moved, so stop the previously running
13497 clock and (if in clock mode) start the other one.
13498 We redisplay both clocks in case we're in ICS mode, because
13499 ICS gives us an update to both clocks after every move.
13500 Note that this routine is called *after* forwardMostMove
13501 is updated, so the last fractional tick must be subtracted
13502 from the color that is *not* on move now.
13507 long lastTickLength;
13509 int flagged = FALSE;
13513 if (StopClockTimer() && appData.clockMode) {
13514 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13515 if (WhiteOnMove(forwardMostMove)) {
13516 if(blackNPS >= 0) lastTickLength = 0;
13517 blackTimeRemaining -= lastTickLength;
13518 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13519 // if(pvInfoList[forwardMostMove-1].time == -1)
13520 pvInfoList[forwardMostMove-1].time = // use GUI time
13521 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13523 if(whiteNPS >= 0) lastTickLength = 0;
13524 whiteTimeRemaining -= lastTickLength;
13525 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13526 // if(pvInfoList[forwardMostMove-1].time == -1)
13527 pvInfoList[forwardMostMove-1].time =
13528 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13530 flagged = CheckFlags();
13532 CheckTimeControl();
13534 if (flagged || !appData.clockMode) return;
13536 switch (gameMode) {
13537 case MachinePlaysBlack:
13538 case MachinePlaysWhite:
13539 case BeginningOfGame:
13540 if (pausing) return;
13544 case PlayFromGameFile:
13552 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13553 if(WhiteOnMove(forwardMostMove))
13554 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13555 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13559 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13560 whiteTimeRemaining : blackTimeRemaining);
13561 StartClockTimer(intendedTickLength);
13565 /* Stop both clocks */
13569 long lastTickLength;
13572 if (!StopClockTimer()) return;
13573 if (!appData.clockMode) return;
13577 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13578 if (WhiteOnMove(forwardMostMove)) {
13579 if(whiteNPS >= 0) lastTickLength = 0;
13580 whiteTimeRemaining -= lastTickLength;
13581 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13583 if(blackNPS >= 0) lastTickLength = 0;
13584 blackTimeRemaining -= lastTickLength;
13585 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13590 /* Start clock of player on move. Time may have been reset, so
13591 if clock is already running, stop and restart it. */
13595 (void) StopClockTimer(); /* in case it was running already */
13596 DisplayBothClocks();
13597 if (CheckFlags()) return;
13599 if (!appData.clockMode) return;
13600 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13602 GetTimeMark(&tickStartTM);
13603 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13604 whiteTimeRemaining : blackTimeRemaining);
13606 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13607 whiteNPS = blackNPS = -1;
13608 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13609 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13610 whiteNPS = first.nps;
13611 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13612 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13613 blackNPS = first.nps;
13614 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13615 whiteNPS = second.nps;
13616 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13617 blackNPS = second.nps;
13618 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13620 StartClockTimer(intendedTickLength);
13627 long second, minute, hour, day;
13629 static char buf[32];
13631 if (ms > 0 && ms <= 9900) {
13632 /* convert milliseconds to tenths, rounding up */
13633 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13635 sprintf(buf, " %03.1f ", tenths/10.0);
13639 /* convert milliseconds to seconds, rounding up */
13640 /* use floating point to avoid strangeness of integer division
13641 with negative dividends on many machines */
13642 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13649 day = second / (60 * 60 * 24);
13650 second = second % (60 * 60 * 24);
13651 hour = second / (60 * 60);
13652 second = second % (60 * 60);
13653 minute = second / 60;
13654 second = second % 60;
13657 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13658 sign, day, hour, minute, second);
13660 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13662 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13669 * This is necessary because some C libraries aren't ANSI C compliant yet.
13672 StrStr(string, match)
13673 char *string, *match;
13677 length = strlen(match);
13679 for (i = strlen(string) - length; i >= 0; i--, string++)
13680 if (!strncmp(match, string, length))
13687 StrCaseStr(string, match)
13688 char *string, *match;
13692 length = strlen(match);
13694 for (i = strlen(string) - length; i >= 0; i--, string++) {
13695 for (j = 0; j < length; j++) {
13696 if (ToLower(match[j]) != ToLower(string[j]))
13699 if (j == length) return string;
13713 c1 = ToLower(*s1++);
13714 c2 = ToLower(*s2++);
13715 if (c1 > c2) return 1;
13716 if (c1 < c2) return -1;
13717 if (c1 == NULLCHAR) return 0;
13726 return isupper(c) ? tolower(c) : c;
13734 return islower(c) ? toupper(c) : c;
13736 #endif /* !_amigados */
13744 if ((ret = (char *) malloc(strlen(s) + 1))) {
13751 StrSavePtr(s, savePtr)
13752 char *s, **savePtr;
13757 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13758 strcpy(*savePtr, s);
13770 clock = time((time_t *)NULL);
13771 tm = localtime(&clock);
13772 sprintf(buf, "%04d.%02d.%02d",
13773 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13774 return StrSave(buf);
13779 PositionToFEN(move, overrideCastling)
13781 char *overrideCastling;
13783 int i, j, fromX, fromY, toX, toY;
13790 whiteToPlay = (gameMode == EditPosition) ?
13791 !blackPlaysFirst : (move % 2 == 0);
13794 /* Piece placement data */
13795 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13797 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13798 if (boards[move][i][j] == EmptySquare) {
13800 } else { ChessSquare piece = boards[move][i][j];
13801 if (emptycount > 0) {
13802 if(emptycount<10) /* [HGM] can be >= 10 */
13803 *p++ = '0' + emptycount;
13804 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13807 if(PieceToChar(piece) == '+') {
13808 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13810 piece = (ChessSquare)(DEMOTED piece);
13812 *p++ = PieceToChar(piece);
13814 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13815 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13820 if (emptycount > 0) {
13821 if(emptycount<10) /* [HGM] can be >= 10 */
13822 *p++ = '0' + emptycount;
13823 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13830 /* [HGM] print Crazyhouse or Shogi holdings */
13831 if( gameInfo.holdingsWidth ) {
13832 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13834 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13835 piece = boards[move][i][BOARD_WIDTH-1];
13836 if( piece != EmptySquare )
13837 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13838 *p++ = PieceToChar(piece);
13840 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13841 piece = boards[move][BOARD_HEIGHT-i-1][0];
13842 if( piece != EmptySquare )
13843 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13844 *p++ = PieceToChar(piece);
13847 if( q == p ) *p++ = '-';
13853 *p++ = whiteToPlay ? 'w' : 'b';
13856 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13857 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13859 if(nrCastlingRights) {
13861 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13862 /* [HGM] write directly from rights */
13863 if(boards[move][CASTLING][2] != NoRights &&
13864 boards[move][CASTLING][0] != NoRights )
13865 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13866 if(boards[move][CASTLING][2] != NoRights &&
13867 boards[move][CASTLING][1] != NoRights )
13868 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13869 if(boards[move][CASTLING][5] != NoRights &&
13870 boards[move][CASTLING][3] != NoRights )
13871 *p++ = boards[move][CASTLING][3] + AAA;
13872 if(boards[move][CASTLING][5] != NoRights &&
13873 boards[move][CASTLING][4] != NoRights )
13874 *p++ = boards[move][CASTLING][4] + AAA;
13877 /* [HGM] write true castling rights */
13878 if( nrCastlingRights == 6 ) {
13879 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13880 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13881 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13882 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13883 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13884 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13885 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13886 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13889 if (q == p) *p++ = '-'; /* No castling rights */
13893 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13894 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13895 /* En passant target square */
13896 if (move > backwardMostMove) {
13897 fromX = moveList[move - 1][0] - AAA;
13898 fromY = moveList[move - 1][1] - ONE;
13899 toX = moveList[move - 1][2] - AAA;
13900 toY = moveList[move - 1][3] - ONE;
13901 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13902 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13903 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13905 /* 2-square pawn move just happened */
13907 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13911 } else if(move == backwardMostMove) {
13912 // [HGM] perhaps we should always do it like this, and forget the above?
13913 if((signed char)boards[move][EP_STATUS] >= 0) {
13914 *p++ = boards[move][EP_STATUS] + AAA;
13915 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13926 /* [HGM] find reversible plies */
13927 { int i = 0, j=move;
13929 if (appData.debugMode) { int k;
13930 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13931 for(k=backwardMostMove; k<=forwardMostMove; k++)
13932 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13936 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13937 if( j == backwardMostMove ) i += initialRulePlies;
13938 sprintf(p, "%d ", i);
13939 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13941 /* Fullmove number */
13942 sprintf(p, "%d", (move / 2) + 1);
13944 return StrSave(buf);
13948 ParseFEN(board, blackPlaysFirst, fen)
13950 int *blackPlaysFirst;
13960 /* [HGM] by default clear Crazyhouse holdings, if present */
13961 if(gameInfo.holdingsWidth) {
13962 for(i=0; i<BOARD_HEIGHT; i++) {
13963 board[i][0] = EmptySquare; /* black holdings */
13964 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13965 board[i][1] = (ChessSquare) 0; /* black counts */
13966 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13970 /* Piece placement data */
13971 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13974 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13975 if (*p == '/') p++;
13976 emptycount = gameInfo.boardWidth - j;
13977 while (emptycount--)
13978 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13980 #if(BOARD_FILES >= 10)
13981 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13982 p++; emptycount=10;
13983 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13984 while (emptycount--)
13985 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13987 } else if (isdigit(*p)) {
13988 emptycount = *p++ - '0';
13989 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13990 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13991 while (emptycount--)
13992 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13993 } else if (*p == '+' || isalpha(*p)) {
13994 if (j >= gameInfo.boardWidth) return FALSE;
13996 piece = CharToPiece(*++p);
13997 if(piece == EmptySquare) return FALSE; /* unknown piece */
13998 piece = (ChessSquare) (PROMOTED piece ); p++;
13999 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14000 } else piece = CharToPiece(*p++);
14002 if(piece==EmptySquare) return FALSE; /* unknown piece */
14003 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14004 piece = (ChessSquare) (PROMOTED piece);
14005 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14008 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14014 while (*p == '/' || *p == ' ') p++;
14016 /* [HGM] look for Crazyhouse holdings here */
14017 while(*p==' ') p++;
14018 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14020 if(*p == '-' ) *p++; /* empty holdings */ else {
14021 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14022 /* if we would allow FEN reading to set board size, we would */
14023 /* have to add holdings and shift the board read so far here */
14024 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14026 if((int) piece >= (int) BlackPawn ) {
14027 i = (int)piece - (int)BlackPawn;
14028 i = PieceToNumber((ChessSquare)i);
14029 if( i >= gameInfo.holdingsSize ) return FALSE;
14030 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14031 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14033 i = (int)piece - (int)WhitePawn;
14034 i = PieceToNumber((ChessSquare)i);
14035 if( i >= gameInfo.holdingsSize ) return FALSE;
14036 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14037 board[i][BOARD_WIDTH-2]++; /* black holdings */
14041 if(*p == ']') *p++;
14044 while(*p == ' ') p++;
14049 *blackPlaysFirst = FALSE;
14052 *blackPlaysFirst = TRUE;
14058 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14059 /* return the extra info in global variiables */
14061 /* set defaults in case FEN is incomplete */
14062 board[EP_STATUS] = EP_UNKNOWN;
14063 for(i=0; i<nrCastlingRights; i++ ) {
14064 board[CASTLING][i] =
14065 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14066 } /* assume possible unless obviously impossible */
14067 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14068 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14069 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14070 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14071 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14072 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14075 while(*p==' ') p++;
14076 if(nrCastlingRights) {
14077 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14078 /* castling indicator present, so default becomes no castlings */
14079 for(i=0; i<nrCastlingRights; i++ ) {
14080 board[CASTLING][i] = NoRights;
14083 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14084 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14085 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14086 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14087 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14089 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14090 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14091 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14095 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14096 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14097 board[CASTLING][2] = whiteKingFile;
14100 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14101 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14102 board[CASTLING][2] = whiteKingFile;
14105 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14106 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14107 board[CASTLING][5] = blackKingFile;
14110 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14111 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14112 board[CASTLING][5] = blackKingFile;
14115 default: /* FRC castlings */
14116 if(c >= 'a') { /* black rights */
14117 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14118 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14119 if(i == BOARD_RGHT) break;
14120 board[CASTLING][5] = i;
14122 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14123 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14125 board[CASTLING][3] = c;
14127 board[CASTLING][4] = c;
14128 } else { /* white rights */
14129 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14130 if(board[0][i] == WhiteKing) break;
14131 if(i == BOARD_RGHT) break;
14132 board[CASTLING][2] = i;
14133 c -= AAA - 'a' + 'A';
14134 if(board[0][c] >= WhiteKing) break;
14136 board[CASTLING][0] = c;
14138 board[CASTLING][1] = c;
14142 if (appData.debugMode) {
14143 fprintf(debugFP, "FEN castling rights:");
14144 for(i=0; i<nrCastlingRights; i++)
14145 fprintf(debugFP, " %d", board[CASTLING][i]);
14146 fprintf(debugFP, "\n");
14149 while(*p==' ') p++;
14152 /* read e.p. field in games that know e.p. capture */
14153 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14154 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14156 p++; board[EP_STATUS] = EP_NONE;
14158 char c = *p++ - AAA;
14160 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14161 if(*p >= '0' && *p <='9') *p++;
14162 board[EP_STATUS] = c;
14167 if(sscanf(p, "%d", &i) == 1) {
14168 FENrulePlies = i; /* 50-move ply counter */
14169 /* (The move number is still ignored) */
14176 EditPositionPasteFEN(char *fen)
14179 Board initial_position;
14181 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14182 DisplayError(_("Bad FEN position in clipboard"), 0);
14185 int savedBlackPlaysFirst = blackPlaysFirst;
14186 EditPositionEvent();
14187 blackPlaysFirst = savedBlackPlaysFirst;
14188 CopyBoard(boards[0], initial_position);
14189 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14190 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14191 DisplayBothClocks();
14192 DrawPosition(FALSE, boards[currentMove]);
14197 static char cseq[12] = "\\ ";
14199 Boolean set_cont_sequence(char *new_seq)
14204 // handle bad attempts to set the sequence
14206 return 0; // acceptable error - no debug
14208 len = strlen(new_seq);
14209 ret = (len > 0) && (len < sizeof(cseq));
14211 strcpy(cseq, new_seq);
14212 else if (appData.debugMode)
14213 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14218 reformat a source message so words don't cross the width boundary. internal
14219 newlines are not removed. returns the wrapped size (no null character unless
14220 included in source message). If dest is NULL, only calculate the size required
14221 for the dest buffer. lp argument indicats line position upon entry, and it's
14222 passed back upon exit.
14224 int wrap(char *dest, char *src, int count, int width, int *lp)
14226 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14228 cseq_len = strlen(cseq);
14229 old_line = line = *lp;
14230 ansi = len = clen = 0;
14232 for (i=0; i < count; i++)
14234 if (src[i] == '\033')
14237 // if we hit the width, back up
14238 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14240 // store i & len in case the word is too long
14241 old_i = i, old_len = len;
14243 // find the end of the last word
14244 while (i && src[i] != ' ' && src[i] != '\n')
14250 // word too long? restore i & len before splitting it
14251 if ((old_i-i+clen) >= width)
14258 if (i && src[i-1] == ' ')
14261 if (src[i] != ' ' && src[i] != '\n')
14268 // now append the newline and continuation sequence
14273 strncpy(dest+len, cseq, cseq_len);
14281 dest[len] = src[i];
14285 if (src[i] == '\n')
14290 if (dest && appData.debugMode)
14292 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14293 count, width, line, len, *lp);
14294 show_bytes(debugFP, src, count);
14295 fprintf(debugFP, "\ndest: ");
14296 show_bytes(debugFP, dest, len);
14297 fprintf(debugFP, "\n");
14299 *lp = dest ? line : old_line;
14304 // [HGM] vari: routines for shelving variations
14307 PushTail(int firstMove, int lastMove)
14309 int i, j, nrMoves = lastMove - firstMove;
14311 if(appData.icsActive) { // only in local mode
14312 forwardMostMove = currentMove; // mimic old ICS behavior
14315 if(storedGames >= MAX_VARIATIONS-1) return;
14317 // push current tail of game on stack
14318 savedResult[storedGames] = gameInfo.result;
14319 savedDetails[storedGames] = gameInfo.resultDetails;
14320 gameInfo.resultDetails = NULL;
14321 savedFirst[storedGames] = firstMove;
14322 savedLast [storedGames] = lastMove;
14323 savedFramePtr[storedGames] = framePtr;
14324 framePtr -= nrMoves; // reserve space for the boards
14325 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14326 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14327 for(j=0; j<MOVE_LEN; j++)
14328 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14329 for(j=0; j<2*MOVE_LEN; j++)
14330 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14331 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14332 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14333 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14334 pvInfoList[firstMove+i-1].depth = 0;
14335 commentList[framePtr+i] = commentList[firstMove+i];
14336 commentList[firstMove+i] = NULL;
14340 forwardMostMove = currentMove; // truncte game so we can start variation
14341 if(storedGames == 1) GreyRevert(FALSE);
14345 PopTail(Boolean annotate)
14348 char buf[8000], moveBuf[20];
14350 if(appData.icsActive) return FALSE; // only in local mode
14351 if(!storedGames) return FALSE; // sanity
14354 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14355 nrMoves = savedLast[storedGames] - currentMove;
14358 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14359 else strcpy(buf, "(");
14360 for(i=currentMove; i<forwardMostMove; i++) {
14362 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14363 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14364 strcat(buf, moveBuf);
14365 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14369 for(i=1; i<nrMoves; i++) { // copy last variation back
14370 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14371 for(j=0; j<MOVE_LEN; j++)
14372 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14373 for(j=0; j<2*MOVE_LEN; j++)
14374 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14375 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14376 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14377 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14378 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14379 commentList[currentMove+i] = commentList[framePtr+i];
14380 commentList[framePtr+i] = NULL;
14382 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14383 framePtr = savedFramePtr[storedGames];
14384 gameInfo.result = savedResult[storedGames];
14385 if(gameInfo.resultDetails != NULL) {
14386 free(gameInfo.resultDetails);
14388 gameInfo.resultDetails = savedDetails[storedGames];
14389 forwardMostMove = currentMove + nrMoves;
14390 if(storedGames == 0) GreyRevert(TRUE);
14396 { // remove all shelved variations
14398 for(i=0; i<storedGames; i++) {
14399 if(savedDetails[i])
14400 free(savedDetails[i]);
14401 savedDetails[i] = NULL;
14403 for(i=framePtr; i<MAX_MOVES; i++) {
14404 if(commentList[i]) free(commentList[i]);
14405 commentList[i] = NULL;
14407 framePtr = MAX_MOVES-1;