2 * backend.c -- Common back end for X and Windows NT versions of
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
5 * Copyright 1991 by Digital Equipment Corporation, Maynard,
6 * Massachusetts. Enhancements Copyright
7 * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
10 * The following terms apply to Digital Equipment Corporation's copyright
12 * ------------------------------------------------------------------------
15 * Permission to use, copy, modify, and distribute this software and its
16 * documentation for any purpose and without fee is hereby granted,
17 * provided that the above copyright notice appear in all copies and that
18 * both that copyright notice and this permission notice appear in
19 * supporting documentation, and that the name of Digital not be
20 * used in advertising or publicity pertaining to distribution of the
21 * software without specific, written prior permission.
23 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
30 * ------------------------------------------------------------------------
32 * The following terms apply to the enhanced version of XBoard
33 * distributed by the Free Software Foundation:
34 * ------------------------------------------------------------------------
36 * GNU XBoard is free software: you can redistribute it and/or modify
37 * it under the terms of the GNU General Public License as published by
38 * the Free Software Foundation, either version 3 of the License, or (at
39 * your option) any later version.
41 * GNU XBoard is distributed in the hope that it will be useful, but
42 * WITHOUT ANY WARRANTY; without even the implied warranty of
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44 * General Public License for more details.
46 * You should have received a copy of the GNU General Public License
47 * along with this program. If not, see http://www.gnu.org/licenses/. *
49 *------------------------------------------------------------------------
50 ** See the file ChangeLog for a revision history. */
52 /* [AS] Also useful here for debugging */
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)
70 #include <sys/types.h>
78 #else /* not STDC_HEADERS */
81 # else /* not HAVE_STRING_H */
83 # endif /* not HAVE_STRING_H */
84 #endif /* not STDC_HEADERS */
87 # include <sys/fcntl.h>
88 #else /* not HAVE_SYS_FCNTL_H */
91 # endif /* HAVE_FCNTL_H */
92 #endif /* not HAVE_SYS_FCNTL_H */
94 #if TIME_WITH_SYS_TIME
95 # include <sys/time.h>
99 # include <sys/time.h>
105 #if defined(_amigados) && !defined(__GNUC__)
110 extern int gettimeofday(struct timeval *, struct timezone *);
118 #include "frontend.h"
125 #include "backendz.h"
129 # define _(s) gettext (s)
130 # define N_(s) gettext_noop (s)
137 /* A point in time */
139 long sec; /* Assuming this is >= 32 bits */
140 int ms; /* Assuming this is >= 16 bits */
143 int establish P((void));
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
145 char *buf, int count, int error));
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void SendToICS P((char *s));
149 void SendToICSDelayed P((char *s, long msdelay));
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
152 void InitPosition P((int redraw));
153 void HandleMachineMove P((char *message, ChessProgramState *cps));
154 int AutoPlayOneMove P((void));
155 int LoadGameOneMove P((ChessMove readAhead));
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
157 int LoadPositionFromFile P((char *filename, int n, char *title));
158 int SavePositionToFile P((char *filename));
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
160 Board board, char *castle, char *ep));
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
162 void ShowMove P((int fromX, int fromY, int toX, int toY));
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
164 /*char*/int promoChar));
165 void BackwardInner P((int target));
166 void ForwardInner P((int target));
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
168 void EditPositionDone P((void));
169 void PrintOpponents P((FILE *fp));
170 void PrintPosition P((FILE *fp, int move));
171 void StartChessProgram P((ChessProgramState *cps));
172 void SendToProgram P((char *message, ChessProgramState *cps));
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendTimeControl P((ChessProgramState *cps,
177 int mps, long tc, int inc, int sd, int st));
178 char *TimeControlTagValue P((void));
179 void Attention P((ChessProgramState *cps));
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
181 void ResurrectChessProgram P((void));
182 void DisplayComment P((int moveNumber, char *text));
183 void DisplayMove P((int moveNumber));
184 void DisplayAnalysis P((void));
186 void ParseGameHistory P((char *game));
187 void ParseBoard12 P((char *string));
188 void StartClocks P((void));
189 void SwitchClocks P((void));
190 void StopClocks P((void));
191 void ResetClocks P((void));
192 char *PGNDate P((void));
193 void SetGameInfo P((void));
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
195 int RegisterMove P((void));
196 void MakeRegisteredMove P((void));
197 void TruncateGame P((void));
198 int looking_at P((char *, int *, char *));
199 void CopyPlayerNameIntoFileName P((char **, char *));
200 char *SavePart P((char *));
201 int SaveGameOldStyle P((FILE *));
202 int SaveGamePGN P((FILE *));
203 void GetTimeMark P((TimeMark *));
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));
205 int CheckFlags P((void));
206 long NextTickLength P((long));
207 void CheckTimeControl P((void));
208 void show_bytes P((FILE *, char *, int));
209 int string_to_rating P((char *str));
210 void ParseFeatures P((char* args, ChessProgramState *cps));
211 void InitBackEnd3 P((void));
212 void FeatureDone P((ChessProgramState* cps, int val));
213 void InitChessProgram P((ChessProgramState *cps, int setup));
214 void OutputKibitz(int window, char *text);
215 int PerpetualChase(int first, int last);
216 int EngineOutputIsUp();
217 void InitDrawingSizes(int x, int y);
220 extern void ConsoleCreate();
223 ChessProgramState *WhitePlayer();
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
225 int VerifyDisplayMode P(());
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
231 extern char installDir[MSG_SIZ];
233 extern int tinyLayout, smallLayout;
234 ChessProgramStats programStats;
235 static int exiting = 0; /* [HGM] moved to top */
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
237 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
238 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
241 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
242 int opponentKibitzes;
244 /* States for ics_getting_history */
246 #define H_REQUESTED 1
247 #define H_GOT_REQ_HEADER 2
248 #define H_GOT_UNREQ_HEADER 3
249 #define H_GETTING_MOVES 4
250 #define H_GOT_UNWANTED_HEADER 5
252 /* whosays values for GameEnds */
261 /* Maximum number of games in a cmail message */
262 #define CMAIL_MAX_GAMES 20
264 /* Different types of move when calling RegisterMove */
266 #define CMAIL_RESIGN 1
268 #define CMAIL_ACCEPT 3
270 /* Different types of result to remember for each game */
271 #define CMAIL_NOT_RESULT 0
272 #define CMAIL_OLD_RESULT 1
273 #define CMAIL_NEW_RESULT 2
275 /* Telnet protocol constants */
286 static char * safeStrCpy( char * dst, const char * src, size_t count )
288 assert( dst != NULL );
289 assert( src != NULL );
292 strncpy( dst, src, count );
293 dst[ count-1 ] = '\0';
298 //[HGM] for future use? Conditioned out for now to suppress warning.
299 static char * safeStrCat( char * dst, const char * src, size_t count )
303 assert( dst != NULL );
304 assert( src != NULL );
307 dst_len = strlen(dst);
309 assert( count > dst_len ); /* Buffer size must be greater than current length */
311 safeStrCpy( dst + dst_len, src, count - dst_len );
317 /* Some compiler can't cast u64 to double
318 * This function do the job for us:
320 * We use the highest bit for cast, this only
321 * works if the highest bit is not
322 * in use (This should not happen)
324 * We used this for all compiler
327 u64ToDouble(u64 value)
330 u64 tmp = value & u64Const(0x7fffffffffffffff);
331 r = (double)(s64)tmp;
332 if (value & u64Const(0x8000000000000000))
333 r += 9.2233720368547758080e18; /* 2^63 */
337 /* Fake up flags for now, as we aren't keeping track of castling
338 availability yet. [HGM] Change of logic: the flag now only
339 indicates the type of castlings allowed by the rule of the game.
340 The actual rights themselves are maintained in the array
341 castlingRights, as part of the game history, and are not probed
347 int flags = F_ALL_CASTLE_OK;
348 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
349 switch (gameInfo.variant) {
351 flags &= ~F_ALL_CASTLE_OK;
352 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
353 flags |= F_IGNORE_CHECK;
355 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
358 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
360 case VariantKriegspiel:
361 flags |= F_KRIEGSPIEL_CAPTURE;
363 case VariantCapaRandom:
364 case VariantFischeRandom:
365 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
366 case VariantNoCastle:
367 case VariantShatranj:
369 flags &= ~F_ALL_CASTLE_OK;
377 FILE *gameFileFP, *debugFP;
380 [AS] Note: sometimes, the sscanf() function is used to parse the input
381 into a fixed-size buffer. Because of this, we must be prepared to
382 receive strings as long as the size of the input buffer, which is currently
383 set to 4K for Windows and 8K for the rest.
384 So, we must either allocate sufficiently large buffers here, or
385 reduce the size of the input buffer in the input reading part.
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
390 char thinkOutput1[MSG_SIZ*10];
392 ChessProgramState first, second;
394 /* premove variables */
397 int premoveFromX = 0;
398 int premoveFromY = 0;
399 int premovePromoChar = 0;
401 Boolean alarmSounded;
402 /* end premove variables */
404 char *ics_prefix = "$";
405 int ics_type = ICS_GENERIC;
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
408 int pauseExamForwardMostMove = 0;
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
415 int whiteFlag = FALSE, blackFlag = FALSE;
416 int userOfferedDraw = FALSE;
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
419 int cmailMoveType[CMAIL_MAX_GAMES];
420 long ics_clock_paused = 0;
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
423 GameMode gameMode = BeginningOfGame;
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
427 int hiddenThinkOutputState = 0; /* [AS] */
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
429 int adjudicateLossPlies = 6;
430 char white_holding[64], black_holding[64];
431 TimeMark lastNodeCountTime;
432 long lastNodeCount=0;
433 int have_sent_ICS_logon = 0;
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
436 long timeControl_2; /* [AS] Allow separate time controls */
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
438 long timeRemaining[2][MAX_MOVES];
440 TimeMark programStartTime;
441 char ics_handle[MSG_SIZ];
442 int have_set_title = 0;
444 /* animateTraining preserves the state of appData.animate
445 * when Training mode is activated. This allows the
446 * response to be animated when appData.animate == TRUE and
447 * appData.animateDragging == TRUE.
449 Boolean animateTraining;
455 Board boards[MAX_MOVES];
456 /* [HGM] Following 7 needed for accurate legality tests: */
457 char epStatus[MAX_MOVES];
458 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
459 char castlingRank[BOARD_SIZE]; // and corresponding ranks
460 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
461 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
462 int initialRulePlies, FENrulePlies;
464 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
468 ChessSquare FIDEArray[2][BOARD_SIZE] = {
469 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
470 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
471 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
472 BlackKing, BlackBishop, BlackKnight, BlackRook }
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
476 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
477 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
478 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
479 BlackKing, BlackKing, BlackKnight, BlackRook }
482 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
483 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
484 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
485 { BlackRook, BlackMan, BlackBishop, BlackQueen,
486 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
490 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
491 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
492 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
493 BlackKing, BlackBishop, BlackKnight, BlackRook }
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
497 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
498 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
499 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
500 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {
506 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
507 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
508 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
509 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
513 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
514 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
515 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
516 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
520 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
521 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
522 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
523 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 ChessSquare GreatArray[2][BOARD_SIZE] = {
527 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
528 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
529 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
530 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 ChessSquare JanusArray[2][BOARD_SIZE] = {
534 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
535 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
536 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
537 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
541 ChessSquare GothicArray[2][BOARD_SIZE] = {
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
543 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
545 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 #define GothicArray CapablancaArray
552 ChessSquare FalconArray[2][BOARD_SIZE] = {
553 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
554 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
555 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
556 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 #define FalconArray CapablancaArray
562 #else // !(BOARD_SIZE>=10)
563 #define XiangqiPosition FIDEArray
564 #define CapablancaArray FIDEArray
565 #define GothicArray FIDEArray
566 #define GreatArray FIDEArray
567 #endif // !(BOARD_SIZE>=10)
570 ChessSquare CourierArray[2][BOARD_SIZE] = {
571 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
572 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
573 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
574 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
576 #else // !(BOARD_SIZE>=12)
577 #define CourierArray CapablancaArray
578 #endif // !(BOARD_SIZE>=12)
581 Board initialPosition;
584 /* Convert str to a rating. Checks for special cases of "----",
586 "++++", etc. Also strips ()'s */
588 string_to_rating(str)
591 while(*str && !isdigit(*str)) ++str;
593 return 0; /* One of the special "no rating" cases */
601 /* Init programStats */
602 programStats.movelist[0] = 0;
603 programStats.depth = 0;
604 programStats.nr_moves = 0;
605 programStats.moves_left = 0;
606 programStats.nodes = 0;
607 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
608 programStats.score = 0;
609 programStats.got_only_move = 0;
610 programStats.got_fail = 0;
611 programStats.line_is_book = 0;
617 int matched, min, sec;
619 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
621 GetTimeMark(&programStartTime);
622 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
625 programStats.ok_to_send = 1;
626 programStats.seen_stat = 0;
629 * Initialize game list
635 * Internet chess server status
637 if (appData.icsActive) {
638 appData.matchMode = FALSE;
639 appData.matchGames = 0;
641 appData.noChessProgram = !appData.zippyPlay;
643 appData.zippyPlay = FALSE;
644 appData.zippyTalk = FALSE;
645 appData.noChessProgram = TRUE;
647 if (*appData.icsHelper != NULLCHAR) {
648 appData.useTelnet = TRUE;
649 appData.telnetProgram = appData.icsHelper;
652 appData.zippyTalk = appData.zippyPlay = FALSE;
655 /* [AS] Initialize pv info list [HGM] and game state */
659 for( i=0; i<MAX_MOVES; i++ ) {
660 pvInfoList[i].depth = -1;
662 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
667 * Parse timeControl resource
669 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
670 appData.movesPerSession)) {
672 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
673 DisplayFatalError(buf, 0, 2);
677 * Parse searchTime resource
679 if (*appData.searchTime != NULLCHAR) {
680 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
682 searchTime = min * 60;
683 } else if (matched == 2) {
684 searchTime = min * 60 + sec;
687 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
688 DisplayFatalError(buf, 0, 2);
692 /* [AS] Adjudication threshold */
693 adjudicateLossThreshold = appData.adjudicateLossThreshold;
695 first.which = "first";
696 second.which = "second";
697 first.maybeThinking = second.maybeThinking = FALSE;
698 first.pr = second.pr = NoProc;
699 first.isr = second.isr = NULL;
700 first.sendTime = second.sendTime = 2;
701 first.sendDrawOffers = 1;
702 if (appData.firstPlaysBlack) {
703 first.twoMachinesColor = "black\n";
704 second.twoMachinesColor = "white\n";
706 first.twoMachinesColor = "white\n";
707 second.twoMachinesColor = "black\n";
709 first.program = appData.firstChessProgram;
710 second.program = appData.secondChessProgram;
711 first.host = appData.firstHost;
712 second.host = appData.secondHost;
713 first.dir = appData.firstDirectory;
714 second.dir = appData.secondDirectory;
715 first.other = &second;
716 second.other = &first;
717 first.initString = appData.initString;
718 second.initString = appData.secondInitString;
719 first.computerString = appData.firstComputerString;
720 second.computerString = appData.secondComputerString;
721 first.useSigint = second.useSigint = TRUE;
722 first.useSigterm = second.useSigterm = TRUE;
723 first.reuse = appData.reuseFirst;
724 second.reuse = appData.reuseSecond;
725 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
726 second.nps = appData.secondNPS;
727 first.useSetboard = second.useSetboard = FALSE;
728 first.useSAN = second.useSAN = FALSE;
729 first.usePing = second.usePing = FALSE;
730 first.lastPing = second.lastPing = 0;
731 first.lastPong = second.lastPong = 0;
732 first.usePlayother = second.usePlayother = FALSE;
733 first.useColors = second.useColors = TRUE;
734 first.useUsermove = second.useUsermove = FALSE;
735 first.sendICS = second.sendICS = FALSE;
736 first.sendName = second.sendName = appData.icsActive;
737 first.sdKludge = second.sdKludge = FALSE;
738 first.stKludge = second.stKludge = FALSE;
739 TidyProgramName(first.program, first.host, first.tidy);
740 TidyProgramName(second.program, second.host, second.tidy);
741 first.matchWins = second.matchWins = 0;
742 strcpy(first.variants, appData.variant);
743 strcpy(second.variants, appData.variant);
744 first.analysisSupport = second.analysisSupport = 2; /* detect */
745 first.analyzing = second.analyzing = FALSE;
746 first.initDone = second.initDone = FALSE;
748 /* New features added by Tord: */
749 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
750 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
751 /* End of new features added by Tord. */
752 first.fenOverride = appData.fenOverride1;
753 second.fenOverride = appData.fenOverride2;
755 /* [HGM] time odds: set factor for each machine */
756 first.timeOdds = appData.firstTimeOdds;
757 second.timeOdds = appData.secondTimeOdds;
759 if(appData.timeOddsMode) {
760 norm = first.timeOdds;
761 if(norm > second.timeOdds) norm = second.timeOdds;
763 first.timeOdds /= norm;
764 second.timeOdds /= norm;
767 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
768 first.accumulateTC = appData.firstAccumulateTC;
769 second.accumulateTC = appData.secondAccumulateTC;
770 first.maxNrOfSessions = second.maxNrOfSessions = 1;
773 first.debug = second.debug = FALSE;
774 first.supportsNPS = second.supportsNPS = UNKNOWN;
777 first.optionSettings = appData.firstOptions;
778 second.optionSettings = appData.secondOptions;
780 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
781 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
782 first.isUCI = appData.firstIsUCI; /* [AS] */
783 second.isUCI = appData.secondIsUCI; /* [AS] */
784 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
785 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
787 if (appData.firstProtocolVersion > PROTOVER ||
788 appData.firstProtocolVersion < 1) {
790 sprintf(buf, _("protocol version %d not supported"),
791 appData.firstProtocolVersion);
792 DisplayFatalError(buf, 0, 2);
794 first.protocolVersion = appData.firstProtocolVersion;
797 if (appData.secondProtocolVersion > PROTOVER ||
798 appData.secondProtocolVersion < 1) {
800 sprintf(buf, _("protocol version %d not supported"),
801 appData.secondProtocolVersion);
802 DisplayFatalError(buf, 0, 2);
804 second.protocolVersion = appData.secondProtocolVersion;
807 if (appData.icsActive) {
808 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
809 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
810 appData.clockMode = FALSE;
811 first.sendTime = second.sendTime = 0;
815 /* Override some settings from environment variables, for backward
816 compatibility. Unfortunately it's not feasible to have the env
817 vars just set defaults, at least in xboard. Ugh.
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
824 if (appData.noChessProgram) {
825 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
826 + strlen(PATCHLEVEL));
827 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
832 while (*q != ' ' && *q != NULLCHAR) q++;
834 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
835 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
836 + strlen(PATCHLEVEL) + (q - p));
837 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
838 strncat(programVersion, p, q - p);
840 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
842 + strlen(PATCHLEVEL) + strlen(first.tidy));
843 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantBerolina: /* might work if TestLegality is off */
903 case VariantCapaRandom: /* should work */
904 case VariantJanus: /* should work */
905 case VariantSuper: /* experimental */
906 case VariantGreat: /* experimental, requires legality testing to be off */
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
912 InitEngineUCI( installDir, &second );
915 int NextIntegerFromString( char ** str, long * value )
920 while( *s == ' ' || *s == '\t' ) {
926 if( *s >= '0' && *s <= '9' ) {
927 while( *s >= '0' && *s <= '9' ) {
928 *value = *value * 10 + (*s - '0');
940 int NextTimeControlFromString( char ** str, long * value )
943 int result = NextIntegerFromString( str, &temp );
946 *value = temp * 60; /* Minutes */
949 result = NextIntegerFromString( str, &temp );
950 *value += temp; /* Seconds */
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
959 int result = -1; long temp, temp2;
961 if(**str != '+') return -1; // old params remain in force!
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
966 /* time only: incremental or sudden-death time control */
967 if(**str == '+') { /* increment follows; read it */
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
972 *moves = 0; *tc = temp * 1000;
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
976 (*str)++; /* classical time control */
977 result = NextTimeControlFromString( str, &temp2);
986 int GetTimeQuota(int movenr)
987 { /* [HGM] get time to add from the multi-session time-control string */
988 int moves=1; /* kludge to force reading of first session */
989 long time, increment;
990 char *s = fullTimeControlString;
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996 if(movenr == -1) return time; /* last move before new session */
997 if(!moves) return increment; /* current session is incremental */
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
999 } while(movenr >= -1); /* try again for next session */
1001 return 0; // no new time quota on this move
1005 ParseTimeControl(tc, ti, mps)
1011 int matched, min, sec;
1013 matched = sscanf(tc, "%d:%d", &min, &sec);
1015 timeControl = min * 60 * 1000;
1016 } else if (matched == 2) {
1017 timeControl = (min * 60 + sec) * 1000;
1026 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1029 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1030 else sprintf(buf, "+%s+%d", tc, ti);
1033 sprintf(buf, "+%d/%s", mps, tc);
1034 else sprintf(buf, "+%s", tc);
1036 fullTimeControlString = StrSave(buf);
1038 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1043 /* Parse second time control */
1046 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1054 timeControl_2 = tc2 * 1000;
1064 timeControl = tc1 * 1000;
1068 timeIncrement = ti * 1000; /* convert to ms */
1069 movesPerSession = 0;
1072 movesPerSession = mps;
1080 if (appData.debugMode) {
1081 fprintf(debugFP, "%s\n", programVersion);
1084 if (appData.matchGames > 0) {
1085 appData.matchMode = TRUE;
1086 } else if (appData.matchMode) {
1087 appData.matchGames = 1;
1089 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1090 appData.matchGames = appData.sameColorGames;
1091 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1092 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1093 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1096 if (appData.noChessProgram || first.protocolVersion == 1) {
1099 /* kludge: allow timeout for initial "feature" commands */
1101 DisplayMessage("", _("Starting chess program"));
1102 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1107 InitBackEnd3 P((void))
1109 GameMode initialMode;
1113 InitChessProgram(&first, startedFromSetupPosition);
1116 if (appData.icsActive) {
1118 /* [DM] Make a console window if needed [HGM] merged ifs */
1123 if (*appData.icsCommPort != NULLCHAR) {
1124 sprintf(buf, _("Could not open comm port %s"),
1125 appData.icsCommPort);
1127 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1128 appData.icsHost, appData.icsPort);
1130 DisplayFatalError(buf, err, 1);
1135 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1137 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1138 } else if (appData.noChessProgram) {
1144 if (*appData.cmailGameName != NULLCHAR) {
1146 OpenLoopback(&cmailPR);
1148 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152 DisplayMessage("", "");
1153 if (StrCaseCmp(appData.initialMode, "") == 0) {
1154 initialMode = BeginningOfGame;
1155 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1156 initialMode = TwoMachinesPlay;
1157 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1158 initialMode = AnalyzeFile;
1159 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1160 initialMode = AnalyzeMode;
1161 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1162 initialMode = MachinePlaysWhite;
1163 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1164 initialMode = MachinePlaysBlack;
1165 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1166 initialMode = EditGame;
1167 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1168 initialMode = EditPosition;
1169 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1170 initialMode = Training;
1172 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1173 DisplayFatalError(buf, 0, 2);
1177 if (appData.matchMode) {
1178 /* Set up machine vs. machine match */
1179 if (appData.noChessProgram) {
1180 DisplayFatalError(_("Can't have a match with no chess programs"),
1186 if (*appData.loadGameFile != NULLCHAR) {
1187 int index = appData.loadGameIndex; // [HGM] autoinc
1188 if(index<0) lastIndex = index = 1;
1189 if (!LoadGameFromFile(appData.loadGameFile,
1191 appData.loadGameFile, FALSE)) {
1192 DisplayFatalError(_("Bad game file"), 0, 1);
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
1196 int index = appData.loadPositionIndex; // [HGM] autoinc
1197 if(index<0) lastIndex = index = 1;
1198 if (!LoadPositionFromFile(appData.loadPositionFile,
1200 appData.loadPositionFile)) {
1201 DisplayFatalError(_("Bad position file"), 0, 1);
1206 } else if (*appData.cmailGameName != NULLCHAR) {
1207 /* Set up cmail mode */
1208 ReloadCmailMsgEvent(TRUE);
1210 /* Set up other modes */
1211 if (initialMode == AnalyzeFile) {
1212 if (*appData.loadGameFile == NULLCHAR) {
1213 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217 if (*appData.loadGameFile != NULLCHAR) {
1218 (void) LoadGameFromFile(appData.loadGameFile,
1219 appData.loadGameIndex,
1220 appData.loadGameFile, TRUE);
1221 } else if (*appData.loadPositionFile != NULLCHAR) {
1222 (void) LoadPositionFromFile(appData.loadPositionFile,
1223 appData.loadPositionIndex,
1224 appData.loadPositionFile);
1225 /* [HGM] try to make self-starting even after FEN load */
1226 /* to allow automatic setup of fairy variants with wtm */
1227 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1228 gameMode = BeginningOfGame;
1229 setboardSpoiledMachineBlack = 1;
1231 /* [HGM] loadPos: make that every new game uses the setup */
1232 /* from file as long as we do not switch variant */
1233 if(!blackPlaysFirst) { int i;
1234 startedFromPositionFile = TRUE;
1235 CopyBoard(filePosition, boards[0]);
1236 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1239 if (initialMode == AnalyzeMode) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1244 if (appData.icsActive) {
1245 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1249 } else if (initialMode == AnalyzeFile) {
1250 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1251 ShowThinkingEvent();
1253 AnalysisPeriodicEvent(1);
1254 } else if (initialMode == MachinePlaysWhite) {
1255 if (appData.noChessProgram) {
1256 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1260 if (appData.icsActive) {
1261 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1265 MachineWhiteEvent();
1266 } else if (initialMode == MachinePlaysBlack) {
1267 if (appData.noChessProgram) {
1268 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1272 if (appData.icsActive) {
1273 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1277 MachineBlackEvent();
1278 } else if (initialMode == TwoMachinesPlay) {
1279 if (appData.noChessProgram) {
1280 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1284 if (appData.icsActive) {
1285 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1290 } else if (initialMode == EditGame) {
1292 } else if (initialMode == EditPosition) {
1293 EditPositionEvent();
1294 } else if (initialMode == Training) {
1295 if (*appData.loadGameFile == NULLCHAR) {
1296 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1305 * Establish will establish a contact to a remote host.port.
1306 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1307 * used to talk to the host.
1308 * Returns 0 if okay, error code if not.
1315 if (*appData.icsCommPort != NULLCHAR) {
1316 /* Talk to the host through a serial comm port */
1317 return OpenCommPort(appData.icsCommPort, &icsPR);
1319 } else if (*appData.gateway != NULLCHAR) {
1320 if (*appData.remoteShell == NULLCHAR) {
1321 /* Use the rcmd protocol to run telnet program on a gateway host */
1322 snprintf(buf, sizeof(buf), "%s %s %s",
1323 appData.telnetProgram, appData.icsHost, appData.icsPort);
1324 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1327 /* Use the rsh program to run telnet program on a gateway host */
1328 if (*appData.remoteUser == NULLCHAR) {
1329 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1330 appData.gateway, appData.telnetProgram,
1331 appData.icsHost, appData.icsPort);
1333 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1334 appData.remoteShell, appData.gateway,
1335 appData.remoteUser, appData.telnetProgram,
1336 appData.icsHost, appData.icsPort);
1338 return StartChildProcess(buf, "", &icsPR);
1341 } else if (appData.useTelnet) {
1342 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1345 /* TCP socket interface differs somewhat between
1346 Unix and NT; handle details in the front end.
1348 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1353 show_bytes(fp, buf, count)
1359 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1360 fprintf(fp, "\\%03o", *buf & 0xff);
1369 /* Returns an errno value */
1371 OutputMaybeTelnet(pr, message, count, outError)
1377 char buf[8192], *p, *q, *buflim;
1378 int left, newcount, outcount;
1380 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1381 *appData.gateway != NULLCHAR) {
1382 if (appData.debugMode) {
1383 fprintf(debugFP, ">ICS: ");
1384 show_bytes(debugFP, message, count);
1385 fprintf(debugFP, "\n");
1387 return OutputToProcess(pr, message, count, outError);
1390 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1397 if (appData.debugMode) {
1398 fprintf(debugFP, ">ICS: ");
1399 show_bytes(debugFP, buf, newcount);
1400 fprintf(debugFP, "\n");
1402 outcount = OutputToProcess(pr, buf, newcount, outError);
1403 if (outcount < newcount) return -1; /* to be sure */
1410 } else if (((unsigned char) *p) == TN_IAC) {
1411 *q++ = (char) TN_IAC;
1418 if (appData.debugMode) {
1419 fprintf(debugFP, ">ICS: ");
1420 show_bytes(debugFP, buf, newcount);
1421 fprintf(debugFP, "\n");
1423 outcount = OutputToProcess(pr, buf, newcount, outError);
1424 if (outcount < newcount) return -1; /* to be sure */
1429 read_from_player(isr, closure, message, count, error)
1436 int outError, outCount;
1437 static int gotEof = 0;
1439 /* Pass data read from player on to ICS */
1442 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1443 if (outCount < count) {
1444 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1446 } else if (count < 0) {
1447 RemoveInputSource(isr);
1448 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1449 } else if (gotEof++ > 0) {
1450 RemoveInputSource(isr);
1451 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1459 int count, outCount, outError;
1461 if (icsPR == NULL) return;
1464 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1465 if (outCount < count) {
1466 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1470 /* This is used for sending logon scripts to the ICS. Sending
1471 without a delay causes problems when using timestamp on ICC
1472 (at least on my machine). */
1474 SendToICSDelayed(s,msdelay)
1478 int count, outCount, outError;
1480 if (icsPR == NULL) return;
1483 if (appData.debugMode) {
1484 fprintf(debugFP, ">ICS: ");
1485 show_bytes(debugFP, s, count);
1486 fprintf(debugFP, "\n");
1488 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1490 if (outCount < count) {
1491 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1496 /* Remove all highlighting escape sequences in s
1497 Also deletes any suffix starting with '('
1500 StripHighlightAndTitle(s)
1503 static char retbuf[MSG_SIZ];
1506 while (*s != NULLCHAR) {
1507 while (*s == '\033') {
1508 while (*s != NULLCHAR && !isalpha(*s)) s++;
1509 if (*s != NULLCHAR) s++;
1511 while (*s != NULLCHAR && *s != '\033') {
1512 if (*s == '(' || *s == '[') {
1523 /* Remove all highlighting escape sequences in s */
1528 static char retbuf[MSG_SIZ];
1531 while (*s != NULLCHAR) {
1532 while (*s == '\033') {
1533 while (*s != NULLCHAR && !isalpha(*s)) s++;
1534 if (*s != NULLCHAR) s++;
1536 while (*s != NULLCHAR && *s != '\033') {
1544 char *variantNames[] = VARIANT_NAMES;
1549 return variantNames[v];
1553 /* Identify a variant from the strings the chess servers use or the
1554 PGN Variant tag names we use. */
1561 VariantClass v = VariantNormal;
1562 int i, found = FALSE;
1567 /* [HGM] skip over optional board-size prefixes */
1568 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1569 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1570 while( *e++ != '_');
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;
1922 if( (int)lowestPiece >= BlackPawn ) {
1925 holdingsStartRow = BOARD_HEIGHT-1;
1928 holdingsColumn = BOARD_WIDTH-1;
1929 countsColumn = BOARD_WIDTH-2;
1930 holdingsStartRow = 0;
1934 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1935 board[i][holdingsColumn] = EmptySquare;
1936 board[i][countsColumn] = (ChessSquare) 0;
1938 while( (p=*holdings++) != NULLCHAR ) {
1939 piece = CharToPiece( ToUpper(p) );
1940 if(piece == EmptySquare) continue;
1941 /*j = (int) piece - (int) WhitePawn;*/
1942 j = PieceToNumber(piece);
1943 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1944 if(j < 0) continue; /* should not happen */
1945 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1946 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1947 board[holdingsStartRow+j*direction][countsColumn]++;
1954 VariantSwitch(Board board, VariantClass newVariant)
1956 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1958 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
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 */
1983 switch(newVariant) {
1985 newWidth = 9; newHeight = 9;
1986 gameInfo.holdingsSize = 7;
1987 case VariantBughouse:
1988 case VariantCrazyhouse:
1989 newHoldingsWidth = 2; break;
1991 newHoldingsWidth = gameInfo.holdingsSize = 0;
1994 if(newWidth != gameInfo.boardWidth ||
1995 newHeight != gameInfo.boardHeight ||
1996 newHoldingsWidth != gameInfo.holdingsWidth ) {
1998 /* shift position to new playing area, if needed */
1999 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2000 for(i=0; i<BOARD_HEIGHT; i++)
2001 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2002 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2004 for(i=0; i<newHeight; i++) {
2005 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2006 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2008 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2009 for(i=0; i<BOARD_HEIGHT; i++)
2010 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015 gameInfo.boardWidth = newWidth;
2016 gameInfo.boardHeight = newHeight;
2017 gameInfo.holdingsWidth = newHoldingsWidth;
2018 gameInfo.variant = newVariant;
2019 InitDrawingSizes(-2, 0);
2021 /* [HGM] The following should definitely be solved in a better way */
2023 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2024 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2025 saveEP = epStatus[0];
2027 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2029 epStatus[0] = saveEP;
2030 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2031 CopyBoard(tempBoard, board); /* restore position received from ICS */
2033 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2035 forwardMostMove = oldForwardMostMove;
2036 backwardMostMove = oldBackwardMostMove;
2037 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2040 static int loggedOn = FALSE;
2042 /*-- Game start info cache: --*/
2044 char gs_kind[MSG_SIZ];
2045 static char player1Name[128] = "";
2046 static char player2Name[128] = "";
2047 static int player1Rating = -1;
2048 static int player2Rating = -1;
2049 /*----------------------------*/
2051 ColorClass curColor = ColorNormal;
2052 int suppressKibitz = 0;
2055 read_from_ics(isr, closure, data, count, error)
2062 #define BUF_SIZE 8192
2063 #define STARTED_NONE 0
2064 #define STARTED_MOVES 1
2065 #define STARTED_BOARD 2
2066 #define STARTED_OBSERVE 3
2067 #define STARTED_HOLDINGS 4
2068 #define STARTED_CHATTER 5
2069 #define STARTED_COMMENT 6
2070 #define STARTED_MOVES_NOHIDE 7
2072 static int started = STARTED_NONE;
2073 static char parse[20000];
2074 static int parse_pos = 0;
2075 static char buf[BUF_SIZE + 1];
2076 static int firstTime = TRUE, intfSet = FALSE;
2077 static ColorClass prevColor = ColorNormal;
2078 static int savingComment = FALSE;
2084 int backup; /* [DM] For zippy color lines */
2087 if (appData.debugMode) {
2089 fprintf(debugFP, "<ICS: ");
2090 show_bytes(debugFP, data, count);
2091 fprintf(debugFP, "\n");
2095 if (appData.debugMode) { int f = forwardMostMove;
2096 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2100 /* If last read ended with a partial line that we couldn't parse,
2101 prepend it to the new read and try again. */
2102 if (leftover_len > 0) {
2103 for (i=0; i<leftover_len; i++)
2104 buf[i] = buf[leftover_start + i];
2107 /* Copy in new characters, removing nulls and \r's */
2108 buf_len = leftover_len;
2109 for (i = 0; i < count; i++) {
2110 if (data[i] != NULLCHAR && data[i] != '\r')
2111 buf[buf_len++] = data[i];
2112 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2113 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
2114 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2117 buf[buf_len] = NULLCHAR;
2118 next_out = leftover_len;
2122 while (i < buf_len) {
2123 /* Deal with part of the TELNET option negotiation
2124 protocol. We refuse to do anything beyond the
2125 defaults, except that we allow the WILL ECHO option,
2126 which ICS uses to turn off password echoing when we are
2127 directly connected to it. We reject this option
2128 if localLineEditing mode is on (always on in xboard)
2129 and we are talking to port 23, which might be a real
2130 telnet server that will try to keep WILL ECHO on permanently.
2132 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2133 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2134 unsigned char option;
2136 switch ((unsigned char) buf[++i]) {
2138 if (appData.debugMode)
2139 fprintf(debugFP, "\n<WILL ");
2140 switch (option = (unsigned char) buf[++i]) {
2142 if (appData.debugMode)
2143 fprintf(debugFP, "ECHO ");
2144 /* Reply only if this is a change, according
2145 to the protocol rules. */
2146 if (remoteEchoOption) break;
2147 if (appData.localLineEditing &&
2148 atoi(appData.icsPort) == TN_PORT) {
2149 TelnetRequest(TN_DONT, TN_ECHO);
2152 TelnetRequest(TN_DO, TN_ECHO);
2153 remoteEchoOption = TRUE;
2157 if (appData.debugMode)
2158 fprintf(debugFP, "%d ", option);
2159 /* Whatever this is, we don't want it. */
2160 TelnetRequest(TN_DONT, option);
2165 if (appData.debugMode)
2166 fprintf(debugFP, "\n<WONT ");
2167 switch (option = (unsigned char) buf[++i]) {
2169 if (appData.debugMode)
2170 fprintf(debugFP, "ECHO ");
2171 /* Reply only if this is a change, according
2172 to the protocol rules. */
2173 if (!remoteEchoOption) break;
2175 TelnetRequest(TN_DONT, TN_ECHO);
2176 remoteEchoOption = FALSE;
2179 if (appData.debugMode)
2180 fprintf(debugFP, "%d ", (unsigned char) option);
2181 /* Whatever this is, it must already be turned
2182 off, because we never agree to turn on
2183 anything non-default, so according to the
2184 protocol rules, we don't reply. */
2189 if (appData.debugMode)
2190 fprintf(debugFP, "\n<DO ");
2191 switch (option = (unsigned char) buf[++i]) {
2193 /* Whatever this is, we refuse to do it. */
2194 if (appData.debugMode)
2195 fprintf(debugFP, "%d ", option);
2196 TelnetRequest(TN_WONT, option);
2201 if (appData.debugMode)
2202 fprintf(debugFP, "\n<DONT ");
2203 switch (option = (unsigned char) buf[++i]) {
2205 if (appData.debugMode)
2206 fprintf(debugFP, "%d ", option);
2207 /* Whatever this is, we are already not doing
2208 it, because we never agree to do anything
2209 non-default, so according to the protocol
2210 rules, we don't reply. */
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<IAC ");
2217 /* Doubled IAC; pass it through */
2221 if (appData.debugMode)
2222 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2223 /* Drop all other telnet commands on the floor */
2226 if (oldi > next_out)
2227 SendToPlayer(&buf[next_out], oldi - next_out);
2233 /* OK, this at least will *usually* work */
2234 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2238 if (loggedOn && !intfSet) {
2239 if (ics_type == ICS_ICC) {
2241 "/set-quietly interface %s\n/set-quietly style 12\n",
2244 } else if (ics_type == ICS_CHESSNET) {
2245 sprintf(str, "/style 12\n");
2247 strcpy(str, "alias $ @\n$set interface ");
2248 strcat(str, programVersion);
2249 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2251 strcat(str, "$iset nohighlight 1\n");
2253 strcat(str, "$iset lock 1\n$style 12\n");
2259 if (started == STARTED_COMMENT) {
2260 /* Accumulate characters in comment */
2261 parse[parse_pos++] = buf[i];
2262 if (buf[i] == '\n') {
2263 parse[parse_pos] = NULLCHAR;
2264 if(!suppressKibitz) // [HGM] kibitz
2265 AppendComment(forwardMostMove, StripHighlight(parse));
2266 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2267 int nrDigit = 0, nrAlph = 0, i;
2268 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2269 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2270 parse[parse_pos] = NULLCHAR;
2271 // try to be smart: if it does not look like search info, it should go to
2272 // ICS interaction window after all, not to engine-output window.
2273 for(i=0; i<parse_pos; i++) { // count letters and digits
2274 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2275 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2276 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2278 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2279 int depth=0; float score;
2280 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2281 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2282 pvInfoList[forwardMostMove-1].depth = depth;
2283 pvInfoList[forwardMostMove-1].score = 100*score;
2285 OutputKibitz(suppressKibitz, parse);
2288 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2289 SendToPlayer(tmp, strlen(tmp));
2292 started = STARTED_NONE;
2294 /* Don't match patterns against characters in chatter */
2299 if (started == STARTED_CHATTER) {
2300 if (buf[i] != '\n') {
2301 /* Don't match patterns against characters in chatter */
2305 started = STARTED_NONE;
2308 /* Kludge to deal with rcmd protocol */
2309 if (firstTime && looking_at(buf, &i, "\001*")) {
2310 DisplayFatalError(&buf[1], 0, 1);
2316 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2319 if (appData.debugMode)
2320 fprintf(debugFP, "ics_type %d\n", ics_type);
2323 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2324 ics_type = ICS_FICS;
2326 if (appData.debugMode)
2327 fprintf(debugFP, "ics_type %d\n", ics_type);
2330 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2331 ics_type = ICS_CHESSNET;
2333 if (appData.debugMode)
2334 fprintf(debugFP, "ics_type %d\n", ics_type);
2339 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2340 looking_at(buf, &i, "Logging you in as \"*\"") ||
2341 looking_at(buf, &i, "will be \"*\""))) {
2342 strcpy(ics_handle, star_match[0]);
2346 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2348 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2349 DisplayIcsInteractionTitle(buf);
2350 have_set_title = TRUE;
2353 /* skip finger notes */
2354 if (started == STARTED_NONE &&
2355 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2356 (buf[i] == '1' && buf[i+1] == '0')) &&
2357 buf[i+2] == ':' && buf[i+3] == ' ') {
2358 started = STARTED_CHATTER;
2363 /* skip formula vars */
2364 if (started == STARTED_NONE &&
2365 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2366 started = STARTED_CHATTER;
2372 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2373 if (appData.autoKibitz && started == STARTED_NONE &&
2374 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2375 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2376 if(looking_at(buf, &i, "* kibitzes: ") &&
2377 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2378 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2379 suppressKibitz = TRUE;
2380 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2381 && (gameMode == IcsPlayingWhite)) ||
2382 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2383 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2384 started = STARTED_CHATTER; // own kibitz we simply discard
2386 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2387 parse_pos = 0; parse[0] = NULLCHAR;
2388 savingComment = TRUE;
2389 suppressKibitz = gameMode != IcsObserving ? 2 :
2390 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2394 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2395 started = STARTED_CHATTER;
2396 suppressKibitz = TRUE;
2398 } // [HGM] kibitz: end of patch
2400 if (appData.zippyTalk || appData.zippyPlay) {
2401 /* [DM] Backup address for color zippy lines */
2405 if (loggedOn == TRUE)
2406 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2407 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2409 if (ZippyControl(buf, &i) ||
2410 ZippyConverse(buf, &i) ||
2411 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2413 if (!appData.colorize) continue;
2417 } // [DM] 'else { ' deleted
2418 if (/* Don't color "message" or "messages" output */
2419 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2420 looking_at(buf, &i, "*. * at *:*: ") ||
2421 looking_at(buf, &i, "--* (*:*): ") ||
2422 /* Regular tells and says */
2423 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2424 looking_at(buf, &i, "* (your partner) tells you: ") ||
2425 looking_at(buf, &i, "* says: ") ||
2426 /* Message notifications (same color as tells) */
2427 looking_at(buf, &i, "* has left a message ") ||
2428 looking_at(buf, &i, "* just sent you a message:\n") ||
2429 /* Whispers and kibitzes */
2430 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2431 looking_at(buf, &i, "* kibitzes: ") ||
2433 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2435 if (tkind == 1 && strchr(star_match[0], ':')) {
2436 /* Avoid "tells you:" spoofs in channels */
2439 if (star_match[0][0] == NULLCHAR ||
2440 strchr(star_match[0], ' ') ||
2441 (tkind == 3 && strchr(star_match[1], ' '))) {
2442 /* Reject bogus matches */
2445 if (appData.colorize) {
2446 if (oldi > next_out) {
2447 SendToPlayer(&buf[next_out], oldi - next_out);
2452 Colorize(ColorTell, FALSE);
2453 curColor = ColorTell;
2456 Colorize(ColorKibitz, FALSE);
2457 curColor = ColorKibitz;
2460 p = strrchr(star_match[1], '(');
2467 Colorize(ColorChannel1, FALSE);
2468 curColor = ColorChannel1;
2470 Colorize(ColorChannel, FALSE);
2471 curColor = ColorChannel;
2475 curColor = ColorNormal;
2479 if (started == STARTED_NONE && appData.autoComment &&
2480 (gameMode == IcsObserving ||
2481 gameMode == IcsPlayingWhite ||
2482 gameMode == IcsPlayingBlack)) {
2483 parse_pos = i - oldi;
2484 memcpy(parse, &buf[oldi], parse_pos);
2485 parse[parse_pos] = NULLCHAR;
2486 started = STARTED_COMMENT;
2487 savingComment = TRUE;
2489 started = STARTED_CHATTER;
2490 savingComment = FALSE;
2497 if (looking_at(buf, &i, "* s-shouts: ") ||
2498 looking_at(buf, &i, "* c-shouts: ")) {
2499 if (appData.colorize) {
2500 if (oldi > next_out) {
2501 SendToPlayer(&buf[next_out], oldi - next_out);
2504 Colorize(ColorSShout, FALSE);
2505 curColor = ColorSShout;
2508 started = STARTED_CHATTER;
2512 if (looking_at(buf, &i, "--->")) {
2517 if (looking_at(buf, &i, "* shouts: ") ||
2518 looking_at(buf, &i, "--> ")) {
2519 if (appData.colorize) {
2520 if (oldi > next_out) {
2521 SendToPlayer(&buf[next_out], oldi - next_out);
2524 Colorize(ColorShout, FALSE);
2525 curColor = ColorShout;
2528 started = STARTED_CHATTER;
2532 if (looking_at( buf, &i, "Challenge:")) {
2533 if (appData.colorize) {
2534 if (oldi > next_out) {
2535 SendToPlayer(&buf[next_out], oldi - next_out);
2538 Colorize(ColorChallenge, FALSE);
2539 curColor = ColorChallenge;
2545 if (looking_at(buf, &i, "* offers you") ||
2546 looking_at(buf, &i, "* offers to be") ||
2547 looking_at(buf, &i, "* would like to") ||
2548 looking_at(buf, &i, "* requests to") ||
2549 looking_at(buf, &i, "Your opponent offers") ||
2550 looking_at(buf, &i, "Your opponent requests")) {
2552 if (appData.colorize) {
2553 if (oldi > next_out) {
2554 SendToPlayer(&buf[next_out], oldi - next_out);
2557 Colorize(ColorRequest, FALSE);
2558 curColor = ColorRequest;
2563 if (looking_at(buf, &i, "* (*) seeking")) {
2564 if (appData.colorize) {
2565 if (oldi > next_out) {
2566 SendToPlayer(&buf[next_out], oldi - next_out);
2569 Colorize(ColorSeek, FALSE);
2570 curColor = ColorSeek;
2575 if (looking_at(buf, &i, "\\ ")) {
2576 if (prevColor != ColorNormal) {
2577 if (oldi > next_out) {
2578 SendToPlayer(&buf[next_out], oldi - next_out);
2581 Colorize(prevColor, TRUE);
2582 curColor = prevColor;
2584 if (savingComment) {
2585 parse_pos = i - oldi;
2586 memcpy(parse, &buf[oldi], parse_pos);
2587 parse[parse_pos] = NULLCHAR;
2588 started = STARTED_COMMENT;
2590 started = STARTED_CHATTER;
2595 if (looking_at(buf, &i, "Black Strength :") ||
2596 looking_at(buf, &i, "<<< style 10 board >>>") ||
2597 looking_at(buf, &i, "<10>") ||
2598 looking_at(buf, &i, "#@#")) {
2599 /* Wrong board style */
2601 SendToICS(ics_prefix);
2602 SendToICS("set style 12\n");
2603 SendToICS(ics_prefix);
2604 SendToICS("refresh\n");
2608 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2610 have_sent_ICS_logon = 1;
2614 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2615 (looking_at(buf, &i, "\n<12> ") ||
2616 looking_at(buf, &i, "<12> "))) {
2618 if (oldi > next_out) {
2619 SendToPlayer(&buf[next_out], oldi - next_out);
2622 started = STARTED_BOARD;
2627 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2628 looking_at(buf, &i, "<b1> ")) {
2629 if (oldi > next_out) {
2630 SendToPlayer(&buf[next_out], oldi - next_out);
2633 started = STARTED_HOLDINGS;
2638 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2640 /* Header for a move list -- first line */
2642 switch (ics_getting_history) {
2646 case BeginningOfGame:
2647 /* User typed "moves" or "oldmoves" while we
2648 were idle. Pretend we asked for these
2649 moves and soak them up so user can step
2650 through them and/or save them.
2653 gameMode = IcsObserving;
2656 ics_getting_history = H_GOT_UNREQ_HEADER;
2658 case EditGame: /*?*/
2659 case EditPosition: /*?*/
2660 /* Should above feature work in these modes too? */
2661 /* For now it doesn't */
2662 ics_getting_history = H_GOT_UNWANTED_HEADER;
2665 ics_getting_history = H_GOT_UNWANTED_HEADER;
2670 /* Is this the right one? */
2671 if (gameInfo.white && gameInfo.black &&
2672 strcmp(gameInfo.white, star_match[0]) == 0 &&
2673 strcmp(gameInfo.black, star_match[2]) == 0) {
2675 ics_getting_history = H_GOT_REQ_HEADER;
2678 case H_GOT_REQ_HEADER:
2679 case H_GOT_UNREQ_HEADER:
2680 case H_GOT_UNWANTED_HEADER:
2681 case H_GETTING_MOVES:
2682 /* Should not happen */
2683 DisplayError(_("Error gathering move list: two headers"), 0);
2684 ics_getting_history = H_FALSE;
2688 /* Save player ratings into gameInfo if needed */
2689 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2690 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2691 (gameInfo.whiteRating == -1 ||
2692 gameInfo.blackRating == -1)) {
2694 gameInfo.whiteRating = string_to_rating(star_match[1]);
2695 gameInfo.blackRating = string_to_rating(star_match[3]);
2696 if (appData.debugMode)
2697 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2698 gameInfo.whiteRating, gameInfo.blackRating);
2703 if (looking_at(buf, &i,
2704 "* * match, initial time: * minute*, increment: * second")) {
2705 /* Header for a move list -- second line */
2706 /* Initial board will follow if this is a wild game */
2707 if (gameInfo.event != NULL) free(gameInfo.event);
2708 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2709 gameInfo.event = StrSave(str);
2710 /* [HGM] we switched variant. Translate boards if needed. */
2711 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2715 if (looking_at(buf, &i, "Move ")) {
2716 /* Beginning of a move list */
2717 switch (ics_getting_history) {
2719 /* Normally should not happen */
2720 /* Maybe user hit reset while we were parsing */
2723 /* Happens if we are ignoring a move list that is not
2724 * the one we just requested. Common if the user
2725 * tries to observe two games without turning off
2728 case H_GETTING_MOVES:
2729 /* Should not happen */
2730 DisplayError(_("Error gathering move list: nested"), 0);
2731 ics_getting_history = H_FALSE;
2733 case H_GOT_REQ_HEADER:
2734 ics_getting_history = H_GETTING_MOVES;
2735 started = STARTED_MOVES;
2737 if (oldi > next_out) {
2738 SendToPlayer(&buf[next_out], oldi - next_out);
2741 case H_GOT_UNREQ_HEADER:
2742 ics_getting_history = H_GETTING_MOVES;
2743 started = STARTED_MOVES_NOHIDE;
2746 case H_GOT_UNWANTED_HEADER:
2747 ics_getting_history = H_FALSE;
2753 if (looking_at(buf, &i, "% ") ||
2754 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2755 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2756 savingComment = FALSE;
2759 case STARTED_MOVES_NOHIDE:
2760 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2761 parse[parse_pos + i - oldi] = NULLCHAR;
2762 ParseGameHistory(parse);
2764 if (appData.zippyPlay && first.initDone) {
2765 FeedMovesToProgram(&first, forwardMostMove);
2766 if (gameMode == IcsPlayingWhite) {
2767 if (WhiteOnMove(forwardMostMove)) {
2768 if (first.sendTime) {
2769 if (first.useColors) {
2770 SendToProgram("black\n", &first);
2772 SendTimeRemaining(&first, TRUE);
2775 if (first.useColors) {
2776 SendToProgram("white\ngo\n", &first);
2778 SendToProgram("go\n", &first);
2781 if (first.useColors) {
2782 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2784 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2786 first.maybeThinking = TRUE;
2788 if (first.usePlayother) {
2789 if (first.sendTime) {
2790 SendTimeRemaining(&first, TRUE);
2792 SendToProgram("playother\n", &first);
2798 } else if (gameMode == IcsPlayingBlack) {
2799 if (!WhiteOnMove(forwardMostMove)) {
2800 if (first.sendTime) {
2801 if (first.useColors) {
2802 SendToProgram("white\n", &first);
2804 SendTimeRemaining(&first, FALSE);
2807 if (first.useColors) {
2808 SendToProgram("black\ngo\n", &first);
2810 SendToProgram("go\n", &first);
2813 if (first.useColors) {
2814 SendToProgram("black\n", &first);
2816 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2818 first.maybeThinking = TRUE;
2820 if (first.usePlayother) {
2821 if (first.sendTime) {
2822 SendTimeRemaining(&first, FALSE);
2824 SendToProgram("playother\n", &first);
2833 if (gameMode == IcsObserving && ics_gamenum == -1) {
2834 /* Moves came from oldmoves or moves command
2835 while we weren't doing anything else.
2837 currentMove = forwardMostMove;
2838 ClearHighlights();/*!!could figure this out*/
2839 flipView = appData.flipView;
2840 DrawPosition(FALSE, boards[currentMove]);
2841 DisplayBothClocks();
2842 sprintf(str, "%s vs. %s",
2843 gameInfo.white, gameInfo.black);
2847 /* Moves were history of an active game */
2848 if (gameInfo.resultDetails != NULL) {
2849 free(gameInfo.resultDetails);
2850 gameInfo.resultDetails = NULL;
2853 HistorySet(parseList, backwardMostMove,
2854 forwardMostMove, currentMove-1);
2855 DisplayMove(currentMove - 1);
2856 if (started == STARTED_MOVES) next_out = i;
2857 started = STARTED_NONE;
2858 ics_getting_history = H_FALSE;
2861 case STARTED_OBSERVE:
2862 started = STARTED_NONE;
2863 SendToICS(ics_prefix);
2864 SendToICS("refresh\n");
2870 if(bookHit) { // [HGM] book: simulate book reply
2871 static char bookMove[MSG_SIZ]; // a bit generous?
2873 programStats.nodes = programStats.depth = programStats.time =
2874 programStats.score = programStats.got_only_move = 0;
2875 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2877 strcpy(bookMove, "move ");
2878 strcat(bookMove, bookHit);
2879 HandleMachineMove(bookMove, &first);
2884 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2885 started == STARTED_HOLDINGS ||
2886 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2887 /* Accumulate characters in move list or board */
2888 parse[parse_pos++] = buf[i];
2891 /* Start of game messages. Mostly we detect start of game
2892 when the first board image arrives. On some versions
2893 of the ICS, though, we need to do a "refresh" after starting
2894 to observe in order to get the current board right away. */
2895 if (looking_at(buf, &i, "Adding game * to observation list")) {
2896 started = STARTED_OBSERVE;
2900 /* Handle auto-observe */
2901 if (appData.autoObserve &&
2902 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2903 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2905 /* Choose the player that was highlighted, if any. */
2906 if (star_match[0][0] == '\033' ||
2907 star_match[1][0] != '\033') {
2908 player = star_match[0];
2910 player = star_match[2];
2912 sprintf(str, "%sobserve %s\n",
2913 ics_prefix, StripHighlightAndTitle(player));
2916 /* Save ratings from notify string */
2917 strcpy(player1Name, star_match[0]);
2918 player1Rating = string_to_rating(star_match[1]);
2919 strcpy(player2Name, star_match[2]);
2920 player2Rating = string_to_rating(star_match[3]);
2922 if (appData.debugMode)
2924 "Ratings from 'Game notification:' %s %d, %s %d\n",
2925 player1Name, player1Rating,
2926 player2Name, player2Rating);
2931 /* Deal with automatic examine mode after a game,
2932 and with IcsObserving -> IcsExamining transition */
2933 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2934 looking_at(buf, &i, "has made you an examiner of game *")) {
2936 int gamenum = atoi(star_match[0]);
2937 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2938 gamenum == ics_gamenum) {
2939 /* We were already playing or observing this game;
2940 no need to refetch history */
2941 gameMode = IcsExamining;
2943 pauseExamForwardMostMove = forwardMostMove;
2944 } else if (currentMove < forwardMostMove) {
2945 ForwardInner(forwardMostMove);
2948 /* I don't think this case really can happen */
2949 SendToICS(ics_prefix);
2950 SendToICS("refresh\n");
2955 /* Error messages */
2956 if (ics_user_moved) {
2957 if (looking_at(buf, &i, "Illegal move") ||
2958 looking_at(buf, &i, "Not a legal move") ||
2959 looking_at(buf, &i, "Your king is in check") ||
2960 looking_at(buf, &i, "It isn't your turn") ||
2961 looking_at(buf, &i, "It is not your move")) {
2964 if (forwardMostMove > backwardMostMove) {
2965 currentMove = --forwardMostMove;
2966 DisplayMove(currentMove - 1); /* before DMError */
2967 DisplayMoveError(_("Illegal move (rejected by ICS)"));
2968 DrawPosition(FALSE, boards[currentMove]);
2970 DisplayBothClocks();
2976 if (looking_at(buf, &i, "still have time") ||
2977 looking_at(buf, &i, "not out of time") ||
2978 looking_at(buf, &i, "either player is out of time") ||
2979 looking_at(buf, &i, "has timeseal; checking")) {
2980 /* We must have called his flag a little too soon */
2981 whiteFlag = blackFlag = FALSE;
2985 if (looking_at(buf, &i, "added * seconds to") ||
2986 looking_at(buf, &i, "seconds were added to")) {
2987 /* Update the clocks */
2988 SendToICS(ics_prefix);
2989 SendToICS("refresh\n");
2993 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2994 ics_clock_paused = TRUE;
2999 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3000 ics_clock_paused = FALSE;
3005 /* Grab player ratings from the Creating: message.
3006 Note we have to check for the special case when
3007 the ICS inserts things like [white] or [black]. */
3008 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3009 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3011 0 player 1 name (not necessarily white)
3013 2 empty, white, or black (IGNORED)
3014 3 player 2 name (not necessarily black)
3017 The names/ratings are sorted out when the game
3018 actually starts (below).
3020 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3021 player1Rating = string_to_rating(star_match[1]);
3022 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3023 player2Rating = string_to_rating(star_match[4]);
3025 if (appData.debugMode)
3027 "Ratings from 'Creating:' %s %d, %s %d\n",
3028 player1Name, player1Rating,
3029 player2Name, player2Rating);
3034 /* Improved generic start/end-of-game messages */
3035 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3036 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3037 /* If tkind == 0: */
3038 /* star_match[0] is the game number */
3039 /* [1] is the white player's name */
3040 /* [2] is the black player's name */
3041 /* For end-of-game: */
3042 /* [3] is the reason for the game end */
3043 /* [4] is a PGN end game-token, preceded by " " */
3044 /* For start-of-game: */
3045 /* [3] begins with "Creating" or "Continuing" */
3046 /* [4] is " *" or empty (don't care). */
3047 int gamenum = atoi(star_match[0]);
3048 char *whitename, *blackname, *why, *endtoken;
3049 ChessMove endtype = (ChessMove) 0;
3052 whitename = star_match[1];
3053 blackname = star_match[2];
3054 why = star_match[3];
3055 endtoken = star_match[4];
3057 whitename = star_match[1];
3058 blackname = star_match[3];
3059 why = star_match[5];
3060 endtoken = star_match[6];
3063 /* Game start messages */
3064 if (strncmp(why, "Creating ", 9) == 0 ||
3065 strncmp(why, "Continuing ", 11) == 0) {
3066 gs_gamenum = gamenum;
3067 strcpy(gs_kind, strchr(why, ' ') + 1);
3069 if (appData.zippyPlay) {
3070 ZippyGameStart(whitename, blackname);
3076 /* Game end messages */
3077 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3078 ics_gamenum != gamenum) {
3081 while (endtoken[0] == ' ') endtoken++;
3082 switch (endtoken[0]) {
3085 endtype = GameUnfinished;
3088 endtype = BlackWins;
3091 if (endtoken[1] == '/')
3092 endtype = GameIsDrawn;
3094 endtype = WhiteWins;
3097 GameEnds(endtype, why, GE_ICS);
3099 if (appData.zippyPlay && first.initDone) {
3100 ZippyGameEnd(endtype, why);
3101 if (first.pr == NULL) {
3102 /* Start the next process early so that we'll
3103 be ready for the next challenge */
3104 StartChessProgram(&first);
3106 /* Send "new" early, in case this command takes
3107 a long time to finish, so that we'll be ready
3108 for the next challenge. */
3109 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3116 if (looking_at(buf, &i, "Removing game * from observation") ||
3117 looking_at(buf, &i, "no longer observing game *") ||
3118 looking_at(buf, &i, "Game * (*) has no examiners")) {
3119 if (gameMode == IcsObserving &&
3120 atoi(star_match[0]) == ics_gamenum)
3122 /* icsEngineAnalyze */
3123 if (appData.icsEngineAnalyze) {
3130 ics_user_moved = FALSE;
3135 if (looking_at(buf, &i, "no longer examining game *")) {
3136 if (gameMode == IcsExamining &&
3137 atoi(star_match[0]) == ics_gamenum)
3141 ics_user_moved = FALSE;
3146 /* Advance leftover_start past any newlines we find,
3147 so only partial lines can get reparsed */
3148 if (looking_at(buf, &i, "\n")) {
3149 prevColor = curColor;
3150 if (curColor != ColorNormal) {
3151 if (oldi > next_out) {
3152 SendToPlayer(&buf[next_out], oldi - next_out);
3155 Colorize(ColorNormal, FALSE);
3156 curColor = ColorNormal;
3158 if (started == STARTED_BOARD) {
3159 started = STARTED_NONE;
3160 parse[parse_pos] = NULLCHAR;
3161 ParseBoard12(parse);
3164 /* Send premove here */
3165 if (appData.premove) {
3167 if (currentMove == 0 &&
3168 gameMode == IcsPlayingWhite &&
3169 appData.premoveWhite) {
3170 sprintf(str, "%s%s\n", ics_prefix,
3171 appData.premoveWhiteText);
3172 if (appData.debugMode)
3173 fprintf(debugFP, "Sending premove:\n");
3175 } else if (currentMove == 1 &&
3176 gameMode == IcsPlayingBlack &&
3177 appData.premoveBlack) {
3178 sprintf(str, "%s%s\n", ics_prefix,
3179 appData.premoveBlackText);
3180 if (appData.debugMode)
3181 fprintf(debugFP, "Sending premove:\n");
3183 } else if (gotPremove) {
3185 ClearPremoveHighlights();
3186 if (appData.debugMode)
3187 fprintf(debugFP, "Sending premove:\n");
3188 UserMoveEvent(premoveFromX, premoveFromY,
3189 premoveToX, premoveToY,
3194 /* Usually suppress following prompt */
3195 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3196 if (looking_at(buf, &i, "*% ")) {
3197 savingComment = FALSE;
3201 } else if (started == STARTED_HOLDINGS) {
3203 char new_piece[MSG_SIZ];
3204 started = STARTED_NONE;
3205 parse[parse_pos] = NULLCHAR;
3206 if (appData.debugMode)
3207 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3208 parse, currentMove);
3209 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3210 gamenum == ics_gamenum) {
3211 if (gameInfo.variant == VariantNormal) {
3212 /* [HGM] We seem to switch variant during a game!
3213 * Presumably no holdings were displayed, so we have
3214 * to move the position two files to the right to
3215 * create room for them!
3217 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3218 /* Get a move list just to see the header, which
3219 will tell us whether this is really bug or zh */
3220 if (ics_getting_history == H_FALSE) {
3221 ics_getting_history = H_REQUESTED;
3222 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3226 new_piece[0] = NULLCHAR;
3227 sscanf(parse, "game %d white [%s black [%s <- %s",
3228 &gamenum, white_holding, black_holding,
3230 white_holding[strlen(white_holding)-1] = NULLCHAR;
3231 black_holding[strlen(black_holding)-1] = NULLCHAR;
3232 /* [HGM] copy holdings to board holdings area */
3233 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3234 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3236 if (appData.zippyPlay && first.initDone) {
3237 ZippyHoldings(white_holding, black_holding,
3241 if (tinyLayout || smallLayout) {
3242 char wh[16], bh[16];
3243 PackHolding(wh, white_holding);
3244 PackHolding(bh, black_holding);
3245 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3246 gameInfo.white, gameInfo.black);
3248 sprintf(str, "%s [%s] vs. %s [%s]",
3249 gameInfo.white, white_holding,
3250 gameInfo.black, black_holding);
3253 DrawPosition(FALSE, boards[currentMove]);
3256 /* Suppress following prompt */
3257 if (looking_at(buf, &i, "*% ")) {
3258 savingComment = FALSE;
3265 i++; /* skip unparsed character and loop back */
3268 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3269 started != STARTED_HOLDINGS && i > next_out) {
3270 SendToPlayer(&buf[next_out], i - next_out);
3273 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3275 leftover_len = buf_len - leftover_start;
3276 /* if buffer ends with something we couldn't parse,
3277 reparse it after appending the next read */
3279 } else if (count == 0) {
3280 RemoveInputSource(isr);
3281 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3283 DisplayFatalError(_("Error reading from ICS"), error, 1);
3288 /* Board style 12 looks like this:
3290 <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
3292 * The "<12> " is stripped before it gets to this routine. The two
3293 * trailing 0's (flip state and clock ticking) are later addition, and
3294 * some chess servers may not have them, or may have only the first.
3295 * Additional trailing fields may be added in the future.
3298 #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"
3300 #define RELATION_OBSERVING_PLAYED 0
3301 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3302 #define RELATION_PLAYING_MYMOVE 1
3303 #define RELATION_PLAYING_NOTMYMOVE -1
3304 #define RELATION_EXAMINING 2
3305 #define RELATION_ISOLATED_BOARD -3
3306 #define RELATION_STARTING_POSITION -4 /* FICS only */
3309 ParseBoard12(string)
3312 GameMode newGameMode;
3313 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3314 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3315 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3316 char to_play, board_chars[200];
3317 char move_str[500], str[500], elapsed_time[500];
3318 char black[32], white[32];
3320 int prevMove = currentMove;
3323 int fromX, fromY, toX, toY;
3325 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3326 char *bookHit = NULL; // [HGM] book
3328 fromX = fromY = toX = toY = -1;
3332 if (appData.debugMode)
3333 fprintf(debugFP, _("Parsing board: %s\n"), string);
3335 move_str[0] = NULLCHAR;
3336 elapsed_time[0] = NULLCHAR;
3337 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3339 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3340 if(string[i] == ' ') { ranks++; files = 0; }
3344 for(j = 0; j <i; j++) board_chars[j] = string[j];
3345 board_chars[i] = '\0';
3348 n = sscanf(string, PATTERN, &to_play, &double_push,
3349 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3350 &gamenum, white, black, &relation, &basetime, &increment,
3351 &white_stren, &black_stren, &white_time, &black_time,
3352 &moveNum, str, elapsed_time, move_str, &ics_flip,
3356 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3357 DisplayError(str, 0);
3361 /* Convert the move number to internal form */
3362 moveNum = (moveNum - 1) * 2;
3363 if (to_play == 'B') moveNum++;
3364 if (moveNum >= MAX_MOVES) {
3365 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3371 case RELATION_OBSERVING_PLAYED:
3372 case RELATION_OBSERVING_STATIC:
3373 if (gamenum == -1) {
3374 /* Old ICC buglet */
3375 relation = RELATION_OBSERVING_STATIC;
3377 newGameMode = IcsObserving;
3379 case RELATION_PLAYING_MYMOVE:
3380 case RELATION_PLAYING_NOTMYMOVE:
3382 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3383 IcsPlayingWhite : IcsPlayingBlack;
3385 case RELATION_EXAMINING:
3386 newGameMode = IcsExamining;
3388 case RELATION_ISOLATED_BOARD:
3390 /* Just display this board. If user was doing something else,
3391 we will forget about it until the next board comes. */
3392 newGameMode = IcsIdle;
3394 case RELATION_STARTING_POSITION:
3395 newGameMode = gameMode;
3399 /* Modify behavior for initial board display on move listing
3402 switch (ics_getting_history) {
3406 case H_GOT_REQ_HEADER:
3407 case H_GOT_UNREQ_HEADER:
3408 /* This is the initial position of the current game */
3409 gamenum = ics_gamenum;
3410 moveNum = 0; /* old ICS bug workaround */
3411 if (to_play == 'B') {
3412 startedFromSetupPosition = TRUE;
3413 blackPlaysFirst = TRUE;
3415 if (forwardMostMove == 0) forwardMostMove = 1;
3416 if (backwardMostMove == 0) backwardMostMove = 1;
3417 if (currentMove == 0) currentMove = 1;
3419 newGameMode = gameMode;
3420 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3422 case H_GOT_UNWANTED_HEADER:
3423 /* This is an initial board that we don't want */
3425 case H_GETTING_MOVES:
3426 /* Should not happen */
3427 DisplayError(_("Error gathering move list: extra board"), 0);
3428 ics_getting_history = H_FALSE;
3432 /* Take action if this is the first board of a new game, or of a
3433 different game than is currently being displayed. */
3434 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3435 relation == RELATION_ISOLATED_BOARD) {
3437 /* Forget the old game and get the history (if any) of the new one */
3438 if (gameMode != BeginningOfGame) {
3442 if (appData.autoRaiseBoard) BoardToTop();
3444 if (gamenum == -1) {
3445 newGameMode = IcsIdle;
3446 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3447 appData.getMoveList) {
3448 /* Need to get game history */
3449 ics_getting_history = H_REQUESTED;
3450 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3454 /* Initially flip the board to have black on the bottom if playing
3455 black or if the ICS flip flag is set, but let the user change
3456 it with the Flip View button. */
3457 flipView = appData.autoFlipView ?
3458 (newGameMode == IcsPlayingBlack) || ics_flip :
3461 /* Done with values from previous mode; copy in new ones */
3462 gameMode = newGameMode;
3464 ics_gamenum = gamenum;
3465 if (gamenum == gs_gamenum) {
3466 int klen = strlen(gs_kind);
3467 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3468 sprintf(str, "ICS %s", gs_kind);
3469 gameInfo.event = StrSave(str);
3471 gameInfo.event = StrSave("ICS game");
3473 gameInfo.site = StrSave(appData.icsHost);
3474 gameInfo.date = PGNDate();
3475 gameInfo.round = StrSave("-");
3476 gameInfo.white = StrSave(white);
3477 gameInfo.black = StrSave(black);
3478 timeControl = basetime * 60 * 1000;
3480 timeIncrement = increment * 1000;
3481 movesPerSession = 0;
3482 gameInfo.timeControl = TimeControlTagValue();
3483 VariantSwitch(board, StringToVariant(gameInfo.event) );
3484 if (appData.debugMode) {
3485 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3486 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3487 setbuf(debugFP, NULL);
3490 gameInfo.outOfBook = NULL;
3492 /* Do we have the ratings? */
3493 if (strcmp(player1Name, white) == 0 &&
3494 strcmp(player2Name, black) == 0) {
3495 if (appData.debugMode)
3496 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3497 player1Rating, player2Rating);
3498 gameInfo.whiteRating = player1Rating;
3499 gameInfo.blackRating = player2Rating;
3500 } else if (strcmp(player2Name, white) == 0 &&
3501 strcmp(player1Name, black) == 0) {
3502 if (appData.debugMode)
3503 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3504 player2Rating, player1Rating);
3505 gameInfo.whiteRating = player2Rating;
3506 gameInfo.blackRating = player1Rating;
3508 player1Name[0] = player2Name[0] = NULLCHAR;
3510 /* Silence shouts if requested */
3511 if (appData.quietPlay &&
3512 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3513 SendToICS(ics_prefix);
3514 SendToICS("set shout 0\n");
3518 /* Deal with midgame name changes */
3520 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3521 if (gameInfo.white) free(gameInfo.white);
3522 gameInfo.white = StrSave(white);
3524 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3525 if (gameInfo.black) free(gameInfo.black);
3526 gameInfo.black = StrSave(black);
3530 /* Throw away game result if anything actually changes in examine mode */
3531 if (gameMode == IcsExamining && !newGame) {
3532 gameInfo.result = GameUnfinished;
3533 if (gameInfo.resultDetails != NULL) {
3534 free(gameInfo.resultDetails);
3535 gameInfo.resultDetails = NULL;
3539 /* In pausing && IcsExamining mode, we ignore boards coming
3540 in if they are in a different variation than we are. */
3541 if (pauseExamInvalid) return;
3542 if (pausing && gameMode == IcsExamining) {
3543 if (moveNum <= pauseExamForwardMostMove) {
3544 pauseExamInvalid = TRUE;
3545 forwardMostMove = pauseExamForwardMostMove;
3550 if (appData.debugMode) {
3551 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3553 /* Parse the board */
3554 for (k = 0; k < ranks; k++) {
3555 for (j = 0; j < files; j++)
3556 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3557 if(gameInfo.holdingsWidth > 1) {
3558 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3559 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3562 CopyBoard(boards[moveNum], board);
3564 startedFromSetupPosition =
3565 !CompareBoards(board, initialPosition);
3566 if(startedFromSetupPosition)
3567 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3570 /* [HGM] Set castling rights. Take the outermost Rooks,
3571 to make it also work for FRC opening positions. Note that board12
3572 is really defective for later FRC positions, as it has no way to
3573 indicate which Rook can castle if they are on the same side of King.
3574 For the initial position we grant rights to the outermost Rooks,
3575 and remember thos rights, and we then copy them on positions
3576 later in an FRC game. This means WB might not recognize castlings with
3577 Rooks that have moved back to their original position as illegal,
3578 but in ICS mode that is not its job anyway.
3580 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3581 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3583 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3584 if(board[0][i] == WhiteRook) j = i;
3585 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3587 if(board[0][i] == WhiteRook) j = i;
3588 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3596 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3597 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3598 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3599 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600 if(board[BOARD_HEIGHT-1][k] == bKing)
3601 initialRights[5] = castlingRights[moveNum][5] = k;
3603 r = castlingRights[moveNum][0] = initialRights[0];
3604 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3605 r = castlingRights[moveNum][1] = initialRights[1];
3606 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3607 r = castlingRights[moveNum][3] = initialRights[3];
3608 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3609 r = castlingRights[moveNum][4] = initialRights[4];
3610 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3611 /* wildcastle kludge: always assume King has rights */
3612 r = castlingRights[moveNum][2] = initialRights[2];
3613 r = castlingRights[moveNum][5] = initialRights[5];
3615 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3616 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3619 if (ics_getting_history == H_GOT_REQ_HEADER ||
3620 ics_getting_history == H_GOT_UNREQ_HEADER) {
3621 /* This was an initial position from a move list, not
3622 the current position */
3626 /* Update currentMove and known move number limits */
3627 newMove = newGame || moveNum > forwardMostMove;
3629 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3630 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3631 takeback = forwardMostMove - moveNum;
3632 for (i = 0; i < takeback; i++) {
3633 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3634 SendToProgram("undo\n", &first);
3639 forwardMostMove = backwardMostMove = currentMove = moveNum;
3640 if (gameMode == IcsExamining && moveNum == 0) {
3641 /* Workaround for ICS limitation: we are not told the wild
3642 type when starting to examine a game. But if we ask for
3643 the move list, the move list header will tell us */
3644 ics_getting_history = H_REQUESTED;
3645 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3648 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3649 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3650 forwardMostMove = moveNum;
3651 if (!pausing || currentMove > forwardMostMove)
3652 currentMove = forwardMostMove;
3654 /* New part of history that is not contiguous with old part */
3655 if (pausing && gameMode == IcsExamining) {
3656 pauseExamInvalid = TRUE;
3657 forwardMostMove = pauseExamForwardMostMove;
3660 forwardMostMove = backwardMostMove = currentMove = moveNum;
3661 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3662 ics_getting_history = H_REQUESTED;
3663 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3668 /* Update the clocks */
3669 if (strchr(elapsed_time, '.')) {
3671 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3672 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3674 /* Time is in seconds */
3675 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3676 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3681 if (appData.zippyPlay && newGame &&
3682 gameMode != IcsObserving && gameMode != IcsIdle &&
3683 gameMode != IcsExamining)
3684 ZippyFirstBoard(moveNum, basetime, increment);
3687 /* Put the move on the move list, first converting
3688 to canonical algebraic form. */
3690 if (appData.debugMode) {
3691 if (appData.debugMode) { int f = forwardMostMove;
3692 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3693 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3695 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3696 fprintf(debugFP, "moveNum = %d\n", moveNum);
3697 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3698 setbuf(debugFP, NULL);
3700 if (moveNum <= backwardMostMove) {
3701 /* We don't know what the board looked like before
3703 strcpy(parseList[moveNum - 1], move_str);
3704 strcat(parseList[moveNum - 1], " ");
3705 strcat(parseList[moveNum - 1], elapsed_time);
3706 moveList[moveNum - 1][0] = NULLCHAR;
3707 } else if (strcmp(move_str, "none") == 0) {
3708 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3709 /* Again, we don't know what the board looked like;
3710 this is really the start of the game. */
3711 parseList[moveNum - 1][0] = NULLCHAR;
3712 moveList[moveNum - 1][0] = NULLCHAR;
3713 backwardMostMove = moveNum;
3714 startedFromSetupPosition = TRUE;
3715 fromX = fromY = toX = toY = -1;
3717 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3718 // So we parse the long-algebraic move string in stead of the SAN move
3719 int valid; char buf[MSG_SIZ], *prom;
3721 // str looks something like "Q/a1-a2"; kill the slash
3723 sprintf(buf, "%c%s", str[0], str+2);
3724 else strcpy(buf, str); // might be castling
3725 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3726 strcat(buf, prom); // long move lacks promo specification!
3727 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3728 if(appData.debugMode)
3729 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3730 strcpy(move_str, buf);
3732 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3733 &fromX, &fromY, &toX, &toY, &promoChar)
3734 || ParseOneMove(buf, moveNum - 1, &moveType,
3735 &fromX, &fromY, &toX, &toY, &promoChar);
3736 // end of long SAN patch
3738 (void) CoordsToAlgebraic(boards[moveNum - 1],
3739 PosFlags(moveNum - 1), EP_UNKNOWN,
3740 fromY, fromX, toY, toX, promoChar,
3741 parseList[moveNum-1]);
3742 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3743 castlingRights[moveNum]) ) {
3749 if(gameInfo.variant != VariantShogi)
3750 strcat(parseList[moveNum - 1], "+");
3753 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3754 strcat(parseList[moveNum - 1], "#");
3757 strcat(parseList[moveNum - 1], " ");
3758 strcat(parseList[moveNum - 1], elapsed_time);
3759 /* currentMoveString is set as a side-effect of ParseOneMove */
3760 strcpy(moveList[moveNum - 1], currentMoveString);
3761 strcat(moveList[moveNum - 1], "\n");
3763 /* Move from ICS was illegal!? Punt. */
3764 if (appData.debugMode) {
3765 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3766 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3769 if (appData.testLegality && appData.debugMode) {
3770 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3771 DisplayError(str, 0);
3774 strcpy(parseList[moveNum - 1], move_str);
3775 strcat(parseList[moveNum - 1], " ");
3776 strcat(parseList[moveNum - 1], elapsed_time);
3777 moveList[moveNum - 1][0] = NULLCHAR;
3778 fromX = fromY = toX = toY = -1;
3781 if (appData.debugMode) {
3782 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783 setbuf(debugFP, NULL);
3787 /* Send move to chess program (BEFORE animating it). */
3788 if (appData.zippyPlay && !newGame && newMove &&
3789 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3791 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3796 DisplayError(str, 0);
3798 if (first.sendTime) {
3799 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3801 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802 if (firstMove && !bookHit) {
3804 if (first.useColors) {
3805 SendToProgram(gameMode == IcsPlayingWhite ?
3807 "black\ngo\n", &first);
3809 SendToProgram("go\n", &first);
3811 first.maybeThinking = TRUE;
3814 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815 if (moveList[moveNum - 1][0] == NULLCHAR) {
3816 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817 DisplayError(str, 0);
3819 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820 SendMoveToProgram(moveNum - 1, &first);
3827 if (moveNum > 0 && !gotPremove) {
3828 /* If move comes from a remote source, animate it. If it
3829 isn't remote, it will have already been animated. */
3830 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3831 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3833 if (!pausing && appData.highlightLastMove) {
3834 SetHighlights(fromX, fromY, toX, toY);
3838 /* Start the clocks */
3839 whiteFlag = blackFlag = FALSE;
3840 appData.clockMode = !(basetime == 0 && increment == 0);
3842 ics_clock_paused = TRUE;
3844 } else if (ticking == 1) {
3845 ics_clock_paused = FALSE;
3847 if (gameMode == IcsIdle ||
3848 relation == RELATION_OBSERVING_STATIC ||
3849 relation == RELATION_EXAMINING ||
3851 DisplayBothClocks();
3855 /* Display opponents and material strengths */
3856 if (gameInfo.variant != VariantBughouse &&
3857 gameInfo.variant != VariantCrazyhouse) {
3858 if (tinyLayout || smallLayout) {
3859 if(gameInfo.variant == VariantNormal)
3860 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3861 gameInfo.white, white_stren, gameInfo.black, black_stren,
3862 basetime, increment);
3864 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3865 gameInfo.white, white_stren, gameInfo.black, black_stren,
3866 basetime, increment, (int) gameInfo.variant);
3868 if(gameInfo.variant == VariantNormal)
3869 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3870 gameInfo.white, white_stren, gameInfo.black, black_stren,
3871 basetime, increment);
3873 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3874 gameInfo.white, white_stren, gameInfo.black, black_stren,
3875 basetime, increment, VariantName(gameInfo.variant));
3878 if (appData.debugMode) {
3879 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3884 /* Display the board */
3887 if (appData.premove)
3889 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3890 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3891 ClearPremoveHighlights();
3893 DrawPosition(FALSE, boards[currentMove]);
3894 DisplayMove(moveNum - 1);
3895 if (appData.ringBellAfterMoves && !ics_user_moved)
3899 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3901 if(bookHit) { // [HGM] book: simulate book reply
3902 static char bookMove[MSG_SIZ]; // a bit generous?
3904 programStats.nodes = programStats.depth = programStats.time =
3905 programStats.score = programStats.got_only_move = 0;
3906 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3908 strcpy(bookMove, "move ");
3909 strcat(bookMove, bookHit);
3910 HandleMachineMove(bookMove, &first);
3919 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3920 ics_getting_history = H_REQUESTED;
3921 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3927 AnalysisPeriodicEvent(force)
3930 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3931 && !force) || !appData.periodicUpdates)
3934 /* Send . command to Crafty to collect stats */
3935 SendToProgram(".\n", &first);
3937 /* Don't send another until we get a response (this makes
3938 us stop sending to old Crafty's which don't understand
3939 the "." command (sending illegal cmds resets node count & time,
3940 which looks bad)) */
3941 programStats.ok_to_send = 0;
3945 SendMoveToProgram(moveNum, cps)
3947 ChessProgramState *cps;
3951 if (cps->useUsermove) {
3952 SendToProgram("usermove ", cps);
3956 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3957 int len = space - parseList[moveNum];
3958 memcpy(buf, parseList[moveNum], len);
3960 buf[len] = NULLCHAR;
3962 sprintf(buf, "%s\n", parseList[moveNum]);
3964 SendToProgram(buf, cps);
3966 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3967 AlphaRank(moveList[moveNum], 4);
3968 SendToProgram(moveList[moveNum], cps);
3969 AlphaRank(moveList[moveNum], 4); // and back
3971 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3972 * the engine. It would be nice to have a better way to identify castle
3974 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3975 && cps->useOOCastle) {
3976 int fromX = moveList[moveNum][0] - AAA;
3977 int fromY = moveList[moveNum][1] - ONE;
3978 int toX = moveList[moveNum][2] - AAA;
3979 int toY = moveList[moveNum][3] - ONE;
3980 if((boards[moveNum][fromY][fromX] == WhiteKing
3981 && boards[moveNum][toY][toX] == WhiteRook)
3982 || (boards[moveNum][fromY][fromX] == BlackKing
3983 && boards[moveNum][toY][toX] == BlackRook)) {
3984 if(toX > fromX) SendToProgram("O-O\n", cps);
3985 else SendToProgram("O-O-O\n", cps);
3987 else SendToProgram(moveList[moveNum], cps);
3989 else SendToProgram(moveList[moveNum], cps);
3990 /* End of additions by Tord */
3993 /* [HGM] setting up the opening has brought engine in force mode! */
3994 /* Send 'go' if we are in a mode where machine should play. */
3995 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3996 (gameMode == TwoMachinesPlay ||
3998 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4000 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4001 SendToProgram("go\n", cps);
4002 if (appData.debugMode) {
4003 fprintf(debugFP, "(extra)\n");
4006 setboardSpoiledMachineBlack = 0;
4010 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4012 int fromX, fromY, toX, toY;
4014 char user_move[MSG_SIZ];
4018 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4019 (int)moveType, fromX, fromY, toX, toY);
4020 DisplayError(user_move + strlen("say "), 0);
4022 case WhiteKingSideCastle:
4023 case BlackKingSideCastle:
4024 case WhiteQueenSideCastleWild:
4025 case BlackQueenSideCastleWild:
4027 case WhiteHSideCastleFR:
4028 case BlackHSideCastleFR:
4030 sprintf(user_move, "o-o\n");
4032 case WhiteQueenSideCastle:
4033 case BlackQueenSideCastle:
4034 case WhiteKingSideCastleWild:
4035 case BlackKingSideCastleWild:
4037 case WhiteASideCastleFR:
4038 case BlackASideCastleFR:
4040 sprintf(user_move, "o-o-o\n");
4042 case WhitePromotionQueen:
4043 case BlackPromotionQueen:
4044 case WhitePromotionRook:
4045 case BlackPromotionRook:
4046 case WhitePromotionBishop:
4047 case BlackPromotionBishop:
4048 case WhitePromotionKnight:
4049 case BlackPromotionKnight:
4050 case WhitePromotionKing:
4051 case BlackPromotionKing:
4052 case WhitePromotionChancellor:
4053 case BlackPromotionChancellor:
4054 case WhitePromotionArchbishop:
4055 case BlackPromotionArchbishop:
4056 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4057 sprintf(user_move, "%c%c%c%c=%c\n",
4058 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4059 PieceToChar(WhiteFerz));
4060 else if(gameInfo.variant == VariantGreat)
4061 sprintf(user_move, "%c%c%c%c=%c\n",
4062 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4063 PieceToChar(WhiteMan));
4065 sprintf(user_move, "%c%c%c%c=%c\n",
4066 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4067 PieceToChar(PromoPiece(moveType)));
4071 sprintf(user_move, "%c@%c%c\n",
4072 ToUpper(PieceToChar((ChessSquare) fromX)),
4073 AAA + toX, ONE + toY);
4076 case WhiteCapturesEnPassant:
4077 case BlackCapturesEnPassant:
4078 case IllegalMove: /* could be a variant we don't quite understand */
4079 sprintf(user_move, "%c%c%c%c\n",
4080 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4083 SendToICS(user_move);
4087 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4092 if (rf == DROP_RANK) {
4093 sprintf(move, "%c@%c%c\n",
4094 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4096 if (promoChar == 'x' || promoChar == NULLCHAR) {
4097 sprintf(move, "%c%c%c%c\n",
4098 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4100 sprintf(move, "%c%c%c%c%c\n",
4101 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4107 ProcessICSInitScript(f)
4112 while (fgets(buf, MSG_SIZ, f)) {
4113 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4120 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4122 AlphaRank(char *move, int n)
4124 // char *p = move, c; int x, y;
4126 if (appData.debugMode) {
4127 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4131 move[2]>='0' && move[2]<='9' &&
4132 move[3]>='a' && move[3]<='x' ) {
4134 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4135 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4137 if(move[0]>='0' && move[0]<='9' &&
4138 move[1]>='a' && move[1]<='x' &&
4139 move[2]>='0' && move[2]<='9' &&
4140 move[3]>='a' && move[3]<='x' ) {
4141 /* input move, Shogi -> normal */
4142 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4143 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4144 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4145 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4148 move[3]>='0' && move[3]<='9' &&
4149 move[2]>='a' && move[2]<='x' ) {
4151 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4152 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4155 move[0]>='a' && move[0]<='x' &&
4156 move[3]>='0' && move[3]<='9' &&
4157 move[2]>='a' && move[2]<='x' ) {
4158 /* output move, normal -> Shogi */
4159 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4160 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4161 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4162 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4163 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4165 if (appData.debugMode) {
4166 fprintf(debugFP, " out = '%s'\n", move);
4170 /* Parser for moves from gnuchess, ICS, or user typein box */
4172 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4175 ChessMove *moveType;
4176 int *fromX, *fromY, *toX, *toY;
4179 if (appData.debugMode) {
4180 fprintf(debugFP, "move to parse: %s\n", move);
4182 *moveType = yylexstr(moveNum, move);
4184 switch (*moveType) {
4185 case WhitePromotionChancellor:
4186 case BlackPromotionChancellor:
4187 case WhitePromotionArchbishop:
4188 case BlackPromotionArchbishop:
4189 case WhitePromotionQueen:
4190 case BlackPromotionQueen:
4191 case WhitePromotionRook:
4192 case BlackPromotionRook:
4193 case WhitePromotionBishop:
4194 case BlackPromotionBishop:
4195 case WhitePromotionKnight:
4196 case BlackPromotionKnight:
4197 case WhitePromotionKing:
4198 case BlackPromotionKing:
4200 case WhiteCapturesEnPassant:
4201 case BlackCapturesEnPassant:
4202 case WhiteKingSideCastle:
4203 case WhiteQueenSideCastle:
4204 case BlackKingSideCastle:
4205 case BlackQueenSideCastle:
4206 case WhiteKingSideCastleWild:
4207 case WhiteQueenSideCastleWild:
4208 case BlackKingSideCastleWild:
4209 case BlackQueenSideCastleWild:
4210 /* Code added by Tord: */
4211 case WhiteHSideCastleFR:
4212 case WhiteASideCastleFR:
4213 case BlackHSideCastleFR:
4214 case BlackASideCastleFR:
4215 /* End of code added by Tord */
4216 case IllegalMove: /* bug or odd chess variant */
4217 *fromX = currentMoveString[0] - AAA;
4218 *fromY = currentMoveString[1] - ONE;
4219 *toX = currentMoveString[2] - AAA;
4220 *toY = currentMoveString[3] - ONE;
4221 *promoChar = currentMoveString[4];
4222 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4223 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4224 if (appData.debugMode) {
4225 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4227 *fromX = *fromY = *toX = *toY = 0;
4230 if (appData.testLegality) {
4231 return (*moveType != IllegalMove);
4233 return !(fromX == fromY && toX == toY);
4238 *fromX = *moveType == WhiteDrop ?
4239 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4240 (int) CharToPiece(ToLower(currentMoveString[0]));
4242 *toX = currentMoveString[2] - AAA;
4243 *toY = currentMoveString[3] - ONE;
4244 *promoChar = NULLCHAR;
4248 case ImpossibleMove:
4249 case (ChessMove) 0: /* end of file */
4258 if (appData.debugMode) {
4259 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4262 *fromX = *fromY = *toX = *toY = 0;
4263 *promoChar = NULLCHAR;
4268 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4269 // All positions will have equal probability, but the current method will not provide a unique
4270 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4276 int piecesLeft[(int)BlackPawn];
4277 int seed, nrOfShuffles;
4279 void GetPositionNumber()
4280 { // sets global variable seed
4283 seed = appData.defaultFrcPosition;
4284 if(seed < 0) { // randomize based on time for negative FRC position numbers
4285 for(i=0; i<50; i++) seed += random();
4286 seed = random() ^ random() >> 8 ^ random() << 8;
4287 if(seed<0) seed = -seed;
4291 int put(Board board, int pieceType, int rank, int n, int shade)
4292 // put the piece on the (n-1)-th empty squares of the given shade
4296 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4297 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4298 board[rank][i] = (ChessSquare) pieceType;
4299 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4301 piecesLeft[pieceType]--;
4309 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4310 // calculate where the next piece goes, (any empty square), and put it there
4314 i = seed % squaresLeft[shade];
4315 nrOfShuffles *= squaresLeft[shade];
4316 seed /= squaresLeft[shade];
4317 put(board, pieceType, rank, i, shade);
4320 void AddTwoPieces(Board board, int pieceType, int rank)
4321 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4323 int i, n=squaresLeft[ANY], j=n-1, k;
4325 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4326 i = seed % k; // pick one
4329 while(i >= j) i -= j--;
4330 j = n - 1 - j; i += j;
4331 put(board, pieceType, rank, j, ANY);
4332 put(board, pieceType, rank, i, ANY);
4335 void SetUpShuffle(Board board, int number)
4339 GetPositionNumber(); nrOfShuffles = 1;
4341 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4342 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4343 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4345 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4347 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4348 p = (int) board[0][i];
4349 if(p < (int) BlackPawn) piecesLeft[p] ++;
4350 board[0][i] = EmptySquare;
4353 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4354 // shuffles restricted to allow normal castling put KRR first
4355 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4356 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4357 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4358 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4359 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4360 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4361 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4362 put(board, WhiteRook, 0, 0, ANY);
4363 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4366 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4367 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4368 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4369 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4370 while(piecesLeft[p] >= 2) {
4371 AddOnePiece(board, p, 0, LITE);
4372 AddOnePiece(board, p, 0, DARK);
4374 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4377 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4378 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4379 // but we leave King and Rooks for last, to possibly obey FRC restriction
4380 if(p == (int)WhiteRook) continue;
4381 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4382 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4385 // now everything is placed, except perhaps King (Unicorn) and Rooks
4387 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4388 // Last King gets castling rights
4389 while(piecesLeft[(int)WhiteUnicorn]) {
4390 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4391 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4394 while(piecesLeft[(int)WhiteKing]) {
4395 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4396 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4401 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4402 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4405 // Only Rooks can be left; simply place them all
4406 while(piecesLeft[(int)WhiteRook]) {
4407 i = put(board, WhiteRook, 0, 0, ANY);
4408 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4411 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4413 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4416 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4417 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4420 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4423 int SetCharTable( char *table, const char * map )
4424 /* [HGM] moved here from winboard.c because of its general usefulness */
4425 /* Basically a safe strcpy that uses the last character as King */
4427 int result = FALSE; int NrPieces;
4429 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4430 && NrPieces >= 12 && !(NrPieces&1)) {
4431 int i; /* [HGM] Accept even length from 12 to 34 */
4433 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4434 for( i=0; i<NrPieces/2-1; i++ ) {
4436 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4438 table[(int) WhiteKing] = map[NrPieces/2-1];
4439 table[(int) BlackKing] = map[NrPieces-1];
4447 void Prelude(Board board)
4448 { // [HGM] superchess: random selection of exo-pieces
4449 int i, j, k; ChessSquare p;
4450 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4452 GetPositionNumber(); // use FRC position number
4454 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4455 SetCharTable(pieceToChar, appData.pieceToCharTable);
4456 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4457 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4460 j = seed%4; seed /= 4;
4461 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4462 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4463 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4464 j = seed%3 + (seed%3 >= j); seed /= 3;
4465 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4466 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4467 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4468 j = seed%3; seed /= 3;
4469 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4470 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4471 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4472 j = seed%2 + (seed%2 >= j); seed /= 2;
4473 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4474 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4475 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4476 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4477 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4478 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4479 put(board, exoPieces[0], 0, 0, ANY);
4480 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4484 InitPosition(redraw)
4487 ChessSquare (* pieces)[BOARD_SIZE];
4488 int i, j, pawnRow, overrule,
4489 oldx = gameInfo.boardWidth,
4490 oldy = gameInfo.boardHeight,
4491 oldh = gameInfo.holdingsWidth,
4492 oldv = gameInfo.variant;
4494 currentMove = forwardMostMove = backwardMostMove = 0;
4495 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4497 /* [AS] Initialize pv info list [HGM] and game status */
4499 for( i=0; i<MAX_MOVES; i++ ) {
4500 pvInfoList[i].depth = 0;
4501 epStatus[i]=EP_NONE;
4502 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4505 initialRulePlies = 0; /* 50-move counter start */
4507 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4508 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4512 /* [HGM] logic here is completely changed. In stead of full positions */
4513 /* the initialized data only consist of the two backranks. The switch */
4514 /* selects which one we will use, which is than copied to the Board */
4515 /* initialPosition, which for the rest is initialized by Pawns and */
4516 /* empty squares. This initial position is then copied to boards[0], */
4517 /* possibly after shuffling, so that it remains available. */
4519 gameInfo.holdingsWidth = 0; /* default board sizes */
4520 gameInfo.boardWidth = 8;
4521 gameInfo.boardHeight = 8;
4522 gameInfo.holdingsSize = 0;
4523 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4524 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4525 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4527 switch (gameInfo.variant) {
4528 case VariantFischeRandom:
4529 shuffleOpenings = TRUE;
4533 case VariantShatranj:
4534 pieces = ShatranjArray;
4535 nrCastlingRights = 0;
4536 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4538 case VariantTwoKings:
4539 pieces = twoKingsArray;
4541 case VariantCapaRandom:
4542 shuffleOpenings = TRUE;
4543 case VariantCapablanca:
4544 pieces = CapablancaArray;
4545 gameInfo.boardWidth = 10;
4546 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4549 pieces = GothicArray;
4550 gameInfo.boardWidth = 10;
4551 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4554 pieces = JanusArray;
4555 gameInfo.boardWidth = 10;
4556 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4557 nrCastlingRights = 6;
4558 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4559 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4560 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4561 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4562 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4563 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4566 pieces = FalconArray;
4567 gameInfo.boardWidth = 10;
4568 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4570 case VariantXiangqi:
4571 pieces = XiangqiArray;
4572 gameInfo.boardWidth = 9;
4573 gameInfo.boardHeight = 10;
4574 nrCastlingRights = 0;
4575 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4578 pieces = ShogiArray;
4579 gameInfo.boardWidth = 9;
4580 gameInfo.boardHeight = 9;
4581 gameInfo.holdingsSize = 7;
4582 nrCastlingRights = 0;
4583 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4585 case VariantCourier:
4586 pieces = CourierArray;
4587 gameInfo.boardWidth = 12;
4588 nrCastlingRights = 0;
4589 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4590 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4592 case VariantKnightmate:
4593 pieces = KnightmateArray;
4594 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4597 pieces = fairyArray;
4598 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4601 pieces = GreatArray;
4602 gameInfo.boardWidth = 10;
4603 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4604 gameInfo.holdingsSize = 8;
4608 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4609 gameInfo.holdingsSize = 8;
4610 startedFromSetupPosition = TRUE;
4612 case VariantCrazyhouse:
4613 case VariantBughouse:
4615 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4616 gameInfo.holdingsSize = 5;
4618 case VariantWildCastle:
4620 /* !!?shuffle with kings guaranteed to be on d or e file */
4621 shuffleOpenings = 1;
4623 case VariantNoCastle:
4625 nrCastlingRights = 0;
4626 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4627 /* !!?unconstrained back-rank shuffle */
4628 shuffleOpenings = 1;
4633 if(appData.NrFiles >= 0) {
4634 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4635 gameInfo.boardWidth = appData.NrFiles;
4637 if(appData.NrRanks >= 0) {
4638 gameInfo.boardHeight = appData.NrRanks;
4640 if(appData.holdingsSize >= 0) {
4641 i = appData.holdingsSize;
4642 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4643 gameInfo.holdingsSize = i;
4645 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4646 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4647 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4649 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4650 if(pawnRow < 1) pawnRow = 1;
4652 /* User pieceToChar list overrules defaults */
4653 if(appData.pieceToCharTable != NULL)
4654 SetCharTable(pieceToChar, appData.pieceToCharTable);
4656 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4658 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4659 s = (ChessSquare) 0; /* account holding counts in guard band */
4660 for( i=0; i<BOARD_HEIGHT; i++ )
4661 initialPosition[i][j] = s;
4663 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4664 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4665 initialPosition[pawnRow][j] = WhitePawn;
4666 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4667 if(gameInfo.variant == VariantXiangqi) {
4669 initialPosition[pawnRow][j] =
4670 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4671 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4672 initialPosition[2][j] = WhiteCannon;
4673 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4677 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4679 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4682 initialPosition[1][j] = WhiteBishop;
4683 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4685 initialPosition[1][j] = WhiteRook;
4686 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4689 if( nrCastlingRights == -1) {
4690 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4691 /* This sets default castling rights from none to normal corners */
4692 /* Variants with other castling rights must set them themselves above */
4693 nrCastlingRights = 6;
4695 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4696 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4697 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4698 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4699 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4700 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4703 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4704 if(gameInfo.variant == VariantGreat) { // promotion commoners
4705 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4706 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4707 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4708 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4711 if(gameInfo.variant == VariantFischeRandom) {
4712 if( appData.defaultFrcPosition < 0 ) {
4713 ShuffleFRC( initialPosition );
4716 SetupFRC( initialPosition, appData.defaultFrcPosition );
4718 startedFromSetupPosition = TRUE;
4721 if (appData.debugMode) {
4722 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4724 if(shuffleOpenings) {
4725 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4726 startedFromSetupPosition = TRUE;
4729 if(startedFromPositionFile) {
4730 /* [HGM] loadPos: use PositionFile for every new game */
4731 CopyBoard(initialPosition, filePosition);
4732 for(i=0; i<nrCastlingRights; i++)
4733 castlingRights[0][i] = initialRights[i] = fileRights[i];
4734 startedFromSetupPosition = TRUE;
4737 CopyBoard(boards[0], initialPosition);
4739 if(oldx != gameInfo.boardWidth ||
4740 oldy != gameInfo.boardHeight ||
4741 oldh != gameInfo.holdingsWidth
4743 || oldv == VariantGothic || // For licensing popups
4744 gameInfo.variant == VariantGothic
4747 || oldv == VariantFalcon ||
4748 gameInfo.variant == VariantFalcon
4751 InitDrawingSizes(-2 ,0);
4754 DrawPosition(TRUE, boards[currentMove]);
4758 SendBoard(cps, moveNum)
4759 ChessProgramState *cps;
4762 char message[MSG_SIZ];
4764 if (cps->useSetboard) {
4765 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4766 sprintf(message, "setboard %s\n", fen);
4767 SendToProgram(message, cps);
4773 /* Kludge to set black to move, avoiding the troublesome and now
4774 * deprecated "black" command.
4776 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4778 SendToProgram("edit\n", cps);
4779 SendToProgram("#\n", cps);
4780 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4781 bp = &boards[moveNum][i][BOARD_LEFT];
4782 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4783 if ((int) *bp < (int) BlackPawn) {
4784 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4786 if(message[0] == '+' || message[0] == '~') {
4787 sprintf(message, "%c%c%c+\n",
4788 PieceToChar((ChessSquare)(DEMOTED *bp)),
4791 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4792 message[1] = BOARD_RGHT - 1 - j + '1';
4793 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4795 SendToProgram(message, cps);
4800 SendToProgram("c\n", cps);
4801 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4802 bp = &boards[moveNum][i][BOARD_LEFT];
4803 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4804 if (((int) *bp != (int) EmptySquare)
4805 && ((int) *bp >= (int) BlackPawn)) {
4806 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4808 if(message[0] == '+' || message[0] == '~') {
4809 sprintf(message, "%c%c%c+\n",
4810 PieceToChar((ChessSquare)(DEMOTED *bp)),
4813 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4814 message[1] = BOARD_RGHT - 1 - j + '1';
4815 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4817 SendToProgram(message, cps);
4822 SendToProgram(".\n", cps);
4824 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4828 IsPromotion(fromX, fromY, toX, toY)
4829 int fromX, fromY, toX, toY;
4831 /* [HGM] add Shogi promotions */
4832 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4835 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4836 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4837 /* [HGM] Note to self: line above also weeds out drops */
4838 piece = boards[currentMove][fromY][fromX];
4839 if(gameInfo.variant == VariantShogi) {
4840 promotionZoneSize = 3;
4841 highestPromotingPiece = (int)WhiteKing;
4842 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4843 and if in normal chess we then allow promotion to King, why not
4844 allow promotion of other piece in Shogi? */
4846 if((int)piece >= BlackPawn) {
4847 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4849 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4851 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4852 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4854 return ( (int)piece <= highestPromotingPiece );
4858 InPalace(row, column)
4860 { /* [HGM] for Xiangqi */
4861 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4862 column < (BOARD_WIDTH + 4)/2 &&
4863 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4868 PieceForSquare (x, y)
4872 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4875 return boards[currentMove][y][x];
4879 OKToStartUserMove(x, y)
4882 ChessSquare from_piece;
4885 if (matchMode) return FALSE;
4886 if (gameMode == EditPosition) return TRUE;
4888 if (x >= 0 && y >= 0)
4889 from_piece = boards[currentMove][y][x];
4891 from_piece = EmptySquare;
4893 if (from_piece == EmptySquare) return FALSE;
4895 white_piece = (int)from_piece >= (int)WhitePawn &&
4896 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4899 case PlayFromGameFile:
4901 case TwoMachinesPlay:
4909 case MachinePlaysWhite:
4910 case IcsPlayingBlack:
4911 if (appData.zippyPlay) return FALSE;
4913 DisplayMoveError(_("You are playing Black"));
4918 case MachinePlaysBlack:
4919 case IcsPlayingWhite:
4920 if (appData.zippyPlay) return FALSE;
4922 DisplayMoveError(_("You are playing White"));
4928 if (!white_piece && WhiteOnMove(currentMove)) {
4929 DisplayMoveError(_("It is White's turn"));
4932 if (white_piece && !WhiteOnMove(currentMove)) {
4933 DisplayMoveError(_("It is Black's turn"));
4936 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4937 /* Editing correspondence game history */
4938 /* Could disallow this or prompt for confirmation */
4941 if (currentMove < forwardMostMove) {
4942 /* Discarding moves */
4943 /* Could prompt for confirmation here,
4944 but I don't think that's such a good idea */
4945 forwardMostMove = currentMove;
4949 case BeginningOfGame:
4950 if (appData.icsActive) return FALSE;
4951 if (!appData.noChessProgram) {
4953 DisplayMoveError(_("You are playing White"));
4960 if (!white_piece && WhiteOnMove(currentMove)) {
4961 DisplayMoveError(_("It is White's turn"));
4964 if (white_piece && !WhiteOnMove(currentMove)) {
4965 DisplayMoveError(_("It is Black's turn"));
4974 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4975 && gameMode != AnalyzeFile && gameMode != Training) {
4976 DisplayMoveError(_("Displayed position is not current"));
4982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4984 int lastLoadGameUseList = FALSE;
4985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4986 ChessMove lastLoadGameStart = (ChessMove) 0;
4990 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4991 int fromX, fromY, toX, toY;
4995 ChessSquare pdown, pup;
4997 if (fromX < 0 || fromY < 0) return ImpossibleMove;
4998 if ((fromX == toX) && (fromY == toY)) {
4999 return ImpossibleMove;
5002 /* [HGM] suppress all moves into holdings area and guard band */
5003 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5004 return ImpossibleMove;
5006 /* [HGM] <sameColor> moved to here from winboard.c */
5007 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5008 pdown = boards[currentMove][fromY][fromX];
5009 pup = boards[currentMove][toY][toX];
5010 if ( gameMode != EditPosition &&
5011 (WhitePawn <= pdown && pdown < BlackPawn &&
5012 WhitePawn <= pup && pup < BlackPawn ||
5013 BlackPawn <= pdown && pdown < EmptySquare &&
5014 BlackPawn <= pup && pup < EmptySquare
5015 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5016 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5017 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5019 return ImpossibleMove;
5021 /* Check if the user is playing in turn. This is complicated because we
5022 let the user "pick up" a piece before it is his turn. So the piece he
5023 tried to pick up may have been captured by the time he puts it down!
5024 Therefore we use the color the user is supposed to be playing in this
5025 test, not the color of the piece that is currently on the starting
5026 square---except in EditGame mode, where the user is playing both
5027 sides; fortunately there the capture race can't happen. (It can
5028 now happen in IcsExamining mode, but that's just too bad. The user
5029 will get a somewhat confusing message in that case.)
5033 case PlayFromGameFile:
5035 case TwoMachinesPlay:
5039 /* We switched into a game mode where moves are not accepted,
5040 perhaps while the mouse button was down. */
5041 return ImpossibleMove;
5043 case MachinePlaysWhite:
5044 /* User is moving for Black */
5045 if (WhiteOnMove(currentMove)) {
5046 DisplayMoveError(_("It is White's turn"));
5047 return ImpossibleMove;
5051 case MachinePlaysBlack:
5052 /* User is moving for White */
5053 if (!WhiteOnMove(currentMove)) {
5054 DisplayMoveError(_("It is Black's turn"));
5055 return ImpossibleMove;
5061 case BeginningOfGame:
5064 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5065 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5066 /* User is moving for Black */
5067 if (WhiteOnMove(currentMove)) {
5068 DisplayMoveError(_("It is White's turn"));
5069 return ImpossibleMove;
5072 /* User is moving for White */
5073 if (!WhiteOnMove(currentMove)) {
5074 DisplayMoveError(_("It is Black's turn"));
5075 return ImpossibleMove;
5080 case IcsPlayingBlack:
5081 /* User is moving for Black */
5082 if (WhiteOnMove(currentMove)) {
5083 if (!appData.premove) {
5084 DisplayMoveError(_("It is White's turn"));
5085 } else if (toX >= 0 && toY >= 0) {
5088 premoveFromX = fromX;
5089 premoveFromY = fromY;
5090 premovePromoChar = promoChar;
5092 if (appData.debugMode)
5093 fprintf(debugFP, "Got premove: fromX %d,"
5094 "fromY %d, toX %d, toY %d\n",
5095 fromX, fromY, toX, toY);
5097 return ImpossibleMove;
5101 case IcsPlayingWhite:
5102 /* User is moving for White */
5103 if (!WhiteOnMove(currentMove)) {
5104 if (!appData.premove) {
5105 DisplayMoveError(_("It is Black's turn"));
5106 } else if (toX >= 0 && toY >= 0) {
5109 premoveFromX = fromX;
5110 premoveFromY = fromY;
5111 premovePromoChar = promoChar;
5113 if (appData.debugMode)
5114 fprintf(debugFP, "Got premove: fromX %d,"
5115 "fromY %d, toX %d, toY %d\n",
5116 fromX, fromY, toX, toY);
5118 return ImpossibleMove;
5126 /* EditPosition, empty square, or different color piece;
5127 click-click move is possible */
5128 if (toX == -2 || toY == -2) {
5129 boards[0][fromY][fromX] = EmptySquare;
5130 return AmbiguousMove;
5131 } else if (toX >= 0 && toY >= 0) {
5132 boards[0][toY][toX] = boards[0][fromY][fromX];
5133 boards[0][fromY][fromX] = EmptySquare;
5134 return AmbiguousMove;
5136 return ImpossibleMove;
5139 /* [HGM] If move started in holdings, it means a drop */
5140 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5141 if( pup != EmptySquare ) return ImpossibleMove;
5142 if(appData.testLegality) {
5143 /* it would be more logical if LegalityTest() also figured out
5144 * which drops are legal. For now we forbid pawns on back rank.
5145 * Shogi is on its own here...
5147 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5148 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5149 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5151 return WhiteDrop; /* Not needed to specify white or black yet */
5154 userOfferedDraw = FALSE;
5156 /* [HGM] always test for legality, to get promotion info */
5157 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5158 epStatus[currentMove], castlingRights[currentMove],
5159 fromY, fromX, toY, toX, promoChar);
5161 /* [HGM] but possibly ignore an IllegalMove result */
5162 if (appData.testLegality) {
5163 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5164 DisplayMoveError(_("Illegal move"));
5165 return ImpossibleMove;
5168 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5170 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5171 function is made into one that returns an OK move type if FinishMove
5172 should be called. This to give the calling driver routine the
5173 opportunity to finish the userMove input with a promotion popup,
5174 without bothering the user with this for invalid or illegal moves */
5176 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5179 /* Common tail of UserMoveEvent and DropMenuEvent */
5181 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5183 int fromX, fromY, toX, toY;
5184 /*char*/int promoChar;
5187 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5188 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5189 // [HGM] superchess: suppress promotions to non-available piece
5190 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5191 if(WhiteOnMove(currentMove)) {
5192 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5194 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5198 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5199 move type in caller when we know the move is a legal promotion */
5200 if(moveType == NormalMove && promoChar)
5201 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5202 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5203 /* [HGM] convert drag-and-drop piece drops to standard form */
5204 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5205 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5206 fromX = boards[currentMove][fromY][fromX];
5210 /* [HGM] <popupFix> The following if has been moved here from
5211 UserMoveEvent(). Because it seemed to belon here (why not allow
5212 piece drops in training games?), and because it can only be
5213 performed after it is known to what we promote. */
5214 if (gameMode == Training) {
5215 /* compare the move played on the board to the next move in the
5216 * game. If they match, display the move and the opponent's response.
5217 * If they don't match, display an error message.
5220 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5221 CopyBoard(testBoard, boards[currentMove]);
5222 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5224 if (CompareBoards(testBoard, boards[currentMove+1])) {
5225 ForwardInner(currentMove+1);
5227 /* Autoplay the opponent's response.
5228 * if appData.animate was TRUE when Training mode was entered,
5229 * the response will be animated.
5231 saveAnimate = appData.animate;
5232 appData.animate = animateTraining;
5233 ForwardInner(currentMove+1);
5234 appData.animate = saveAnimate;
5236 /* check for the end of the game */
5237 if (currentMove >= forwardMostMove) {
5238 gameMode = PlayFromGameFile;
5240 SetTrainingModeOff();
5241 DisplayInformation(_("End of game"));
5244 DisplayError(_("Incorrect move"), 0);
5249 /* Ok, now we know that the move is good, so we can kill
5250 the previous line in Analysis Mode */
5251 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5252 forwardMostMove = currentMove;
5255 /* If we need the chess program but it's dead, restart it */
5256 ResurrectChessProgram();
5258 /* A user move restarts a paused game*/
5262 thinkOutput[0] = NULLCHAR;
5264 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5266 if (gameMode == BeginningOfGame) {
5267 if (appData.noChessProgram) {
5268 gameMode = EditGame;
5272 gameMode = MachinePlaysBlack;
5275 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5277 if (first.sendName) {
5278 sprintf(buf, "name %s\n", gameInfo.white);
5279 SendToProgram(buf, &first);
5285 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5286 /* Relay move to ICS or chess engine */
5287 if (appData.icsActive) {
5288 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5289 gameMode == IcsExamining) {
5290 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5294 if (first.sendTime && (gameMode == BeginningOfGame ||
5295 gameMode == MachinePlaysWhite ||
5296 gameMode == MachinePlaysBlack)) {
5297 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5299 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5300 // [HGM] book: if program might be playing, let it use book
5301 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5302 first.maybeThinking = TRUE;
5303 } else SendMoveToProgram(forwardMostMove-1, &first);
5304 if (currentMove == cmailOldMove + 1) {
5305 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5309 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5313 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5314 EP_UNKNOWN, castlingRights[currentMove]) ) {
5320 if (WhiteOnMove(currentMove)) {
5321 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5323 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5327 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5332 case MachinePlaysBlack:
5333 case MachinePlaysWhite:
5334 /* disable certain menu options while machine is thinking */
5335 SetMachineThinkingEnables();
5342 if(bookHit) { // [HGM] book: simulate book reply
5343 static char bookMove[MSG_SIZ]; // a bit generous?
5345 programStats.nodes = programStats.depth = programStats.time =
5346 programStats.score = programStats.got_only_move = 0;
5347 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5349 strcpy(bookMove, "move ");
5350 strcat(bookMove, bookHit);
5351 HandleMachineMove(bookMove, &first);
5357 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5358 int fromX, fromY, toX, toY;
5361 /* [HGM] This routine was added to allow calling of its two logical
5362 parts from other modules in the old way. Before, UserMoveEvent()
5363 automatically called FinishMove() if the move was OK, and returned
5364 otherwise. I separated the two, in order to make it possible to
5365 slip a promotion popup in between. But that it always needs two
5366 calls, to the first part, (now called UserMoveTest() ), and to
5367 FinishMove if the first part succeeded. Calls that do not need
5368 to do anything in between, can call this routine the old way.
5370 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5371 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5372 if(moveType != ImpossibleMove)
5373 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5376 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5378 // char * hint = lastHint;
5379 FrontEndProgramStats stats;
5381 stats.which = cps == &first ? 0 : 1;
5382 stats.depth = cpstats->depth;
5383 stats.nodes = cpstats->nodes;
5384 stats.score = cpstats->score;
5385 stats.time = cpstats->time;
5386 stats.pv = cpstats->movelist;
5387 stats.hint = lastHint;
5388 stats.an_move_index = 0;
5389 stats.an_move_count = 0;
5391 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5392 stats.hint = cpstats->move_name;
5393 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5394 stats.an_move_count = cpstats->nr_moves;
5397 SetProgramStats( &stats );
5400 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5401 { // [HGM] book: this routine intercepts moves to simulate book replies
5402 char *bookHit = NULL;
5404 //first determine if the incoming move brings opponent into his book
5405 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5406 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5407 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5408 if(bookHit != NULL && !cps->bookSuspend) {
5409 // make sure opponent is not going to reply after receiving move to book position
5410 SendToProgram("force\n", cps);
5411 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5413 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5414 // now arrange restart after book miss
5416 // after a book hit we never send 'go', and the code after the call to this routine
5417 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5419 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5420 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5421 SendToProgram(buf, cps);
5422 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5423 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5424 SendToProgram("go\n", cps);
5425 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5426 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5427 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5428 SendToProgram("go\n", cps);
5429 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5431 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5435 ChessProgramState *savedState;
5436 void DeferredBookMove(void)
5438 if(savedState->lastPing != savedState->lastPong)
5439 ScheduleDelayedEvent(DeferredBookMove, 10);
5441 HandleMachineMove(savedMessage, savedState);
5445 HandleMachineMove(message, cps)
5447 ChessProgramState *cps;
5449 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5450 char realname[MSG_SIZ];
5451 int fromX, fromY, toX, toY;
5458 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5460 * Kludge to ignore BEL characters
5462 while (*message == '\007') message++;
5465 * [HGM] engine debug message: ignore lines starting with '#' character
5467 if(cps->debug && *message == '#') return;
5470 * Look for book output
5472 if (cps == &first && bookRequested) {
5473 if (message[0] == '\t' || message[0] == ' ') {
5474 /* Part of the book output is here; append it */
5475 strcat(bookOutput, message);
5476 strcat(bookOutput, " \n");
5478 } else if (bookOutput[0] != NULLCHAR) {
5479 /* All of book output has arrived; display it */
5480 char *p = bookOutput;
5481 while (*p != NULLCHAR) {
5482 if (*p == '\t') *p = ' ';
5485 DisplayInformation(bookOutput);
5486 bookRequested = FALSE;
5487 /* Fall through to parse the current output */
5492 * Look for machine move.
5494 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5495 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5497 /* This method is only useful on engines that support ping */
5498 if (cps->lastPing != cps->lastPong) {
5499 if (gameMode == BeginningOfGame) {
5500 /* Extra move from before last new; ignore */
5501 if (appData.debugMode) {
5502 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5505 if (appData.debugMode) {
5506 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5507 cps->which, gameMode);
5510 SendToProgram("undo\n", cps);
5516 case BeginningOfGame:
5517 /* Extra move from before last reset; ignore */
5518 if (appData.debugMode) {
5519 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5526 /* Extra move after we tried to stop. The mode test is
5527 not a reliable way of detecting this problem, but it's
5528 the best we can do on engines that don't support ping.
5530 if (appData.debugMode) {
5531 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5532 cps->which, gameMode);
5534 SendToProgram("undo\n", cps);
5537 case MachinePlaysWhite:
5538 case IcsPlayingWhite:
5539 machineWhite = TRUE;
5542 case MachinePlaysBlack:
5543 case IcsPlayingBlack:
5544 machineWhite = FALSE;
5547 case TwoMachinesPlay:
5548 machineWhite = (cps->twoMachinesColor[0] == 'w');
5551 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5552 if (appData.debugMode) {
5554 "Ignoring move out of turn by %s, gameMode %d"
5555 ", forwardMost %d\n",
5556 cps->which, gameMode, forwardMostMove);
5561 if (appData.debugMode) { int f = forwardMostMove;
5562 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5563 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5565 if(cps->alphaRank) AlphaRank(machineMove, 4);
5566 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5567 &fromX, &fromY, &toX, &toY, &promoChar)) {
5568 /* Machine move could not be parsed; ignore it. */
5569 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5570 machineMove, cps->which);
5571 DisplayError(buf1, 0);
5572 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5573 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5574 if (gameMode == TwoMachinesPlay) {
5575 GameEnds(machineWhite ? BlackWins : WhiteWins,
5581 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5582 /* So we have to redo legality test with true e.p. status here, */
5583 /* to make sure an illegal e.p. capture does not slip through, */
5584 /* to cause a forfeit on a justified illegal-move complaint */
5585 /* of the opponent. */
5586 if( gameMode==TwoMachinesPlay && appData.testLegality
5587 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5590 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5591 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5592 fromY, fromX, toY, toX, promoChar);
5593 if (appData.debugMode) {
5595 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5596 castlingRights[forwardMostMove][i], castlingRank[i]);
5597 fprintf(debugFP, "castling rights\n");
5599 if(moveType == IllegalMove) {
5600 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5601 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5602 GameEnds(machineWhite ? BlackWins : WhiteWins,
5605 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5606 /* [HGM] Kludge to handle engines that send FRC-style castling
5607 when they shouldn't (like TSCP-Gothic) */
5609 case WhiteASideCastleFR:
5610 case BlackASideCastleFR:
5612 currentMoveString[2]++;
5614 case WhiteHSideCastleFR:
5615 case BlackHSideCastleFR:
5617 currentMoveString[2]--;
5619 default: ; // nothing to do, but suppresses warning of pedantic compilers
5622 hintRequested = FALSE;
5623 lastHint[0] = NULLCHAR;
5624 bookRequested = FALSE;
5625 /* Program may be pondering now */
5626 cps->maybeThinking = TRUE;
5627 if (cps->sendTime == 2) cps->sendTime = 1;
5628 if (cps->offeredDraw) cps->offeredDraw--;
5631 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5633 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5635 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5636 char buf[3*MSG_SIZ];
5638 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5639 programStats.score / 100.,
5641 programStats.time / 100.,
5642 u64ToDouble(programStats.nodes),
5643 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5644 programStats.movelist);
5649 /* currentMoveString is set as a side-effect of ParseOneMove */
5650 strcpy(machineMove, currentMoveString);
5651 strcat(machineMove, "\n");
5652 strcpy(moveList[forwardMostMove], machineMove);
5654 /* [AS] Save move info and clear stats for next move */
5655 pvInfoList[ forwardMostMove ].score = programStats.score;
5656 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5657 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5658 ClearProgramStats();
5659 thinkOutput[0] = NULLCHAR;
5660 hiddenThinkOutputState = 0;
5662 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5664 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5665 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5668 while( count < adjudicateLossPlies ) {
5669 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5672 score = -score; /* Flip score for winning side */
5675 if( score > adjudicateLossThreshold ) {
5682 if( count >= adjudicateLossPlies ) {
5683 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5685 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5686 "Xboard adjudication",
5693 if( gameMode == TwoMachinesPlay ) {
5694 // [HGM] some adjudications useful with buggy engines
5695 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5696 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5699 if( appData.testLegality )
5700 { /* [HGM] Some more adjudications for obstinate engines */
5701 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5702 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5703 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5704 static int moveCount = 6;
5706 char *reason = NULL;
5708 /* Count what is on board. */
5709 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5710 { ChessSquare p = boards[forwardMostMove][i][j];
5714 { /* count B,N,R and other of each side */
5717 NrK++; break; // [HGM] atomic: count Kings
5721 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5722 bishopsColor |= 1 << ((i^j)&1);
5727 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5728 bishopsColor |= 1 << ((i^j)&1);
5743 PawnAdvance += m; NrPawns++;
5745 NrPieces += (p != EmptySquare);
5746 NrW += ((int)p < (int)BlackPawn);
5747 if(gameInfo.variant == VariantXiangqi &&
5748 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5749 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5750 NrW -= ((int)p < (int)BlackPawn);
5754 /* Some material-based adjudications that have to be made before stalemate test */
5755 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5756 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5757 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5758 if(appData.checkMates) {
5759 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5760 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5761 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5762 "Xboard adjudication: King destroyed", GE_XBOARD );
5767 /* Bare King in Shatranj (loses) or Losers (wins) */
5768 if( NrW == 1 || NrPieces - NrW == 1) {
5769 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5770 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5771 if(appData.checkMates) {
5772 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5773 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5774 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5775 "Xboard adjudication: Bare king", GE_XBOARD );
5779 if( gameInfo.variant == VariantShatranj && --bare < 0)
5781 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5782 if(appData.checkMates) {
5783 /* but only adjudicate if adjudication enabled */
5784 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5785 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5786 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5787 "Xboard adjudication: Bare king", GE_XBOARD );
5794 // don't wait for engine to announce game end if we can judge ourselves
5795 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5796 castlingRights[forwardMostMove]) ) {
5798 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5799 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5800 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5801 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5804 reason = "Xboard adjudication: 3rd check";
5805 epStatus[forwardMostMove] = EP_CHECKMATE;
5815 reason = "Xboard adjudication: Stalemate";
5816 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5817 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5818 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5819 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5820 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5821 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5822 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5823 EP_CHECKMATE : EP_WINS);
5824 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5825 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5829 reason = "Xboard adjudication: Checkmate";
5830 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5834 switch(i = epStatus[forwardMostMove]) {
5836 result = GameIsDrawn; break;
5838 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5840 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5842 result = (ChessMove) 0;
5844 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5845 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5846 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5847 GameEnds( result, reason, GE_XBOARD );
5851 /* Next absolutely insufficient mating material. */
5852 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5853 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5854 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5855 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5856 { /* KBK, KNK, KK of KBKB with like Bishops */
5858 /* always flag draws, for judging claims */
5859 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5861 if(appData.materialDraws) {
5862 /* but only adjudicate them if adjudication enabled */
5863 SendToProgram("force\n", cps->other); // suppress reply
5864 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5865 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5866 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5871 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5873 ( NrWR == 1 && NrBR == 1 /* KRKR */
5874 || NrWQ==1 && NrBQ==1 /* KQKQ */
5875 || NrWN==2 || NrBN==2 /* KNNK */
5876 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5878 if(--moveCount < 0 && appData.trivialDraws)
5879 { /* if the first 3 moves do not show a tactical win, declare draw */
5880 SendToProgram("force\n", cps->other); // suppress reply
5881 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5882 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5883 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5886 } else moveCount = 6;
5890 if (appData.debugMode) { int i;
5891 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5892 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5893 appData.drawRepeats);
5894 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5895 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5899 /* Check for rep-draws */
5901 for(k = forwardMostMove-2;
5902 k>=backwardMostMove && k>=forwardMostMove-100 &&
5903 epStatus[k] < EP_UNKNOWN &&
5904 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5908 if (appData.debugMode) {
5909 fprintf(debugFP, " loop\n");
5912 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5914 if (appData.debugMode) {
5915 fprintf(debugFP, "match\n");
5918 /* compare castling rights */
5919 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5920 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5921 rights++; /* King lost rights, while rook still had them */
5922 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5923 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5924 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5925 rights++; /* but at least one rook lost them */
5927 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5928 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5930 if( castlingRights[forwardMostMove][5] >= 0 ) {
5931 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5932 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5936 if (appData.debugMode) {
5937 for(i=0; i<nrCastlingRights; i++)
5938 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5941 if (appData.debugMode) {
5942 fprintf(debugFP, " %d %d\n", rights, k);
5945 if( rights == 0 && ++count > appData.drawRepeats-2
5946 && appData.drawRepeats > 1) {
5947 /* adjudicate after user-specified nr of repeats */
5948 SendToProgram("force\n", cps->other); // suppress reply
5949 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5950 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5951 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5952 // [HGM] xiangqi: check for forbidden perpetuals
5953 int m, ourPerpetual = 1, hisPerpetual = 1;
5954 for(m=forwardMostMove; m>k; m-=2) {
5955 if(MateTest(boards[m], PosFlags(m),
5956 EP_NONE, castlingRights[m]) != MT_CHECK)
5957 ourPerpetual = 0; // the current mover did not always check
5958 if(MateTest(boards[m-1], PosFlags(m-1),
5959 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5960 hisPerpetual = 0; // the opponent did not always check
5962 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5963 ourPerpetual, hisPerpetual);
5964 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5965 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5966 "Xboard adjudication: perpetual checking", GE_XBOARD );
5969 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5970 break; // (or we would have caught him before). Abort repetition-checking loop.
5971 // Now check for perpetual chases
5972 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5973 hisPerpetual = PerpetualChase(k, forwardMostMove);
5974 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5975 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5976 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5977 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5980 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5981 break; // Abort repetition-checking loop.
5983 // if neither of us is checking or chasing all the time, or both are, it is draw
5985 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5988 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5989 epStatus[forwardMostMove] = EP_REP_DRAW;
5993 /* Now we test for 50-move draws. Determine ply count */
5994 count = forwardMostMove;
5995 /* look for last irreversble move */
5996 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5998 /* if we hit starting position, add initial plies */
5999 if( count == backwardMostMove )
6000 count -= initialRulePlies;
6001 count = forwardMostMove - count;
6003 epStatus[forwardMostMove] = EP_RULE_DRAW;
6004 /* this is used to judge if draw claims are legal */
6005 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6006 SendToProgram("force\n", cps->other); // suppress reply
6007 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6008 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6009 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6013 /* if draw offer is pending, treat it as a draw claim
6014 * when draw condition present, to allow engines a way to
6015 * claim draws before making their move to avoid a race
6016 * condition occurring after their move
6018 if( cps->other->offeredDraw || cps->offeredDraw ) {
6020 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6021 p = "Draw claim: 50-move rule";
6022 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6023 p = "Draw claim: 3-fold repetition";
6024 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6025 p = "Draw claim: insufficient mating material";
6027 SendToProgram("force\n", cps->other); // suppress reply
6028 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6029 GameEnds( GameIsDrawn, p, GE_XBOARD );
6030 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6036 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6037 SendToProgram("force\n", cps->other); // suppress reply
6038 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6039 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6041 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6048 if (gameMode == TwoMachinesPlay) {
6049 /* [HGM] relaying draw offers moved to after reception of move */
6050 /* and interpreting offer as claim if it brings draw condition */
6051 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6052 SendToProgram("draw\n", cps->other);
6054 if (cps->other->sendTime) {
6055 SendTimeRemaining(cps->other,
6056 cps->other->twoMachinesColor[0] == 'w');
6058 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6059 if (firstMove && !bookHit) {
6061 if (cps->other->useColors) {
6062 SendToProgram(cps->other->twoMachinesColor, cps->other);
6064 SendToProgram("go\n", cps->other);
6066 cps->other->maybeThinking = TRUE;
6069 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6071 if (!pausing && appData.ringBellAfterMoves) {
6076 * Reenable menu items that were disabled while
6077 * machine was thinking
6079 if (gameMode != TwoMachinesPlay)
6080 SetUserThinkingEnables();
6082 // [HGM] book: after book hit opponent has received move and is now in force mode
6083 // force the book reply into it, and then fake that it outputted this move by jumping
6084 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6086 static char bookMove[MSG_SIZ]; // a bit generous?
6088 strcpy(bookMove, "move ");
6089 strcat(bookMove, bookHit);
6092 programStats.nodes = programStats.depth = programStats.time =
6093 programStats.score = programStats.got_only_move = 0;
6094 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6096 if(cps->lastPing != cps->lastPong) {
6097 savedMessage = message; // args for deferred call
6099 ScheduleDelayedEvent(DeferredBookMove, 10);
6108 /* Set special modes for chess engines. Later something general
6109 * could be added here; for now there is just one kludge feature,
6110 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6111 * when "xboard" is given as an interactive command.
6113 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6114 cps->useSigint = FALSE;
6115 cps->useSigterm = FALSE;
6118 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6119 * want this, I was asked to put it in, and obliged.
6121 if (!strncmp(message, "setboard ", 9)) {
6122 Board initial_position; int i;
6124 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6126 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6127 DisplayError(_("Bad FEN received from engine"), 0);
6130 Reset(FALSE, FALSE);
6131 CopyBoard(boards[0], initial_position);
6132 initialRulePlies = FENrulePlies;
6133 epStatus[0] = FENepStatus;
6134 for( i=0; i<nrCastlingRights; i++ )
6135 castlingRights[0][i] = FENcastlingRights[i];
6136 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6137 else gameMode = MachinePlaysBlack;
6138 DrawPosition(FALSE, boards[currentMove]);
6144 * Look for communication commands
6146 if (!strncmp(message, "telluser ", 9)) {
6147 DisplayNote(message + 9);
6150 if (!strncmp(message, "tellusererror ", 14)) {
6151 DisplayError(message + 14, 0);
6154 if (!strncmp(message, "tellopponent ", 13)) {
6155 if (appData.icsActive) {
6157 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6161 DisplayNote(message + 13);
6165 if (!strncmp(message, "tellothers ", 11)) {
6166 if (appData.icsActive) {
6168 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6174 if (!strncmp(message, "tellall ", 8)) {
6175 if (appData.icsActive) {
6177 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6181 DisplayNote(message + 8);
6185 if (strncmp(message, "warning", 7) == 0) {
6186 /* Undocumented feature, use tellusererror in new code */
6187 DisplayError(message, 0);
6190 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6191 strcpy(realname, cps->tidy);
6192 strcat(realname, " query");
6193 AskQuestion(realname, buf2, buf1, cps->pr);
6196 /* Commands from the engine directly to ICS. We don't allow these to be
6197 * sent until we are logged on. Crafty kibitzes have been known to
6198 * interfere with the login process.
6201 if (!strncmp(message, "tellics ", 8)) {
6202 SendToICS(message + 8);
6206 if (!strncmp(message, "tellicsnoalias ", 15)) {
6207 SendToICS(ics_prefix);
6208 SendToICS(message + 15);
6212 /* The following are for backward compatibility only */
6213 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6214 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6215 SendToICS(ics_prefix);
6221 if (strncmp(message, "feature ", 8) == 0) {
6222 ParseFeatures(message+8, cps);
6224 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6228 * If the move is illegal, cancel it and redraw the board.
6229 * Also deal with other error cases. Matching is rather loose
6230 * here to accommodate engines written before the spec.
6232 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6233 strncmp(message, "Error", 5) == 0) {
6234 if (StrStr(message, "name") ||
6235 StrStr(message, "rating") || StrStr(message, "?") ||
6236 StrStr(message, "result") || StrStr(message, "board") ||
6237 StrStr(message, "bk") || StrStr(message, "computer") ||
6238 StrStr(message, "variant") || StrStr(message, "hint") ||
6239 StrStr(message, "random") || StrStr(message, "depth") ||
6240 StrStr(message, "accepted")) {
6243 if (StrStr(message, "protover")) {
6244 /* Program is responding to input, so it's apparently done
6245 initializing, and this error message indicates it is
6246 protocol version 1. So we don't need to wait any longer
6247 for it to initialize and send feature commands. */
6248 FeatureDone(cps, 1);
6249 cps->protocolVersion = 1;
6252 cps->maybeThinking = FALSE;
6254 if (StrStr(message, "draw")) {
6255 /* Program doesn't have "draw" command */
6256 cps->sendDrawOffers = 0;
6259 if (cps->sendTime != 1 &&
6260 (StrStr(message, "time") || StrStr(message, "otim"))) {
6261 /* Program apparently doesn't have "time" or "otim" command */
6265 if (StrStr(message, "analyze")) {
6266 cps->analysisSupport = FALSE;
6267 cps->analyzing = FALSE;
6269 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6270 DisplayError(buf2, 0);
6273 if (StrStr(message, "(no matching move)st")) {
6274 /* Special kludge for GNU Chess 4 only */
6275 cps->stKludge = TRUE;
6276 SendTimeControl(cps, movesPerSession, timeControl,
6277 timeIncrement, appData.searchDepth,
6281 if (StrStr(message, "(no matching move)sd")) {
6282 /* Special kludge for GNU Chess 4 only */
6283 cps->sdKludge = TRUE;
6284 SendTimeControl(cps, movesPerSession, timeControl,
6285 timeIncrement, appData.searchDepth,
6289 if (!StrStr(message, "llegal")) {
6292 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6293 gameMode == IcsIdle) return;
6294 if (forwardMostMove <= backwardMostMove) return;
6296 /* Following removed: it caused a bug where a real illegal move
6297 message in analyze mored would be ignored. */
6298 if (cps == &first && programStats.ok_to_send == 0) {
6299 /* Bogus message from Crafty responding to "." This filtering
6300 can miss some of the bad messages, but fortunately the bug
6301 is fixed in current Crafty versions, so it doesn't matter. */
6305 if (pausing) PauseEvent();
6306 if (gameMode == PlayFromGameFile) {
6307 /* Stop reading this game file */
6308 gameMode = EditGame;
6311 currentMove = --forwardMostMove;
6312 DisplayMove(currentMove-1); /* before DisplayMoveError */
6314 DisplayBothClocks();
6315 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6316 parseList[currentMove], cps->which);
6317 DisplayMoveError(buf1);
6318 DrawPosition(FALSE, boards[currentMove]);
6320 /* [HGM] illegal-move claim should forfeit game when Xboard */
6321 /* only passes fully legal moves */
6322 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6323 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6324 "False illegal-move claim", GE_XBOARD );
6328 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6329 /* Program has a broken "time" command that
6330 outputs a string not ending in newline.
6336 * If chess program startup fails, exit with an error message.
6337 * Attempts to recover here are futile.
6339 if ((StrStr(message, "unknown host") != NULL)
6340 || (StrStr(message, "No remote directory") != NULL)
6341 || (StrStr(message, "not found") != NULL)
6342 || (StrStr(message, "No such file") != NULL)
6343 || (StrStr(message, "can't alloc") != NULL)
6344 || (StrStr(message, "Permission denied") != NULL)) {
6346 cps->maybeThinking = FALSE;
6347 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6348 cps->which, cps->program, cps->host, message);
6349 RemoveInputSource(cps->isr);
6350 DisplayFatalError(buf1, 0, 1);
6355 * Look for hint output
6357 if (sscanf(message, "Hint: %s", buf1) == 1) {
6358 if (cps == &first && hintRequested) {
6359 hintRequested = FALSE;
6360 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6361 &fromX, &fromY, &toX, &toY, &promoChar)) {
6362 (void) CoordsToAlgebraic(boards[forwardMostMove],
6363 PosFlags(forwardMostMove), EP_UNKNOWN,
6364 fromY, fromX, toY, toX, promoChar, buf1);
6365 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6366 DisplayInformation(buf2);
6368 /* Hint move could not be parsed!? */
6369 snprintf(buf2, sizeof(buf2),
6370 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6372 DisplayError(buf2, 0);
6375 strcpy(lastHint, buf1);
6381 * Ignore other messages if game is not in progress
6383 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6384 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6387 * look for win, lose, draw, or draw offer
6389 if (strncmp(message, "1-0", 3) == 0) {
6390 char *p, *q, *r = "";
6391 p = strchr(message, '{');
6399 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6401 } else if (strncmp(message, "0-1", 3) == 0) {
6402 char *p, *q, *r = "";
6403 p = strchr(message, '{');
6411 /* Kludge for Arasan 4.1 bug */
6412 if (strcmp(r, "Black resigns") == 0) {
6413 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6416 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6418 } else if (strncmp(message, "1/2", 3) == 0) {
6419 char *p, *q, *r = "";
6420 p = strchr(message, '{');
6429 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6432 } else if (strncmp(message, "White resign", 12) == 0) {
6433 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6435 } else if (strncmp(message, "Black resign", 12) == 0) {
6436 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6438 } else if (strncmp(message, "White matches", 13) == 0 ||
6439 strncmp(message, "Black matches", 13) == 0 ) {
6440 /* [HGM] ignore GNUShogi noises */
6442 } else if (strncmp(message, "White", 5) == 0 &&
6443 message[5] != '(' &&
6444 StrStr(message, "Black") == NULL) {
6445 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6447 } else if (strncmp(message, "Black", 5) == 0 &&
6448 message[5] != '(') {
6449 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6451 } else if (strcmp(message, "resign") == 0 ||
6452 strcmp(message, "computer resigns") == 0) {
6454 case MachinePlaysBlack:
6455 case IcsPlayingBlack:
6456 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6458 case MachinePlaysWhite:
6459 case IcsPlayingWhite:
6460 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6462 case TwoMachinesPlay:
6463 if (cps->twoMachinesColor[0] == 'w')
6464 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6466 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6473 } else if (strncmp(message, "opponent mates", 14) == 0) {
6475 case MachinePlaysBlack:
6476 case IcsPlayingBlack:
6477 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6479 case MachinePlaysWhite:
6480 case IcsPlayingWhite:
6481 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6483 case TwoMachinesPlay:
6484 if (cps->twoMachinesColor[0] == 'w')
6485 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6487 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6494 } else if (strncmp(message, "computer mates", 14) == 0) {
6496 case MachinePlaysBlack:
6497 case IcsPlayingBlack:
6498 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6500 case MachinePlaysWhite:
6501 case IcsPlayingWhite:
6502 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6504 case TwoMachinesPlay:
6505 if (cps->twoMachinesColor[0] == 'w')
6506 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6508 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6515 } else if (strncmp(message, "checkmate", 9) == 0) {
6516 if (WhiteOnMove(forwardMostMove)) {
6517 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6519 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6522 } else if (strstr(message, "Draw") != NULL ||
6523 strstr(message, "game is a draw") != NULL) {
6524 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6526 } else if (strstr(message, "offer") != NULL &&
6527 strstr(message, "draw") != NULL) {
6529 if (appData.zippyPlay && first.initDone) {
6530 /* Relay offer to ICS */
6531 SendToICS(ics_prefix);
6532 SendToICS("draw\n");
6535 cps->offeredDraw = 2; /* valid until this engine moves twice */
6536 if (gameMode == TwoMachinesPlay) {
6537 if (cps->other->offeredDraw) {
6538 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6539 /* [HGM] in two-machine mode we delay relaying draw offer */
6540 /* until after we also have move, to see if it is really claim */
6544 if (cps->other->sendDrawOffers) {
6545 SendToProgram("draw\n", cps->other);
6549 } else if (gameMode == MachinePlaysWhite ||
6550 gameMode == MachinePlaysBlack) {
6551 if (userOfferedDraw) {
6552 DisplayInformation(_("Machine accepts your draw offer"));
6553 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6555 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6562 * Look for thinking output
6564 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6565 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6567 int plylev, mvleft, mvtot, curscore, time;
6568 char mvname[MOVE_LEN];
6572 int prefixHint = FALSE;
6573 mvname[0] = NULLCHAR;
6576 case MachinePlaysBlack:
6577 case IcsPlayingBlack:
6578 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6580 case MachinePlaysWhite:
6581 case IcsPlayingWhite:
6582 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6587 case IcsObserving: /* [DM] icsEngineAnalyze */
6588 if (!appData.icsEngineAnalyze) ignore = TRUE;
6590 case TwoMachinesPlay:
6591 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6602 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6603 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6605 if (plyext != ' ' && plyext != '\t') {
6609 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6610 if( cps->scoreIsAbsolute &&
6611 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6613 curscore = -curscore;
6617 programStats.depth = plylev;
6618 programStats.nodes = nodes;
6619 programStats.time = time;
6620 programStats.score = curscore;
6621 programStats.got_only_move = 0;
6623 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6626 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6627 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6628 if(WhiteOnMove(forwardMostMove))
6629 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6630 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6633 /* Buffer overflow protection */
6634 if (buf1[0] != NULLCHAR) {
6635 if (strlen(buf1) >= sizeof(programStats.movelist)
6636 && appData.debugMode) {
6638 "PV is too long; using the first %d bytes.\n",
6639 sizeof(programStats.movelist) - 1);
6642 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6644 sprintf(programStats.movelist, " no PV\n");
6647 if (programStats.seen_stat) {
6648 programStats.ok_to_send = 1;
6651 if (strchr(programStats.movelist, '(') != NULL) {
6652 programStats.line_is_book = 1;
6653 programStats.nr_moves = 0;
6654 programStats.moves_left = 0;
6656 programStats.line_is_book = 0;
6659 SendProgramStatsToFrontend( cps, &programStats );
6662 [AS] Protect the thinkOutput buffer from overflow... this
6663 is only useful if buf1 hasn't overflowed first!
6665 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6667 (gameMode == TwoMachinesPlay ?
6668 ToUpper(cps->twoMachinesColor[0]) : ' '),
6669 ((double) curscore) / 100.0,
6670 prefixHint ? lastHint : "",
6671 prefixHint ? " " : "" );
6673 if( buf1[0] != NULLCHAR ) {
6674 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6676 if( strlen(buf1) > max_len ) {
6677 if( appData.debugMode) {
6678 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6680 buf1[max_len+1] = '\0';
6683 strcat( thinkOutput, buf1 );
6686 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6687 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6688 DisplayMove(currentMove - 1);
6693 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6694 /* crafty (9.25+) says "(only move) <move>"
6695 * if there is only 1 legal move
6697 sscanf(p, "(only move) %s", buf1);
6698 sprintf(thinkOutput, "%s (only move)", buf1);
6699 sprintf(programStats.movelist, "%s (only move)", buf1);
6700 programStats.depth = 1;
6701 programStats.nr_moves = 1;
6702 programStats.moves_left = 1;
6703 programStats.nodes = 1;
6704 programStats.time = 1;
6705 programStats.got_only_move = 1;
6707 /* Not really, but we also use this member to
6708 mean "line isn't going to change" (Crafty
6709 isn't searching, so stats won't change) */
6710 programStats.line_is_book = 1;
6712 SendProgramStatsToFrontend( cps, &programStats );
6714 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6715 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6716 DisplayMove(currentMove - 1);
6720 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6721 &time, &nodes, &plylev, &mvleft,
6722 &mvtot, mvname) >= 5) {
6723 /* The stat01: line is from Crafty (9.29+) in response
6724 to the "." command */
6725 programStats.seen_stat = 1;
6726 cps->maybeThinking = TRUE;
6728 if (programStats.got_only_move || !appData.periodicUpdates)
6731 programStats.depth = plylev;
6732 programStats.time = time;
6733 programStats.nodes = nodes;
6734 programStats.moves_left = mvleft;
6735 programStats.nr_moves = mvtot;
6736 strcpy(programStats.move_name, mvname);
6737 programStats.ok_to_send = 1;
6738 programStats.movelist[0] = '\0';
6740 SendProgramStatsToFrontend( cps, &programStats );
6745 } else if (strncmp(message,"++",2) == 0) {
6746 /* Crafty 9.29+ outputs this */
6747 programStats.got_fail = 2;
6750 } else if (strncmp(message,"--",2) == 0) {
6751 /* Crafty 9.29+ outputs this */
6752 programStats.got_fail = 1;
6755 } else if (thinkOutput[0] != NULLCHAR &&
6756 strncmp(message, " ", 4) == 0) {
6757 unsigned message_len;
6760 while (*p && *p == ' ') p++;
6762 message_len = strlen( p );
6764 /* [AS] Avoid buffer overflow */
6765 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6766 strcat(thinkOutput, " ");
6767 strcat(thinkOutput, p);
6770 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6771 strcat(programStats.movelist, " ");
6772 strcat(programStats.movelist, p);
6775 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6776 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6777 DisplayMove(currentMove - 1);
6786 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6787 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6789 ChessProgramStats cpstats;
6791 if (plyext != ' ' && plyext != '\t') {
6795 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6796 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6797 curscore = -curscore;
6800 cpstats.depth = plylev;
6801 cpstats.nodes = nodes;
6802 cpstats.time = time;
6803 cpstats.score = curscore;
6804 cpstats.got_only_move = 0;
6805 cpstats.movelist[0] = '\0';
6807 if (buf1[0] != NULLCHAR) {
6808 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6811 cpstats.ok_to_send = 0;
6812 cpstats.line_is_book = 0;
6813 cpstats.nr_moves = 0;
6814 cpstats.moves_left = 0;
6816 SendProgramStatsToFrontend( cps, &cpstats );
6823 /* Parse a game score from the character string "game", and
6824 record it as the history of the current game. The game
6825 score is NOT assumed to start from the standard position.
6826 The display is not updated in any way.
6829 ParseGameHistory(game)
6833 int fromX, fromY, toX, toY, boardIndex;
6838 if (appData.debugMode)
6839 fprintf(debugFP, "Parsing game history: %s\n", game);
6841 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6842 gameInfo.site = StrSave(appData.icsHost);
6843 gameInfo.date = PGNDate();
6844 gameInfo.round = StrSave("-");
6846 /* Parse out names of players */
6847 while (*game == ' ') game++;
6849 while (*game != ' ') *p++ = *game++;
6851 gameInfo.white = StrSave(buf);
6852 while (*game == ' ') game++;
6854 while (*game != ' ' && *game != '\n') *p++ = *game++;
6856 gameInfo.black = StrSave(buf);
6859 boardIndex = blackPlaysFirst ? 1 : 0;
6862 yyboardindex = boardIndex;
6863 moveType = (ChessMove) yylex();
6865 case IllegalMove: /* maybe suicide chess, etc. */
6866 if (appData.debugMode) {
6867 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6868 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6869 setbuf(debugFP, NULL);
6871 case WhitePromotionChancellor:
6872 case BlackPromotionChancellor:
6873 case WhitePromotionArchbishop:
6874 case BlackPromotionArchbishop:
6875 case WhitePromotionQueen:
6876 case BlackPromotionQueen:
6877 case WhitePromotionRook:
6878 case BlackPromotionRook:
6879 case WhitePromotionBishop:
6880 case BlackPromotionBishop:
6881 case WhitePromotionKnight:
6882 case BlackPromotionKnight:
6883 case WhitePromotionKing:
6884 case BlackPromotionKing:
6886 case WhiteCapturesEnPassant:
6887 case BlackCapturesEnPassant:
6888 case WhiteKingSideCastle:
6889 case WhiteQueenSideCastle:
6890 case BlackKingSideCastle:
6891 case BlackQueenSideCastle:
6892 case WhiteKingSideCastleWild:
6893 case WhiteQueenSideCastleWild:
6894 case BlackKingSideCastleWild:
6895 case BlackQueenSideCastleWild:
6897 case WhiteHSideCastleFR:
6898 case WhiteASideCastleFR:
6899 case BlackHSideCastleFR:
6900 case BlackASideCastleFR:
6902 fromX = currentMoveString[0] - AAA;
6903 fromY = currentMoveString[1] - ONE;
6904 toX = currentMoveString[2] - AAA;
6905 toY = currentMoveString[3] - ONE;
6906 promoChar = currentMoveString[4];
6910 fromX = moveType == WhiteDrop ?
6911 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6912 (int) CharToPiece(ToLower(currentMoveString[0]));
6914 toX = currentMoveString[2] - AAA;
6915 toY = currentMoveString[3] - ONE;
6916 promoChar = NULLCHAR;
6920 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6921 if (appData.debugMode) {
6922 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6923 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6924 setbuf(debugFP, NULL);
6926 DisplayError(buf, 0);
6928 case ImpossibleMove:
6930 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6931 if (appData.debugMode) {
6932 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6933 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6934 setbuf(debugFP, NULL);
6936 DisplayError(buf, 0);
6938 case (ChessMove) 0: /* end of file */
6939 if (boardIndex < backwardMostMove) {
6940 /* Oops, gap. How did that happen? */
6941 DisplayError(_("Gap in move list"), 0);
6944 backwardMostMove = blackPlaysFirst ? 1 : 0;
6945 if (boardIndex > forwardMostMove) {
6946 forwardMostMove = boardIndex;
6950 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6951 strcat(parseList[boardIndex-1], " ");
6952 strcat(parseList[boardIndex-1], yy_text);
6964 case GameUnfinished:
6965 if (gameMode == IcsExamining) {
6966 if (boardIndex < backwardMostMove) {
6967 /* Oops, gap. How did that happen? */
6970 backwardMostMove = blackPlaysFirst ? 1 : 0;
6973 gameInfo.result = moveType;
6974 p = strchr(yy_text, '{');
6975 if (p == NULL) p = strchr(yy_text, '(');
6978 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6980 q = strchr(p, *p == '{' ? '}' : ')');
6981 if (q != NULL) *q = NULLCHAR;
6984 gameInfo.resultDetails = StrSave(p);
6987 if (boardIndex >= forwardMostMove &&
6988 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6989 backwardMostMove = blackPlaysFirst ? 1 : 0;
6992 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6993 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6994 parseList[boardIndex]);
6995 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6996 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6997 /* currentMoveString is set as a side-effect of yylex */
6998 strcpy(moveList[boardIndex], currentMoveString);
6999 strcat(moveList[boardIndex], "\n");
7001 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7002 castlingRights[boardIndex], &epStatus[boardIndex]);
7003 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7004 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7010 if(gameInfo.variant != VariantShogi)
7011 strcat(parseList[boardIndex - 1], "+");
7015 strcat(parseList[boardIndex - 1], "#");
7022 /* Apply a move to the given board */
7024 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7025 int fromX, fromY, toX, toY;
7031 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7033 /* [HGM] compute & store e.p. status and castling rights for new position */
7034 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7037 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7041 if( board[toY][toX] != EmptySquare )
7044 if( board[fromY][fromX] == WhitePawn ) {
7045 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7048 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7049 gameInfo.variant != VariantBerolina || toX < fromX)
7050 *ep = toX | berolina;
7051 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7052 gameInfo.variant != VariantBerolina || toX > fromX)
7056 if( board[fromY][fromX] == BlackPawn ) {
7057 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7059 if( toY-fromY== -2) {
7060 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7061 gameInfo.variant != VariantBerolina || toX < fromX)
7062 *ep = toX | berolina;
7063 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7064 gameInfo.variant != VariantBerolina || toX > fromX)
7069 for(i=0; i<nrCastlingRights; i++) {
7070 if(castling[i] == fromX && castlingRank[i] == fromY ||
7071 castling[i] == toX && castlingRank[i] == toY
7072 ) castling[i] = -1; // revoke for moved or captured piece
7077 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7078 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7079 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7081 if (fromX == toX && fromY == toY) return;
7083 if (fromY == DROP_RANK) {
7085 piece = board[toY][toX] = (ChessSquare) fromX;
7087 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7088 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7089 if(gameInfo.variant == VariantKnightmate)
7090 king += (int) WhiteUnicorn - (int) WhiteKing;
7092 /* Code added by Tord: */
7093 /* FRC castling assumed when king captures friendly rook. */
7094 if (board[fromY][fromX] == WhiteKing &&
7095 board[toY][toX] == WhiteRook) {
7096 board[fromY][fromX] = EmptySquare;
7097 board[toY][toX] = EmptySquare;
7099 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7101 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7103 } else if (board[fromY][fromX] == BlackKing &&
7104 board[toY][toX] == BlackRook) {
7105 board[fromY][fromX] = EmptySquare;
7106 board[toY][toX] = EmptySquare;
7108 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7110 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7112 /* End of code added by Tord */
7114 } else if (board[fromY][fromX] == king
7115 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7116 && toY == fromY && toX > fromX+1) {
7117 board[fromY][fromX] = EmptySquare;
7118 board[toY][toX] = king;
7119 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7120 board[fromY][BOARD_RGHT-1] = EmptySquare;
7121 } else if (board[fromY][fromX] == king
7122 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7123 && toY == fromY && toX < fromX-1) {
7124 board[fromY][fromX] = EmptySquare;
7125 board[toY][toX] = king;
7126 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7127 board[fromY][BOARD_LEFT] = EmptySquare;
7128 } else if (board[fromY][fromX] == WhitePawn
7129 && toY == BOARD_HEIGHT-1
7130 && gameInfo.variant != VariantXiangqi
7132 /* white pawn promotion */
7133 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7134 if (board[toY][toX] == EmptySquare) {
7135 board[toY][toX] = WhiteQueen;
7137 if(gameInfo.variant==VariantBughouse ||
7138 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7139 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7140 board[fromY][fromX] = EmptySquare;
7141 } else if ((fromY == BOARD_HEIGHT-4)
7143 && gameInfo.variant != VariantXiangqi
7144 && gameInfo.variant != VariantBerolina
7145 && (board[fromY][fromX] == WhitePawn)
7146 && (board[toY][toX] == EmptySquare)) {
7147 board[fromY][fromX] = EmptySquare;
7148 board[toY][toX] = WhitePawn;
7149 captured = board[toY - 1][toX];
7150 board[toY - 1][toX] = EmptySquare;
7151 } else if ((fromY == BOARD_HEIGHT-4)
7153 && gameInfo.variant == VariantBerolina
7154 && (board[fromY][fromX] == WhitePawn)
7155 && (board[toY][toX] == EmptySquare)) {
7156 board[fromY][fromX] = EmptySquare;
7157 board[toY][toX] = WhitePawn;
7158 if(oldEP & EP_BEROLIN_A) {
7159 captured = board[fromY][fromX-1];
7160 board[fromY][fromX-1] = EmptySquare;
7161 }else{ captured = board[fromY][fromX+1];
7162 board[fromY][fromX+1] = EmptySquare;
7164 } else if (board[fromY][fromX] == king
7165 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7166 && toY == fromY && toX > fromX+1) {
7167 board[fromY][fromX] = EmptySquare;
7168 board[toY][toX] = king;
7169 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7170 board[fromY][BOARD_RGHT-1] = EmptySquare;
7171 } else if (board[fromY][fromX] == king
7172 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7173 && toY == fromY && toX < fromX-1) {
7174 board[fromY][fromX] = EmptySquare;
7175 board[toY][toX] = king;
7176 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7177 board[fromY][BOARD_LEFT] = EmptySquare;
7178 } else if (fromY == 7 && fromX == 3
7179 && board[fromY][fromX] == BlackKing
7180 && toY == 7 && toX == 5) {
7181 board[fromY][fromX] = EmptySquare;
7182 board[toY][toX] = BlackKing;
7183 board[fromY][7] = EmptySquare;
7184 board[toY][4] = BlackRook;
7185 } else if (fromY == 7 && fromX == 3
7186 && board[fromY][fromX] == BlackKing
7187 && toY == 7 && toX == 1) {
7188 board[fromY][fromX] = EmptySquare;
7189 board[toY][toX] = BlackKing;
7190 board[fromY][0] = EmptySquare;
7191 board[toY][2] = BlackRook;
7192 } else if (board[fromY][fromX] == BlackPawn
7194 && gameInfo.variant != VariantXiangqi
7196 /* black pawn promotion */
7197 board[0][toX] = CharToPiece(ToLower(promoChar));
7198 if (board[0][toX] == EmptySquare) {
7199 board[0][toX] = BlackQueen;
7201 if(gameInfo.variant==VariantBughouse ||
7202 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7203 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7204 board[fromY][fromX] = EmptySquare;
7205 } else if ((fromY == 3)
7207 && gameInfo.variant != VariantXiangqi
7208 && gameInfo.variant != VariantBerolina
7209 && (board[fromY][fromX] == BlackPawn)
7210 && (board[toY][toX] == EmptySquare)) {
7211 board[fromY][fromX] = EmptySquare;
7212 board[toY][toX] = BlackPawn;
7213 captured = board[toY + 1][toX];
7214 board[toY + 1][toX] = EmptySquare;
7215 } else if ((fromY == 3)
7217 && gameInfo.variant == VariantBerolina
7218 && (board[fromY][fromX] == BlackPawn)
7219 && (board[toY][toX] == EmptySquare)) {
7220 board[fromY][fromX] = EmptySquare;
7221 board[toY][toX] = BlackPawn;
7222 if(oldEP & EP_BEROLIN_A) {
7223 captured = board[fromY][fromX-1];
7224 board[fromY][fromX-1] = EmptySquare;
7225 }else{ captured = board[fromY][fromX+1];
7226 board[fromY][fromX+1] = EmptySquare;
7229 board[toY][toX] = board[fromY][fromX];
7230 board[fromY][fromX] = EmptySquare;
7233 /* [HGM] now we promote for Shogi, if needed */
7234 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7235 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7238 if (gameInfo.holdingsWidth != 0) {
7240 /* !!A lot more code needs to be written to support holdings */
7241 /* [HGM] OK, so I have written it. Holdings are stored in the */
7242 /* penultimate board files, so they are automaticlly stored */
7243 /* in the game history. */
7244 if (fromY == DROP_RANK) {
7245 /* Delete from holdings, by decreasing count */
7246 /* and erasing image if necessary */
7248 if(p < (int) BlackPawn) { /* white drop */
7249 p -= (int)WhitePawn;
7250 if(p >= gameInfo.holdingsSize) p = 0;
7251 if(--board[p][BOARD_WIDTH-2] == 0)
7252 board[p][BOARD_WIDTH-1] = EmptySquare;
7253 } else { /* black drop */
7254 p -= (int)BlackPawn;
7255 if(p >= gameInfo.holdingsSize) p = 0;
7256 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7257 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7260 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7261 && gameInfo.variant != VariantBughouse ) {
7262 /* [HGM] holdings: Add to holdings, if holdings exist */
7263 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7264 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7265 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7268 if (p >= (int) BlackPawn) {
7269 p -= (int)BlackPawn;
7270 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7271 /* in Shogi restore piece to its original first */
7272 captured = (ChessSquare) (DEMOTED captured);
7275 p = PieceToNumber((ChessSquare)p);
7276 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7277 board[p][BOARD_WIDTH-2]++;
7278 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7280 p -= (int)WhitePawn;
7281 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7282 captured = (ChessSquare) (DEMOTED captured);
7285 p = PieceToNumber((ChessSquare)p);
7286 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7287 board[BOARD_HEIGHT-1-p][1]++;
7288 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7292 } else if (gameInfo.variant == VariantAtomic) {
7293 if (captured != EmptySquare) {
7295 for (y = toY-1; y <= toY+1; y++) {
7296 for (x = toX-1; x <= toX+1; x++) {
7297 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7298 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7299 board[y][x] = EmptySquare;
7303 board[toY][toX] = EmptySquare;
7306 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7307 /* [HGM] Shogi promotions */
7308 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7311 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7312 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7313 // [HGM] superchess: take promotion piece out of holdings
7314 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7315 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7316 if(!--board[k][BOARD_WIDTH-2])
7317 board[k][BOARD_WIDTH-1] = EmptySquare;
7319 if(!--board[BOARD_HEIGHT-1-k][1])
7320 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7326 /* Updates forwardMostMove */
7328 MakeMove(fromX, fromY, toX, toY, promoChar)
7329 int fromX, fromY, toX, toY;
7332 // forwardMostMove++; // [HGM] bare: moved downstream
7334 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7335 int timeLeft; static int lastLoadFlag=0; int king, piece;
7336 piece = boards[forwardMostMove][fromY][fromX];
7337 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7338 if(gameInfo.variant == VariantKnightmate)
7339 king += (int) WhiteUnicorn - (int) WhiteKing;
7340 if(forwardMostMove == 0) {
7342 fprintf(serverMoves, "%s;", second.tidy);
7343 fprintf(serverMoves, "%s;", first.tidy);
7344 if(!blackPlaysFirst)
7345 fprintf(serverMoves, "%s;", second.tidy);
7346 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7347 lastLoadFlag = loadFlag;
7349 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7350 // print castling suffix
7351 if( toY == fromY && piece == king ) {
7353 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7355 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7358 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7359 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7360 boards[forwardMostMove][toY][toX] == EmptySquare
7362 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7364 if(promoChar != NULLCHAR)
7365 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7367 fprintf(serverMoves, "/%d/%d",
7368 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7369 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7370 else timeLeft = blackTimeRemaining/1000;
7371 fprintf(serverMoves, "/%d", timeLeft);
7373 fflush(serverMoves);
7376 if (forwardMostMove+1 >= MAX_MOVES) {
7377 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7382 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7383 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7384 if (commentList[forwardMostMove+1] != NULL) {
7385 free(commentList[forwardMostMove+1]);
7386 commentList[forwardMostMove+1] = NULL;
7388 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7389 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7390 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7391 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7392 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7393 gameInfo.result = GameUnfinished;
7394 if (gameInfo.resultDetails != NULL) {
7395 free(gameInfo.resultDetails);
7396 gameInfo.resultDetails = NULL;
7398 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7399 moveList[forwardMostMove - 1]);
7400 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7401 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7402 fromY, fromX, toY, toX, promoChar,
7403 parseList[forwardMostMove - 1]);
7404 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7405 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7406 castlingRights[forwardMostMove]) ) {
7412 if(gameInfo.variant != VariantShogi)
7413 strcat(parseList[forwardMostMove - 1], "+");
7417 strcat(parseList[forwardMostMove - 1], "#");
7420 if (appData.debugMode) {
7421 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7426 /* Updates currentMove if not pausing */
7428 ShowMove(fromX, fromY, toX, toY)
7430 int instant = (gameMode == PlayFromGameFile) ?
7431 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7432 if(appData.noGUI) return;
7433 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7435 if (forwardMostMove == currentMove + 1) {
7436 AnimateMove(boards[forwardMostMove - 1],
7437 fromX, fromY, toX, toY);
7439 if (appData.highlightLastMove) {
7440 SetHighlights(fromX, fromY, toX, toY);
7443 currentMove = forwardMostMove;
7446 if (instant) return;
7448 DisplayMove(currentMove - 1);
7449 DrawPosition(FALSE, boards[currentMove]);
7450 DisplayBothClocks();
7451 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7454 void SendEgtPath(ChessProgramState *cps)
7455 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7456 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7458 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7461 char c, *q = name+1, *r, *s;
7463 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7464 while(*p && *p != ',') *q++ = *p++;
7466 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7467 strcmp(name, ",nalimov:") == 0 ) {
7468 // take nalimov path from the menu-changeable option first, if it is defined
7469 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7470 SendToProgram(buf,cps); // send egtbpath command for nalimov
7472 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7473 (s = StrStr(appData.egtFormats, name)) != NULL) {
7474 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7475 s = r = StrStr(s, ":") + 1; // beginning of path info
7476 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7477 c = *r; *r = 0; // temporarily null-terminate path info
7478 *--q = 0; // strip of trailig ':' from name
7479 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7481 SendToProgram(buf,cps); // send egtbpath command for this format
7483 if(*p == ',') p++; // read away comma to position for next format name
7488 InitChessProgram(cps, setup)
7489 ChessProgramState *cps;
7490 int setup; /* [HGM] needed to setup FRC opening position */
7492 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7493 if (appData.noChessProgram) return;
7494 hintRequested = FALSE;
7495 bookRequested = FALSE;
7497 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7498 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7499 if(cps->memSize) { /* [HGM] memory */
7500 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7501 SendToProgram(buf, cps);
7503 SendEgtPath(cps); /* [HGM] EGT */
7504 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7505 sprintf(buf, "cores %d\n", appData.smpCores);
7506 SendToProgram(buf, cps);
7509 SendToProgram(cps->initString, cps);
7510 if (gameInfo.variant != VariantNormal &&
7511 gameInfo.variant != VariantLoadable
7512 /* [HGM] also send variant if board size non-standard */
7513 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7515 char *v = VariantName(gameInfo.variant);
7516 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7517 /* [HGM] in protocol 1 we have to assume all variants valid */
7518 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7519 DisplayFatalError(buf, 0, 1);
7523 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7524 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7525 if( gameInfo.variant == VariantXiangqi )
7526 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7527 if( gameInfo.variant == VariantShogi )
7528 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7529 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7530 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7531 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7532 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7533 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7534 if( gameInfo.variant == VariantCourier )
7535 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7536 if( gameInfo.variant == VariantSuper )
7537 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7538 if( gameInfo.variant == VariantGreat )
7539 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7542 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7543 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7544 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7545 if(StrStr(cps->variants, b) == NULL) {
7546 // specific sized variant not known, check if general sizing allowed
7547 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7548 if(StrStr(cps->variants, "boardsize") == NULL) {
7549 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7550 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7551 DisplayFatalError(buf, 0, 1);
7554 /* [HGM] here we really should compare with the maximum supported board size */
7557 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7558 sprintf(buf, "variant %s\n", b);
7559 SendToProgram(buf, cps);
7561 currentlyInitializedVariant = gameInfo.variant;
7563 /* [HGM] send opening position in FRC to first engine */
7565 SendToProgram("force\n", cps);
7567 /* engine is now in force mode! Set flag to wake it up after first move. */
7568 setboardSpoiledMachineBlack = 1;
7572 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7573 SendToProgram(buf, cps);
7575 cps->maybeThinking = FALSE;
7576 cps->offeredDraw = 0;
7577 if (!appData.icsActive) {
7578 SendTimeControl(cps, movesPerSession, timeControl,
7579 timeIncrement, appData.searchDepth,
7582 if (appData.showThinking
7583 // [HGM] thinking: four options require thinking output to be sent
7584 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7586 SendToProgram("post\n", cps);
7588 SendToProgram("hard\n", cps);
7589 if (!appData.ponderNextMove) {
7590 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7591 it without being sure what state we are in first. "hard"
7592 is not a toggle, so that one is OK.
7594 SendToProgram("easy\n", cps);
7597 sprintf(buf, "ping %d\n", ++cps->lastPing);
7598 SendToProgram(buf, cps);
7600 cps->initDone = TRUE;
7605 StartChessProgram(cps)
7606 ChessProgramState *cps;
7611 if (appData.noChessProgram) return;
7612 cps->initDone = FALSE;
7614 if (strcmp(cps->host, "localhost") == 0) {
7615 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7616 } else if (*appData.remoteShell == NULLCHAR) {
7617 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7619 if (*appData.remoteUser == NULLCHAR) {
7620 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7623 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7624 cps->host, appData.remoteUser, cps->program);
7626 err = StartChildProcess(buf, "", &cps->pr);
7630 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7631 DisplayFatalError(buf, err, 1);
7637 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7638 if (cps->protocolVersion > 1) {
7639 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7640 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7641 cps->comboCnt = 0; // and values of combo boxes
7642 SendToProgram(buf, cps);
7644 SendToProgram("xboard\n", cps);
7650 TwoMachinesEventIfReady P((void))
7652 if (first.lastPing != first.lastPong) {
7653 DisplayMessage("", _("Waiting for first chess program"));
7654 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7657 if (second.lastPing != second.lastPong) {
7658 DisplayMessage("", _("Waiting for second chess program"));
7659 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7667 NextMatchGame P((void))
7669 int index; /* [HGM] autoinc: step lod index during match */
7671 if (*appData.loadGameFile != NULLCHAR) {
7672 index = appData.loadGameIndex;
7673 if(index < 0) { // [HGM] autoinc
7674 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7675 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7677 LoadGameFromFile(appData.loadGameFile,
7679 appData.loadGameFile, FALSE);
7680 } else if (*appData.loadPositionFile != NULLCHAR) {
7681 index = appData.loadPositionIndex;
7682 if(index < 0) { // [HGM] autoinc
7683 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7684 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7686 LoadPositionFromFile(appData.loadPositionFile,
7688 appData.loadPositionFile);
7690 TwoMachinesEventIfReady();
7693 void UserAdjudicationEvent( int result )
7695 ChessMove gameResult = GameIsDrawn;
7698 gameResult = WhiteWins;
7700 else if( result < 0 ) {
7701 gameResult = BlackWins;
7704 if( gameMode == TwoMachinesPlay ) {
7705 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7711 GameEnds(result, resultDetails, whosays)
7713 char *resultDetails;
7716 GameMode nextGameMode;
7720 if(endingGame) return; /* [HGM] crash: forbid recursion */
7723 if (appData.debugMode) {
7724 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7725 result, resultDetails ? resultDetails : "(null)", whosays);
7728 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7729 /* If we are playing on ICS, the server decides when the
7730 game is over, but the engine can offer to draw, claim
7734 if (appData.zippyPlay && first.initDone) {
7735 if (result == GameIsDrawn) {
7736 /* In case draw still needs to be claimed */
7737 SendToICS(ics_prefix);
7738 SendToICS("draw\n");
7739 } else if (StrCaseStr(resultDetails, "resign")) {
7740 SendToICS(ics_prefix);
7741 SendToICS("resign\n");
7745 endingGame = 0; /* [HGM] crash */
7749 /* If we're loading the game from a file, stop */
7750 if (whosays == GE_FILE) {
7751 (void) StopLoadGameTimer();
7755 /* Cancel draw offers */
7756 first.offeredDraw = second.offeredDraw = 0;
7758 /* If this is an ICS game, only ICS can really say it's done;
7759 if not, anyone can. */
7760 isIcsGame = (gameMode == IcsPlayingWhite ||
7761 gameMode == IcsPlayingBlack ||
7762 gameMode == IcsObserving ||
7763 gameMode == IcsExamining);
7765 if (!isIcsGame || whosays == GE_ICS) {
7766 /* OK -- not an ICS game, or ICS said it was done */
7768 if (!isIcsGame && !appData.noChessProgram)
7769 SetUserThinkingEnables();
7771 /* [HGM] if a machine claims the game end we verify this claim */
7772 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7773 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7775 ChessMove trueResult = (ChessMove) -1;
7777 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7778 first.twoMachinesColor[0] :
7779 second.twoMachinesColor[0] ;
7781 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7782 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7783 /* [HGM] verify: engine mate claims accepted if they were flagged */
7784 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7786 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7787 /* [HGM] verify: engine mate claims accepted if they were flagged */
7788 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7790 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7791 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7794 // now verify win claims, but not in drop games, as we don't understand those yet
7795 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7796 || gameInfo.variant == VariantGreat) &&
7797 (result == WhiteWins && claimer == 'w' ||
7798 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7799 if (appData.debugMode) {
7800 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7801 result, epStatus[forwardMostMove], forwardMostMove);
7803 if(result != trueResult) {
7804 sprintf(buf, "False win claim: '%s'", resultDetails);
7805 result = claimer == 'w' ? BlackWins : WhiteWins;
7806 resultDetails = buf;
7809 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7810 && (forwardMostMove <= backwardMostMove ||
7811 epStatus[forwardMostMove-1] > EP_DRAWS ||
7812 (claimer=='b')==(forwardMostMove&1))
7814 /* [HGM] verify: draws that were not flagged are false claims */
7815 sprintf(buf, "False draw claim: '%s'", resultDetails);
7816 result = claimer == 'w' ? BlackWins : WhiteWins;
7817 resultDetails = buf;
7819 /* (Claiming a loss is accepted no questions asked!) */
7821 /* [HGM] bare: don't allow bare King to win */
7822 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7823 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7824 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7825 && result != GameIsDrawn)
7826 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7827 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7828 int p = (int)boards[forwardMostMove][i][j] - color;
7829 if(p >= 0 && p <= (int)WhiteKing) k++;
7831 if (appData.debugMode) {
7832 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7833 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7836 result = GameIsDrawn;
7837 sprintf(buf, "%s but bare king", resultDetails);
7838 resultDetails = buf;
7844 if(serverMoves != NULL && !loadFlag) { char c = '=';
7845 if(result==WhiteWins) c = '+';
7846 if(result==BlackWins) c = '-';
7847 if(resultDetails != NULL)
7848 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7850 if (resultDetails != NULL) {
7851 gameInfo.result = result;
7852 gameInfo.resultDetails = StrSave(resultDetails);
7854 /* display last move only if game was not loaded from file */
7855 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7856 DisplayMove(currentMove - 1);
7858 if (forwardMostMove != 0) {
7859 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7860 if (*appData.saveGameFile != NULLCHAR) {
7861 SaveGameToFile(appData.saveGameFile, TRUE);
7862 } else if (appData.autoSaveGames) {
7865 if (*appData.savePositionFile != NULLCHAR) {
7866 SavePositionToFile(appData.savePositionFile);
7871 /* Tell program how game ended in case it is learning */
7872 /* [HGM] Moved this to after saving the PGN, just in case */
7873 /* engine died and we got here through time loss. In that */
7874 /* case we will get a fatal error writing the pipe, which */
7875 /* would otherwise lose us the PGN. */
7876 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7877 /* output during GameEnds should never be fatal anymore */
7878 if (gameMode == MachinePlaysWhite ||
7879 gameMode == MachinePlaysBlack ||
7880 gameMode == TwoMachinesPlay ||
7881 gameMode == IcsPlayingWhite ||
7882 gameMode == IcsPlayingBlack ||
7883 gameMode == BeginningOfGame) {
7885 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7887 if (first.pr != NoProc) {
7888 SendToProgram(buf, &first);
7890 if (second.pr != NoProc &&
7891 gameMode == TwoMachinesPlay) {
7892 SendToProgram(buf, &second);
7897 if (appData.icsActive) {
7898 if (appData.quietPlay &&
7899 (gameMode == IcsPlayingWhite ||
7900 gameMode == IcsPlayingBlack)) {
7901 SendToICS(ics_prefix);
7902 SendToICS("set shout 1\n");
7904 nextGameMode = IcsIdle;
7905 ics_user_moved = FALSE;
7906 /* clean up premove. It's ugly when the game has ended and the
7907 * premove highlights are still on the board.
7911 ClearPremoveHighlights();
7912 DrawPosition(FALSE, boards[currentMove]);
7914 if (whosays == GE_ICS) {
7917 if (gameMode == IcsPlayingWhite)
7919 else if(gameMode == IcsPlayingBlack)
7923 if (gameMode == IcsPlayingBlack)
7925 else if(gameMode == IcsPlayingWhite)
7932 PlayIcsUnfinishedSound();
7935 } else if (gameMode == EditGame ||
7936 gameMode == PlayFromGameFile ||
7937 gameMode == AnalyzeMode ||
7938 gameMode == AnalyzeFile) {
7939 nextGameMode = gameMode;
7941 nextGameMode = EndOfGame;
7946 nextGameMode = gameMode;
7949 if (appData.noChessProgram) {
7950 gameMode = nextGameMode;
7952 endingGame = 0; /* [HGM] crash */
7957 /* Put first chess program into idle state */
7958 if (first.pr != NoProc &&
7959 (gameMode == MachinePlaysWhite ||
7960 gameMode == MachinePlaysBlack ||
7961 gameMode == TwoMachinesPlay ||
7962 gameMode == IcsPlayingWhite ||
7963 gameMode == IcsPlayingBlack ||
7964 gameMode == BeginningOfGame)) {
7965 SendToProgram("force\n", &first);
7966 if (first.usePing) {
7968 sprintf(buf, "ping %d\n", ++first.lastPing);
7969 SendToProgram(buf, &first);
7972 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7973 /* Kill off first chess program */
7974 if (first.isr != NULL)
7975 RemoveInputSource(first.isr);
7978 if (first.pr != NoProc) {
7980 DoSleep( appData.delayBeforeQuit );
7981 SendToProgram("quit\n", &first);
7982 DoSleep( appData.delayAfterQuit );
7983 DestroyChildProcess(first.pr, first.useSigterm);
7988 /* Put second chess program into idle state */
7989 if (second.pr != NoProc &&
7990 gameMode == TwoMachinesPlay) {
7991 SendToProgram("force\n", &second);
7992 if (second.usePing) {
7994 sprintf(buf, "ping %d\n", ++second.lastPing);
7995 SendToProgram(buf, &second);
7998 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7999 /* Kill off second chess program */
8000 if (second.isr != NULL)
8001 RemoveInputSource(second.isr);
8004 if (second.pr != NoProc) {
8005 DoSleep( appData.delayBeforeQuit );
8006 SendToProgram("quit\n", &second);
8007 DoSleep( appData.delayAfterQuit );
8008 DestroyChildProcess(second.pr, second.useSigterm);
8013 if (matchMode && gameMode == TwoMachinesPlay) {
8016 if (first.twoMachinesColor[0] == 'w') {
8023 if (first.twoMachinesColor[0] == 'b') {
8032 if (matchGame < appData.matchGames) {
8034 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8035 tmp = first.twoMachinesColor;
8036 first.twoMachinesColor = second.twoMachinesColor;
8037 second.twoMachinesColor = tmp;
8039 gameMode = nextGameMode;
8041 if(appData.matchPause>10000 || appData.matchPause<10)
8042 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8043 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8044 endingGame = 0; /* [HGM] crash */
8048 gameMode = nextGameMode;
8049 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8050 first.tidy, second.tidy,
8051 first.matchWins, second.matchWins,
8052 appData.matchGames - (first.matchWins + second.matchWins));
8053 DisplayFatalError(buf, 0, 0);
8056 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8057 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8059 gameMode = nextGameMode;
8061 endingGame = 0; /* [HGM] crash */
8064 /* Assumes program was just initialized (initString sent).
8065 Leaves program in force mode. */
8067 FeedMovesToProgram(cps, upto)
8068 ChessProgramState *cps;
8073 if (appData.debugMode)
8074 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8075 startedFromSetupPosition ? "position and " : "",
8076 backwardMostMove, upto, cps->which);
8077 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8078 // [HGM] variantswitch: make engine aware of new variant
8079 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8080 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8081 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8082 SendToProgram(buf, cps);
8083 currentlyInitializedVariant = gameInfo.variant;
8085 SendToProgram("force\n", cps);
8086 if (startedFromSetupPosition) {
8087 SendBoard(cps, backwardMostMove);
8088 if (appData.debugMode) {
8089 fprintf(debugFP, "feedMoves\n");
8092 for (i = backwardMostMove; i < upto; i++) {
8093 SendMoveToProgram(i, cps);
8099 ResurrectChessProgram()
8101 /* The chess program may have exited.
8102 If so, restart it and feed it all the moves made so far. */
8104 if (appData.noChessProgram || first.pr != NoProc) return;
8106 StartChessProgram(&first);
8107 InitChessProgram(&first, FALSE);
8108 FeedMovesToProgram(&first, currentMove);
8110 if (!first.sendTime) {
8111 /* can't tell gnuchess what its clock should read,
8112 so we bow to its notion. */
8114 timeRemaining[0][currentMove] = whiteTimeRemaining;
8115 timeRemaining[1][currentMove] = blackTimeRemaining;
8118 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8119 appData.icsEngineAnalyze) && first.analysisSupport) {
8120 SendToProgram("analyze\n", &first);
8121 first.analyzing = TRUE;
8134 if (appData.debugMode) {
8135 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8136 redraw, init, gameMode);
8138 pausing = pauseExamInvalid = FALSE;
8139 startedFromSetupPosition = blackPlaysFirst = FALSE;
8141 whiteFlag = blackFlag = FALSE;
8142 userOfferedDraw = FALSE;
8143 hintRequested = bookRequested = FALSE;
8144 first.maybeThinking = FALSE;
8145 second.maybeThinking = FALSE;
8146 first.bookSuspend = FALSE; // [HGM] book
8147 second.bookSuspend = FALSE;
8148 thinkOutput[0] = NULLCHAR;
8149 lastHint[0] = NULLCHAR;
8150 ClearGameInfo(&gameInfo);
8151 gameInfo.variant = StringToVariant(appData.variant);
8152 ics_user_moved = ics_clock_paused = FALSE;
8153 ics_getting_history = H_FALSE;
8155 white_holding[0] = black_holding[0] = NULLCHAR;
8156 ClearProgramStats();
8157 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8161 flipView = appData.flipView;
8162 ClearPremoveHighlights();
8164 alarmSounded = FALSE;
8166 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8167 if(appData.serverMovesName != NULL) {
8168 /* [HGM] prepare to make moves file for broadcasting */
8169 clock_t t = clock();
8170 if(serverMoves != NULL) fclose(serverMoves);
8171 serverMoves = fopen(appData.serverMovesName, "r");
8172 if(serverMoves != NULL) {
8173 fclose(serverMoves);
8174 /* delay 15 sec before overwriting, so all clients can see end */
8175 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8177 serverMoves = fopen(appData.serverMovesName, "w");
8181 gameMode = BeginningOfGame;
8183 if(appData.icsActive) gameInfo.variant = VariantNormal;
8184 InitPosition(redraw);
8185 for (i = 0; i < MAX_MOVES; i++) {
8186 if (commentList[i] != NULL) {
8187 free(commentList[i]);
8188 commentList[i] = NULL;
8192 timeRemaining[0][0] = whiteTimeRemaining;
8193 timeRemaining[1][0] = blackTimeRemaining;
8194 if (first.pr == NULL) {
8195 StartChessProgram(&first);
8198 InitChessProgram(&first, startedFromSetupPosition);
8201 DisplayMessage("", "");
8202 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8209 if (!AutoPlayOneMove())
8211 if (matchMode || appData.timeDelay == 0)
8213 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8215 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8224 int fromX, fromY, toX, toY;
8226 if (appData.debugMode) {
8227 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8230 if (gameMode != PlayFromGameFile)
8233 if (currentMove >= forwardMostMove) {
8234 gameMode = EditGame;
8237 /* [AS] Clear current move marker at the end of a game */
8238 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8243 toX = moveList[currentMove][2] - AAA;
8244 toY = moveList[currentMove][3] - ONE;
8246 if (moveList[currentMove][1] == '@') {
8247 if (appData.highlightLastMove) {
8248 SetHighlights(-1, -1, toX, toY);
8251 fromX = moveList[currentMove][0] - AAA;
8252 fromY = moveList[currentMove][1] - ONE;
8254 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8256 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8258 if (appData.highlightLastMove) {
8259 SetHighlights(fromX, fromY, toX, toY);
8262 DisplayMove(currentMove);
8263 SendMoveToProgram(currentMove++, &first);
8264 DisplayBothClocks();
8265 DrawPosition(FALSE, boards[currentMove]);
8266 // [HGM] PV info: always display, routine tests if empty
8267 DisplayComment(currentMove - 1, commentList[currentMove]);
8273 LoadGameOneMove(readAhead)
8274 ChessMove readAhead;
8276 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8277 char promoChar = NULLCHAR;
8282 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8283 gameMode != AnalyzeMode && gameMode != Training) {
8288 yyboardindex = forwardMostMove;
8289 if (readAhead != (ChessMove)0) {
8290 moveType = readAhead;
8292 if (gameFileFP == NULL)
8294 moveType = (ChessMove) yylex();
8300 if (appData.debugMode)
8301 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8303 if (*p == '{' || *p == '[' || *p == '(') {
8304 p[strlen(p) - 1] = NULLCHAR;
8308 /* append the comment but don't display it */
8309 while (*p == '\n') p++;
8310 AppendComment(currentMove, p);
8313 case WhiteCapturesEnPassant:
8314 case BlackCapturesEnPassant:
8315 case WhitePromotionChancellor:
8316 case BlackPromotionChancellor:
8317 case WhitePromotionArchbishop:
8318 case BlackPromotionArchbishop:
8319 case WhitePromotionCentaur:
8320 case BlackPromotionCentaur:
8321 case WhitePromotionQueen:
8322 case BlackPromotionQueen:
8323 case WhitePromotionRook:
8324 case BlackPromotionRook:
8325 case WhitePromotionBishop:
8326 case BlackPromotionBishop:
8327 case WhitePromotionKnight:
8328 case BlackPromotionKnight:
8329 case WhitePromotionKing:
8330 case BlackPromotionKing:
8332 case WhiteKingSideCastle:
8333 case WhiteQueenSideCastle:
8334 case BlackKingSideCastle:
8335 case BlackQueenSideCastle:
8336 case WhiteKingSideCastleWild:
8337 case WhiteQueenSideCastleWild:
8338 case BlackKingSideCastleWild:
8339 case BlackQueenSideCastleWild:
8341 case WhiteHSideCastleFR:
8342 case WhiteASideCastleFR:
8343 case BlackHSideCastleFR:
8344 case BlackASideCastleFR:
8346 if (appData.debugMode)
8347 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8348 fromX = currentMoveString[0] - AAA;
8349 fromY = currentMoveString[1] - ONE;
8350 toX = currentMoveString[2] - AAA;
8351 toY = currentMoveString[3] - ONE;
8352 promoChar = currentMoveString[4];
8357 if (appData.debugMode)
8358 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8359 fromX = moveType == WhiteDrop ?
8360 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8361 (int) CharToPiece(ToLower(currentMoveString[0]));
8363 toX = currentMoveString[2] - AAA;
8364 toY = currentMoveString[3] - ONE;
8370 case GameUnfinished:
8371 if (appData.debugMode)
8372 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8373 p = strchr(yy_text, '{');
8374 if (p == NULL) p = strchr(yy_text, '(');
8377 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8379 q = strchr(p, *p == '{' ? '}' : ')');
8380 if (q != NULL) *q = NULLCHAR;
8383 GameEnds(moveType, p, GE_FILE);
8385 if (cmailMsgLoaded) {
8387 flipView = WhiteOnMove(currentMove);
8388 if (moveType == GameUnfinished) flipView = !flipView;
8389 if (appData.debugMode)
8390 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8394 case (ChessMove) 0: /* end of file */
8395 if (appData.debugMode)
8396 fprintf(debugFP, "Parser hit end of file\n");
8397 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8398 EP_UNKNOWN, castlingRights[currentMove]) ) {
8404 if (WhiteOnMove(currentMove)) {
8405 GameEnds(BlackWins, "Black mates", GE_FILE);
8407 GameEnds(WhiteWins, "White mates", GE_FILE);
8411 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8418 if (lastLoadGameStart == GNUChessGame) {
8419 /* GNUChessGames have numbers, but they aren't move numbers */
8420 if (appData.debugMode)
8421 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8422 yy_text, (int) moveType);
8423 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8425 /* else fall thru */
8430 /* Reached start of next game in file */
8431 if (appData.debugMode)
8432 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8433 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8434 EP_UNKNOWN, castlingRights[currentMove]) ) {
8440 if (WhiteOnMove(currentMove)) {
8441 GameEnds(BlackWins, "Black mates", GE_FILE);
8443 GameEnds(WhiteWins, "White mates", GE_FILE);
8447 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8453 case PositionDiagram: /* should not happen; ignore */
8454 case ElapsedTime: /* ignore */
8455 case NAG: /* ignore */
8456 if (appData.debugMode)
8457 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8458 yy_text, (int) moveType);
8459 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8462 if (appData.testLegality) {
8463 if (appData.debugMode)
8464 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8465 sprintf(move, _("Illegal move: %d.%s%s"),
8466 (forwardMostMove / 2) + 1,
8467 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8468 DisplayError(move, 0);
8471 if (appData.debugMode)
8472 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8473 yy_text, currentMoveString);
8474 fromX = currentMoveString[0] - AAA;
8475 fromY = currentMoveString[1] - ONE;
8476 toX = currentMoveString[2] - AAA;
8477 toY = currentMoveString[3] - ONE;
8478 promoChar = currentMoveString[4];
8483 if (appData.debugMode)
8484 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8485 sprintf(move, _("Ambiguous move: %d.%s%s"),
8486 (forwardMostMove / 2) + 1,
8487 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8488 DisplayError(move, 0);
8493 case ImpossibleMove:
8494 if (appData.debugMode)
8495 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8496 sprintf(move, _("Illegal move: %d.%s%s"),
8497 (forwardMostMove / 2) + 1,
8498 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8499 DisplayError(move, 0);
8505 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8506 DrawPosition(FALSE, boards[currentMove]);
8507 DisplayBothClocks();
8508 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8509 DisplayComment(currentMove - 1, commentList[currentMove]);
8511 (void) StopLoadGameTimer();
8513 cmailOldMove = forwardMostMove;
8516 /* currentMoveString is set as a side-effect of yylex */
8517 strcat(currentMoveString, "\n");
8518 strcpy(moveList[forwardMostMove], currentMoveString);
8520 thinkOutput[0] = NULLCHAR;
8521 MakeMove(fromX, fromY, toX, toY, promoChar);
8522 currentMove = forwardMostMove;
8527 /* Load the nth game from the given file */
8529 LoadGameFromFile(filename, n, title, useList)
8533 /*Boolean*/ int useList;
8538 if (strcmp(filename, "-") == 0) {
8542 f = fopen(filename, "rb");
8544 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8545 DisplayError(buf, errno);
8549 if (fseek(f, 0, 0) == -1) {
8550 /* f is not seekable; probably a pipe */
8553 if (useList && n == 0) {
8554 int error = GameListBuild(f);
8556 DisplayError(_("Cannot build game list"), error);
8557 } else if (!ListEmpty(&gameList) &&
8558 ((ListGame *) gameList.tailPred)->number > 1) {
8559 GameListPopUp(f, title);
8566 return LoadGame(f, n, title, FALSE);
8571 MakeRegisteredMove()
8573 int fromX, fromY, toX, toY;
8575 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8576 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8579 if (appData.debugMode)
8580 fprintf(debugFP, "Restoring %s for game %d\n",
8581 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8583 thinkOutput[0] = NULLCHAR;
8584 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8585 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8586 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8587 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8588 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8589 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8590 MakeMove(fromX, fromY, toX, toY, promoChar);
8591 ShowMove(fromX, fromY, toX, toY);
8593 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8594 EP_UNKNOWN, castlingRights[currentMove]) ) {
8601 if (WhiteOnMove(currentMove)) {
8602 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8604 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8609 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8616 if (WhiteOnMove(currentMove)) {
8617 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8619 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8624 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8635 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8637 CmailLoadGame(f, gameNumber, title, useList)
8645 if (gameNumber > nCmailGames) {
8646 DisplayError(_("No more games in this message"), 0);
8649 if (f == lastLoadGameFP) {
8650 int offset = gameNumber - lastLoadGameNumber;
8652 cmailMsg[0] = NULLCHAR;
8653 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8654 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8655 nCmailMovesRegistered--;
8657 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8658 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8659 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8662 if (! RegisterMove()) return FALSE;
8666 retVal = LoadGame(f, gameNumber, title, useList);
8668 /* Make move registered during previous look at this game, if any */
8669 MakeRegisteredMove();
8671 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8672 commentList[currentMove]
8673 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8674 DisplayComment(currentMove - 1, commentList[currentMove]);
8680 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8685 int gameNumber = lastLoadGameNumber + offset;
8686 if (lastLoadGameFP == NULL) {
8687 DisplayError(_("No game has been loaded yet"), 0);
8690 if (gameNumber <= 0) {
8691 DisplayError(_("Can't back up any further"), 0);
8694 if (cmailMsgLoaded) {
8695 return CmailLoadGame(lastLoadGameFP, gameNumber,
8696 lastLoadGameTitle, lastLoadGameUseList);
8698 return LoadGame(lastLoadGameFP, gameNumber,
8699 lastLoadGameTitle, lastLoadGameUseList);
8705 /* Load the nth game from open file f */
8707 LoadGame(f, gameNumber, title, useList)
8715 int gn = gameNumber;
8716 ListGame *lg = NULL;
8719 GameMode oldGameMode;
8720 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8722 if (appData.debugMode)
8723 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8725 if (gameMode == Training )
8726 SetTrainingModeOff();
8728 oldGameMode = gameMode;
8729 if (gameMode != BeginningOfGame) {
8734 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8735 fclose(lastLoadGameFP);
8739 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8742 fseek(f, lg->offset, 0);
8743 GameListHighlight(gameNumber);
8747 DisplayError(_("Game number out of range"), 0);
8752 if (fseek(f, 0, 0) == -1) {
8753 if (f == lastLoadGameFP ?
8754 gameNumber == lastLoadGameNumber + 1 :
8758 DisplayError(_("Can't seek on game file"), 0);
8764 lastLoadGameNumber = gameNumber;
8765 strcpy(lastLoadGameTitle, title);
8766 lastLoadGameUseList = useList;
8770 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8771 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8772 lg->gameInfo.black);
8774 } else if (*title != NULLCHAR) {
8775 if (gameNumber > 1) {
8776 sprintf(buf, "%s %d", title, gameNumber);
8779 DisplayTitle(title);
8783 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8784 gameMode = PlayFromGameFile;
8788 currentMove = forwardMostMove = backwardMostMove = 0;
8789 CopyBoard(boards[0], initialPosition);
8793 * Skip the first gn-1 games in the file.
8794 * Also skip over anything that precedes an identifiable
8795 * start of game marker, to avoid being confused by
8796 * garbage at the start of the file. Currently
8797 * recognized start of game markers are the move number "1",
8798 * the pattern "gnuchess .* game", the pattern
8799 * "^[#;%] [^ ]* game file", and a PGN tag block.
8800 * A game that starts with one of the latter two patterns
8801 * will also have a move number 1, possibly
8802 * following a position diagram.
8803 * 5-4-02: Let's try being more lenient and allowing a game to
8804 * start with an unnumbered move. Does that break anything?
8806 cm = lastLoadGameStart = (ChessMove) 0;
8808 yyboardindex = forwardMostMove;
8809 cm = (ChessMove) yylex();
8812 if (cmailMsgLoaded) {
8813 nCmailGames = CMAIL_MAX_GAMES - gn;
8816 DisplayError(_("Game not found in file"), 0);
8823 lastLoadGameStart = cm;
8827 switch (lastLoadGameStart) {
8834 gn--; /* count this game */
8835 lastLoadGameStart = cm;
8844 switch (lastLoadGameStart) {
8849 gn--; /* count this game */
8850 lastLoadGameStart = cm;
8853 lastLoadGameStart = cm; /* game counted already */
8861 yyboardindex = forwardMostMove;
8862 cm = (ChessMove) yylex();
8863 } while (cm == PGNTag || cm == Comment);
8870 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8871 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8872 != CMAIL_OLD_RESULT) {
8874 cmailResult[ CMAIL_MAX_GAMES
8875 - gn - 1] = CMAIL_OLD_RESULT;
8881 /* Only a NormalMove can be at the start of a game
8882 * without a position diagram. */
8883 if (lastLoadGameStart == (ChessMove) 0) {
8885 lastLoadGameStart = MoveNumberOne;
8894 if (appData.debugMode)
8895 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8897 if (cm == XBoardGame) {
8898 /* Skip any header junk before position diagram and/or move 1 */
8900 yyboardindex = forwardMostMove;
8901 cm = (ChessMove) yylex();
8903 if (cm == (ChessMove) 0 ||
8904 cm == GNUChessGame || cm == XBoardGame) {
8905 /* Empty game; pretend end-of-file and handle later */
8910 if (cm == MoveNumberOne || cm == PositionDiagram ||
8911 cm == PGNTag || cm == Comment)
8914 } else if (cm == GNUChessGame) {
8915 if (gameInfo.event != NULL) {
8916 free(gameInfo.event);
8918 gameInfo.event = StrSave(yy_text);
8921 startedFromSetupPosition = FALSE;
8922 while (cm == PGNTag) {
8923 if (appData.debugMode)
8924 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8925 err = ParsePGNTag(yy_text, &gameInfo);
8926 if (!err) numPGNTags++;
8928 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8929 if(gameInfo.variant != oldVariant) {
8930 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8932 oldVariant = gameInfo.variant;
8933 if (appData.debugMode)
8934 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8938 if (gameInfo.fen != NULL) {
8939 Board initial_position;
8940 startedFromSetupPosition = TRUE;
8941 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8943 DisplayError(_("Bad FEN position in file"), 0);
8946 CopyBoard(boards[0], initial_position);
8947 if (blackPlaysFirst) {
8948 currentMove = forwardMostMove = backwardMostMove = 1;
8949 CopyBoard(boards[1], initial_position);
8950 strcpy(moveList[0], "");
8951 strcpy(parseList[0], "");
8952 timeRemaining[0][1] = whiteTimeRemaining;
8953 timeRemaining[1][1] = blackTimeRemaining;
8954 if (commentList[0] != NULL) {
8955 commentList[1] = commentList[0];
8956 commentList[0] = NULL;
8959 currentMove = forwardMostMove = backwardMostMove = 0;
8961 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8963 initialRulePlies = FENrulePlies;
8964 epStatus[forwardMostMove] = FENepStatus;
8965 for( i=0; i< nrCastlingRights; i++ )
8966 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8968 yyboardindex = forwardMostMove;
8970 gameInfo.fen = NULL;
8973 yyboardindex = forwardMostMove;
8974 cm = (ChessMove) yylex();
8976 /* Handle comments interspersed among the tags */
8977 while (cm == Comment) {
8979 if (appData.debugMode)
8980 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8982 if (*p == '{' || *p == '[' || *p == '(') {
8983 p[strlen(p) - 1] = NULLCHAR;
8986 while (*p == '\n') p++;
8987 AppendComment(currentMove, p);
8988 yyboardindex = forwardMostMove;
8989 cm = (ChessMove) yylex();
8993 /* don't rely on existence of Event tag since if game was
8994 * pasted from clipboard the Event tag may not exist
8996 if (numPGNTags > 0){
8998 if (gameInfo.variant == VariantNormal) {
8999 gameInfo.variant = StringToVariant(gameInfo.event);
9002 if( appData.autoDisplayTags ) {
9003 tags = PGNTags(&gameInfo);
9004 TagsPopUp(tags, CmailMsg());
9009 /* Make something up, but don't display it now */
9014 if (cm == PositionDiagram) {
9017 Board initial_position;
9019 if (appData.debugMode)
9020 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9022 if (!startedFromSetupPosition) {
9024 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9025 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9035 initial_position[i][j++] = CharToPiece(*p);
9038 while (*p == ' ' || *p == '\t' ||
9039 *p == '\n' || *p == '\r') p++;
9041 if (strncmp(p, "black", strlen("black"))==0)
9042 blackPlaysFirst = TRUE;
9044 blackPlaysFirst = FALSE;
9045 startedFromSetupPosition = TRUE;
9047 CopyBoard(boards[0], initial_position);
9048 if (blackPlaysFirst) {
9049 currentMove = forwardMostMove = backwardMostMove = 1;
9050 CopyBoard(boards[1], initial_position);
9051 strcpy(moveList[0], "");
9052 strcpy(parseList[0], "");
9053 timeRemaining[0][1] = whiteTimeRemaining;
9054 timeRemaining[1][1] = blackTimeRemaining;
9055 if (commentList[0] != NULL) {
9056 commentList[1] = commentList[0];
9057 commentList[0] = NULL;
9060 currentMove = forwardMostMove = backwardMostMove = 0;
9063 yyboardindex = forwardMostMove;
9064 cm = (ChessMove) yylex();
9067 if (first.pr == NoProc) {
9068 StartChessProgram(&first);
9070 InitChessProgram(&first, FALSE);
9071 SendToProgram("force\n", &first);
9072 if (startedFromSetupPosition) {
9073 SendBoard(&first, forwardMostMove);
9074 if (appData.debugMode) {
9075 fprintf(debugFP, "Load Game\n");
9077 DisplayBothClocks();
9080 /* [HGM] server: flag to write setup moves in broadcast file as one */
9081 loadFlag = appData.suppressLoadMoves;
9083 while (cm == Comment) {
9085 if (appData.debugMode)
9086 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9088 if (*p == '{' || *p == '[' || *p == '(') {
9089 p[strlen(p) - 1] = NULLCHAR;
9092 while (*p == '\n') p++;
9093 AppendComment(currentMove, p);
9094 yyboardindex = forwardMostMove;
9095 cm = (ChessMove) yylex();
9098 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9099 cm == WhiteWins || cm == BlackWins ||
9100 cm == GameIsDrawn || cm == GameUnfinished) {
9101 DisplayMessage("", _("No moves in game"));
9102 if (cmailMsgLoaded) {
9103 if (appData.debugMode)
9104 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9108 DrawPosition(FALSE, boards[currentMove]);
9109 DisplayBothClocks();
9110 gameMode = EditGame;
9117 // [HGM] PV info: routine tests if comment empty
9118 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9119 DisplayComment(currentMove - 1, commentList[currentMove]);
9121 if (!matchMode && appData.timeDelay != 0)
9122 DrawPosition(FALSE, boards[currentMove]);
9124 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9125 programStats.ok_to_send = 1;
9128 /* if the first token after the PGN tags is a move
9129 * and not move number 1, retrieve it from the parser
9131 if (cm != MoveNumberOne)
9132 LoadGameOneMove(cm);
9134 /* load the remaining moves from the file */
9135 while (LoadGameOneMove((ChessMove)0)) {
9136 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9137 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9140 /* rewind to the start of the game */
9141 currentMove = backwardMostMove;
9143 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9145 if (oldGameMode == AnalyzeFile ||
9146 oldGameMode == AnalyzeMode) {
9150 if (matchMode || appData.timeDelay == 0) {
9152 gameMode = EditGame;
9154 } else if (appData.timeDelay > 0) {
9158 if (appData.debugMode)
9159 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9161 loadFlag = 0; /* [HGM] true game starts */
9165 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9167 ReloadPosition(offset)
9170 int positionNumber = lastLoadPositionNumber + offset;
9171 if (lastLoadPositionFP == NULL) {
9172 DisplayError(_("No position has been loaded yet"), 0);
9175 if (positionNumber <= 0) {
9176 DisplayError(_("Can't back up any further"), 0);
9179 return LoadPosition(lastLoadPositionFP, positionNumber,
9180 lastLoadPositionTitle);
9183 /* Load the nth position from the given file */
9185 LoadPositionFromFile(filename, n, title)
9193 if (strcmp(filename, "-") == 0) {
9194 return LoadPosition(stdin, n, "stdin");
9196 f = fopen(filename, "rb");
9198 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9199 DisplayError(buf, errno);
9202 return LoadPosition(f, n, title);
9207 /* Load the nth position from the given open file, and close it */
9209 LoadPosition(f, positionNumber, title)
9214 char *p, line[MSG_SIZ];
9215 Board initial_position;
9216 int i, j, fenMode, pn;
9218 if (gameMode == Training )
9219 SetTrainingModeOff();
9221 if (gameMode != BeginningOfGame) {
9224 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9225 fclose(lastLoadPositionFP);
9227 if (positionNumber == 0) positionNumber = 1;
9228 lastLoadPositionFP = f;
9229 lastLoadPositionNumber = positionNumber;
9230 strcpy(lastLoadPositionTitle, title);
9231 if (first.pr == NoProc) {
9232 StartChessProgram(&first);
9233 InitChessProgram(&first, FALSE);
9235 pn = positionNumber;
9236 if (positionNumber < 0) {
9237 /* Negative position number means to seek to that byte offset */
9238 if (fseek(f, -positionNumber, 0) == -1) {
9239 DisplayError(_("Can't seek on position file"), 0);
9244 if (fseek(f, 0, 0) == -1) {
9245 if (f == lastLoadPositionFP ?
9246 positionNumber == lastLoadPositionNumber + 1 :
9247 positionNumber == 1) {
9250 DisplayError(_("Can't seek on position file"), 0);
9255 /* See if this file is FEN or old-style xboard */
9256 if (fgets(line, MSG_SIZ, f) == NULL) {
9257 DisplayError(_("Position not found in file"), 0);
9266 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9267 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9268 case '1': case '2': case '3': case '4': case '5': case '6':
9269 case '7': case '8': case '9':
9270 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9271 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9272 case 'C': case 'W': case 'c': case 'w':
9277 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9278 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9282 if (fenMode || line[0] == '#') pn--;
9284 /* skip positions before number pn */
9285 if (fgets(line, MSG_SIZ, f) == NULL) {
9287 DisplayError(_("Position not found in file"), 0);
9290 if (fenMode || line[0] == '#') pn--;
9295 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9296 DisplayError(_("Bad FEN position in file"), 0);
9300 (void) fgets(line, MSG_SIZ, f);
9301 (void) fgets(line, MSG_SIZ, f);
9303 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9304 (void) fgets(line, MSG_SIZ, f);
9305 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9308 initial_position[i][j++] = CharToPiece(*p);
9312 blackPlaysFirst = FALSE;
9314 (void) fgets(line, MSG_SIZ, f);
9315 if (strncmp(line, "black", strlen("black"))==0)
9316 blackPlaysFirst = TRUE;
9319 startedFromSetupPosition = TRUE;
9321 SendToProgram("force\n", &first);
9322 CopyBoard(boards[0], initial_position);
9323 if (blackPlaysFirst) {
9324 currentMove = forwardMostMove = backwardMostMove = 1;
9325 strcpy(moveList[0], "");
9326 strcpy(parseList[0], "");
9327 CopyBoard(boards[1], initial_position);
9328 DisplayMessage("", _("Black to play"));
9330 currentMove = forwardMostMove = backwardMostMove = 0;
9331 DisplayMessage("", _("White to play"));
9333 /* [HGM] copy FEN attributes as well */
9335 initialRulePlies = FENrulePlies;
9336 epStatus[forwardMostMove] = FENepStatus;
9337 for( i=0; i< nrCastlingRights; i++ )
9338 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9340 SendBoard(&first, forwardMostMove);
9341 if (appData.debugMode) {
9343 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9344 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9345 fprintf(debugFP, "Load Position\n");
9348 if (positionNumber > 1) {
9349 sprintf(line, "%s %d", title, positionNumber);
9352 DisplayTitle(title);
9354 gameMode = EditGame;
9357 timeRemaining[0][1] = whiteTimeRemaining;
9358 timeRemaining[1][1] = blackTimeRemaining;
9359 DrawPosition(FALSE, boards[currentMove]);
9366 CopyPlayerNameIntoFileName(dest, src)
9369 while (*src != NULLCHAR && *src != ',') {
9374 *(*dest)++ = *src++;
9379 char *DefaultFileName(ext)
9382 static char def[MSG_SIZ];
9385 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9387 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9389 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9398 /* Save the current game to the given file */
9400 SaveGameToFile(filename, append)
9407 if (strcmp(filename, "-") == 0) {
9408 return SaveGame(stdout, 0, NULL);
9410 f = fopen(filename, append ? "a" : "w");
9412 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9413 DisplayError(buf, errno);
9416 return SaveGame(f, 0, NULL);
9425 static char buf[MSG_SIZ];
9428 p = strchr(str, ' ');
9429 if (p == NULL) return str;
9430 strncpy(buf, str, p - str);
9431 buf[p - str] = NULLCHAR;
9435 #define PGN_MAX_LINE 75
9437 #define PGN_SIDE_WHITE 0
9438 #define PGN_SIDE_BLACK 1
9441 static int FindFirstMoveOutOfBook( int side )
9445 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9446 int index = backwardMostMove;
9447 int has_book_hit = 0;
9449 if( (index % 2) != side ) {
9453 while( index < forwardMostMove ) {
9454 /* Check to see if engine is in book */
9455 int depth = pvInfoList[index].depth;
9456 int score = pvInfoList[index].score;
9462 else if( score == 0 && depth == 63 ) {
9463 in_book = 1; /* Zappa */
9465 else if( score == 2 && depth == 99 ) {
9466 in_book = 1; /* Abrok */
9469 has_book_hit += in_book;
9485 void GetOutOfBookInfo( char * buf )
9489 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9491 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9492 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9496 if( oob[0] >= 0 || oob[1] >= 0 ) {
9497 for( i=0; i<2; i++ ) {
9501 if( i > 0 && oob[0] >= 0 ) {
9505 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9506 sprintf( buf+strlen(buf), "%s%.2f",
9507 pvInfoList[idx].score >= 0 ? "+" : "",
9508 pvInfoList[idx].score / 100.0 );
9514 /* Save game in PGN style and close the file */
9519 int i, offset, linelen, newblock;
9523 int movelen, numlen, blank;
9524 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9526 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9528 tm = time((time_t *) NULL);
9530 PrintPGNTags(f, &gameInfo);
9532 if (backwardMostMove > 0 || startedFromSetupPosition) {
9533 char *fen = PositionToFEN(backwardMostMove, NULL);
9534 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9535 fprintf(f, "\n{--------------\n");
9536 PrintPosition(f, backwardMostMove);
9537 fprintf(f, "--------------}\n");
9541 /* [AS] Out of book annotation */
9542 if( appData.saveOutOfBookInfo ) {
9545 GetOutOfBookInfo( buf );
9547 if( buf[0] != '\0' ) {
9548 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9555 i = backwardMostMove;
9559 while (i < forwardMostMove) {
9560 /* Print comments preceding this move */
9561 if (commentList[i] != NULL) {
9562 if (linelen > 0) fprintf(f, "\n");
9563 fprintf(f, "{\n%s}\n", commentList[i]);
9568 /* Format move number */
9570 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9573 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9575 numtext[0] = NULLCHAR;
9578 numlen = strlen(numtext);
9581 /* Print move number */
9582 blank = linelen > 0 && numlen > 0;
9583 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9592 fprintf(f, numtext);
9596 strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9597 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9598 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9599 int p = movelen - 1;
9600 if(move_buffer[p] == ' ') p--;
9601 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9602 while(p && move_buffer[--p] != '(');
9603 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9608 blank = linelen > 0 && movelen > 0;
9609 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9618 fprintf(f, move_buffer);
9621 /* [AS] Add PV info if present */
9622 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9623 /* [HGM] add time */
9624 char buf[MSG_SIZ]; int seconds = 0;
9627 if(i >= backwardMostMove) {
9629 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9630 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9632 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9633 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9635 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9637 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9640 if( seconds <= 0) buf[0] = 0; else
9641 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9642 seconds = (seconds + 4)/10; // round to full seconds
9643 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9644 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9647 sprintf( move_buffer, "{%s%.2f/%d%s}",
9648 pvInfoList[i].score >= 0 ? "+" : "",
9649 pvInfoList[i].score / 100.0,
9650 pvInfoList[i].depth,
9653 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9655 /* Print score/depth */
9656 blank = linelen > 0 && movelen > 0;
9657 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9666 fprintf(f, move_buffer);
9673 /* Start a new line */
9674 if (linelen > 0) fprintf(f, "\n");
9676 /* Print comments after last move */
9677 if (commentList[i] != NULL) {
9678 fprintf(f, "{\n%s}\n", commentList[i]);
9682 if (gameInfo.resultDetails != NULL &&
9683 gameInfo.resultDetails[0] != NULLCHAR) {
9684 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9685 PGNResult(gameInfo.result));
9687 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9694 /* Save game in old style and close the file */
9702 tm = time((time_t *) NULL);
9704 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9707 if (backwardMostMove > 0 || startedFromSetupPosition) {
9708 fprintf(f, "\n[--------------\n");
9709 PrintPosition(f, backwardMostMove);
9710 fprintf(f, "--------------]\n");
9715 i = backwardMostMove;
9716 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9718 while (i < forwardMostMove) {
9719 if (commentList[i] != NULL) {
9720 fprintf(f, "[%s]\n", commentList[i]);
9724 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9727 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9729 if (commentList[i] != NULL) {
9733 if (i >= forwardMostMove) {
9737 fprintf(f, "%s\n", parseList[i]);
9742 if (commentList[i] != NULL) {
9743 fprintf(f, "[%s]\n", commentList[i]);
9746 /* This isn't really the old style, but it's close enough */
9747 if (gameInfo.resultDetails != NULL &&
9748 gameInfo.resultDetails[0] != NULLCHAR) {
9749 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9750 gameInfo.resultDetails);
9752 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9759 /* Save the current game to open file f and close the file */
9761 SaveGame(f, dummy, dummy2)
9766 if (gameMode == EditPosition) EditPositionDone();
9767 if (appData.oldSaveStyle)
9768 return SaveGameOldStyle(f);
9770 return SaveGamePGN(f);
9773 /* Save the current position to the given file */
9775 SavePositionToFile(filename)
9781 if (strcmp(filename, "-") == 0) {
9782 return SavePosition(stdout, 0, NULL);
9784 f = fopen(filename, "a");
9786 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9787 DisplayError(buf, errno);
9790 SavePosition(f, 0, NULL);
9796 /* Save the current position to the given open file and close the file */
9798 SavePosition(f, dummy, dummy2)
9806 if (appData.oldSaveStyle) {
9807 tm = time((time_t *) NULL);
9809 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9811 fprintf(f, "[--------------\n");
9812 PrintPosition(f, currentMove);
9813 fprintf(f, "--------------]\n");
9815 fen = PositionToFEN(currentMove, NULL);
9816 fprintf(f, "%s\n", fen);
9824 ReloadCmailMsgEvent(unregister)
9828 static char *inFilename = NULL;
9829 static char *outFilename;
9831 struct stat inbuf, outbuf;
9834 /* Any registered moves are unregistered if unregister is set, */
9835 /* i.e. invoked by the signal handler */
9837 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9838 cmailMoveRegistered[i] = FALSE;
9839 if (cmailCommentList[i] != NULL) {
9840 free(cmailCommentList[i]);
9841 cmailCommentList[i] = NULL;
9844 nCmailMovesRegistered = 0;
9847 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9848 cmailResult[i] = CMAIL_NOT_RESULT;
9852 if (inFilename == NULL) {
9853 /* Because the filenames are static they only get malloced once */
9854 /* and they never get freed */
9855 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9856 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9858 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9859 sprintf(outFilename, "%s.out", appData.cmailGameName);
9862 status = stat(outFilename, &outbuf);
9864 cmailMailedMove = FALSE;
9866 status = stat(inFilename, &inbuf);
9867 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9870 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9871 counts the games, notes how each one terminated, etc.
9873 It would be nice to remove this kludge and instead gather all
9874 the information while building the game list. (And to keep it
9875 in the game list nodes instead of having a bunch of fixed-size
9876 parallel arrays.) Note this will require getting each game's
9877 termination from the PGN tags, as the game list builder does
9878 not process the game moves. --mann
9880 cmailMsgLoaded = TRUE;
9881 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9883 /* Load first game in the file or popup game menu */
9884 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9894 char string[MSG_SIZ];
9896 if ( cmailMailedMove
9897 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9898 return TRUE; /* Allow free viewing */
9901 /* Unregister move to ensure that we don't leave RegisterMove */
9902 /* with the move registered when the conditions for registering no */
9904 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9905 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9906 nCmailMovesRegistered --;
9908 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9910 free(cmailCommentList[lastLoadGameNumber - 1]);
9911 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9915 if (cmailOldMove == -1) {
9916 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9920 if (currentMove > cmailOldMove + 1) {
9921 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9925 if (currentMove < cmailOldMove) {
9926 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9930 if (forwardMostMove > currentMove) {
9931 /* Silently truncate extra moves */
9935 if ( (currentMove == cmailOldMove + 1)
9936 || ( (currentMove == cmailOldMove)
9937 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9938 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9939 if (gameInfo.result != GameUnfinished) {
9940 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9943 if (commentList[currentMove] != NULL) {
9944 cmailCommentList[lastLoadGameNumber - 1]
9945 = StrSave(commentList[currentMove]);
9947 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9949 if (appData.debugMode)
9950 fprintf(debugFP, "Saving %s for game %d\n",
9951 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9954 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9956 f = fopen(string, "w");
9957 if (appData.oldSaveStyle) {
9958 SaveGameOldStyle(f); /* also closes the file */
9960 sprintf(string, "%s.pos.out", appData.cmailGameName);
9961 f = fopen(string, "w");
9962 SavePosition(f, 0, NULL); /* also closes the file */
9964 fprintf(f, "{--------------\n");
9965 PrintPosition(f, currentMove);
9966 fprintf(f, "--------------}\n\n");
9968 SaveGame(f, 0, NULL); /* also closes the file*/
9971 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9972 nCmailMovesRegistered ++;
9973 } else if (nCmailGames == 1) {
9974 DisplayError(_("You have not made a move yet"), 0);
9985 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9986 FILE *commandOutput;
9987 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9988 int nBytes = 0; /* Suppress warnings on uninitialized variables */
9994 if (! cmailMsgLoaded) {
9995 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9999 if (nCmailGames == nCmailResults) {
10000 DisplayError(_("No unfinished games"), 0);
10004 #if CMAIL_PROHIBIT_REMAIL
10005 if (cmailMailedMove) {
10006 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);
10007 DisplayError(msg, 0);
10012 if (! (cmailMailedMove || RegisterMove())) return;
10014 if ( cmailMailedMove
10015 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10016 sprintf(string, partCommandString,
10017 appData.debugMode ? " -v" : "", appData.cmailGameName);
10018 commandOutput = popen(string, "r");
10020 if (commandOutput == NULL) {
10021 DisplayError(_("Failed to invoke cmail"), 0);
10023 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10024 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10026 if (nBuffers > 1) {
10027 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10028 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10029 nBytes = MSG_SIZ - 1;
10031 (void) memcpy(msg, buffer, nBytes);
10033 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10035 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10036 cmailMailedMove = TRUE; /* Prevent >1 moves */
10039 for (i = 0; i < nCmailGames; i ++) {
10040 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10045 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10047 sprintf(buffer, "%s/%s.%s.archive",
10049 appData.cmailGameName,
10051 LoadGameFromFile(buffer, 1, buffer, FALSE);
10052 cmailMsgLoaded = FALSE;
10056 DisplayInformation(msg);
10057 pclose(commandOutput);
10060 if ((*cmailMsg) != '\0') {
10061 DisplayInformation(cmailMsg);
10066 #endif /* !WIN32 */
10075 int prependComma = 0;
10077 char string[MSG_SIZ]; /* Space for game-list */
10080 if (!cmailMsgLoaded) return "";
10082 if (cmailMailedMove) {
10083 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10085 /* Create a list of games left */
10086 sprintf(string, "[");
10087 for (i = 0; i < nCmailGames; i ++) {
10088 if (! ( cmailMoveRegistered[i]
10089 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10090 if (prependComma) {
10091 sprintf(number, ",%d", i + 1);
10093 sprintf(number, "%d", i + 1);
10097 strcat(string, number);
10100 strcat(string, "]");
10102 if (nCmailMovesRegistered + nCmailResults == 0) {
10103 switch (nCmailGames) {
10106 _("Still need to make move for game\n"));
10111 _("Still need to make moves for both games\n"));
10116 _("Still need to make moves for all %d games\n"),
10121 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10124 _("Still need to make a move for game %s\n"),
10129 if (nCmailResults == nCmailGames) {
10130 sprintf(cmailMsg, _("No unfinished games\n"));
10132 sprintf(cmailMsg, _("Ready to send mail\n"));
10138 _("Still need to make moves for games %s\n"),
10150 if (gameMode == Training)
10151 SetTrainingModeOff();
10154 cmailMsgLoaded = FALSE;
10155 if (appData.icsActive) {
10156 SendToICS(ics_prefix);
10157 SendToICS("refresh\n");
10167 /* Give up on clean exit */
10171 /* Keep trying for clean exit */
10175 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10177 if (telnetISR != NULL) {
10178 RemoveInputSource(telnetISR);
10180 if (icsPR != NoProc) {
10181 DestroyChildProcess(icsPR, TRUE);
10184 /* Save game if resource set and not already saved by GameEnds() */
10185 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10186 && forwardMostMove > 0) {
10187 if (*appData.saveGameFile != NULLCHAR) {
10188 SaveGameToFile(appData.saveGameFile, TRUE);
10189 } else if (appData.autoSaveGames) {
10192 if (*appData.savePositionFile != NULLCHAR) {
10193 SavePositionToFile(appData.savePositionFile);
10196 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10198 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10199 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10201 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10202 /* make sure this other one finishes before killing it! */
10203 if(endingGame) { int count = 0;
10204 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10205 while(endingGame && count++ < 10) DoSleep(1);
10206 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10209 /* Kill off chess programs */
10210 if (first.pr != NoProc) {
10213 DoSleep( appData.delayBeforeQuit );
10214 SendToProgram("quit\n", &first);
10215 DoSleep( appData.delayAfterQuit );
10216 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10218 if (second.pr != NoProc) {
10219 DoSleep( appData.delayBeforeQuit );
10220 SendToProgram("quit\n", &second);
10221 DoSleep( appData.delayAfterQuit );
10222 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10224 if (first.isr != NULL) {
10225 RemoveInputSource(first.isr);
10227 if (second.isr != NULL) {
10228 RemoveInputSource(second.isr);
10231 ShutDownFrontEnd();
10238 if (appData.debugMode)
10239 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10243 if (gameMode == MachinePlaysWhite ||
10244 gameMode == MachinePlaysBlack) {
10247 DisplayBothClocks();
10249 if (gameMode == PlayFromGameFile) {
10250 if (appData.timeDelay >= 0)
10251 AutoPlayGameLoop();
10252 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10253 Reset(FALSE, TRUE);
10254 SendToICS(ics_prefix);
10255 SendToICS("refresh\n");
10256 } else if (currentMove < forwardMostMove) {
10257 ForwardInner(forwardMostMove);
10259 pauseExamInvalid = FALSE;
10261 switch (gameMode) {
10265 pauseExamForwardMostMove = forwardMostMove;
10266 pauseExamInvalid = FALSE;
10269 case IcsPlayingWhite:
10270 case IcsPlayingBlack:
10274 case PlayFromGameFile:
10275 (void) StopLoadGameTimer();
10279 case BeginningOfGame:
10280 if (appData.icsActive) return;
10281 /* else fall through */
10282 case MachinePlaysWhite:
10283 case MachinePlaysBlack:
10284 case TwoMachinesPlay:
10285 if (forwardMostMove == 0)
10286 return; /* don't pause if no one has moved */
10287 if ((gameMode == MachinePlaysWhite &&
10288 !WhiteOnMove(forwardMostMove)) ||
10289 (gameMode == MachinePlaysBlack &&
10290 WhiteOnMove(forwardMostMove))) {
10303 char title[MSG_SIZ];
10305 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10306 strcpy(title, _("Edit comment"));
10308 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10309 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10310 parseList[currentMove - 1]);
10313 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10320 char *tags = PGNTags(&gameInfo);
10321 EditTagsPopUp(tags);
10328 if (appData.noChessProgram || gameMode == AnalyzeMode)
10331 if (gameMode != AnalyzeFile) {
10332 if (!appData.icsEngineAnalyze) {
10334 if (gameMode != EditGame) return;
10336 ResurrectChessProgram();
10337 SendToProgram("analyze\n", &first);
10338 first.analyzing = TRUE;
10339 /*first.maybeThinking = TRUE;*/
10340 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10341 AnalysisPopUp(_("Analysis"),
10342 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10344 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10349 StartAnalysisClock();
10350 GetTimeMark(&lastNodeCountTime);
10357 if (appData.noChessProgram || gameMode == AnalyzeFile)
10360 if (gameMode != AnalyzeMode) {
10362 if (gameMode != EditGame) return;
10363 ResurrectChessProgram();
10364 SendToProgram("analyze\n", &first);
10365 first.analyzing = TRUE;
10366 /*first.maybeThinking = TRUE;*/
10367 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10368 AnalysisPopUp(_("Analysis"),
10369 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10371 gameMode = AnalyzeFile;
10376 StartAnalysisClock();
10377 GetTimeMark(&lastNodeCountTime);
10382 MachineWhiteEvent()
10385 char *bookHit = NULL;
10387 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10391 if (gameMode == PlayFromGameFile ||
10392 gameMode == TwoMachinesPlay ||
10393 gameMode == Training ||
10394 gameMode == AnalyzeMode ||
10395 gameMode == EndOfGame)
10398 if (gameMode == EditPosition)
10399 EditPositionDone();
10401 if (!WhiteOnMove(currentMove)) {
10402 DisplayError(_("It is not White's turn"), 0);
10406 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10409 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10410 gameMode == AnalyzeFile)
10413 ResurrectChessProgram(); /* in case it isn't running */
10414 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10415 gameMode = MachinePlaysWhite;
10418 gameMode = MachinePlaysWhite;
10422 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10424 if (first.sendName) {
10425 sprintf(buf, "name %s\n", gameInfo.black);
10426 SendToProgram(buf, &first);
10428 if (first.sendTime) {
10429 if (first.useColors) {
10430 SendToProgram("black\n", &first); /*gnu kludge*/
10432 SendTimeRemaining(&first, TRUE);
10434 if (first.useColors) {
10435 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10437 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10438 SetMachineThinkingEnables();
10439 first.maybeThinking = TRUE;
10442 if (appData.autoFlipView && !flipView) {
10443 flipView = !flipView;
10444 DrawPosition(FALSE, NULL);
10445 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10448 if(bookHit) { // [HGM] book: simulate book reply
10449 static char bookMove[MSG_SIZ]; // a bit generous?
10451 programStats.nodes = programStats.depth = programStats.time =
10452 programStats.score = programStats.got_only_move = 0;
10453 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10455 strcpy(bookMove, "move ");
10456 strcat(bookMove, bookHit);
10457 HandleMachineMove(bookMove, &first);
10462 MachineBlackEvent()
10465 char *bookHit = NULL;
10467 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10471 if (gameMode == PlayFromGameFile ||
10472 gameMode == TwoMachinesPlay ||
10473 gameMode == Training ||
10474 gameMode == AnalyzeMode ||
10475 gameMode == EndOfGame)
10478 if (gameMode == EditPosition)
10479 EditPositionDone();
10481 if (WhiteOnMove(currentMove)) {
10482 DisplayError(_("It is not Black's turn"), 0);
10486 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10489 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10490 gameMode == AnalyzeFile)
10493 ResurrectChessProgram(); /* in case it isn't running */
10494 gameMode = MachinePlaysBlack;
10498 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10500 if (first.sendName) {
10501 sprintf(buf, "name %s\n", gameInfo.white);
10502 SendToProgram(buf, &first);
10504 if (first.sendTime) {
10505 if (first.useColors) {
10506 SendToProgram("white\n", &first); /*gnu kludge*/
10508 SendTimeRemaining(&first, FALSE);
10510 if (first.useColors) {
10511 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10513 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10514 SetMachineThinkingEnables();
10515 first.maybeThinking = TRUE;
10518 if (appData.autoFlipView && flipView) {
10519 flipView = !flipView;
10520 DrawPosition(FALSE, NULL);
10521 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10523 if(bookHit) { // [HGM] book: simulate book reply
10524 static char bookMove[MSG_SIZ]; // a bit generous?
10526 programStats.nodes = programStats.depth = programStats.time =
10527 programStats.score = programStats.got_only_move = 0;
10528 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10530 strcpy(bookMove, "move ");
10531 strcat(bookMove, bookHit);
10532 HandleMachineMove(bookMove, &first);
10538 DisplayTwoMachinesTitle()
10541 if (appData.matchGames > 0) {
10542 if (first.twoMachinesColor[0] == 'w') {
10543 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10544 gameInfo.white, gameInfo.black,
10545 first.matchWins, second.matchWins,
10546 matchGame - 1 - (first.matchWins + second.matchWins));
10548 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10549 gameInfo.white, gameInfo.black,
10550 second.matchWins, first.matchWins,
10551 matchGame - 1 - (first.matchWins + second.matchWins));
10554 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10560 TwoMachinesEvent P((void))
10564 ChessProgramState *onmove;
10565 char *bookHit = NULL;
10567 if (appData.noChessProgram) return;
10569 switch (gameMode) {
10570 case TwoMachinesPlay:
10572 case MachinePlaysWhite:
10573 case MachinePlaysBlack:
10574 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10575 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10579 case BeginningOfGame:
10580 case PlayFromGameFile:
10583 if (gameMode != EditGame) return;
10586 EditPositionDone();
10597 forwardMostMove = currentMove;
10598 ResurrectChessProgram(); /* in case first program isn't running */
10600 if (second.pr == NULL) {
10601 StartChessProgram(&second);
10602 if (second.protocolVersion == 1) {
10603 TwoMachinesEventIfReady();
10605 /* kludge: allow timeout for initial "feature" command */
10607 DisplayMessage("", _("Starting second chess program"));
10608 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10612 DisplayMessage("", "");
10613 InitChessProgram(&second, FALSE);
10614 SendToProgram("force\n", &second);
10615 if (startedFromSetupPosition) {
10616 SendBoard(&second, backwardMostMove);
10617 if (appData.debugMode) {
10618 fprintf(debugFP, "Two Machines\n");
10621 for (i = backwardMostMove; i < forwardMostMove; i++) {
10622 SendMoveToProgram(i, &second);
10625 gameMode = TwoMachinesPlay;
10629 DisplayTwoMachinesTitle();
10631 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10637 SendToProgram(first.computerString, &first);
10638 if (first.sendName) {
10639 sprintf(buf, "name %s\n", second.tidy);
10640 SendToProgram(buf, &first);
10642 SendToProgram(second.computerString, &second);
10643 if (second.sendName) {
10644 sprintf(buf, "name %s\n", first.tidy);
10645 SendToProgram(buf, &second);
10649 if (!first.sendTime || !second.sendTime) {
10650 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10651 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10653 if (onmove->sendTime) {
10654 if (onmove->useColors) {
10655 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10657 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10659 if (onmove->useColors) {
10660 SendToProgram(onmove->twoMachinesColor, onmove);
10662 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10663 // SendToProgram("go\n", onmove);
10664 onmove->maybeThinking = TRUE;
10665 SetMachineThinkingEnables();
10669 if(bookHit) { // [HGM] book: simulate book reply
10670 static char bookMove[MSG_SIZ]; // a bit generous?
10672 programStats.nodes = programStats.depth = programStats.time =
10673 programStats.score = programStats.got_only_move = 0;
10674 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10676 strcpy(bookMove, "move ");
10677 strcat(bookMove, bookHit);
10678 HandleMachineMove(bookMove, &first);
10685 if (gameMode == Training) {
10686 SetTrainingModeOff();
10687 gameMode = PlayFromGameFile;
10688 DisplayMessage("", _("Training mode off"));
10690 gameMode = Training;
10691 animateTraining = appData.animate;
10693 /* make sure we are not already at the end of the game */
10694 if (currentMove < forwardMostMove) {
10695 SetTrainingModeOn();
10696 DisplayMessage("", _("Training mode on"));
10698 gameMode = PlayFromGameFile;
10699 DisplayError(_("Already at end of game"), 0);
10708 if (!appData.icsActive) return;
10709 switch (gameMode) {
10710 case IcsPlayingWhite:
10711 case IcsPlayingBlack:
10714 case BeginningOfGame:
10722 EditPositionDone();
10735 gameMode = IcsIdle;
10746 switch (gameMode) {
10748 SetTrainingModeOff();
10750 case MachinePlaysWhite:
10751 case MachinePlaysBlack:
10752 case BeginningOfGame:
10753 SendToProgram("force\n", &first);
10754 SetUserThinkingEnables();
10756 case PlayFromGameFile:
10757 (void) StopLoadGameTimer();
10758 if (gameFileFP != NULL) {
10763 EditPositionDone();
10768 SendToProgram("force\n", &first);
10770 case TwoMachinesPlay:
10771 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10772 ResurrectChessProgram();
10773 SetUserThinkingEnables();
10776 ResurrectChessProgram();
10778 case IcsPlayingBlack:
10779 case IcsPlayingWhite:
10780 DisplayError(_("Warning: You are still playing a game"), 0);
10783 DisplayError(_("Warning: You are still observing a game"), 0);
10786 DisplayError(_("Warning: You are still examining a game"), 0);
10797 first.offeredDraw = second.offeredDraw = 0;
10799 if (gameMode == PlayFromGameFile) {
10800 whiteTimeRemaining = timeRemaining[0][currentMove];
10801 blackTimeRemaining = timeRemaining[1][currentMove];
10805 if (gameMode == MachinePlaysWhite ||
10806 gameMode == MachinePlaysBlack ||
10807 gameMode == TwoMachinesPlay ||
10808 gameMode == EndOfGame) {
10809 i = forwardMostMove;
10810 while (i > currentMove) {
10811 SendToProgram("undo\n", &first);
10814 whiteTimeRemaining = timeRemaining[0][currentMove];
10815 blackTimeRemaining = timeRemaining[1][currentMove];
10816 DisplayBothClocks();
10817 if (whiteFlag || blackFlag) {
10818 whiteFlag = blackFlag = 0;
10823 gameMode = EditGame;
10830 EditPositionEvent()
10832 if (gameMode == EditPosition) {
10838 if (gameMode != EditGame) return;
10840 gameMode = EditPosition;
10843 if (currentMove > 0)
10844 CopyBoard(boards[0], boards[currentMove]);
10846 blackPlaysFirst = !WhiteOnMove(currentMove);
10848 currentMove = forwardMostMove = backwardMostMove = 0;
10849 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10856 /* [DM] icsEngineAnalyze - possible call from other functions */
10857 if (appData.icsEngineAnalyze) {
10858 appData.icsEngineAnalyze = FALSE;
10860 DisplayMessage("",_("Close ICS engine analyze..."));
10862 if (first.analysisSupport && first.analyzing) {
10863 SendToProgram("exit\n", &first);
10864 first.analyzing = FALSE;
10867 thinkOutput[0] = NULLCHAR;
10873 startedFromSetupPosition = TRUE;
10874 InitChessProgram(&first, FALSE);
10875 SendToProgram("force\n", &first);
10876 if (blackPlaysFirst) {
10877 strcpy(moveList[0], "");
10878 strcpy(parseList[0], "");
10879 currentMove = forwardMostMove = backwardMostMove = 1;
10880 CopyBoard(boards[1], boards[0]);
10881 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10883 epStatus[1] = epStatus[0];
10884 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10887 currentMove = forwardMostMove = backwardMostMove = 0;
10889 SendBoard(&first, forwardMostMove);
10890 if (appData.debugMode) {
10891 fprintf(debugFP, "EditPosDone\n");
10894 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10895 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10896 gameMode = EditGame;
10898 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10899 ClearHighlights(); /* [AS] */
10902 /* Pause for `ms' milliseconds */
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10913 } while (SubtractTimeMarks(&m2, &m1) < ms);
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10918 SendMultiLineToICS(buf)
10921 char temp[MSG_SIZ+1], *p;
10928 strncpy(temp, buf, len);
10933 if (*p == '\n' || *p == '\r')
10938 strcat(temp, "\n");
10940 SendToPlayer(temp, strlen(temp));
10944 SetWhiteToPlayEvent()
10946 if (gameMode == EditPosition) {
10947 blackPlaysFirst = FALSE;
10948 DisplayBothClocks(); /* works because currentMove is 0 */
10949 } else if (gameMode == IcsExamining) {
10950 SendToICS(ics_prefix);
10951 SendToICS("tomove white\n");
10956 SetBlackToPlayEvent()
10958 if (gameMode == EditPosition) {
10959 blackPlaysFirst = TRUE;
10960 currentMove = 1; /* kludge */
10961 DisplayBothClocks();
10963 } else if (gameMode == IcsExamining) {
10964 SendToICS(ics_prefix);
10965 SendToICS("tomove black\n");
10970 EditPositionMenuEvent(selection, x, y)
10971 ChessSquare selection;
10975 ChessSquare piece = boards[0][y][x];
10977 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10979 switch (selection) {
10981 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10982 SendToICS(ics_prefix);
10983 SendToICS("bsetup clear\n");
10984 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10985 SendToICS(ics_prefix);
10986 SendToICS("clearboard\n");
10988 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10989 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10990 for (y = 0; y < BOARD_HEIGHT; y++) {
10991 if (gameMode == IcsExamining) {
10992 if (boards[currentMove][y][x] != EmptySquare) {
10993 sprintf(buf, "%sx@%c%c\n", ics_prefix,
10998 boards[0][y][x] = p;
11003 if (gameMode == EditPosition) {
11004 DrawPosition(FALSE, boards[0]);
11009 SetWhiteToPlayEvent();
11013 SetBlackToPlayEvent();
11017 if (gameMode == IcsExamining) {
11018 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11021 boards[0][y][x] = EmptySquare;
11022 DrawPosition(FALSE, boards[0]);
11027 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11028 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11029 selection = (ChessSquare) (PROMOTED piece);
11030 } else if(piece == EmptySquare) selection = WhiteSilver;
11031 else selection = (ChessSquare)((int)piece - 1);
11035 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11036 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11037 selection = (ChessSquare) (DEMOTED piece);
11038 } else if(piece == EmptySquare) selection = BlackSilver;
11039 else selection = (ChessSquare)((int)piece + 1);
11044 if(gameInfo.variant == VariantShatranj ||
11045 gameInfo.variant == VariantXiangqi ||
11046 gameInfo.variant == VariantCourier )
11047 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11052 if(gameInfo.variant == VariantXiangqi)
11053 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11054 if(gameInfo.variant == VariantKnightmate)
11055 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11058 if (gameMode == IcsExamining) {
11059 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11060 PieceToChar(selection), AAA + x, ONE + y);
11063 boards[0][y][x] = selection;
11064 DrawPosition(FALSE, boards[0]);
11072 DropMenuEvent(selection, x, y)
11073 ChessSquare selection;
11076 ChessMove moveType;
11078 switch (gameMode) {
11079 case IcsPlayingWhite:
11080 case MachinePlaysBlack:
11081 if (!WhiteOnMove(currentMove)) {
11082 DisplayMoveError(_("It is Black's turn"));
11085 moveType = WhiteDrop;
11087 case IcsPlayingBlack:
11088 case MachinePlaysWhite:
11089 if (WhiteOnMove(currentMove)) {
11090 DisplayMoveError(_("It is White's turn"));
11093 moveType = BlackDrop;
11096 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11102 if (moveType == BlackDrop && selection < BlackPawn) {
11103 selection = (ChessSquare) ((int) selection
11104 + (int) BlackPawn - (int) WhitePawn);
11106 if (boards[currentMove][y][x] != EmptySquare) {
11107 DisplayMoveError(_("That square is occupied"));
11111 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11117 /* Accept a pending offer of any kind from opponent */
11119 if (appData.icsActive) {
11120 SendToICS(ics_prefix);
11121 SendToICS("accept\n");
11122 } else if (cmailMsgLoaded) {
11123 if (currentMove == cmailOldMove &&
11124 commentList[cmailOldMove] != NULL &&
11125 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11126 "Black offers a draw" : "White offers a draw")) {
11128 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11129 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11131 DisplayError(_("There is no pending offer on this move"), 0);
11132 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11135 /* Not used for offers from chess program */
11142 /* Decline a pending offer of any kind from opponent */
11144 if (appData.icsActive) {
11145 SendToICS(ics_prefix);
11146 SendToICS("decline\n");
11147 } else if (cmailMsgLoaded) {
11148 if (currentMove == cmailOldMove &&
11149 commentList[cmailOldMove] != NULL &&
11150 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11151 "Black offers a draw" : "White offers a draw")) {
11153 AppendComment(cmailOldMove, "Draw declined");
11154 DisplayComment(cmailOldMove - 1, "Draw declined");
11157 DisplayError(_("There is no pending offer on this move"), 0);
11160 /* Not used for offers from chess program */
11167 /* Issue ICS rematch command */
11168 if (appData.icsActive) {
11169 SendToICS(ics_prefix);
11170 SendToICS("rematch\n");
11177 /* Call your opponent's flag (claim a win on time) */
11178 if (appData.icsActive) {
11179 SendToICS(ics_prefix);
11180 SendToICS("flag\n");
11182 switch (gameMode) {
11185 case MachinePlaysWhite:
11188 GameEnds(GameIsDrawn, "Both players ran out of time",
11191 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11193 DisplayError(_("Your opponent is not out of time"), 0);
11196 case MachinePlaysBlack:
11199 GameEnds(GameIsDrawn, "Both players ran out of time",
11202 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11204 DisplayError(_("Your opponent is not out of time"), 0);
11214 /* Offer draw or accept pending draw offer from opponent */
11216 if (appData.icsActive) {
11217 /* Note: tournament rules require draw offers to be
11218 made after you make your move but before you punch
11219 your clock. Currently ICS doesn't let you do that;
11220 instead, you immediately punch your clock after making
11221 a move, but you can offer a draw at any time. */
11223 SendToICS(ics_prefix);
11224 SendToICS("draw\n");
11225 } else if (cmailMsgLoaded) {
11226 if (currentMove == cmailOldMove &&
11227 commentList[cmailOldMove] != NULL &&
11228 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11229 "Black offers a draw" : "White offers a draw")) {
11230 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11231 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11232 } else if (currentMove == cmailOldMove + 1) {
11233 char *offer = WhiteOnMove(cmailOldMove) ?
11234 "White offers a draw" : "Black offers a draw";
11235 AppendComment(currentMove, offer);
11236 DisplayComment(currentMove - 1, offer);
11237 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11239 DisplayError(_("You must make your move before offering a draw"), 0);
11240 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11242 } else if (first.offeredDraw) {
11243 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11245 if (first.sendDrawOffers) {
11246 SendToProgram("draw\n", &first);
11247 userOfferedDraw = TRUE;
11255 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11257 if (appData.icsActive) {
11258 SendToICS(ics_prefix);
11259 SendToICS("adjourn\n");
11261 /* Currently GNU Chess doesn't offer or accept Adjourns */
11269 /* Offer Abort or accept pending Abort offer from opponent */
11271 if (appData.icsActive) {
11272 SendToICS(ics_prefix);
11273 SendToICS("abort\n");
11275 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11282 /* Resign. You can do this even if it's not your turn. */
11284 if (appData.icsActive) {
11285 SendToICS(ics_prefix);
11286 SendToICS("resign\n");
11288 switch (gameMode) {
11289 case MachinePlaysWhite:
11290 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11292 case MachinePlaysBlack:
11293 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11296 if (cmailMsgLoaded) {
11298 if (WhiteOnMove(cmailOldMove)) {
11299 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11301 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11303 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11314 StopObservingEvent()
11316 /* Stop observing current games */
11317 SendToICS(ics_prefix);
11318 SendToICS("unobserve\n");
11322 StopExaminingEvent()
11324 /* Stop observing current game */
11325 SendToICS(ics_prefix);
11326 SendToICS("unexamine\n");
11330 ForwardInner(target)
11335 if (appData.debugMode)
11336 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11337 target, currentMove, forwardMostMove);
11339 if (gameMode == EditPosition)
11342 if (gameMode == PlayFromGameFile && !pausing)
11345 if (gameMode == IcsExamining && pausing)
11346 limit = pauseExamForwardMostMove;
11348 limit = forwardMostMove;
11350 if (target > limit) target = limit;
11352 if (target > 0 && moveList[target - 1][0]) {
11353 int fromX, fromY, toX, toY;
11354 toX = moveList[target - 1][2] - AAA;
11355 toY = moveList[target - 1][3] - ONE;
11356 if (moveList[target - 1][1] == '@') {
11357 if (appData.highlightLastMove) {
11358 SetHighlights(-1, -1, toX, toY);
11361 fromX = moveList[target - 1][0] - AAA;
11362 fromY = moveList[target - 1][1] - ONE;
11363 if (target == currentMove + 1) {
11364 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11366 if (appData.highlightLastMove) {
11367 SetHighlights(fromX, fromY, toX, toY);
11371 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11372 gameMode == Training || gameMode == PlayFromGameFile ||
11373 gameMode == AnalyzeFile) {
11374 while (currentMove < target) {
11375 SendMoveToProgram(currentMove++, &first);
11378 currentMove = target;
11381 if (gameMode == EditGame || gameMode == EndOfGame) {
11382 whiteTimeRemaining = timeRemaining[0][currentMove];
11383 blackTimeRemaining = timeRemaining[1][currentMove];
11385 DisplayBothClocks();
11386 DisplayMove(currentMove - 1);
11387 DrawPosition(FALSE, boards[currentMove]);
11388 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11389 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11390 DisplayComment(currentMove - 1, commentList[currentMove]);
11398 if (gameMode == IcsExamining && !pausing) {
11399 SendToICS(ics_prefix);
11400 SendToICS("forward\n");
11402 ForwardInner(currentMove + 1);
11409 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11410 /* to optimze, we temporarily turn off analysis mode while we feed
11411 * the remaining moves to the engine. Otherwise we get analysis output
11414 if (first.analysisSupport) {
11415 SendToProgram("exit\nforce\n", &first);
11416 first.analyzing = FALSE;
11420 if (gameMode == IcsExamining && !pausing) {
11421 SendToICS(ics_prefix);
11422 SendToICS("forward 999999\n");
11424 ForwardInner(forwardMostMove);
11427 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11428 /* we have fed all the moves, so reactivate analysis mode */
11429 SendToProgram("analyze\n", &first);
11430 first.analyzing = TRUE;
11431 /*first.maybeThinking = TRUE;*/
11432 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11437 BackwardInner(target)
11440 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11442 if (appData.debugMode)
11443 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11444 target, currentMove, forwardMostMove);
11446 if (gameMode == EditPosition) return;
11447 if (currentMove <= backwardMostMove) {
11449 DrawPosition(full_redraw, boards[currentMove]);
11452 if (gameMode == PlayFromGameFile && !pausing)
11455 if (moveList[target][0]) {
11456 int fromX, fromY, toX, toY;
11457 toX = moveList[target][2] - AAA;
11458 toY = moveList[target][3] - ONE;
11459 if (moveList[target][1] == '@') {
11460 if (appData.highlightLastMove) {
11461 SetHighlights(-1, -1, toX, toY);
11464 fromX = moveList[target][0] - AAA;
11465 fromY = moveList[target][1] - ONE;
11466 if (target == currentMove - 1) {
11467 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11469 if (appData.highlightLastMove) {
11470 SetHighlights(fromX, fromY, toX, toY);
11474 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11475 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11476 while (currentMove > target) {
11477 SendToProgram("undo\n", &first);
11481 currentMove = target;
11484 if (gameMode == EditGame || gameMode == EndOfGame) {
11485 whiteTimeRemaining = timeRemaining[0][currentMove];
11486 blackTimeRemaining = timeRemaining[1][currentMove];
11488 DisplayBothClocks();
11489 DisplayMove(currentMove - 1);
11490 DrawPosition(full_redraw, boards[currentMove]);
11491 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11492 // [HGM] PV info: routine tests if comment empty
11493 DisplayComment(currentMove - 1, commentList[currentMove]);
11499 if (gameMode == IcsExamining && !pausing) {
11500 SendToICS(ics_prefix);
11501 SendToICS("backward\n");
11503 BackwardInner(currentMove - 1);
11510 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11511 /* to optimze, we temporarily turn off analysis mode while we undo
11512 * all the moves. Otherwise we get analysis output after each undo.
11514 if (first.analysisSupport) {
11515 SendToProgram("exit\nforce\n", &first);
11516 first.analyzing = FALSE;
11520 if (gameMode == IcsExamining && !pausing) {
11521 SendToICS(ics_prefix);
11522 SendToICS("backward 999999\n");
11524 BackwardInner(backwardMostMove);
11527 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11528 /* we have fed all the moves, so reactivate analysis mode */
11529 SendToProgram("analyze\n", &first);
11530 first.analyzing = TRUE;
11531 /*first.maybeThinking = TRUE;*/
11532 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11539 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11540 if (to >= forwardMostMove) to = forwardMostMove;
11541 if (to <= backwardMostMove) to = backwardMostMove;
11542 if (to < currentMove) {
11552 if (gameMode != IcsExamining) {
11553 DisplayError(_("You are not examining a game"), 0);
11557 DisplayError(_("You can't revert while pausing"), 0);
11560 SendToICS(ics_prefix);
11561 SendToICS("revert\n");
11567 switch (gameMode) {
11568 case MachinePlaysWhite:
11569 case MachinePlaysBlack:
11570 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11571 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11574 if (forwardMostMove < 2) return;
11575 currentMove = forwardMostMove = forwardMostMove - 2;
11576 whiteTimeRemaining = timeRemaining[0][currentMove];
11577 blackTimeRemaining = timeRemaining[1][currentMove];
11578 DisplayBothClocks();
11579 DisplayMove(currentMove - 1);
11580 ClearHighlights();/*!! could figure this out*/
11581 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11582 SendToProgram("remove\n", &first);
11583 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11586 case BeginningOfGame:
11590 case IcsPlayingWhite:
11591 case IcsPlayingBlack:
11592 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11593 SendToICS(ics_prefix);
11594 SendToICS("takeback 2\n");
11596 SendToICS(ics_prefix);
11597 SendToICS("takeback 1\n");
11606 ChessProgramState *cps;
11608 switch (gameMode) {
11609 case MachinePlaysWhite:
11610 if (!WhiteOnMove(forwardMostMove)) {
11611 DisplayError(_("It is your turn"), 0);
11616 case MachinePlaysBlack:
11617 if (WhiteOnMove(forwardMostMove)) {
11618 DisplayError(_("It is your turn"), 0);
11623 case TwoMachinesPlay:
11624 if (WhiteOnMove(forwardMostMove) ==
11625 (first.twoMachinesColor[0] == 'w')) {
11631 case BeginningOfGame:
11635 SendToProgram("?\n", cps);
11639 TruncateGameEvent()
11642 if (gameMode != EditGame) return;
11649 if (forwardMostMove > currentMove) {
11650 if (gameInfo.resultDetails != NULL) {
11651 free(gameInfo.resultDetails);
11652 gameInfo.resultDetails = NULL;
11653 gameInfo.result = GameUnfinished;
11655 forwardMostMove = currentMove;
11656 HistorySet(parseList, backwardMostMove, forwardMostMove,
11664 if (appData.noChessProgram) return;
11665 switch (gameMode) {
11666 case MachinePlaysWhite:
11667 if (WhiteOnMove(forwardMostMove)) {
11668 DisplayError(_("Wait until your turn"), 0);
11672 case BeginningOfGame:
11673 case MachinePlaysBlack:
11674 if (!WhiteOnMove(forwardMostMove)) {
11675 DisplayError(_("Wait until your turn"), 0);
11680 DisplayError(_("No hint available"), 0);
11683 SendToProgram("hint\n", &first);
11684 hintRequested = TRUE;
11690 if (appData.noChessProgram) return;
11691 switch (gameMode) {
11692 case MachinePlaysWhite:
11693 if (WhiteOnMove(forwardMostMove)) {
11694 DisplayError(_("Wait until your turn"), 0);
11698 case BeginningOfGame:
11699 case MachinePlaysBlack:
11700 if (!WhiteOnMove(forwardMostMove)) {
11701 DisplayError(_("Wait until your turn"), 0);
11706 EditPositionDone();
11708 case TwoMachinesPlay:
11713 SendToProgram("bk\n", &first);
11714 bookOutput[0] = NULLCHAR;
11715 bookRequested = TRUE;
11721 char *tags = PGNTags(&gameInfo);
11722 TagsPopUp(tags, CmailMsg());
11726 /* end button procedures */
11729 PrintPosition(fp, move)
11735 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11736 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11737 char c = PieceToChar(boards[move][i][j]);
11738 fputc(c == 'x' ? '.' : c, fp);
11739 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11742 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11743 fprintf(fp, "white to play\n");
11745 fprintf(fp, "black to play\n");
11752 if (gameInfo.white != NULL) {
11753 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11759 /* Find last component of program's own name, using some heuristics */
11761 TidyProgramName(prog, host, buf)
11762 char *prog, *host, buf[MSG_SIZ];
11765 int local = (strcmp(host, "localhost") == 0);
11766 while (!local && (p = strchr(prog, ';')) != NULL) {
11768 while (*p == ' ') p++;
11771 if (*prog == '"' || *prog == '\'') {
11772 q = strchr(prog + 1, *prog);
11774 q = strchr(prog, ' ');
11776 if (q == NULL) q = prog + strlen(prog);
11778 while (p >= prog && *p != '/' && *p != '\\') p--;
11780 if(p == prog && *p == '"') p++;
11781 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11782 memcpy(buf, p, q - p);
11783 buf[q - p] = NULLCHAR;
11791 TimeControlTagValue()
11794 if (!appData.clockMode) {
11796 } else if (movesPerSession > 0) {
11797 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11798 } else if (timeIncrement == 0) {
11799 sprintf(buf, "%ld", timeControl/1000);
11801 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11803 return StrSave(buf);
11809 /* This routine is used only for certain modes */
11810 VariantClass v = gameInfo.variant;
11811 ClearGameInfo(&gameInfo);
11812 gameInfo.variant = v;
11814 switch (gameMode) {
11815 case MachinePlaysWhite:
11816 gameInfo.event = StrSave( appData.pgnEventHeader );
11817 gameInfo.site = StrSave(HostName());
11818 gameInfo.date = PGNDate();
11819 gameInfo.round = StrSave("-");
11820 gameInfo.white = StrSave(first.tidy);
11821 gameInfo.black = StrSave(UserName());
11822 gameInfo.timeControl = TimeControlTagValue();
11825 case MachinePlaysBlack:
11826 gameInfo.event = StrSave( appData.pgnEventHeader );
11827 gameInfo.site = StrSave(HostName());
11828 gameInfo.date = PGNDate();
11829 gameInfo.round = StrSave("-");
11830 gameInfo.white = StrSave(UserName());
11831 gameInfo.black = StrSave(first.tidy);
11832 gameInfo.timeControl = TimeControlTagValue();
11835 case TwoMachinesPlay:
11836 gameInfo.event = StrSave( appData.pgnEventHeader );
11837 gameInfo.site = StrSave(HostName());
11838 gameInfo.date = PGNDate();
11839 if (matchGame > 0) {
11841 sprintf(buf, "%d", matchGame);
11842 gameInfo.round = StrSave(buf);
11844 gameInfo.round = StrSave("-");
11846 if (first.twoMachinesColor[0] == 'w') {
11847 gameInfo.white = StrSave(first.tidy);
11848 gameInfo.black = StrSave(second.tidy);
11850 gameInfo.white = StrSave(second.tidy);
11851 gameInfo.black = StrSave(first.tidy);
11853 gameInfo.timeControl = TimeControlTagValue();
11857 gameInfo.event = StrSave("Edited game");
11858 gameInfo.site = StrSave(HostName());
11859 gameInfo.date = PGNDate();
11860 gameInfo.round = StrSave("-");
11861 gameInfo.white = StrSave("-");
11862 gameInfo.black = StrSave("-");
11866 gameInfo.event = StrSave("Edited position");
11867 gameInfo.site = StrSave(HostName());
11868 gameInfo.date = PGNDate();
11869 gameInfo.round = StrSave("-");
11870 gameInfo.white = StrSave("-");
11871 gameInfo.black = StrSave("-");
11874 case IcsPlayingWhite:
11875 case IcsPlayingBlack:
11880 case PlayFromGameFile:
11881 gameInfo.event = StrSave("Game from non-PGN file");
11882 gameInfo.site = StrSave(HostName());
11883 gameInfo.date = PGNDate();
11884 gameInfo.round = StrSave("-");
11885 gameInfo.white = StrSave("?");
11886 gameInfo.black = StrSave("?");
11895 ReplaceComment(index, text)
11901 while (*text == '\n') text++;
11902 len = strlen(text);
11903 while (len > 0 && text[len - 1] == '\n') len--;
11905 if (commentList[index] != NULL)
11906 free(commentList[index]);
11909 commentList[index] = NULL;
11912 commentList[index] = (char *) malloc(len + 2);
11913 strncpy(commentList[index], text, len);
11914 commentList[index][len] = '\n';
11915 commentList[index][len + 1] = NULLCHAR;
11928 if (ch == '\r') continue;
11930 } while (ch != '\0');
11934 AppendComment(index, text)
11941 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11944 while (*text == '\n') text++;
11945 len = strlen(text);
11946 while (len > 0 && text[len - 1] == '\n') len--;
11948 if (len == 0) return;
11950 if (commentList[index] != NULL) {
11951 old = commentList[index];
11952 oldlen = strlen(old);
11953 commentList[index] = (char *) malloc(oldlen + len + 2);
11954 strcpy(commentList[index], old);
11956 strncpy(&commentList[index][oldlen], text, len);
11957 commentList[index][oldlen + len] = '\n';
11958 commentList[index][oldlen + len + 1] = NULLCHAR;
11960 commentList[index] = (char *) malloc(len + 2);
11961 strncpy(commentList[index], text, len);
11962 commentList[index][len] = '\n';
11963 commentList[index][len + 1] = NULLCHAR;
11967 static char * FindStr( char * text, char * sub_text )
11969 char * result = strstr( text, sub_text );
11971 if( result != NULL ) {
11972 result += strlen( sub_text );
11978 /* [AS] Try to extract PV info from PGN comment */
11979 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11980 char *GetInfoFromComment( int index, char * text )
11984 if( text != NULL && index > 0 ) {
11987 int time = -1, sec = 0, deci;
11988 char * s_eval = FindStr( text, "[%eval " );
11989 char * s_emt = FindStr( text, "[%emt " );
11991 if( s_eval != NULL || s_emt != NULL ) {
11995 if( s_eval != NULL ) {
11996 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12000 if( delim != ']' ) {
12005 if( s_emt != NULL ) {
12009 /* We expect something like: [+|-]nnn.nn/dd */
12012 sep = strchr( text, '/' );
12013 if( sep == NULL || sep < (text+4) ) {
12017 time = -1; sec = -1; deci = -1;
12018 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12019 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12020 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12021 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12025 if( score_lo < 0 || score_lo >= 100 ) {
12029 if(sec >= 0) time = 600*time + 10*sec; else
12030 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12032 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12034 /* [HGM] PV time: now locate end of PV info */
12035 while( *++sep >= '0' && *sep <= '9'); // strip depth
12037 while( *++sep >= '0' && *sep <= '9'); // strip time
12039 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12041 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12042 while(*sep == ' ') sep++;
12053 pvInfoList[index-1].depth = depth;
12054 pvInfoList[index-1].score = score;
12055 pvInfoList[index-1].time = 10*time; // centi-sec
12061 SendToProgram(message, cps)
12063 ChessProgramState *cps;
12065 int count, outCount, error;
12068 if (cps->pr == NULL) return;
12071 if (appData.debugMode) {
12074 fprintf(debugFP, "%ld >%-6s: %s",
12075 SubtractTimeMarks(&now, &programStartTime),
12076 cps->which, message);
12079 count = strlen(message);
12080 outCount = OutputToProcess(cps->pr, message, count, &error);
12081 if (outCount < count && !exiting
12082 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12083 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12084 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12085 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12086 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12087 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12089 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12091 gameInfo.resultDetails = buf;
12093 DisplayFatalError(buf, error, 1);
12098 ReceiveFromProgram(isr, closure, message, count, error)
12099 InputSourceRef isr;
12107 ChessProgramState *cps = (ChessProgramState *)closure;
12109 if (isr != cps->isr) return; /* Killed intentionally */
12113 _("Error: %s chess program (%s) exited unexpectedly"),
12114 cps->which, cps->program);
12115 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12116 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12117 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12118 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12120 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12122 gameInfo.resultDetails = buf;
12124 RemoveInputSource(cps->isr);
12125 DisplayFatalError(buf, 0, 1);
12128 _("Error reading from %s chess program (%s)"),
12129 cps->which, cps->program);
12130 RemoveInputSource(cps->isr);
12132 /* [AS] Program is misbehaving badly... kill it */
12133 if( count == -2 ) {
12134 DestroyChildProcess( cps->pr, 9 );
12138 DisplayFatalError(buf, error, 1);
12143 if ((end_str = strchr(message, '\r')) != NULL)
12144 *end_str = NULLCHAR;
12145 if ((end_str = strchr(message, '\n')) != NULL)
12146 *end_str = NULLCHAR;
12148 if (appData.debugMode) {
12149 TimeMark now; int print = 1;
12150 char *quote = ""; char c; int i;
12152 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12153 char start = message[0];
12154 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12155 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12156 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12157 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12158 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12159 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12160 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12161 { quote = "# "; print = (appData.engineComments == 2); }
12162 message[0] = start; // restore original message
12166 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12167 SubtractTimeMarks(&now, &programStartTime), cps->which,
12173 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12174 if (appData.icsEngineAnalyze) {
12175 if (strstr(message, "whisper") != NULL ||
12176 strstr(message, "kibitz") != NULL ||
12177 strstr(message, "tellics") != NULL) return;
12180 HandleMachineMove(message, cps);
12185 SendTimeControl(cps, mps, tc, inc, sd, st)
12186 ChessProgramState *cps;
12187 int mps, inc, sd, st;
12193 if( timeControl_2 > 0 ) {
12194 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12195 tc = timeControl_2;
12198 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12199 inc /= cps->timeOdds;
12200 st /= cps->timeOdds;
12202 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12205 /* Set exact time per move, normally using st command */
12206 if (cps->stKludge) {
12207 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12209 if (seconds == 0) {
12210 sprintf(buf, "level 1 %d\n", st/60);
12212 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12215 sprintf(buf, "st %d\n", st);
12218 /* Set conventional or incremental time control, using level command */
12219 if (seconds == 0) {
12220 /* Note old gnuchess bug -- minutes:seconds used to not work.
12221 Fixed in later versions, but still avoid :seconds
12222 when seconds is 0. */
12223 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12225 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12226 seconds, inc/1000);
12229 SendToProgram(buf, cps);
12231 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12232 /* Orthogonally, limit search to given depth */
12234 if (cps->sdKludge) {
12235 sprintf(buf, "depth\n%d\n", sd);
12237 sprintf(buf, "sd %d\n", sd);
12239 SendToProgram(buf, cps);
12242 if(cps->nps > 0) { /* [HGM] nps */
12243 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12245 sprintf(buf, "nps %d\n", cps->nps);
12246 SendToProgram(buf, cps);
12251 ChessProgramState *WhitePlayer()
12252 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12254 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12255 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12261 SendTimeRemaining(cps, machineWhite)
12262 ChessProgramState *cps;
12263 int /*boolean*/ machineWhite;
12265 char message[MSG_SIZ];
12268 /* Note: this routine must be called when the clocks are stopped
12269 or when they have *just* been set or switched; otherwise
12270 it will be off by the time since the current tick started.
12272 if (machineWhite) {
12273 time = whiteTimeRemaining / 10;
12274 otime = blackTimeRemaining / 10;
12276 time = blackTimeRemaining / 10;
12277 otime = whiteTimeRemaining / 10;
12279 /* [HGM] translate opponent's time by time-odds factor */
12280 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12281 if (appData.debugMode) {
12282 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12285 if (time <= 0) time = 1;
12286 if (otime <= 0) otime = 1;
12288 sprintf(message, "time %ld\n", time);
12289 SendToProgram(message, cps);
12291 sprintf(message, "otim %ld\n", otime);
12292 SendToProgram(message, cps);
12296 BoolFeature(p, name, loc, cps)
12300 ChessProgramState *cps;
12303 int len = strlen(name);
12305 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12307 sscanf(*p, "%d", &val);
12309 while (**p && **p != ' ') (*p)++;
12310 sprintf(buf, "accepted %s\n", name);
12311 SendToProgram(buf, cps);
12318 IntFeature(p, name, loc, cps)
12322 ChessProgramState *cps;
12325 int len = strlen(name);
12326 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12328 sscanf(*p, "%d", loc);
12329 while (**p && **p != ' ') (*p)++;
12330 sprintf(buf, "accepted %s\n", name);
12331 SendToProgram(buf, cps);
12338 StringFeature(p, name, loc, cps)
12342 ChessProgramState *cps;
12345 int len = strlen(name);
12346 if (strncmp((*p), name, len) == 0
12347 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12349 sscanf(*p, "%[^\"]", loc);
12350 while (**p && **p != '\"') (*p)++;
12351 if (**p == '\"') (*p)++;
12352 sprintf(buf, "accepted %s\n", name);
12353 SendToProgram(buf, cps);
12360 ParseOption(Option *opt, ChessProgramState *cps)
12361 // [HGM] options: process the string that defines an engine option, and determine
12362 // name, type, default value, and allowed value range
12364 char *p, *q, buf[MSG_SIZ];
12365 int n, min = (-1)<<31, max = 1<<31, def;
12367 if(p = strstr(opt->name, " -spin ")) {
12368 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12369 if(max < min) max = min; // enforce consistency
12370 if(def < min) def = min;
12371 if(def > max) def = max;
12376 } else if(p = strstr(opt->name, " -string ")) {
12377 opt->textValue = p+9;
12378 opt->type = TextBox;
12379 } else if(p = strstr(opt->name, " -check ")) {
12380 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12381 opt->value = (def != 0);
12382 opt->type = CheckBox;
12383 } else if(p = strstr(opt->name, " -combo ")) {
12384 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12385 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12386 opt->value = n = 0;
12387 while(q = StrStr(q, " /// ")) {
12388 n++; *q = 0; // count choices, and null-terminate each of them
12390 if(*q == '*') { // remember default, which is marked with * prefix
12394 cps->comboList[cps->comboCnt++] = q;
12396 cps->comboList[cps->comboCnt++] = NULL;
12398 opt->type = ComboBox;
12399 } else if(p = strstr(opt->name, " -button")) {
12400 opt->type = Button;
12401 } else if(p = strstr(opt->name, " -save")) {
12402 opt->type = SaveButton;
12403 } else return FALSE;
12404 *p = 0; // terminate option name
12405 // now look if the command-line options define a setting for this engine option.
12406 if(cps->optionSettings && cps->optionSettings[0])
12407 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12408 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12409 sprintf(buf, "option %s", p);
12410 if(p = strstr(buf, ",")) *p = 0;
12412 SendToProgram(buf, cps);
12418 FeatureDone(cps, val)
12419 ChessProgramState* cps;
12422 DelayedEventCallback cb = GetDelayedEvent();
12423 if ((cb == InitBackEnd3 && cps == &first) ||
12424 (cb == TwoMachinesEventIfReady && cps == &second)) {
12425 CancelDelayedEvent();
12426 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12428 cps->initDone = val;
12431 /* Parse feature command from engine */
12433 ParseFeatures(args, cps)
12435 ChessProgramState *cps;
12443 while (*p == ' ') p++;
12444 if (*p == NULLCHAR) return;
12446 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12447 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12448 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12449 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12450 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12451 if (BoolFeature(&p, "reuse", &val, cps)) {
12452 /* Engine can disable reuse, but can't enable it if user said no */
12453 if (!val) cps->reuse = FALSE;
12456 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12457 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12458 if (gameMode == TwoMachinesPlay) {
12459 DisplayTwoMachinesTitle();
12465 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12466 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12467 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12468 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12469 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12470 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12471 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12472 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12473 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12474 if (IntFeature(&p, "done", &val, cps)) {
12475 FeatureDone(cps, val);
12478 /* Added by Tord: */
12479 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12480 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12481 /* End of additions by Tord */
12483 /* [HGM] added features: */
12484 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12485 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12486 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12487 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12488 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12489 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12490 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12491 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12492 if(cps->nrOptions >= MAX_OPTIONS) {
12494 sprintf(buf, "%s engine has too many options\n", cps->which);
12495 DisplayError(buf, 0);
12499 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12500 /* End of additions by HGM */
12502 /* unknown feature: complain and skip */
12504 while (*q && *q != '=') q++;
12505 sprintf(buf, "rejected %.*s\n", q-p, p);
12506 SendToProgram(buf, cps);
12512 while (*p && *p != '\"') p++;
12513 if (*p == '\"') p++;
12515 while (*p && *p != ' ') p++;
12523 PeriodicUpdatesEvent(newState)
12526 if (newState == appData.periodicUpdates)
12529 appData.periodicUpdates=newState;
12531 /* Display type changes, so update it now */
12534 /* Get the ball rolling again... */
12536 AnalysisPeriodicEvent(1);
12537 StartAnalysisClock();
12542 PonderNextMoveEvent(newState)
12545 if (newState == appData.ponderNextMove) return;
12546 if (gameMode == EditPosition) EditPositionDone();
12548 SendToProgram("hard\n", &first);
12549 if (gameMode == TwoMachinesPlay) {
12550 SendToProgram("hard\n", &second);
12553 SendToProgram("easy\n", &first);
12554 thinkOutput[0] = NULLCHAR;
12555 if (gameMode == TwoMachinesPlay) {
12556 SendToProgram("easy\n", &second);
12559 appData.ponderNextMove = newState;
12563 NewSettingEvent(option, command, value)
12569 if (gameMode == EditPosition) EditPositionDone();
12570 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12571 SendToProgram(buf, &first);
12572 if (gameMode == TwoMachinesPlay) {
12573 SendToProgram(buf, &second);
12578 ShowThinkingEvent()
12579 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12581 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12582 int newState = appData.showThinking
12583 // [HGM] thinking: other features now need thinking output as well
12584 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12586 if (oldState == newState) return;
12587 oldState = newState;
12588 if (gameMode == EditPosition) EditPositionDone();
12590 SendToProgram("post\n", &first);
12591 if (gameMode == TwoMachinesPlay) {
12592 SendToProgram("post\n", &second);
12595 SendToProgram("nopost\n", &first);
12596 thinkOutput[0] = NULLCHAR;
12597 if (gameMode == TwoMachinesPlay) {
12598 SendToProgram("nopost\n", &second);
12601 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12605 AskQuestionEvent(title, question, replyPrefix, which)
12606 char *title; char *question; char *replyPrefix; char *which;
12608 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12609 if (pr == NoProc) return;
12610 AskQuestion(title, question, replyPrefix, pr);
12614 DisplayMove(moveNumber)
12617 char message[MSG_SIZ];
12619 char cpThinkOutput[MSG_SIZ];
12621 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12623 if (moveNumber == forwardMostMove - 1 ||
12624 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12626 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12628 if (strchr(cpThinkOutput, '\n')) {
12629 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12632 *cpThinkOutput = NULLCHAR;
12635 /* [AS] Hide thinking from human user */
12636 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12637 *cpThinkOutput = NULLCHAR;
12638 if( thinkOutput[0] != NULLCHAR ) {
12641 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12642 cpThinkOutput[i] = '.';
12644 cpThinkOutput[i] = NULLCHAR;
12645 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12649 if (moveNumber == forwardMostMove - 1 &&
12650 gameInfo.resultDetails != NULL) {
12651 if (gameInfo.resultDetails[0] == NULLCHAR) {
12652 sprintf(res, " %s", PGNResult(gameInfo.result));
12654 sprintf(res, " {%s} %s",
12655 gameInfo.resultDetails, PGNResult(gameInfo.result));
12661 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12662 DisplayMessage(res, cpThinkOutput);
12664 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12665 WhiteOnMove(moveNumber) ? " " : ".. ",
12666 parseList[moveNumber], res);
12667 DisplayMessage(message, cpThinkOutput);
12672 DisplayAnalysisText(text)
12677 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12678 || appData.icsEngineAnalyze) {
12679 sprintf(buf, "Analysis (%s)", first.tidy);
12680 AnalysisPopUp(buf, text);
12688 while (*str && isspace(*str)) ++str;
12689 while (*str && !isspace(*str)) ++str;
12690 if (!*str) return 1;
12691 while (*str && isspace(*str)) ++str;
12692 if (!*str) return 1;
12700 char lst[MSG_SIZ / 2];
12702 static char *xtra[] = { "", " (--)", " (++)" };
12705 if (programStats.time == 0) {
12706 programStats.time = 1;
12709 if (programStats.got_only_move) {
12710 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12712 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12714 nps = (u64ToDouble(programStats.nodes) /
12715 ((double)programStats.time /100.0));
12717 cs = programStats.time % 100;
12718 s = programStats.time / 100;
12724 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12725 if (programStats.move_name[0] != NULLCHAR) {
12726 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12727 programStats.depth,
12728 programStats.nr_moves-programStats.moves_left,
12729 programStats.nr_moves, programStats.move_name,
12730 ((float)programStats.score)/100.0, lst,
12731 only_one_move(lst)?
12732 xtra[programStats.got_fail] : "",
12733 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12735 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12736 programStats.depth,
12737 programStats.nr_moves-programStats.moves_left,
12738 programStats.nr_moves, ((float)programStats.score)/100.0,
12740 only_one_move(lst)?
12741 xtra[programStats.got_fail] : "",
12742 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12745 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12746 programStats.depth,
12747 ((float)programStats.score)/100.0,
12749 only_one_move(lst)?
12750 xtra[programStats.got_fail] : "",
12751 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12754 DisplayAnalysisText(buf);
12758 DisplayComment(moveNumber, text)
12762 char title[MSG_SIZ];
12763 char buf[8000]; // comment can be long!
12766 if( appData.autoDisplayComment ) {
12767 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12768 strcpy(title, "Comment");
12770 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12771 WhiteOnMove(moveNumber) ? " " : ".. ",
12772 parseList[moveNumber]);
12774 // [HGM] PV info: display PV info together with (or as) comment
12775 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12776 if(text == NULL) text = "";
12777 score = pvInfoList[moveNumber].score;
12778 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12779 depth, (pvInfoList[moveNumber].time+50)/100, text);
12782 } else title[0] = 0;
12785 CommentPopUp(title, text);
12788 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12789 * might be busy thinking or pondering. It can be omitted if your
12790 * gnuchess is configured to stop thinking immediately on any user
12791 * input. However, that gnuchess feature depends on the FIONREAD
12792 * ioctl, which does not work properly on some flavors of Unix.
12796 ChessProgramState *cps;
12799 if (!cps->useSigint) return;
12800 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12801 switch (gameMode) {
12802 case MachinePlaysWhite:
12803 case MachinePlaysBlack:
12804 case TwoMachinesPlay:
12805 case IcsPlayingWhite:
12806 case IcsPlayingBlack:
12809 /* Skip if we know it isn't thinking */
12810 if (!cps->maybeThinking) return;
12811 if (appData.debugMode)
12812 fprintf(debugFP, "Interrupting %s\n", cps->which);
12813 InterruptChildProcess(cps->pr);
12814 cps->maybeThinking = FALSE;
12819 #endif /*ATTENTION*/
12825 if (whiteTimeRemaining <= 0) {
12828 if (appData.icsActive) {
12829 if (appData.autoCallFlag &&
12830 gameMode == IcsPlayingBlack && !blackFlag) {
12831 SendToICS(ics_prefix);
12832 SendToICS("flag\n");
12836 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12838 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12839 if (appData.autoCallFlag) {
12840 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12847 if (blackTimeRemaining <= 0) {
12850 if (appData.icsActive) {
12851 if (appData.autoCallFlag &&
12852 gameMode == IcsPlayingWhite && !whiteFlag) {
12853 SendToICS(ics_prefix);
12854 SendToICS("flag\n");
12858 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12860 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12861 if (appData.autoCallFlag) {
12862 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12875 if (!appData.clockMode || appData.icsActive ||
12876 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12879 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12881 if ( !WhiteOnMove(forwardMostMove) )
12882 /* White made time control */
12883 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12884 /* [HGM] time odds: correct new time quota for time odds! */
12885 / WhitePlayer()->timeOdds;
12887 /* Black made time control */
12888 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12889 / WhitePlayer()->other->timeOdds;
12893 DisplayBothClocks()
12895 int wom = gameMode == EditPosition ?
12896 !blackPlaysFirst : WhiteOnMove(currentMove);
12897 DisplayWhiteClock(whiteTimeRemaining, wom);
12898 DisplayBlackClock(blackTimeRemaining, !wom);
12902 /* Timekeeping seems to be a portability nightmare. I think everyone
12903 has ftime(), but I'm really not sure, so I'm including some ifdefs
12904 to use other calls if you don't. Clocks will be less accurate if
12905 you have neither ftime nor gettimeofday.
12908 /* VS 2008 requires the #include outside of the function */
12909 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12910 #include <sys/timeb.h>
12913 /* Get the current time as a TimeMark */
12918 #if HAVE_GETTIMEOFDAY
12920 struct timeval timeVal;
12921 struct timezone timeZone;
12923 gettimeofday(&timeVal, &timeZone);
12924 tm->sec = (long) timeVal.tv_sec;
12925 tm->ms = (int) (timeVal.tv_usec / 1000L);
12927 #else /*!HAVE_GETTIMEOFDAY*/
12930 // include <sys/timeb.h> / moved to just above start of function
12931 struct timeb timeB;
12934 tm->sec = (long) timeB.time;
12935 tm->ms = (int) timeB.millitm;
12937 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12938 tm->sec = (long) time(NULL);
12944 /* Return the difference in milliseconds between two
12945 time marks. We assume the difference will fit in a long!
12948 SubtractTimeMarks(tm2, tm1)
12949 TimeMark *tm2, *tm1;
12951 return 1000L*(tm2->sec - tm1->sec) +
12952 (long) (tm2->ms - tm1->ms);
12957 * Code to manage the game clocks.
12959 * In tournament play, black starts the clock and then white makes a move.
12960 * We give the human user a slight advantage if he is playing white---the
12961 * clocks don't run until he makes his first move, so it takes zero time.
12962 * Also, we don't account for network lag, so we could get out of sync
12963 * with GNU Chess's clock -- but then, referees are always right.
12966 static TimeMark tickStartTM;
12967 static long intendedTickLength;
12970 NextTickLength(timeRemaining)
12971 long timeRemaining;
12973 long nominalTickLength, nextTickLength;
12975 if (timeRemaining > 0L && timeRemaining <= 10000L)
12976 nominalTickLength = 100L;
12978 nominalTickLength = 1000L;
12979 nextTickLength = timeRemaining % nominalTickLength;
12980 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12982 return nextTickLength;
12985 /* Adjust clock one minute up or down */
12987 AdjustClock(Boolean which, int dir)
12989 if(which) blackTimeRemaining += 60000*dir;
12990 else whiteTimeRemaining += 60000*dir;
12991 DisplayBothClocks();
12994 /* Stop clocks and reset to a fresh time control */
12998 (void) StopClockTimer();
12999 if (appData.icsActive) {
13000 whiteTimeRemaining = blackTimeRemaining = 0;
13001 } else { /* [HGM] correct new time quote for time odds */
13002 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13003 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13005 if (whiteFlag || blackFlag) {
13007 whiteFlag = blackFlag = FALSE;
13009 DisplayBothClocks();
13012 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13014 /* Decrement running clock by amount of time that has passed */
13018 long timeRemaining;
13019 long lastTickLength, fudge;
13022 if (!appData.clockMode) return;
13023 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13027 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13029 /* Fudge if we woke up a little too soon */
13030 fudge = intendedTickLength - lastTickLength;
13031 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13033 if (WhiteOnMove(forwardMostMove)) {
13034 if(whiteNPS >= 0) lastTickLength = 0;
13035 timeRemaining = whiteTimeRemaining -= lastTickLength;
13036 DisplayWhiteClock(whiteTimeRemaining - fudge,
13037 WhiteOnMove(currentMove));
13039 if(blackNPS >= 0) lastTickLength = 0;
13040 timeRemaining = blackTimeRemaining -= lastTickLength;
13041 DisplayBlackClock(blackTimeRemaining - fudge,
13042 !WhiteOnMove(currentMove));
13045 if (CheckFlags()) return;
13048 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13049 StartClockTimer(intendedTickLength);
13051 /* if the time remaining has fallen below the alarm threshold, sound the
13052 * alarm. if the alarm has sounded and (due to a takeback or time control
13053 * with increment) the time remaining has increased to a level above the
13054 * threshold, reset the alarm so it can sound again.
13057 if (appData.icsActive && appData.icsAlarm) {
13059 /* make sure we are dealing with the user's clock */
13060 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13061 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13064 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13065 alarmSounded = FALSE;
13066 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13068 alarmSounded = TRUE;
13074 /* A player has just moved, so stop the previously running
13075 clock and (if in clock mode) start the other one.
13076 We redisplay both clocks in case we're in ICS mode, because
13077 ICS gives us an update to both clocks after every move.
13078 Note that this routine is called *after* forwardMostMove
13079 is updated, so the last fractional tick must be subtracted
13080 from the color that is *not* on move now.
13085 long lastTickLength;
13087 int flagged = FALSE;
13091 if (StopClockTimer() && appData.clockMode) {
13092 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13093 if (WhiteOnMove(forwardMostMove)) {
13094 if(blackNPS >= 0) lastTickLength = 0;
13095 blackTimeRemaining -= lastTickLength;
13096 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13097 // if(pvInfoList[forwardMostMove-1].time == -1)
13098 pvInfoList[forwardMostMove-1].time = // use GUI time
13099 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13101 if(whiteNPS >= 0) lastTickLength = 0;
13102 whiteTimeRemaining -= lastTickLength;
13103 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13104 // if(pvInfoList[forwardMostMove-1].time == -1)
13105 pvInfoList[forwardMostMove-1].time =
13106 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13108 flagged = CheckFlags();
13110 CheckTimeControl();
13112 if (flagged || !appData.clockMode) return;
13114 switch (gameMode) {
13115 case MachinePlaysBlack:
13116 case MachinePlaysWhite:
13117 case BeginningOfGame:
13118 if (pausing) return;
13122 case PlayFromGameFile:
13131 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13132 whiteTimeRemaining : blackTimeRemaining);
13133 StartClockTimer(intendedTickLength);
13137 /* Stop both clocks */
13141 long lastTickLength;
13144 if (!StopClockTimer()) return;
13145 if (!appData.clockMode) return;
13149 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13150 if (WhiteOnMove(forwardMostMove)) {
13151 if(whiteNPS >= 0) lastTickLength = 0;
13152 whiteTimeRemaining -= lastTickLength;
13153 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13155 if(blackNPS >= 0) lastTickLength = 0;
13156 blackTimeRemaining -= lastTickLength;
13157 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13162 /* Start clock of player on move. Time may have been reset, so
13163 if clock is already running, stop and restart it. */
13167 (void) StopClockTimer(); /* in case it was running already */
13168 DisplayBothClocks();
13169 if (CheckFlags()) return;
13171 if (!appData.clockMode) return;
13172 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13174 GetTimeMark(&tickStartTM);
13175 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13176 whiteTimeRemaining : blackTimeRemaining);
13178 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13179 whiteNPS = blackNPS = -1;
13180 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13181 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13182 whiteNPS = first.nps;
13183 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13184 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13185 blackNPS = first.nps;
13186 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13187 whiteNPS = second.nps;
13188 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13189 blackNPS = second.nps;
13190 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13192 StartClockTimer(intendedTickLength);
13199 long second, minute, hour, day;
13201 static char buf[32];
13203 if (ms > 0 && ms <= 9900) {
13204 /* convert milliseconds to tenths, rounding up */
13205 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13207 sprintf(buf, " %03.1f ", tenths/10.0);
13211 /* convert milliseconds to seconds, rounding up */
13212 /* use floating point to avoid strangeness of integer division
13213 with negative dividends on many machines */
13214 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13221 day = second / (60 * 60 * 24);
13222 second = second % (60 * 60 * 24);
13223 hour = second / (60 * 60);
13224 second = second % (60 * 60);
13225 minute = second / 60;
13226 second = second % 60;
13229 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13230 sign, day, hour, minute, second);
13232 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13234 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13241 * This is necessary because some C libraries aren't ANSI C compliant yet.
13244 StrStr(string, match)
13245 char *string, *match;
13249 length = strlen(match);
13251 for (i = strlen(string) - length; i >= 0; i--, string++)
13252 if (!strncmp(match, string, length))
13259 StrCaseStr(string, match)
13260 char *string, *match;
13264 length = strlen(match);
13266 for (i = strlen(string) - length; i >= 0; i--, string++) {
13267 for (j = 0; j < length; j++) {
13268 if (ToLower(match[j]) != ToLower(string[j]))
13271 if (j == length) return string;
13285 c1 = ToLower(*s1++);
13286 c2 = ToLower(*s2++);
13287 if (c1 > c2) return 1;
13288 if (c1 < c2) return -1;
13289 if (c1 == NULLCHAR) return 0;
13298 return isupper(c) ? tolower(c) : c;
13306 return islower(c) ? toupper(c) : c;
13308 #endif /* !_amigados */
13316 if ((ret = (char *) malloc(strlen(s) + 1))) {
13323 StrSavePtr(s, savePtr)
13324 char *s, **savePtr;
13329 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13330 strcpy(*savePtr, s);
13342 clock = time((time_t *)NULL);
13343 tm = localtime(&clock);
13344 sprintf(buf, "%04d.%02d.%02d",
13345 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13346 return StrSave(buf);
13351 PositionToFEN(move, overrideCastling)
13353 char *overrideCastling;
13355 int i, j, fromX, fromY, toX, toY;
13362 whiteToPlay = (gameMode == EditPosition) ?
13363 !blackPlaysFirst : (move % 2 == 0);
13366 /* Piece placement data */
13367 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13369 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13370 if (boards[move][i][j] == EmptySquare) {
13372 } else { ChessSquare piece = boards[move][i][j];
13373 if (emptycount > 0) {
13374 if(emptycount<10) /* [HGM] can be >= 10 */
13375 *p++ = '0' + emptycount;
13376 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13379 if(PieceToChar(piece) == '+') {
13380 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13382 piece = (ChessSquare)(DEMOTED piece);
13384 *p++ = PieceToChar(piece);
13386 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13387 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13392 if (emptycount > 0) {
13393 if(emptycount<10) /* [HGM] can be >= 10 */
13394 *p++ = '0' + emptycount;
13395 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13402 /* [HGM] print Crazyhouse or Shogi holdings */
13403 if( gameInfo.holdingsWidth ) {
13404 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13406 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13407 piece = boards[move][i][BOARD_WIDTH-1];
13408 if( piece != EmptySquare )
13409 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13410 *p++ = PieceToChar(piece);
13412 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13413 piece = boards[move][BOARD_HEIGHT-i-1][0];
13414 if( piece != EmptySquare )
13415 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13416 *p++ = PieceToChar(piece);
13419 if( q == p ) *p++ = '-';
13425 *p++ = whiteToPlay ? 'w' : 'b';
13428 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13429 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13431 if(nrCastlingRights) {
13433 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13434 /* [HGM] write directly from rights */
13435 if(castlingRights[move][2] >= 0 &&
13436 castlingRights[move][0] >= 0 )
13437 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13438 if(castlingRights[move][2] >= 0 &&
13439 castlingRights[move][1] >= 0 )
13440 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13441 if(castlingRights[move][5] >= 0 &&
13442 castlingRights[move][3] >= 0 )
13443 *p++ = castlingRights[move][3] + AAA;
13444 if(castlingRights[move][5] >= 0 &&
13445 castlingRights[move][4] >= 0 )
13446 *p++ = castlingRights[move][4] + AAA;
13449 /* [HGM] write true castling rights */
13450 if( nrCastlingRights == 6 ) {
13451 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13452 castlingRights[move][2] >= 0 ) *p++ = 'K';
13453 if(castlingRights[move][1] == BOARD_LEFT &&
13454 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13455 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13456 castlingRights[move][5] >= 0 ) *p++ = 'k';
13457 if(castlingRights[move][4] == BOARD_LEFT &&
13458 castlingRights[move][5] >= 0 ) *p++ = 'q';
13461 if (q == p) *p++ = '-'; /* No castling rights */
13465 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13466 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13467 /* En passant target square */
13468 if (move > backwardMostMove) {
13469 fromX = moveList[move - 1][0] - AAA;
13470 fromY = moveList[move - 1][1] - ONE;
13471 toX = moveList[move - 1][2] - AAA;
13472 toY = moveList[move - 1][3] - ONE;
13473 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13474 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13475 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13477 /* 2-square pawn move just happened */
13479 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13490 /* [HGM] find reversible plies */
13491 { int i = 0, j=move;
13493 if (appData.debugMode) { int k;
13494 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13495 for(k=backwardMostMove; k<=forwardMostMove; k++)
13496 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13500 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13501 if( j == backwardMostMove ) i += initialRulePlies;
13502 sprintf(p, "%d ", i);
13503 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13505 /* Fullmove number */
13506 sprintf(p, "%d", (move / 2) + 1);
13508 return StrSave(buf);
13512 ParseFEN(board, blackPlaysFirst, fen)
13514 int *blackPlaysFirst;
13524 /* [HGM] by default clear Crazyhouse holdings, if present */
13525 if(gameInfo.holdingsWidth) {
13526 for(i=0; i<BOARD_HEIGHT; i++) {
13527 board[i][0] = EmptySquare; /* black holdings */
13528 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13529 board[i][1] = (ChessSquare) 0; /* black counts */
13530 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13534 /* Piece placement data */
13535 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13538 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13539 if (*p == '/') p++;
13540 emptycount = gameInfo.boardWidth - j;
13541 while (emptycount--)
13542 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13544 #if(BOARD_SIZE >= 10)
13545 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13546 p++; emptycount=10;
13547 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13548 while (emptycount--)
13549 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13551 } else if (isdigit(*p)) {
13552 emptycount = *p++ - '0';
13553 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13554 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13555 while (emptycount--)
13556 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13557 } else if (*p == '+' || isalpha(*p)) {
13558 if (j >= gameInfo.boardWidth) return FALSE;
13560 piece = CharToPiece(*++p);
13561 if(piece == EmptySquare) return FALSE; /* unknown piece */
13562 piece = (ChessSquare) (PROMOTED piece ); p++;
13563 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13564 } else piece = CharToPiece(*p++);
13566 if(piece==EmptySquare) return FALSE; /* unknown piece */
13567 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13568 piece = (ChessSquare) (PROMOTED piece);
13569 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13572 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13578 while (*p == '/' || *p == ' ') p++;
13580 /* [HGM] look for Crazyhouse holdings here */
13581 while(*p==' ') p++;
13582 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13584 if(*p == '-' ) *p++; /* empty holdings */ else {
13585 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13586 /* if we would allow FEN reading to set board size, we would */
13587 /* have to add holdings and shift the board read so far here */
13588 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13590 if((int) piece >= (int) BlackPawn ) {
13591 i = (int)piece - (int)BlackPawn;
13592 i = PieceToNumber((ChessSquare)i);
13593 if( i >= gameInfo.holdingsSize ) return FALSE;
13594 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13595 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13597 i = (int)piece - (int)WhitePawn;
13598 i = PieceToNumber((ChessSquare)i);
13599 if( i >= gameInfo.holdingsSize ) return FALSE;
13600 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13601 board[i][BOARD_WIDTH-2]++; /* black holdings */
13605 if(*p == ']') *p++;
13608 while(*p == ' ') p++;
13613 *blackPlaysFirst = FALSE;
13616 *blackPlaysFirst = TRUE;
13622 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13623 /* return the extra info in global variiables */
13625 /* set defaults in case FEN is incomplete */
13626 FENepStatus = EP_UNKNOWN;
13627 for(i=0; i<nrCastlingRights; i++ ) {
13628 FENcastlingRights[i] =
13629 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13630 } /* assume possible unless obviously impossible */
13631 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13632 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13633 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13634 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13635 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13636 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13639 while(*p==' ') p++;
13640 if(nrCastlingRights) {
13641 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13642 /* castling indicator present, so default becomes no castlings */
13643 for(i=0; i<nrCastlingRights; i++ ) {
13644 FENcastlingRights[i] = -1;
13647 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13648 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13649 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13650 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13651 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13653 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13654 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13655 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13659 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13660 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13661 FENcastlingRights[2] = whiteKingFile;
13664 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13665 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13666 FENcastlingRights[2] = whiteKingFile;
13669 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13670 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13671 FENcastlingRights[5] = blackKingFile;
13674 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13675 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13676 FENcastlingRights[5] = blackKingFile;
13679 default: /* FRC castlings */
13680 if(c >= 'a') { /* black rights */
13681 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13682 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13683 if(i == BOARD_RGHT) break;
13684 FENcastlingRights[5] = i;
13686 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13687 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13689 FENcastlingRights[3] = c;
13691 FENcastlingRights[4] = c;
13692 } else { /* white rights */
13693 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13694 if(board[0][i] == WhiteKing) break;
13695 if(i == BOARD_RGHT) break;
13696 FENcastlingRights[2] = i;
13697 c -= AAA - 'a' + 'A';
13698 if(board[0][c] >= WhiteKing) break;
13700 FENcastlingRights[0] = c;
13702 FENcastlingRights[1] = c;
13706 if (appData.debugMode) {
13707 fprintf(debugFP, "FEN castling rights:");
13708 for(i=0; i<nrCastlingRights; i++)
13709 fprintf(debugFP, " %d", FENcastlingRights[i]);
13710 fprintf(debugFP, "\n");
13713 while(*p==' ') p++;
13716 /* read e.p. field in games that know e.p. capture */
13717 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13718 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13720 p++; FENepStatus = EP_NONE;
13722 char c = *p++ - AAA;
13724 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13725 if(*p >= '0' && *p <='9') *p++;
13731 if(sscanf(p, "%d", &i) == 1) {
13732 FENrulePlies = i; /* 50-move ply counter */
13733 /* (The move number is still ignored) */
13740 EditPositionPasteFEN(char *fen)
13743 Board initial_position;
13745 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13746 DisplayError(_("Bad FEN position in clipboard"), 0);
13749 int savedBlackPlaysFirst = blackPlaysFirst;
13750 EditPositionEvent();
13751 blackPlaysFirst = savedBlackPlaysFirst;
13752 CopyBoard(boards[0], initial_position);
13753 /* [HGM] copy FEN attributes as well */
13755 initialRulePlies = FENrulePlies;
13756 epStatus[0] = FENepStatus;
13757 for( i=0; i<nrCastlingRights; i++ )
13758 castlingRights[0][i] = FENcastlingRights[i];
13760 EditPositionDone();
13761 DisplayBothClocks();
13762 DrawPosition(FALSE, boards[currentMove]);