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 (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2958 if (looking_at(buf, &i, "Illegal move") ||
2959 looking_at(buf, &i, "Not a legal move") ||
2960 looking_at(buf, &i, "Your king is in check") ||
2961 looking_at(buf, &i, "It isn't your turn") ||
2962 looking_at(buf, &i, "It is not your move")) {
2964 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2965 currentMove = --forwardMostMove;
2966 DisplayMove(currentMove - 1); /* before DMError */
2967 DrawPosition(FALSE, boards[currentMove]);
2969 DisplayBothClocks();
2971 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2977 if (looking_at(buf, &i, "still have time") ||
2978 looking_at(buf, &i, "not out of time") ||
2979 looking_at(buf, &i, "either player is out of time") ||
2980 looking_at(buf, &i, "has timeseal; checking")) {
2981 /* We must have called his flag a little too soon */
2982 whiteFlag = blackFlag = FALSE;
2986 if (looking_at(buf, &i, "added * seconds to") ||
2987 looking_at(buf, &i, "seconds were added to")) {
2988 /* Update the clocks */
2989 SendToICS(ics_prefix);
2990 SendToICS("refresh\n");
2994 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2995 ics_clock_paused = TRUE;
3000 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3001 ics_clock_paused = FALSE;
3006 /* Grab player ratings from the Creating: message.
3007 Note we have to check for the special case when
3008 the ICS inserts things like [white] or [black]. */
3009 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3010 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3012 0 player 1 name (not necessarily white)
3014 2 empty, white, or black (IGNORED)
3015 3 player 2 name (not necessarily black)
3018 The names/ratings are sorted out when the game
3019 actually starts (below).
3021 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3022 player1Rating = string_to_rating(star_match[1]);
3023 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3024 player2Rating = string_to_rating(star_match[4]);
3026 if (appData.debugMode)
3028 "Ratings from 'Creating:' %s %d, %s %d\n",
3029 player1Name, player1Rating,
3030 player2Name, player2Rating);
3035 /* Improved generic start/end-of-game messages */
3036 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3037 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3038 /* If tkind == 0: */
3039 /* star_match[0] is the game number */
3040 /* [1] is the white player's name */
3041 /* [2] is the black player's name */
3042 /* For end-of-game: */
3043 /* [3] is the reason for the game end */
3044 /* [4] is a PGN end game-token, preceded by " " */
3045 /* For start-of-game: */
3046 /* [3] begins with "Creating" or "Continuing" */
3047 /* [4] is " *" or empty (don't care). */
3048 int gamenum = atoi(star_match[0]);
3049 char *whitename, *blackname, *why, *endtoken;
3050 ChessMove endtype = (ChessMove) 0;
3053 whitename = star_match[1];
3054 blackname = star_match[2];
3055 why = star_match[3];
3056 endtoken = star_match[4];
3058 whitename = star_match[1];
3059 blackname = star_match[3];
3060 why = star_match[5];
3061 endtoken = star_match[6];
3064 /* Game start messages */
3065 if (strncmp(why, "Creating ", 9) == 0 ||
3066 strncmp(why, "Continuing ", 11) == 0) {
3067 gs_gamenum = gamenum;
3068 strcpy(gs_kind, strchr(why, ' ') + 1);
3070 if (appData.zippyPlay) {
3071 ZippyGameStart(whitename, blackname);
3077 /* Game end messages */
3078 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3079 ics_gamenum != gamenum) {
3082 while (endtoken[0] == ' ') endtoken++;
3083 switch (endtoken[0]) {
3086 endtype = GameUnfinished;
3089 endtype = BlackWins;
3092 if (endtoken[1] == '/')
3093 endtype = GameIsDrawn;
3095 endtype = WhiteWins;
3098 GameEnds(endtype, why, GE_ICS);
3100 if (appData.zippyPlay && first.initDone) {
3101 ZippyGameEnd(endtype, why);
3102 if (first.pr == NULL) {
3103 /* Start the next process early so that we'll
3104 be ready for the next challenge */
3105 StartChessProgram(&first);
3107 /* Send "new" early, in case this command takes
3108 a long time to finish, so that we'll be ready
3109 for the next challenge. */
3110 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3117 if (looking_at(buf, &i, "Removing game * from observation") ||
3118 looking_at(buf, &i, "no longer observing game *") ||
3119 looking_at(buf, &i, "Game * (*) has no examiners")) {
3120 if (gameMode == IcsObserving &&
3121 atoi(star_match[0]) == ics_gamenum)
3123 /* icsEngineAnalyze */
3124 if (appData.icsEngineAnalyze) {
3131 ics_user_moved = FALSE;
3136 if (looking_at(buf, &i, "no longer examining game *")) {
3137 if (gameMode == IcsExamining &&
3138 atoi(star_match[0]) == ics_gamenum)
3142 ics_user_moved = FALSE;
3147 /* Advance leftover_start past any newlines we find,
3148 so only partial lines can get reparsed */
3149 if (looking_at(buf, &i, "\n")) {
3150 prevColor = curColor;
3151 if (curColor != ColorNormal) {
3152 if (oldi > next_out) {
3153 SendToPlayer(&buf[next_out], oldi - next_out);
3156 Colorize(ColorNormal, FALSE);
3157 curColor = ColorNormal;
3159 if (started == STARTED_BOARD) {
3160 started = STARTED_NONE;
3161 parse[parse_pos] = NULLCHAR;
3162 ParseBoard12(parse);
3165 /* Send premove here */
3166 if (appData.premove) {
3168 if (currentMove == 0 &&
3169 gameMode == IcsPlayingWhite &&
3170 appData.premoveWhite) {
3171 sprintf(str, "%s%s\n", ics_prefix,
3172 appData.premoveWhiteText);
3173 if (appData.debugMode)
3174 fprintf(debugFP, "Sending premove:\n");
3176 } else if (currentMove == 1 &&
3177 gameMode == IcsPlayingBlack &&
3178 appData.premoveBlack) {
3179 sprintf(str, "%s%s\n", ics_prefix,
3180 appData.premoveBlackText);
3181 if (appData.debugMode)
3182 fprintf(debugFP, "Sending premove:\n");
3184 } else if (gotPremove) {
3186 ClearPremoveHighlights();
3187 if (appData.debugMode)
3188 fprintf(debugFP, "Sending premove:\n");
3189 UserMoveEvent(premoveFromX, premoveFromY,
3190 premoveToX, premoveToY,
3195 /* Usually suppress following prompt */
3196 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3197 if (looking_at(buf, &i, "*% ")) {
3198 savingComment = FALSE;
3202 } else if (started == STARTED_HOLDINGS) {
3204 char new_piece[MSG_SIZ];
3205 started = STARTED_NONE;
3206 parse[parse_pos] = NULLCHAR;
3207 if (appData.debugMode)
3208 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3209 parse, currentMove);
3210 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3211 gamenum == ics_gamenum) {
3212 if (gameInfo.variant == VariantNormal) {
3213 /* [HGM] We seem to switch variant during a game!
3214 * Presumably no holdings were displayed, so we have
3215 * to move the position two files to the right to
3216 * create room for them!
3218 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3219 /* Get a move list just to see the header, which
3220 will tell us whether this is really bug or zh */
3221 if (ics_getting_history == H_FALSE) {
3222 ics_getting_history = H_REQUESTED;
3223 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3227 new_piece[0] = NULLCHAR;
3228 sscanf(parse, "game %d white [%s black [%s <- %s",
3229 &gamenum, white_holding, black_holding,
3231 white_holding[strlen(white_holding)-1] = NULLCHAR;
3232 black_holding[strlen(black_holding)-1] = NULLCHAR;
3233 /* [HGM] copy holdings to board holdings area */
3234 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3235 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3237 if (appData.zippyPlay && first.initDone) {
3238 ZippyHoldings(white_holding, black_holding,
3242 if (tinyLayout || smallLayout) {
3243 char wh[16], bh[16];
3244 PackHolding(wh, white_holding);
3245 PackHolding(bh, black_holding);
3246 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3247 gameInfo.white, gameInfo.black);
3249 sprintf(str, "%s [%s] vs. %s [%s]",
3250 gameInfo.white, white_holding,
3251 gameInfo.black, black_holding);
3254 DrawPosition(FALSE, boards[currentMove]);
3257 /* Suppress following prompt */
3258 if (looking_at(buf, &i, "*% ")) {
3259 savingComment = FALSE;
3266 i++; /* skip unparsed character and loop back */
3269 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3270 started != STARTED_HOLDINGS && i > next_out) {
3271 SendToPlayer(&buf[next_out], i - next_out);
3274 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3276 leftover_len = buf_len - leftover_start;
3277 /* if buffer ends with something we couldn't parse,
3278 reparse it after appending the next read */
3280 } else if (count == 0) {
3281 RemoveInputSource(isr);
3282 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3284 DisplayFatalError(_("Error reading from ICS"), error, 1);
3289 /* Board style 12 looks like this:
3291 <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
3293 * The "<12> " is stripped before it gets to this routine. The two
3294 * trailing 0's (flip state and clock ticking) are later addition, and
3295 * some chess servers may not have them, or may have only the first.
3296 * Additional trailing fields may be added in the future.
3299 #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"
3301 #define RELATION_OBSERVING_PLAYED 0
3302 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3303 #define RELATION_PLAYING_MYMOVE 1
3304 #define RELATION_PLAYING_NOTMYMOVE -1
3305 #define RELATION_EXAMINING 2
3306 #define RELATION_ISOLATED_BOARD -3
3307 #define RELATION_STARTING_POSITION -4 /* FICS only */
3310 ParseBoard12(string)
3313 GameMode newGameMode;
3314 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3315 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3316 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3317 char to_play, board_chars[200];
3318 char move_str[500], str[500], elapsed_time[500];
3319 char black[32], white[32];
3321 int prevMove = currentMove;
3324 int fromX, fromY, toX, toY;
3326 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3327 char *bookHit = NULL; // [HGM] book
3329 fromX = fromY = toX = toY = -1;
3333 if (appData.debugMode)
3334 fprintf(debugFP, _("Parsing board: %s\n"), string);
3336 move_str[0] = NULLCHAR;
3337 elapsed_time[0] = NULLCHAR;
3338 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3340 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3341 if(string[i] == ' ') { ranks++; files = 0; }
3345 for(j = 0; j <i; j++) board_chars[j] = string[j];
3346 board_chars[i] = '\0';
3349 n = sscanf(string, PATTERN, &to_play, &double_push,
3350 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3351 &gamenum, white, black, &relation, &basetime, &increment,
3352 &white_stren, &black_stren, &white_time, &black_time,
3353 &moveNum, str, elapsed_time, move_str, &ics_flip,
3357 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3358 DisplayError(str, 0);
3362 /* Convert the move number to internal form */
3363 moveNum = (moveNum - 1) * 2;
3364 if (to_play == 'B') moveNum++;
3365 if (moveNum >= MAX_MOVES) {
3366 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3372 case RELATION_OBSERVING_PLAYED:
3373 case RELATION_OBSERVING_STATIC:
3374 if (gamenum == -1) {
3375 /* Old ICC buglet */
3376 relation = RELATION_OBSERVING_STATIC;
3378 newGameMode = IcsObserving;
3380 case RELATION_PLAYING_MYMOVE:
3381 case RELATION_PLAYING_NOTMYMOVE:
3383 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3384 IcsPlayingWhite : IcsPlayingBlack;
3386 case RELATION_EXAMINING:
3387 newGameMode = IcsExamining;
3389 case RELATION_ISOLATED_BOARD:
3391 /* Just display this board. If user was doing something else,
3392 we will forget about it until the next board comes. */
3393 newGameMode = IcsIdle;
3395 case RELATION_STARTING_POSITION:
3396 newGameMode = gameMode;
3400 /* Modify behavior for initial board display on move listing
3403 switch (ics_getting_history) {
3407 case H_GOT_REQ_HEADER:
3408 case H_GOT_UNREQ_HEADER:
3409 /* This is the initial position of the current game */
3410 gamenum = ics_gamenum;
3411 moveNum = 0; /* old ICS bug workaround */
3412 if (to_play == 'B') {
3413 startedFromSetupPosition = TRUE;
3414 blackPlaysFirst = TRUE;
3416 if (forwardMostMove == 0) forwardMostMove = 1;
3417 if (backwardMostMove == 0) backwardMostMove = 1;
3418 if (currentMove == 0) currentMove = 1;
3420 newGameMode = gameMode;
3421 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3423 case H_GOT_UNWANTED_HEADER:
3424 /* This is an initial board that we don't want */
3426 case H_GETTING_MOVES:
3427 /* Should not happen */
3428 DisplayError(_("Error gathering move list: extra board"), 0);
3429 ics_getting_history = H_FALSE;
3433 /* Take action if this is the first board of a new game, or of a
3434 different game than is currently being displayed. */
3435 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3436 relation == RELATION_ISOLATED_BOARD) {
3438 /* Forget the old game and get the history (if any) of the new one */
3439 if (gameMode != BeginningOfGame) {
3443 if (appData.autoRaiseBoard) BoardToTop();
3445 if (gamenum == -1) {
3446 newGameMode = IcsIdle;
3447 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3448 appData.getMoveList) {
3449 /* Need to get game history */
3450 ics_getting_history = H_REQUESTED;
3451 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3455 /* Initially flip the board to have black on the bottom if playing
3456 black or if the ICS flip flag is set, but let the user change
3457 it with the Flip View button. */
3458 flipView = appData.autoFlipView ?
3459 (newGameMode == IcsPlayingBlack) || ics_flip :
3462 /* Done with values from previous mode; copy in new ones */
3463 gameMode = newGameMode;
3465 ics_gamenum = gamenum;
3466 if (gamenum == gs_gamenum) {
3467 int klen = strlen(gs_kind);
3468 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3469 sprintf(str, "ICS %s", gs_kind);
3470 gameInfo.event = StrSave(str);
3472 gameInfo.event = StrSave("ICS game");
3474 gameInfo.site = StrSave(appData.icsHost);
3475 gameInfo.date = PGNDate();
3476 gameInfo.round = StrSave("-");
3477 gameInfo.white = StrSave(white);
3478 gameInfo.black = StrSave(black);
3479 timeControl = basetime * 60 * 1000;
3481 timeIncrement = increment * 1000;
3482 movesPerSession = 0;
3483 gameInfo.timeControl = TimeControlTagValue();
3484 VariantSwitch(board, StringToVariant(gameInfo.event) );
3485 if (appData.debugMode) {
3486 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3487 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3488 setbuf(debugFP, NULL);
3491 gameInfo.outOfBook = NULL;
3493 /* Do we have the ratings? */
3494 if (strcmp(player1Name, white) == 0 &&
3495 strcmp(player2Name, black) == 0) {
3496 if (appData.debugMode)
3497 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3498 player1Rating, player2Rating);
3499 gameInfo.whiteRating = player1Rating;
3500 gameInfo.blackRating = player2Rating;
3501 } else if (strcmp(player2Name, white) == 0 &&
3502 strcmp(player1Name, black) == 0) {
3503 if (appData.debugMode)
3504 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3505 player2Rating, player1Rating);
3506 gameInfo.whiteRating = player2Rating;
3507 gameInfo.blackRating = player1Rating;
3509 player1Name[0] = player2Name[0] = NULLCHAR;
3511 /* Silence shouts if requested */
3512 if (appData.quietPlay &&
3513 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3514 SendToICS(ics_prefix);
3515 SendToICS("set shout 0\n");
3519 /* Deal with midgame name changes */
3521 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3522 if (gameInfo.white) free(gameInfo.white);
3523 gameInfo.white = StrSave(white);
3525 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3526 if (gameInfo.black) free(gameInfo.black);
3527 gameInfo.black = StrSave(black);
3531 /* Throw away game result if anything actually changes in examine mode */
3532 if (gameMode == IcsExamining && !newGame) {
3533 gameInfo.result = GameUnfinished;
3534 if (gameInfo.resultDetails != NULL) {
3535 free(gameInfo.resultDetails);
3536 gameInfo.resultDetails = NULL;
3540 /* In pausing && IcsExamining mode, we ignore boards coming
3541 in if they are in a different variation than we are. */
3542 if (pauseExamInvalid) return;
3543 if (pausing && gameMode == IcsExamining) {
3544 if (moveNum <= pauseExamForwardMostMove) {
3545 pauseExamInvalid = TRUE;
3546 forwardMostMove = pauseExamForwardMostMove;
3551 if (appData.debugMode) {
3552 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3554 /* Parse the board */
3555 for (k = 0; k < ranks; k++) {
3556 for (j = 0; j < files; j++)
3557 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3558 if(gameInfo.holdingsWidth > 1) {
3559 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3560 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3563 CopyBoard(boards[moveNum], board);
3565 startedFromSetupPosition =
3566 !CompareBoards(board, initialPosition);
3567 if(startedFromSetupPosition)
3568 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3571 /* [HGM] Set castling rights. Take the outermost Rooks,
3572 to make it also work for FRC opening positions. Note that board12
3573 is really defective for later FRC positions, as it has no way to
3574 indicate which Rook can castle if they are on the same side of King.
3575 For the initial position we grant rights to the outermost Rooks,
3576 and remember thos rights, and we then copy them on positions
3577 later in an FRC game. This means WB might not recognize castlings with
3578 Rooks that have moved back to their original position as illegal,
3579 but in ICS mode that is not its job anyway.
3581 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3582 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3584 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3585 if(board[0][i] == WhiteRook) j = i;
3586 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3587 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3588 if(board[0][i] == WhiteRook) j = i;
3589 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3590 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3591 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3592 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3593 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3594 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3595 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3597 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3598 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3599 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3600 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601 if(board[BOARD_HEIGHT-1][k] == bKing)
3602 initialRights[5] = castlingRights[moveNum][5] = k;
3604 r = castlingRights[moveNum][0] = initialRights[0];
3605 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3606 r = castlingRights[moveNum][1] = initialRights[1];
3607 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3608 r = castlingRights[moveNum][3] = initialRights[3];
3609 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3610 r = castlingRights[moveNum][4] = initialRights[4];
3611 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3612 /* wildcastle kludge: always assume King has rights */
3613 r = castlingRights[moveNum][2] = initialRights[2];
3614 r = castlingRights[moveNum][5] = initialRights[5];
3616 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3617 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3620 if (ics_getting_history == H_GOT_REQ_HEADER ||
3621 ics_getting_history == H_GOT_UNREQ_HEADER) {
3622 /* This was an initial position from a move list, not
3623 the current position */
3627 /* Update currentMove and known move number limits */
3628 newMove = newGame || moveNum > forwardMostMove;
3630 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3631 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3632 takeback = forwardMostMove - moveNum;
3633 for (i = 0; i < takeback; i++) {
3634 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3635 SendToProgram("undo\n", &first);
3640 forwardMostMove = backwardMostMove = currentMove = moveNum;
3641 if (gameMode == IcsExamining && moveNum == 0) {
3642 /* Workaround for ICS limitation: we are not told the wild
3643 type when starting to examine a game. But if we ask for
3644 the move list, the move list header will tell us */
3645 ics_getting_history = H_REQUESTED;
3646 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3649 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3650 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3651 forwardMostMove = moveNum;
3652 if (!pausing || currentMove > forwardMostMove)
3653 currentMove = forwardMostMove;
3655 /* New part of history that is not contiguous with old part */
3656 if (pausing && gameMode == IcsExamining) {
3657 pauseExamInvalid = TRUE;
3658 forwardMostMove = pauseExamForwardMostMove;
3661 forwardMostMove = backwardMostMove = currentMove = moveNum;
3662 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3663 ics_getting_history = H_REQUESTED;
3664 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3669 /* Update the clocks */
3670 if (strchr(elapsed_time, '.')) {
3672 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3673 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3675 /* Time is in seconds */
3676 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3677 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3682 if (appData.zippyPlay && newGame &&
3683 gameMode != IcsObserving && gameMode != IcsIdle &&
3684 gameMode != IcsExamining)
3685 ZippyFirstBoard(moveNum, basetime, increment);
3688 /* Put the move on the move list, first converting
3689 to canonical algebraic form. */
3691 if (appData.debugMode) {
3692 if (appData.debugMode) { int f = forwardMostMove;
3693 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3694 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3696 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3697 fprintf(debugFP, "moveNum = %d\n", moveNum);
3698 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3699 setbuf(debugFP, NULL);
3701 if (moveNum <= backwardMostMove) {
3702 /* We don't know what the board looked like before
3704 strcpy(parseList[moveNum - 1], move_str);
3705 strcat(parseList[moveNum - 1], " ");
3706 strcat(parseList[moveNum - 1], elapsed_time);
3707 moveList[moveNum - 1][0] = NULLCHAR;
3708 } else if (strcmp(move_str, "none") == 0) {
3709 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3710 /* Again, we don't know what the board looked like;
3711 this is really the start of the game. */
3712 parseList[moveNum - 1][0] = NULLCHAR;
3713 moveList[moveNum - 1][0] = NULLCHAR;
3714 backwardMostMove = moveNum;
3715 startedFromSetupPosition = TRUE;
3716 fromX = fromY = toX = toY = -1;
3718 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3719 // So we parse the long-algebraic move string in stead of the SAN move
3720 int valid; char buf[MSG_SIZ], *prom;
3722 // str looks something like "Q/a1-a2"; kill the slash
3724 sprintf(buf, "%c%s", str[0], str+2);
3725 else strcpy(buf, str); // might be castling
3726 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3727 strcat(buf, prom); // long move lacks promo specification!
3728 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3729 if(appData.debugMode)
3730 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3731 strcpy(move_str, buf);
3733 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3734 &fromX, &fromY, &toX, &toY, &promoChar)
3735 || ParseOneMove(buf, moveNum - 1, &moveType,
3736 &fromX, &fromY, &toX, &toY, &promoChar);
3737 // end of long SAN patch
3739 (void) CoordsToAlgebraic(boards[moveNum - 1],
3740 PosFlags(moveNum - 1), EP_UNKNOWN,
3741 fromY, fromX, toY, toX, promoChar,
3742 parseList[moveNum-1]);
3743 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3744 castlingRights[moveNum]) ) {
3750 if(gameInfo.variant != VariantShogi)
3751 strcat(parseList[moveNum - 1], "+");
3754 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3755 strcat(parseList[moveNum - 1], "#");
3758 strcat(parseList[moveNum - 1], " ");
3759 strcat(parseList[moveNum - 1], elapsed_time);
3760 /* currentMoveString is set as a side-effect of ParseOneMove */
3761 strcpy(moveList[moveNum - 1], currentMoveString);
3762 strcat(moveList[moveNum - 1], "\n");
3764 /* Move from ICS was illegal!? Punt. */
3765 if (appData.debugMode) {
3766 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3767 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3770 if (appData.testLegality && appData.debugMode) {
3771 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3772 DisplayError(str, 0);
3775 strcpy(parseList[moveNum - 1], move_str);
3776 strcat(parseList[moveNum - 1], " ");
3777 strcat(parseList[moveNum - 1], elapsed_time);
3778 moveList[moveNum - 1][0] = NULLCHAR;
3779 fromX = fromY = toX = toY = -1;
3782 if (appData.debugMode) {
3783 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3784 setbuf(debugFP, NULL);
3788 /* Send move to chess program (BEFORE animating it). */
3789 if (appData.zippyPlay && !newGame && newMove &&
3790 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3792 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3793 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3794 if (moveList[moveNum - 1][0] == NULLCHAR) {
3795 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3797 DisplayError(str, 0);
3799 if (first.sendTime) {
3800 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3802 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3803 if (firstMove && !bookHit) {
3805 if (first.useColors) {
3806 SendToProgram(gameMode == IcsPlayingWhite ?
3808 "black\ngo\n", &first);
3810 SendToProgram("go\n", &first);
3812 first.maybeThinking = TRUE;
3815 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3816 if (moveList[moveNum - 1][0] == NULLCHAR) {
3817 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3818 DisplayError(str, 0);
3820 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3821 SendMoveToProgram(moveNum - 1, &first);
3828 if (moveNum > 0 && !gotPremove) {
3829 /* If move comes from a remote source, animate it. If it
3830 isn't remote, it will have already been animated. */
3831 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3832 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3834 if (!pausing && appData.highlightLastMove) {
3835 SetHighlights(fromX, fromY, toX, toY);
3839 /* Start the clocks */
3840 whiteFlag = blackFlag = FALSE;
3841 appData.clockMode = !(basetime == 0 && increment == 0);
3843 ics_clock_paused = TRUE;
3845 } else if (ticking == 1) {
3846 ics_clock_paused = FALSE;
3848 if (gameMode == IcsIdle ||
3849 relation == RELATION_OBSERVING_STATIC ||
3850 relation == RELATION_EXAMINING ||
3852 DisplayBothClocks();
3856 /* Display opponents and material strengths */
3857 if (gameInfo.variant != VariantBughouse &&
3858 gameInfo.variant != VariantCrazyhouse) {
3859 if (tinyLayout || smallLayout) {
3860 if(gameInfo.variant == VariantNormal)
3861 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3862 gameInfo.white, white_stren, gameInfo.black, black_stren,
3863 basetime, increment);
3865 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3866 gameInfo.white, white_stren, gameInfo.black, black_stren,
3867 basetime, increment, (int) gameInfo.variant);
3869 if(gameInfo.variant == VariantNormal)
3870 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3871 gameInfo.white, white_stren, gameInfo.black, black_stren,
3872 basetime, increment);
3874 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3875 gameInfo.white, white_stren, gameInfo.black, black_stren,
3876 basetime, increment, VariantName(gameInfo.variant));
3879 if (appData.debugMode) {
3880 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3885 /* Display the board */
3888 if (appData.premove)
3890 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3891 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3892 ClearPremoveHighlights();
3894 DrawPosition(FALSE, boards[currentMove]);
3895 DisplayMove(moveNum - 1);
3896 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3897 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3898 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3899 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3903 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3905 if(bookHit) { // [HGM] book: simulate book reply
3906 static char bookMove[MSG_SIZ]; // a bit generous?
3908 programStats.nodes = programStats.depth = programStats.time =
3909 programStats.score = programStats.got_only_move = 0;
3910 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3912 strcpy(bookMove, "move ");
3913 strcat(bookMove, bookHit);
3914 HandleMachineMove(bookMove, &first);
3923 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3924 ics_getting_history = H_REQUESTED;
3925 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3931 AnalysisPeriodicEvent(force)
3934 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3935 && !force) || !appData.periodicUpdates)
3938 /* Send . command to Crafty to collect stats */
3939 SendToProgram(".\n", &first);
3941 /* Don't send another until we get a response (this makes
3942 us stop sending to old Crafty's which don't understand
3943 the "." command (sending illegal cmds resets node count & time,
3944 which looks bad)) */
3945 programStats.ok_to_send = 0;
3949 SendMoveToProgram(moveNum, cps)
3951 ChessProgramState *cps;
3955 if (cps->useUsermove) {
3956 SendToProgram("usermove ", cps);
3960 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3961 int len = space - parseList[moveNum];
3962 memcpy(buf, parseList[moveNum], len);
3964 buf[len] = NULLCHAR;
3966 sprintf(buf, "%s\n", parseList[moveNum]);
3968 SendToProgram(buf, cps);
3970 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3971 AlphaRank(moveList[moveNum], 4);
3972 SendToProgram(moveList[moveNum], cps);
3973 AlphaRank(moveList[moveNum], 4); // and back
3975 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3976 * the engine. It would be nice to have a better way to identify castle
3978 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3979 && cps->useOOCastle) {
3980 int fromX = moveList[moveNum][0] - AAA;
3981 int fromY = moveList[moveNum][1] - ONE;
3982 int toX = moveList[moveNum][2] - AAA;
3983 int toY = moveList[moveNum][3] - ONE;
3984 if((boards[moveNum][fromY][fromX] == WhiteKing
3985 && boards[moveNum][toY][toX] == WhiteRook)
3986 || (boards[moveNum][fromY][fromX] == BlackKing
3987 && boards[moveNum][toY][toX] == BlackRook)) {
3988 if(toX > fromX) SendToProgram("O-O\n", cps);
3989 else SendToProgram("O-O-O\n", cps);
3991 else SendToProgram(moveList[moveNum], cps);
3993 else SendToProgram(moveList[moveNum], cps);
3994 /* End of additions by Tord */
3997 /* [HGM] setting up the opening has brought engine in force mode! */
3998 /* Send 'go' if we are in a mode where machine should play. */
3999 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4000 (gameMode == TwoMachinesPlay ||
4002 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4004 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4005 SendToProgram("go\n", cps);
4006 if (appData.debugMode) {
4007 fprintf(debugFP, "(extra)\n");
4010 setboardSpoiledMachineBlack = 0;
4014 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4016 int fromX, fromY, toX, toY;
4018 char user_move[MSG_SIZ];
4022 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4023 (int)moveType, fromX, fromY, toX, toY);
4024 DisplayError(user_move + strlen("say "), 0);
4026 case WhiteKingSideCastle:
4027 case BlackKingSideCastle:
4028 case WhiteQueenSideCastleWild:
4029 case BlackQueenSideCastleWild:
4031 case WhiteHSideCastleFR:
4032 case BlackHSideCastleFR:
4034 sprintf(user_move, "o-o\n");
4036 case WhiteQueenSideCastle:
4037 case BlackQueenSideCastle:
4038 case WhiteKingSideCastleWild:
4039 case BlackKingSideCastleWild:
4041 case WhiteASideCastleFR:
4042 case BlackASideCastleFR:
4044 sprintf(user_move, "o-o-o\n");
4046 case WhitePromotionQueen:
4047 case BlackPromotionQueen:
4048 case WhitePromotionRook:
4049 case BlackPromotionRook:
4050 case WhitePromotionBishop:
4051 case BlackPromotionBishop:
4052 case WhitePromotionKnight:
4053 case BlackPromotionKnight:
4054 case WhitePromotionKing:
4055 case BlackPromotionKing:
4056 case WhitePromotionChancellor:
4057 case BlackPromotionChancellor:
4058 case WhitePromotionArchbishop:
4059 case BlackPromotionArchbishop:
4060 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4061 sprintf(user_move, "%c%c%c%c=%c\n",
4062 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4063 PieceToChar(WhiteFerz));
4064 else if(gameInfo.variant == VariantGreat)
4065 sprintf(user_move, "%c%c%c%c=%c\n",
4066 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4067 PieceToChar(WhiteMan));
4069 sprintf(user_move, "%c%c%c%c=%c\n",
4070 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4071 PieceToChar(PromoPiece(moveType)));
4075 sprintf(user_move, "%c@%c%c\n",
4076 ToUpper(PieceToChar((ChessSquare) fromX)),
4077 AAA + toX, ONE + toY);
4080 case WhiteCapturesEnPassant:
4081 case BlackCapturesEnPassant:
4082 case IllegalMove: /* could be a variant we don't quite understand */
4083 sprintf(user_move, "%c%c%c%c\n",
4084 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4087 SendToICS(user_move);
4091 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4096 if (rf == DROP_RANK) {
4097 sprintf(move, "%c@%c%c\n",
4098 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4100 if (promoChar == 'x' || promoChar == NULLCHAR) {
4101 sprintf(move, "%c%c%c%c\n",
4102 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4104 sprintf(move, "%c%c%c%c%c\n",
4105 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4111 ProcessICSInitScript(f)
4116 while (fgets(buf, MSG_SIZ, f)) {
4117 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4124 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4126 AlphaRank(char *move, int n)
4128 // char *p = move, c; int x, y;
4130 if (appData.debugMode) {
4131 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4135 move[2]>='0' && move[2]<='9' &&
4136 move[3]>='a' && move[3]<='x' ) {
4138 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4139 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4141 if(move[0]>='0' && move[0]<='9' &&
4142 move[1]>='a' && move[1]<='x' &&
4143 move[2]>='0' && move[2]<='9' &&
4144 move[3]>='a' && move[3]<='x' ) {
4145 /* input move, Shogi -> normal */
4146 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4147 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4148 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4149 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4152 move[3]>='0' && move[3]<='9' &&
4153 move[2]>='a' && move[2]<='x' ) {
4155 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4156 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4159 move[0]>='a' && move[0]<='x' &&
4160 move[3]>='0' && move[3]<='9' &&
4161 move[2]>='a' && move[2]<='x' ) {
4162 /* output move, normal -> Shogi */
4163 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4164 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4165 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4166 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4167 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4169 if (appData.debugMode) {
4170 fprintf(debugFP, " out = '%s'\n", move);
4174 /* Parser for moves from gnuchess, ICS, or user typein box */
4176 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4179 ChessMove *moveType;
4180 int *fromX, *fromY, *toX, *toY;
4183 if (appData.debugMode) {
4184 fprintf(debugFP, "move to parse: %s\n", move);
4186 *moveType = yylexstr(moveNum, move);
4188 switch (*moveType) {
4189 case WhitePromotionChancellor:
4190 case BlackPromotionChancellor:
4191 case WhitePromotionArchbishop:
4192 case BlackPromotionArchbishop:
4193 case WhitePromotionQueen:
4194 case BlackPromotionQueen:
4195 case WhitePromotionRook:
4196 case BlackPromotionRook:
4197 case WhitePromotionBishop:
4198 case BlackPromotionBishop:
4199 case WhitePromotionKnight:
4200 case BlackPromotionKnight:
4201 case WhitePromotionKing:
4202 case BlackPromotionKing:
4204 case WhiteCapturesEnPassant:
4205 case BlackCapturesEnPassant:
4206 case WhiteKingSideCastle:
4207 case WhiteQueenSideCastle:
4208 case BlackKingSideCastle:
4209 case BlackQueenSideCastle:
4210 case WhiteKingSideCastleWild:
4211 case WhiteQueenSideCastleWild:
4212 case BlackKingSideCastleWild:
4213 case BlackQueenSideCastleWild:
4214 /* Code added by Tord: */
4215 case WhiteHSideCastleFR:
4216 case WhiteASideCastleFR:
4217 case BlackHSideCastleFR:
4218 case BlackASideCastleFR:
4219 /* End of code added by Tord */
4220 case IllegalMove: /* bug or odd chess variant */
4221 *fromX = currentMoveString[0] - AAA;
4222 *fromY = currentMoveString[1] - ONE;
4223 *toX = currentMoveString[2] - AAA;
4224 *toY = currentMoveString[3] - ONE;
4225 *promoChar = currentMoveString[4];
4226 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4227 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4228 if (appData.debugMode) {
4229 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4231 *fromX = *fromY = *toX = *toY = 0;
4234 if (appData.testLegality) {
4235 return (*moveType != IllegalMove);
4237 return !(fromX == fromY && toX == toY);
4242 *fromX = *moveType == WhiteDrop ?
4243 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4244 (int) CharToPiece(ToLower(currentMoveString[0]));
4246 *toX = currentMoveString[2] - AAA;
4247 *toY = currentMoveString[3] - ONE;
4248 *promoChar = NULLCHAR;
4252 case ImpossibleMove:
4253 case (ChessMove) 0: /* end of file */
4262 if (appData.debugMode) {
4263 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4266 *fromX = *fromY = *toX = *toY = 0;
4267 *promoChar = NULLCHAR;
4272 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4273 // All positions will have equal probability, but the current method will not provide a unique
4274 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4280 int piecesLeft[(int)BlackPawn];
4281 int seed, nrOfShuffles;
4283 void GetPositionNumber()
4284 { // sets global variable seed
4287 seed = appData.defaultFrcPosition;
4288 if(seed < 0) { // randomize based on time for negative FRC position numbers
4289 for(i=0; i<50; i++) seed += random();
4290 seed = random() ^ random() >> 8 ^ random() << 8;
4291 if(seed<0) seed = -seed;
4295 int put(Board board, int pieceType, int rank, int n, int shade)
4296 // put the piece on the (n-1)-th empty squares of the given shade
4300 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4301 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4302 board[rank][i] = (ChessSquare) pieceType;
4303 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4305 piecesLeft[pieceType]--;
4313 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4314 // calculate where the next piece goes, (any empty square), and put it there
4318 i = seed % squaresLeft[shade];
4319 nrOfShuffles *= squaresLeft[shade];
4320 seed /= squaresLeft[shade];
4321 put(board, pieceType, rank, i, shade);
4324 void AddTwoPieces(Board board, int pieceType, int rank)
4325 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4327 int i, n=squaresLeft[ANY], j=n-1, k;
4329 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4330 i = seed % k; // pick one
4333 while(i >= j) i -= j--;
4334 j = n - 1 - j; i += j;
4335 put(board, pieceType, rank, j, ANY);
4336 put(board, pieceType, rank, i, ANY);
4339 void SetUpShuffle(Board board, int number)
4343 GetPositionNumber(); nrOfShuffles = 1;
4345 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4346 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4347 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4349 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4351 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4352 p = (int) board[0][i];
4353 if(p < (int) BlackPawn) piecesLeft[p] ++;
4354 board[0][i] = EmptySquare;
4357 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4358 // shuffles restricted to allow normal castling put KRR first
4359 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4360 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4361 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4362 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4363 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4364 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4365 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4366 put(board, WhiteRook, 0, 0, ANY);
4367 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4370 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4371 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4372 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4373 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4374 while(piecesLeft[p] >= 2) {
4375 AddOnePiece(board, p, 0, LITE);
4376 AddOnePiece(board, p, 0, DARK);
4378 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4381 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4382 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4383 // but we leave King and Rooks for last, to possibly obey FRC restriction
4384 if(p == (int)WhiteRook) continue;
4385 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4386 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4389 // now everything is placed, except perhaps King (Unicorn) and Rooks
4391 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4392 // Last King gets castling rights
4393 while(piecesLeft[(int)WhiteUnicorn]) {
4394 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4395 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4398 while(piecesLeft[(int)WhiteKing]) {
4399 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4400 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4405 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4406 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4409 // Only Rooks can be left; simply place them all
4410 while(piecesLeft[(int)WhiteRook]) {
4411 i = put(board, WhiteRook, 0, 0, ANY);
4412 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4415 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4417 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4420 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4421 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4424 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4427 int SetCharTable( char *table, const char * map )
4428 /* [HGM] moved here from winboard.c because of its general usefulness */
4429 /* Basically a safe strcpy that uses the last character as King */
4431 int result = FALSE; int NrPieces;
4433 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4434 && NrPieces >= 12 && !(NrPieces&1)) {
4435 int i; /* [HGM] Accept even length from 12 to 34 */
4437 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4438 for( i=0; i<NrPieces/2-1; i++ ) {
4440 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4442 table[(int) WhiteKing] = map[NrPieces/2-1];
4443 table[(int) BlackKing] = map[NrPieces-1];
4451 void Prelude(Board board)
4452 { // [HGM] superchess: random selection of exo-pieces
4453 int i, j, k; ChessSquare p;
4454 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4456 GetPositionNumber(); // use FRC position number
4458 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4459 SetCharTable(pieceToChar, appData.pieceToCharTable);
4460 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4461 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4464 j = seed%4; seed /= 4;
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 >= j); seed /= 3;
4469 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = 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%3; seed /= 3;
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%2 + (seed%2 >= j); seed /= 2;
4477 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4478 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4479 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4480 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4481 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4482 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4483 put(board, exoPieces[0], 0, 0, ANY);
4484 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4488 InitPosition(redraw)
4491 ChessSquare (* pieces)[BOARD_SIZE];
4492 int i, j, pawnRow, overrule,
4493 oldx = gameInfo.boardWidth,
4494 oldy = gameInfo.boardHeight,
4495 oldh = gameInfo.holdingsWidth,
4496 oldv = gameInfo.variant;
4498 currentMove = forwardMostMove = backwardMostMove = 0;
4499 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4501 /* [AS] Initialize pv info list [HGM] and game status */
4503 for( i=0; i<MAX_MOVES; i++ ) {
4504 pvInfoList[i].depth = 0;
4505 epStatus[i]=EP_NONE;
4506 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4509 initialRulePlies = 0; /* 50-move counter start */
4511 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4512 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4516 /* [HGM] logic here is completely changed. In stead of full positions */
4517 /* the initialized data only consist of the two backranks. The switch */
4518 /* selects which one we will use, which is than copied to the Board */
4519 /* initialPosition, which for the rest is initialized by Pawns and */
4520 /* empty squares. This initial position is then copied to boards[0], */
4521 /* possibly after shuffling, so that it remains available. */
4523 gameInfo.holdingsWidth = 0; /* default board sizes */
4524 gameInfo.boardWidth = 8;
4525 gameInfo.boardHeight = 8;
4526 gameInfo.holdingsSize = 0;
4527 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4528 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4529 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4531 switch (gameInfo.variant) {
4532 case VariantFischeRandom:
4533 shuffleOpenings = TRUE;
4537 case VariantShatranj:
4538 pieces = ShatranjArray;
4539 nrCastlingRights = 0;
4540 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4542 case VariantTwoKings:
4543 pieces = twoKingsArray;
4545 case VariantCapaRandom:
4546 shuffleOpenings = TRUE;
4547 case VariantCapablanca:
4548 pieces = CapablancaArray;
4549 gameInfo.boardWidth = 10;
4550 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4553 pieces = GothicArray;
4554 gameInfo.boardWidth = 10;
4555 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4558 pieces = JanusArray;
4559 gameInfo.boardWidth = 10;
4560 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4561 nrCastlingRights = 6;
4562 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4563 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4564 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4565 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4566 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4567 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4570 pieces = FalconArray;
4571 gameInfo.boardWidth = 10;
4572 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4574 case VariantXiangqi:
4575 pieces = XiangqiArray;
4576 gameInfo.boardWidth = 9;
4577 gameInfo.boardHeight = 10;
4578 nrCastlingRights = 0;
4579 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4582 pieces = ShogiArray;
4583 gameInfo.boardWidth = 9;
4584 gameInfo.boardHeight = 9;
4585 gameInfo.holdingsSize = 7;
4586 nrCastlingRights = 0;
4587 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4589 case VariantCourier:
4590 pieces = CourierArray;
4591 gameInfo.boardWidth = 12;
4592 nrCastlingRights = 0;
4593 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4594 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4596 case VariantKnightmate:
4597 pieces = KnightmateArray;
4598 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4601 pieces = fairyArray;
4602 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4605 pieces = GreatArray;
4606 gameInfo.boardWidth = 10;
4607 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4608 gameInfo.holdingsSize = 8;
4612 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4613 gameInfo.holdingsSize = 8;
4614 startedFromSetupPosition = TRUE;
4616 case VariantCrazyhouse:
4617 case VariantBughouse:
4619 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4620 gameInfo.holdingsSize = 5;
4622 case VariantWildCastle:
4624 /* !!?shuffle with kings guaranteed to be on d or e file */
4625 shuffleOpenings = 1;
4627 case VariantNoCastle:
4629 nrCastlingRights = 0;
4630 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4631 /* !!?unconstrained back-rank shuffle */
4632 shuffleOpenings = 1;
4637 if(appData.NrFiles >= 0) {
4638 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4639 gameInfo.boardWidth = appData.NrFiles;
4641 if(appData.NrRanks >= 0) {
4642 gameInfo.boardHeight = appData.NrRanks;
4644 if(appData.holdingsSize >= 0) {
4645 i = appData.holdingsSize;
4646 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4647 gameInfo.holdingsSize = i;
4649 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4650 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4651 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4653 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4654 if(pawnRow < 1) pawnRow = 1;
4656 /* User pieceToChar list overrules defaults */
4657 if(appData.pieceToCharTable != NULL)
4658 SetCharTable(pieceToChar, appData.pieceToCharTable);
4660 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4662 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4663 s = (ChessSquare) 0; /* account holding counts in guard band */
4664 for( i=0; i<BOARD_HEIGHT; i++ )
4665 initialPosition[i][j] = s;
4667 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4668 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4669 initialPosition[pawnRow][j] = WhitePawn;
4670 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4671 if(gameInfo.variant == VariantXiangqi) {
4673 initialPosition[pawnRow][j] =
4674 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4675 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4676 initialPosition[2][j] = WhiteCannon;
4677 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4681 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4683 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4686 initialPosition[1][j] = WhiteBishop;
4687 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4689 initialPosition[1][j] = WhiteRook;
4690 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4693 if( nrCastlingRights == -1) {
4694 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4695 /* This sets default castling rights from none to normal corners */
4696 /* Variants with other castling rights must set them themselves above */
4697 nrCastlingRights = 6;
4699 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4700 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4701 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4702 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4703 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4704 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4707 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4708 if(gameInfo.variant == VariantGreat) { // promotion commoners
4709 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4710 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4711 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4712 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4715 if(gameInfo.variant == VariantFischeRandom) {
4716 if( appData.defaultFrcPosition < 0 ) {
4717 ShuffleFRC( initialPosition );
4720 SetupFRC( initialPosition, appData.defaultFrcPosition );
4722 startedFromSetupPosition = TRUE;
4725 if (appData.debugMode) {
4726 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4728 if(shuffleOpenings) {
4729 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4730 startedFromSetupPosition = TRUE;
4733 if(startedFromPositionFile) {
4734 /* [HGM] loadPos: use PositionFile for every new game */
4735 CopyBoard(initialPosition, filePosition);
4736 for(i=0; i<nrCastlingRights; i++)
4737 castlingRights[0][i] = initialRights[i] = fileRights[i];
4738 startedFromSetupPosition = TRUE;
4741 CopyBoard(boards[0], initialPosition);
4743 if(oldx != gameInfo.boardWidth ||
4744 oldy != gameInfo.boardHeight ||
4745 oldh != gameInfo.holdingsWidth
4747 || oldv == VariantGothic || // For licensing popups
4748 gameInfo.variant == VariantGothic
4751 || oldv == VariantFalcon ||
4752 gameInfo.variant == VariantFalcon
4755 InitDrawingSizes(-2 ,0);
4758 DrawPosition(TRUE, boards[currentMove]);
4762 SendBoard(cps, moveNum)
4763 ChessProgramState *cps;
4766 char message[MSG_SIZ];
4768 if (cps->useSetboard) {
4769 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4770 sprintf(message, "setboard %s\n", fen);
4771 SendToProgram(message, cps);
4777 /* Kludge to set black to move, avoiding the troublesome and now
4778 * deprecated "black" command.
4780 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4782 SendToProgram("edit\n", cps);
4783 SendToProgram("#\n", cps);
4784 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4785 bp = &boards[moveNum][i][BOARD_LEFT];
4786 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4787 if ((int) *bp < (int) BlackPawn) {
4788 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4790 if(message[0] == '+' || message[0] == '~') {
4791 sprintf(message, "%c%c%c+\n",
4792 PieceToChar((ChessSquare)(DEMOTED *bp)),
4795 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4796 message[1] = BOARD_RGHT - 1 - j + '1';
4797 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4799 SendToProgram(message, cps);
4804 SendToProgram("c\n", cps);
4805 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4806 bp = &boards[moveNum][i][BOARD_LEFT];
4807 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4808 if (((int) *bp != (int) EmptySquare)
4809 && ((int) *bp >= (int) BlackPawn)) {
4810 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4812 if(message[0] == '+' || message[0] == '~') {
4813 sprintf(message, "%c%c%c+\n",
4814 PieceToChar((ChessSquare)(DEMOTED *bp)),
4817 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4818 message[1] = BOARD_RGHT - 1 - j + '1';
4819 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821 SendToProgram(message, cps);
4826 SendToProgram(".\n", cps);
4828 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4832 IsPromotion(fromX, fromY, toX, toY)
4833 int fromX, fromY, toX, toY;
4835 /* [HGM] add Shogi promotions */
4836 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4839 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4840 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4841 /* [HGM] Note to self: line above also weeds out drops */
4842 piece = boards[currentMove][fromY][fromX];
4843 if(gameInfo.variant == VariantShogi) {
4844 promotionZoneSize = 3;
4845 highestPromotingPiece = (int)WhiteKing;
4846 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4847 and if in normal chess we then allow promotion to King, why not
4848 allow promotion of other piece in Shogi? */
4850 if((int)piece >= BlackPawn) {
4851 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4853 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4855 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4856 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4858 return ( (int)piece <= highestPromotingPiece );
4862 InPalace(row, column)
4864 { /* [HGM] for Xiangqi */
4865 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4866 column < (BOARD_WIDTH + 4)/2 &&
4867 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4872 PieceForSquare (x, y)
4876 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4879 return boards[currentMove][y][x];
4883 OKToStartUserMove(x, y)
4886 ChessSquare from_piece;
4889 if (matchMode) return FALSE;
4890 if (gameMode == EditPosition) return TRUE;
4892 if (x >= 0 && y >= 0)
4893 from_piece = boards[currentMove][y][x];
4895 from_piece = EmptySquare;
4897 if (from_piece == EmptySquare) return FALSE;
4899 white_piece = (int)from_piece >= (int)WhitePawn &&
4900 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4903 case PlayFromGameFile:
4905 case TwoMachinesPlay:
4913 case MachinePlaysWhite:
4914 case IcsPlayingBlack:
4915 if (appData.zippyPlay) return FALSE;
4917 DisplayMoveError(_("You are playing Black"));
4922 case MachinePlaysBlack:
4923 case IcsPlayingWhite:
4924 if (appData.zippyPlay) return FALSE;
4926 DisplayMoveError(_("You are playing White"));
4932 if (!white_piece && WhiteOnMove(currentMove)) {
4933 DisplayMoveError(_("It is White's turn"));
4936 if (white_piece && !WhiteOnMove(currentMove)) {
4937 DisplayMoveError(_("It is Black's turn"));
4940 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4941 /* Editing correspondence game history */
4942 /* Could disallow this or prompt for confirmation */
4945 if (currentMove < forwardMostMove) {
4946 /* Discarding moves */
4947 /* Could prompt for confirmation here,
4948 but I don't think that's such a good idea */
4949 forwardMostMove = currentMove;
4953 case BeginningOfGame:
4954 if (appData.icsActive) return FALSE;
4955 if (!appData.noChessProgram) {
4957 DisplayMoveError(_("You are playing White"));
4964 if (!white_piece && WhiteOnMove(currentMove)) {
4965 DisplayMoveError(_("It is White's turn"));
4968 if (white_piece && !WhiteOnMove(currentMove)) {
4969 DisplayMoveError(_("It is Black's turn"));
4978 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4979 && gameMode != AnalyzeFile && gameMode != Training) {
4980 DisplayMoveError(_("Displayed position is not current"));
4986 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4987 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4988 int lastLoadGameUseList = FALSE;
4989 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4990 ChessMove lastLoadGameStart = (ChessMove) 0;
4994 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4995 int fromX, fromY, toX, toY;
4999 ChessSquare pdown, pup;
5001 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5002 if ((fromX == toX) && (fromY == toY)) {
5003 return ImpossibleMove;
5006 /* [HGM] suppress all moves into holdings area and guard band */
5007 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5008 return ImpossibleMove;
5010 /* [HGM] <sameColor> moved to here from winboard.c */
5011 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5012 pdown = boards[currentMove][fromY][fromX];
5013 pup = boards[currentMove][toY][toX];
5014 if ( gameMode != EditPosition &&
5015 (WhitePawn <= pdown && pdown < BlackPawn &&
5016 WhitePawn <= pup && pup < BlackPawn ||
5017 BlackPawn <= pdown && pdown < EmptySquare &&
5018 BlackPawn <= pup && pup < EmptySquare
5019 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5020 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5021 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5023 return ImpossibleMove;
5025 /* Check if the user is playing in turn. This is complicated because we
5026 let the user "pick up" a piece before it is his turn. So the piece he
5027 tried to pick up may have been captured by the time he puts it down!
5028 Therefore we use the color the user is supposed to be playing in this
5029 test, not the color of the piece that is currently on the starting
5030 square---except in EditGame mode, where the user is playing both
5031 sides; fortunately there the capture race can't happen. (It can
5032 now happen in IcsExamining mode, but that's just too bad. The user
5033 will get a somewhat confusing message in that case.)
5037 case PlayFromGameFile:
5039 case TwoMachinesPlay:
5043 /* We switched into a game mode where moves are not accepted,
5044 perhaps while the mouse button was down. */
5045 return ImpossibleMove;
5047 case MachinePlaysWhite:
5048 /* User is moving for Black */
5049 if (WhiteOnMove(currentMove)) {
5050 DisplayMoveError(_("It is White's turn"));
5051 return ImpossibleMove;
5055 case MachinePlaysBlack:
5056 /* User is moving for White */
5057 if (!WhiteOnMove(currentMove)) {
5058 DisplayMoveError(_("It is Black's turn"));
5059 return ImpossibleMove;
5065 case BeginningOfGame:
5068 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5069 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5070 /* User is moving for Black */
5071 if (WhiteOnMove(currentMove)) {
5072 DisplayMoveError(_("It is White's turn"));
5073 return ImpossibleMove;
5076 /* User is moving for White */
5077 if (!WhiteOnMove(currentMove)) {
5078 DisplayMoveError(_("It is Black's turn"));
5079 return ImpossibleMove;
5084 case IcsPlayingBlack:
5085 /* User is moving for Black */
5086 if (WhiteOnMove(currentMove)) {
5087 if (!appData.premove) {
5088 DisplayMoveError(_("It is White's turn"));
5089 } else if (toX >= 0 && toY >= 0) {
5092 premoveFromX = fromX;
5093 premoveFromY = fromY;
5094 premovePromoChar = promoChar;
5096 if (appData.debugMode)
5097 fprintf(debugFP, "Got premove: fromX %d,"
5098 "fromY %d, toX %d, toY %d\n",
5099 fromX, fromY, toX, toY);
5101 return ImpossibleMove;
5105 case IcsPlayingWhite:
5106 /* User is moving for White */
5107 if (!WhiteOnMove(currentMove)) {
5108 if (!appData.premove) {
5109 DisplayMoveError(_("It is Black's turn"));
5110 } else if (toX >= 0 && toY >= 0) {
5113 premoveFromX = fromX;
5114 premoveFromY = fromY;
5115 premovePromoChar = promoChar;
5117 if (appData.debugMode)
5118 fprintf(debugFP, "Got premove: fromX %d,"
5119 "fromY %d, toX %d, toY %d\n",
5120 fromX, fromY, toX, toY);
5122 return ImpossibleMove;
5130 /* EditPosition, empty square, or different color piece;
5131 click-click move is possible */
5132 if (toX == -2 || toY == -2) {
5133 boards[0][fromY][fromX] = EmptySquare;
5134 return AmbiguousMove;
5135 } else if (toX >= 0 && toY >= 0) {
5136 boards[0][toY][toX] = boards[0][fromY][fromX];
5137 boards[0][fromY][fromX] = EmptySquare;
5138 return AmbiguousMove;
5140 return ImpossibleMove;
5143 /* [HGM] If move started in holdings, it means a drop */
5144 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5145 if( pup != EmptySquare ) return ImpossibleMove;
5146 if(appData.testLegality) {
5147 /* it would be more logical if LegalityTest() also figured out
5148 * which drops are legal. For now we forbid pawns on back rank.
5149 * Shogi is on its own here...
5151 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5152 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5153 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5155 return WhiteDrop; /* Not needed to specify white or black yet */
5158 userOfferedDraw = FALSE;
5160 /* [HGM] always test for legality, to get promotion info */
5161 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5162 epStatus[currentMove], castlingRights[currentMove],
5163 fromY, fromX, toY, toX, promoChar);
5165 /* [HGM] but possibly ignore an IllegalMove result */
5166 if (appData.testLegality) {
5167 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5168 DisplayMoveError(_("Illegal move"));
5169 return ImpossibleMove;
5172 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5174 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5175 function is made into one that returns an OK move type if FinishMove
5176 should be called. This to give the calling driver routine the
5177 opportunity to finish the userMove input with a promotion popup,
5178 without bothering the user with this for invalid or illegal moves */
5180 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5183 /* Common tail of UserMoveEvent and DropMenuEvent */
5185 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5187 int fromX, fromY, toX, toY;
5188 /*char*/int promoChar;
5191 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5192 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5193 // [HGM] superchess: suppress promotions to non-available piece
5194 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5195 if(WhiteOnMove(currentMove)) {
5196 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5198 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5202 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5203 move type in caller when we know the move is a legal promotion */
5204 if(moveType == NormalMove && promoChar)
5205 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5206 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5207 /* [HGM] convert drag-and-drop piece drops to standard form */
5208 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5209 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5210 fromX = boards[currentMove][fromY][fromX];
5214 /* [HGM] <popupFix> The following if has been moved here from
5215 UserMoveEvent(). Because it seemed to belon here (why not allow
5216 piece drops in training games?), and because it can only be
5217 performed after it is known to what we promote. */
5218 if (gameMode == Training) {
5219 /* compare the move played on the board to the next move in the
5220 * game. If they match, display the move and the opponent's response.
5221 * If they don't match, display an error message.
5224 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5225 CopyBoard(testBoard, boards[currentMove]);
5226 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5228 if (CompareBoards(testBoard, boards[currentMove+1])) {
5229 ForwardInner(currentMove+1);
5231 /* Autoplay the opponent's response.
5232 * if appData.animate was TRUE when Training mode was entered,
5233 * the response will be animated.
5235 saveAnimate = appData.animate;
5236 appData.animate = animateTraining;
5237 ForwardInner(currentMove+1);
5238 appData.animate = saveAnimate;
5240 /* check for the end of the game */
5241 if (currentMove >= forwardMostMove) {
5242 gameMode = PlayFromGameFile;
5244 SetTrainingModeOff();
5245 DisplayInformation(_("End of game"));
5248 DisplayError(_("Incorrect move"), 0);
5253 /* Ok, now we know that the move is good, so we can kill
5254 the previous line in Analysis Mode */
5255 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5256 forwardMostMove = currentMove;
5259 /* If we need the chess program but it's dead, restart it */
5260 ResurrectChessProgram();
5262 /* A user move restarts a paused game*/
5266 thinkOutput[0] = NULLCHAR;
5268 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5270 if (gameMode == BeginningOfGame) {
5271 if (appData.noChessProgram) {
5272 gameMode = EditGame;
5276 gameMode = MachinePlaysBlack;
5279 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5281 if (first.sendName) {
5282 sprintf(buf, "name %s\n", gameInfo.white);
5283 SendToProgram(buf, &first);
5289 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5290 /* Relay move to ICS or chess engine */
5291 if (appData.icsActive) {
5292 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5293 gameMode == IcsExamining) {
5294 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5298 if (first.sendTime && (gameMode == BeginningOfGame ||
5299 gameMode == MachinePlaysWhite ||
5300 gameMode == MachinePlaysBlack)) {
5301 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5303 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5304 // [HGM] book: if program might be playing, let it use book
5305 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5306 first.maybeThinking = TRUE;
5307 } else SendMoveToProgram(forwardMostMove-1, &first);
5308 if (currentMove == cmailOldMove + 1) {
5309 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5313 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5317 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5318 EP_UNKNOWN, castlingRights[currentMove]) ) {
5324 if (WhiteOnMove(currentMove)) {
5325 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5327 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5331 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5336 case MachinePlaysBlack:
5337 case MachinePlaysWhite:
5338 /* disable certain menu options while machine is thinking */
5339 SetMachineThinkingEnables();
5346 if(bookHit) { // [HGM] book: simulate book reply
5347 static char bookMove[MSG_SIZ]; // a bit generous?
5349 programStats.nodes = programStats.depth = programStats.time =
5350 programStats.score = programStats.got_only_move = 0;
5351 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5353 strcpy(bookMove, "move ");
5354 strcat(bookMove, bookHit);
5355 HandleMachineMove(bookMove, &first);
5361 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5362 int fromX, fromY, toX, toY;
5365 /* [HGM] This routine was added to allow calling of its two logical
5366 parts from other modules in the old way. Before, UserMoveEvent()
5367 automatically called FinishMove() if the move was OK, and returned
5368 otherwise. I separated the two, in order to make it possible to
5369 slip a promotion popup in between. But that it always needs two
5370 calls, to the first part, (now called UserMoveTest() ), and to
5371 FinishMove if the first part succeeded. Calls that do not need
5372 to do anything in between, can call this routine the old way.
5374 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5375 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5376 if(moveType != ImpossibleMove)
5377 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5380 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5382 // char * hint = lastHint;
5383 FrontEndProgramStats stats;
5385 stats.which = cps == &first ? 0 : 1;
5386 stats.depth = cpstats->depth;
5387 stats.nodes = cpstats->nodes;
5388 stats.score = cpstats->score;
5389 stats.time = cpstats->time;
5390 stats.pv = cpstats->movelist;
5391 stats.hint = lastHint;
5392 stats.an_move_index = 0;
5393 stats.an_move_count = 0;
5395 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5396 stats.hint = cpstats->move_name;
5397 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5398 stats.an_move_count = cpstats->nr_moves;
5401 SetProgramStats( &stats );
5404 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5405 { // [HGM] book: this routine intercepts moves to simulate book replies
5406 char *bookHit = NULL;
5408 //first determine if the incoming move brings opponent into his book
5409 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5410 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5411 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5412 if(bookHit != NULL && !cps->bookSuspend) {
5413 // make sure opponent is not going to reply after receiving move to book position
5414 SendToProgram("force\n", cps);
5415 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5417 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5418 // now arrange restart after book miss
5420 // after a book hit we never send 'go', and the code after the call to this routine
5421 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5423 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5424 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5425 SendToProgram(buf, cps);
5426 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5427 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5428 SendToProgram("go\n", cps);
5429 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5430 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5431 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5432 SendToProgram("go\n", cps);
5433 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5435 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5439 ChessProgramState *savedState;
5440 void DeferredBookMove(void)
5442 if(savedState->lastPing != savedState->lastPong)
5443 ScheduleDelayedEvent(DeferredBookMove, 10);
5445 HandleMachineMove(savedMessage, savedState);
5449 HandleMachineMove(message, cps)
5451 ChessProgramState *cps;
5453 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5454 char realname[MSG_SIZ];
5455 int fromX, fromY, toX, toY;
5462 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5464 * Kludge to ignore BEL characters
5466 while (*message == '\007') message++;
5469 * [HGM] engine debug message: ignore lines starting with '#' character
5471 if(cps->debug && *message == '#') return;
5474 * Look for book output
5476 if (cps == &first && bookRequested) {
5477 if (message[0] == '\t' || message[0] == ' ') {
5478 /* Part of the book output is here; append it */
5479 strcat(bookOutput, message);
5480 strcat(bookOutput, " \n");
5482 } else if (bookOutput[0] != NULLCHAR) {
5483 /* All of book output has arrived; display it */
5484 char *p = bookOutput;
5485 while (*p != NULLCHAR) {
5486 if (*p == '\t') *p = ' ';
5489 DisplayInformation(bookOutput);
5490 bookRequested = FALSE;
5491 /* Fall through to parse the current output */
5496 * Look for machine move.
5498 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5499 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5501 /* This method is only useful on engines that support ping */
5502 if (cps->lastPing != cps->lastPong) {
5503 if (gameMode == BeginningOfGame) {
5504 /* Extra move from before last new; ignore */
5505 if (appData.debugMode) {
5506 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5509 if (appData.debugMode) {
5510 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5511 cps->which, gameMode);
5514 SendToProgram("undo\n", cps);
5520 case BeginningOfGame:
5521 /* Extra move from before last reset; ignore */
5522 if (appData.debugMode) {
5523 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5530 /* Extra move after we tried to stop. The mode test is
5531 not a reliable way of detecting this problem, but it's
5532 the best we can do on engines that don't support ping.
5534 if (appData.debugMode) {
5535 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5536 cps->which, gameMode);
5538 SendToProgram("undo\n", cps);
5541 case MachinePlaysWhite:
5542 case IcsPlayingWhite:
5543 machineWhite = TRUE;
5546 case MachinePlaysBlack:
5547 case IcsPlayingBlack:
5548 machineWhite = FALSE;
5551 case TwoMachinesPlay:
5552 machineWhite = (cps->twoMachinesColor[0] == 'w');
5555 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5556 if (appData.debugMode) {
5558 "Ignoring move out of turn by %s, gameMode %d"
5559 ", forwardMost %d\n",
5560 cps->which, gameMode, forwardMostMove);
5565 if (appData.debugMode) { int f = forwardMostMove;
5566 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5567 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5569 if(cps->alphaRank) AlphaRank(machineMove, 4);
5570 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5571 &fromX, &fromY, &toX, &toY, &promoChar)) {
5572 /* Machine move could not be parsed; ignore it. */
5573 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5574 machineMove, cps->which);
5575 DisplayError(buf1, 0);
5576 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5577 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5578 if (gameMode == TwoMachinesPlay) {
5579 GameEnds(machineWhite ? BlackWins : WhiteWins,
5585 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5586 /* So we have to redo legality test with true e.p. status here, */
5587 /* to make sure an illegal e.p. capture does not slip through, */
5588 /* to cause a forfeit on a justified illegal-move complaint */
5589 /* of the opponent. */
5590 if( gameMode==TwoMachinesPlay && appData.testLegality
5591 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5594 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5595 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5596 fromY, fromX, toY, toX, promoChar);
5597 if (appData.debugMode) {
5599 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5600 castlingRights[forwardMostMove][i], castlingRank[i]);
5601 fprintf(debugFP, "castling rights\n");
5603 if(moveType == IllegalMove) {
5604 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5605 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5606 GameEnds(machineWhite ? BlackWins : WhiteWins,
5609 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5610 /* [HGM] Kludge to handle engines that send FRC-style castling
5611 when they shouldn't (like TSCP-Gothic) */
5613 case WhiteASideCastleFR:
5614 case BlackASideCastleFR:
5616 currentMoveString[2]++;
5618 case WhiteHSideCastleFR:
5619 case BlackHSideCastleFR:
5621 currentMoveString[2]--;
5623 default: ; // nothing to do, but suppresses warning of pedantic compilers
5626 hintRequested = FALSE;
5627 lastHint[0] = NULLCHAR;
5628 bookRequested = FALSE;
5629 /* Program may be pondering now */
5630 cps->maybeThinking = TRUE;
5631 if (cps->sendTime == 2) cps->sendTime = 1;
5632 if (cps->offeredDraw) cps->offeredDraw--;
5635 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5637 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5639 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5640 char buf[3*MSG_SIZ];
5642 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5643 programStats.score / 100.,
5645 programStats.time / 100.,
5646 u64ToDouble(programStats.nodes),
5647 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5648 programStats.movelist);
5653 /* currentMoveString is set as a side-effect of ParseOneMove */
5654 strcpy(machineMove, currentMoveString);
5655 strcat(machineMove, "\n");
5656 strcpy(moveList[forwardMostMove], machineMove);
5658 /* [AS] Save move info and clear stats for next move */
5659 pvInfoList[ forwardMostMove ].score = programStats.score;
5660 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5661 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5662 ClearProgramStats();
5663 thinkOutput[0] = NULLCHAR;
5664 hiddenThinkOutputState = 0;
5666 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5668 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5669 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5672 while( count < adjudicateLossPlies ) {
5673 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5676 score = -score; /* Flip score for winning side */
5679 if( score > adjudicateLossThreshold ) {
5686 if( count >= adjudicateLossPlies ) {
5687 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5689 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5690 "Xboard adjudication",
5697 if( gameMode == TwoMachinesPlay ) {
5698 // [HGM] some adjudications useful with buggy engines
5699 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5700 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5703 if( appData.testLegality )
5704 { /* [HGM] Some more adjudications for obstinate engines */
5705 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5706 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5707 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5708 static int moveCount = 6;
5710 char *reason = NULL;
5712 /* Count what is on board. */
5713 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5714 { ChessSquare p = boards[forwardMostMove][i][j];
5718 { /* count B,N,R and other of each side */
5721 NrK++; break; // [HGM] atomic: count Kings
5725 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5726 bishopsColor |= 1 << ((i^j)&1);
5731 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5732 bishopsColor |= 1 << ((i^j)&1);
5747 PawnAdvance += m; NrPawns++;
5749 NrPieces += (p != EmptySquare);
5750 NrW += ((int)p < (int)BlackPawn);
5751 if(gameInfo.variant == VariantXiangqi &&
5752 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5753 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5754 NrW -= ((int)p < (int)BlackPawn);
5758 /* Some material-based adjudications that have to be made before stalemate test */
5759 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5760 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5761 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5762 if(appData.checkMates) {
5763 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5764 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5765 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5766 "Xboard adjudication: King destroyed", GE_XBOARD );
5771 /* Bare King in Shatranj (loses) or Losers (wins) */
5772 if( NrW == 1 || NrPieces - NrW == 1) {
5773 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5774 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5775 if(appData.checkMates) {
5776 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5777 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5778 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5779 "Xboard adjudication: Bare king", GE_XBOARD );
5783 if( gameInfo.variant == VariantShatranj && --bare < 0)
5785 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5786 if(appData.checkMates) {
5787 /* but only adjudicate if adjudication enabled */
5788 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5789 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5790 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5791 "Xboard adjudication: Bare king", GE_XBOARD );
5798 // don't wait for engine to announce game end if we can judge ourselves
5799 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5800 castlingRights[forwardMostMove]) ) {
5802 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5803 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5804 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5805 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5808 reason = "Xboard adjudication: 3rd check";
5809 epStatus[forwardMostMove] = EP_CHECKMATE;
5819 reason = "Xboard adjudication: Stalemate";
5820 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5821 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5822 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5823 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5824 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5825 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5826 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5827 EP_CHECKMATE : EP_WINS);
5828 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5829 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5833 reason = "Xboard adjudication: Checkmate";
5834 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5838 switch(i = epStatus[forwardMostMove]) {
5840 result = GameIsDrawn; break;
5842 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5844 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5846 result = (ChessMove) 0;
5848 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5849 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5850 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5851 GameEnds( result, reason, GE_XBOARD );
5855 /* Next absolutely insufficient mating material. */
5856 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5857 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5858 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5859 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5860 { /* KBK, KNK, KK of KBKB with like Bishops */
5862 /* always flag draws, for judging claims */
5863 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5865 if(appData.materialDraws) {
5866 /* but only adjudicate them if adjudication enabled */
5867 SendToProgram("force\n", cps->other); // suppress reply
5868 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5869 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5870 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5875 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5877 ( NrWR == 1 && NrBR == 1 /* KRKR */
5878 || NrWQ==1 && NrBQ==1 /* KQKQ */
5879 || NrWN==2 || NrBN==2 /* KNNK */
5880 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5882 if(--moveCount < 0 && appData.trivialDraws)
5883 { /* if the first 3 moves do not show a tactical win, declare draw */
5884 SendToProgram("force\n", cps->other); // suppress reply
5885 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5886 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5887 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5890 } else moveCount = 6;
5894 if (appData.debugMode) { int i;
5895 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5896 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5897 appData.drawRepeats);
5898 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5899 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5903 /* Check for rep-draws */
5905 for(k = forwardMostMove-2;
5906 k>=backwardMostMove && k>=forwardMostMove-100 &&
5907 epStatus[k] < EP_UNKNOWN &&
5908 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5912 if (appData.debugMode) {
5913 fprintf(debugFP, " loop\n");
5916 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5918 if (appData.debugMode) {
5919 fprintf(debugFP, "match\n");
5922 /* compare castling rights */
5923 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5924 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5925 rights++; /* King lost rights, while rook still had them */
5926 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5927 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5928 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5929 rights++; /* but at least one rook lost them */
5931 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5932 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5934 if( castlingRights[forwardMostMove][5] >= 0 ) {
5935 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5936 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5940 if (appData.debugMode) {
5941 for(i=0; i<nrCastlingRights; i++)
5942 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5945 if (appData.debugMode) {
5946 fprintf(debugFP, " %d %d\n", rights, k);
5949 if( rights == 0 && ++count > appData.drawRepeats-2
5950 && appData.drawRepeats > 1) {
5951 /* adjudicate after user-specified nr of repeats */
5952 SendToProgram("force\n", cps->other); // suppress reply
5953 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5954 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5955 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5956 // [HGM] xiangqi: check for forbidden perpetuals
5957 int m, ourPerpetual = 1, hisPerpetual = 1;
5958 for(m=forwardMostMove; m>k; m-=2) {
5959 if(MateTest(boards[m], PosFlags(m),
5960 EP_NONE, castlingRights[m]) != MT_CHECK)
5961 ourPerpetual = 0; // the current mover did not always check
5962 if(MateTest(boards[m-1], PosFlags(m-1),
5963 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5964 hisPerpetual = 0; // the opponent did not always check
5966 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5967 ourPerpetual, hisPerpetual);
5968 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5969 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5970 "Xboard adjudication: perpetual checking", GE_XBOARD );
5973 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5974 break; // (or we would have caught him before). Abort repetition-checking loop.
5975 // Now check for perpetual chases
5976 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5977 hisPerpetual = PerpetualChase(k, forwardMostMove);
5978 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5979 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5980 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5981 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5984 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5985 break; // Abort repetition-checking loop.
5987 // if neither of us is checking or chasing all the time, or both are, it is draw
5989 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5992 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5993 epStatus[forwardMostMove] = EP_REP_DRAW;
5997 /* Now we test for 50-move draws. Determine ply count */
5998 count = forwardMostMove;
5999 /* look for last irreversble move */
6000 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6002 /* if we hit starting position, add initial plies */
6003 if( count == backwardMostMove )
6004 count -= initialRulePlies;
6005 count = forwardMostMove - count;
6007 epStatus[forwardMostMove] = EP_RULE_DRAW;
6008 /* this is used to judge if draw claims are legal */
6009 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6010 SendToProgram("force\n", cps->other); // suppress reply
6011 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6012 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6013 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6017 /* if draw offer is pending, treat it as a draw claim
6018 * when draw condition present, to allow engines a way to
6019 * claim draws before making their move to avoid a race
6020 * condition occurring after their move
6022 if( cps->other->offeredDraw || cps->offeredDraw ) {
6024 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6025 p = "Draw claim: 50-move rule";
6026 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6027 p = "Draw claim: 3-fold repetition";
6028 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6029 p = "Draw claim: insufficient mating material";
6031 SendToProgram("force\n", cps->other); // suppress reply
6032 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6033 GameEnds( GameIsDrawn, p, GE_XBOARD );
6034 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6040 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6041 SendToProgram("force\n", cps->other); // suppress reply
6042 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6043 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6045 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6052 if (gameMode == TwoMachinesPlay) {
6053 /* [HGM] relaying draw offers moved to after reception of move */
6054 /* and interpreting offer as claim if it brings draw condition */
6055 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6056 SendToProgram("draw\n", cps->other);
6058 if (cps->other->sendTime) {
6059 SendTimeRemaining(cps->other,
6060 cps->other->twoMachinesColor[0] == 'w');
6062 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6063 if (firstMove && !bookHit) {
6065 if (cps->other->useColors) {
6066 SendToProgram(cps->other->twoMachinesColor, cps->other);
6068 SendToProgram("go\n", cps->other);
6070 cps->other->maybeThinking = TRUE;
6073 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6075 if (!pausing && appData.ringBellAfterMoves) {
6080 * Reenable menu items that were disabled while
6081 * machine was thinking
6083 if (gameMode != TwoMachinesPlay)
6084 SetUserThinkingEnables();
6086 // [HGM] book: after book hit opponent has received move and is now in force mode
6087 // force the book reply into it, and then fake that it outputted this move by jumping
6088 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6090 static char bookMove[MSG_SIZ]; // a bit generous?
6092 strcpy(bookMove, "move ");
6093 strcat(bookMove, bookHit);
6096 programStats.nodes = programStats.depth = programStats.time =
6097 programStats.score = programStats.got_only_move = 0;
6098 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6100 if(cps->lastPing != cps->lastPong) {
6101 savedMessage = message; // args for deferred call
6103 ScheduleDelayedEvent(DeferredBookMove, 10);
6112 /* Set special modes for chess engines. Later something general
6113 * could be added here; for now there is just one kludge feature,
6114 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6115 * when "xboard" is given as an interactive command.
6117 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6118 cps->useSigint = FALSE;
6119 cps->useSigterm = FALSE;
6122 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6123 * want this, I was asked to put it in, and obliged.
6125 if (!strncmp(message, "setboard ", 9)) {
6126 Board initial_position; int i;
6128 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6130 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6131 DisplayError(_("Bad FEN received from engine"), 0);
6134 Reset(FALSE, FALSE);
6135 CopyBoard(boards[0], initial_position);
6136 initialRulePlies = FENrulePlies;
6137 epStatus[0] = FENepStatus;
6138 for( i=0; i<nrCastlingRights; i++ )
6139 castlingRights[0][i] = FENcastlingRights[i];
6140 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6141 else gameMode = MachinePlaysBlack;
6142 DrawPosition(FALSE, boards[currentMove]);
6148 * Look for communication commands
6150 if (!strncmp(message, "telluser ", 9)) {
6151 DisplayNote(message + 9);
6154 if (!strncmp(message, "tellusererror ", 14)) {
6155 DisplayError(message + 14, 0);
6158 if (!strncmp(message, "tellopponent ", 13)) {
6159 if (appData.icsActive) {
6161 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6165 DisplayNote(message + 13);
6169 if (!strncmp(message, "tellothers ", 11)) {
6170 if (appData.icsActive) {
6172 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6178 if (!strncmp(message, "tellall ", 8)) {
6179 if (appData.icsActive) {
6181 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6185 DisplayNote(message + 8);
6189 if (strncmp(message, "warning", 7) == 0) {
6190 /* Undocumented feature, use tellusererror in new code */
6191 DisplayError(message, 0);
6194 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6195 strcpy(realname, cps->tidy);
6196 strcat(realname, " query");
6197 AskQuestion(realname, buf2, buf1, cps->pr);
6200 /* Commands from the engine directly to ICS. We don't allow these to be
6201 * sent until we are logged on. Crafty kibitzes have been known to
6202 * interfere with the login process.
6205 if (!strncmp(message, "tellics ", 8)) {
6206 SendToICS(message + 8);
6210 if (!strncmp(message, "tellicsnoalias ", 15)) {
6211 SendToICS(ics_prefix);
6212 SendToICS(message + 15);
6216 /* The following are for backward compatibility only */
6217 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6218 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6219 SendToICS(ics_prefix);
6225 if (strncmp(message, "feature ", 8) == 0) {
6226 ParseFeatures(message+8, cps);
6228 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6232 * If the move is illegal, cancel it and redraw the board.
6233 * Also deal with other error cases. Matching is rather loose
6234 * here to accommodate engines written before the spec.
6236 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6237 strncmp(message, "Error", 5) == 0) {
6238 if (StrStr(message, "name") ||
6239 StrStr(message, "rating") || StrStr(message, "?") ||
6240 StrStr(message, "result") || StrStr(message, "board") ||
6241 StrStr(message, "bk") || StrStr(message, "computer") ||
6242 StrStr(message, "variant") || StrStr(message, "hint") ||
6243 StrStr(message, "random") || StrStr(message, "depth") ||
6244 StrStr(message, "accepted")) {
6247 if (StrStr(message, "protover")) {
6248 /* Program is responding to input, so it's apparently done
6249 initializing, and this error message indicates it is
6250 protocol version 1. So we don't need to wait any longer
6251 for it to initialize and send feature commands. */
6252 FeatureDone(cps, 1);
6253 cps->protocolVersion = 1;
6256 cps->maybeThinking = FALSE;
6258 if (StrStr(message, "draw")) {
6259 /* Program doesn't have "draw" command */
6260 cps->sendDrawOffers = 0;
6263 if (cps->sendTime != 1 &&
6264 (StrStr(message, "time") || StrStr(message, "otim"))) {
6265 /* Program apparently doesn't have "time" or "otim" command */
6269 if (StrStr(message, "analyze")) {
6270 cps->analysisSupport = FALSE;
6271 cps->analyzing = FALSE;
6273 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6274 DisplayError(buf2, 0);
6277 if (StrStr(message, "(no matching move)st")) {
6278 /* Special kludge for GNU Chess 4 only */
6279 cps->stKludge = TRUE;
6280 SendTimeControl(cps, movesPerSession, timeControl,
6281 timeIncrement, appData.searchDepth,
6285 if (StrStr(message, "(no matching move)sd")) {
6286 /* Special kludge for GNU Chess 4 only */
6287 cps->sdKludge = TRUE;
6288 SendTimeControl(cps, movesPerSession, timeControl,
6289 timeIncrement, appData.searchDepth,
6293 if (!StrStr(message, "llegal")) {
6296 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6297 gameMode == IcsIdle) return;
6298 if (forwardMostMove <= backwardMostMove) return;
6300 /* Following removed: it caused a bug where a real illegal move
6301 message in analyze mored would be ignored. */
6302 if (cps == &first && programStats.ok_to_send == 0) {
6303 /* Bogus message from Crafty responding to "." This filtering
6304 can miss some of the bad messages, but fortunately the bug
6305 is fixed in current Crafty versions, so it doesn't matter. */
6309 if (pausing) PauseEvent();
6310 if (gameMode == PlayFromGameFile) {
6311 /* Stop reading this game file */
6312 gameMode = EditGame;
6315 currentMove = --forwardMostMove;
6316 DisplayMove(currentMove-1); /* before DisplayMoveError */
6318 DisplayBothClocks();
6319 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6320 parseList[currentMove], cps->which);
6321 DisplayMoveError(buf1);
6322 DrawPosition(FALSE, boards[currentMove]);
6324 /* [HGM] illegal-move claim should forfeit game when Xboard */
6325 /* only passes fully legal moves */
6326 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6327 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6328 "False illegal-move claim", GE_XBOARD );
6332 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6333 /* Program has a broken "time" command that
6334 outputs a string not ending in newline.
6340 * If chess program startup fails, exit with an error message.
6341 * Attempts to recover here are futile.
6343 if ((StrStr(message, "unknown host") != NULL)
6344 || (StrStr(message, "No remote directory") != NULL)
6345 || (StrStr(message, "not found") != NULL)
6346 || (StrStr(message, "No such file") != NULL)
6347 || (StrStr(message, "can't alloc") != NULL)
6348 || (StrStr(message, "Permission denied") != NULL)) {
6350 cps->maybeThinking = FALSE;
6351 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6352 cps->which, cps->program, cps->host, message);
6353 RemoveInputSource(cps->isr);
6354 DisplayFatalError(buf1, 0, 1);
6359 * Look for hint output
6361 if (sscanf(message, "Hint: %s", buf1) == 1) {
6362 if (cps == &first && hintRequested) {
6363 hintRequested = FALSE;
6364 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6365 &fromX, &fromY, &toX, &toY, &promoChar)) {
6366 (void) CoordsToAlgebraic(boards[forwardMostMove],
6367 PosFlags(forwardMostMove), EP_UNKNOWN,
6368 fromY, fromX, toY, toX, promoChar, buf1);
6369 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6370 DisplayInformation(buf2);
6372 /* Hint move could not be parsed!? */
6373 snprintf(buf2, sizeof(buf2),
6374 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6376 DisplayError(buf2, 0);
6379 strcpy(lastHint, buf1);
6385 * Ignore other messages if game is not in progress
6387 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6388 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6391 * look for win, lose, draw, or draw offer
6393 if (strncmp(message, "1-0", 3) == 0) {
6394 char *p, *q, *r = "";
6395 p = strchr(message, '{');
6403 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6405 } else if (strncmp(message, "0-1", 3) == 0) {
6406 char *p, *q, *r = "";
6407 p = strchr(message, '{');
6415 /* Kludge for Arasan 4.1 bug */
6416 if (strcmp(r, "Black resigns") == 0) {
6417 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6420 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6422 } else if (strncmp(message, "1/2", 3) == 0) {
6423 char *p, *q, *r = "";
6424 p = strchr(message, '{');
6433 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6436 } else if (strncmp(message, "White resign", 12) == 0) {
6437 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6439 } else if (strncmp(message, "Black resign", 12) == 0) {
6440 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6442 } else if (strncmp(message, "White matches", 13) == 0 ||
6443 strncmp(message, "Black matches", 13) == 0 ) {
6444 /* [HGM] ignore GNUShogi noises */
6446 } else if (strncmp(message, "White", 5) == 0 &&
6447 message[5] != '(' &&
6448 StrStr(message, "Black") == NULL) {
6449 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6451 } else if (strncmp(message, "Black", 5) == 0 &&
6452 message[5] != '(') {
6453 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6455 } else if (strcmp(message, "resign") == 0 ||
6456 strcmp(message, "computer resigns") == 0) {
6458 case MachinePlaysBlack:
6459 case IcsPlayingBlack:
6460 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6462 case MachinePlaysWhite:
6463 case IcsPlayingWhite:
6464 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6466 case TwoMachinesPlay:
6467 if (cps->twoMachinesColor[0] == 'w')
6468 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6470 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6477 } else if (strncmp(message, "opponent mates", 14) == 0) {
6479 case MachinePlaysBlack:
6480 case IcsPlayingBlack:
6481 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6483 case MachinePlaysWhite:
6484 case IcsPlayingWhite:
6485 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6487 case TwoMachinesPlay:
6488 if (cps->twoMachinesColor[0] == 'w')
6489 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6491 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6498 } else if (strncmp(message, "computer mates", 14) == 0) {
6500 case MachinePlaysBlack:
6501 case IcsPlayingBlack:
6502 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6504 case MachinePlaysWhite:
6505 case IcsPlayingWhite:
6506 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6508 case TwoMachinesPlay:
6509 if (cps->twoMachinesColor[0] == 'w')
6510 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6512 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6519 } else if (strncmp(message, "checkmate", 9) == 0) {
6520 if (WhiteOnMove(forwardMostMove)) {
6521 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6523 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6526 } else if (strstr(message, "Draw") != NULL ||
6527 strstr(message, "game is a draw") != NULL) {
6528 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6530 } else if (strstr(message, "offer") != NULL &&
6531 strstr(message, "draw") != NULL) {
6533 if (appData.zippyPlay && first.initDone) {
6534 /* Relay offer to ICS */
6535 SendToICS(ics_prefix);
6536 SendToICS("draw\n");
6539 cps->offeredDraw = 2; /* valid until this engine moves twice */
6540 if (gameMode == TwoMachinesPlay) {
6541 if (cps->other->offeredDraw) {
6542 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6543 /* [HGM] in two-machine mode we delay relaying draw offer */
6544 /* until after we also have move, to see if it is really claim */
6548 if (cps->other->sendDrawOffers) {
6549 SendToProgram("draw\n", cps->other);
6553 } else if (gameMode == MachinePlaysWhite ||
6554 gameMode == MachinePlaysBlack) {
6555 if (userOfferedDraw) {
6556 DisplayInformation(_("Machine accepts your draw offer"));
6557 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6559 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6566 * Look for thinking output
6568 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6569 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6571 int plylev, mvleft, mvtot, curscore, time;
6572 char mvname[MOVE_LEN];
6576 int prefixHint = FALSE;
6577 mvname[0] = NULLCHAR;
6580 case MachinePlaysBlack:
6581 case IcsPlayingBlack:
6582 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6584 case MachinePlaysWhite:
6585 case IcsPlayingWhite:
6586 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6591 case IcsObserving: /* [DM] icsEngineAnalyze */
6592 if (!appData.icsEngineAnalyze) ignore = TRUE;
6594 case TwoMachinesPlay:
6595 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6606 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6607 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6609 if (plyext != ' ' && plyext != '\t') {
6613 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6614 if( cps->scoreIsAbsolute &&
6615 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6617 curscore = -curscore;
6621 programStats.depth = plylev;
6622 programStats.nodes = nodes;
6623 programStats.time = time;
6624 programStats.score = curscore;
6625 programStats.got_only_move = 0;
6627 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6630 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6631 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6632 if(WhiteOnMove(forwardMostMove))
6633 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6634 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6637 /* Buffer overflow protection */
6638 if (buf1[0] != NULLCHAR) {
6639 if (strlen(buf1) >= sizeof(programStats.movelist)
6640 && appData.debugMode) {
6642 "PV is too long; using the first %d bytes.\n",
6643 sizeof(programStats.movelist) - 1);
6646 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6648 sprintf(programStats.movelist, " no PV\n");
6651 if (programStats.seen_stat) {
6652 programStats.ok_to_send = 1;
6655 if (strchr(programStats.movelist, '(') != NULL) {
6656 programStats.line_is_book = 1;
6657 programStats.nr_moves = 0;
6658 programStats.moves_left = 0;
6660 programStats.line_is_book = 0;
6663 SendProgramStatsToFrontend( cps, &programStats );
6666 [AS] Protect the thinkOutput buffer from overflow... this
6667 is only useful if buf1 hasn't overflowed first!
6669 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6671 (gameMode == TwoMachinesPlay ?
6672 ToUpper(cps->twoMachinesColor[0]) : ' '),
6673 ((double) curscore) / 100.0,
6674 prefixHint ? lastHint : "",
6675 prefixHint ? " " : "" );
6677 if( buf1[0] != NULLCHAR ) {
6678 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6680 if( strlen(buf1) > max_len ) {
6681 if( appData.debugMode) {
6682 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6684 buf1[max_len+1] = '\0';
6687 strcat( thinkOutput, buf1 );
6690 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6691 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6692 DisplayMove(currentMove - 1);
6697 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6698 /* crafty (9.25+) says "(only move) <move>"
6699 * if there is only 1 legal move
6701 sscanf(p, "(only move) %s", buf1);
6702 sprintf(thinkOutput, "%s (only move)", buf1);
6703 sprintf(programStats.movelist, "%s (only move)", buf1);
6704 programStats.depth = 1;
6705 programStats.nr_moves = 1;
6706 programStats.moves_left = 1;
6707 programStats.nodes = 1;
6708 programStats.time = 1;
6709 programStats.got_only_move = 1;
6711 /* Not really, but we also use this member to
6712 mean "line isn't going to change" (Crafty
6713 isn't searching, so stats won't change) */
6714 programStats.line_is_book = 1;
6716 SendProgramStatsToFrontend( cps, &programStats );
6718 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6719 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6720 DisplayMove(currentMove - 1);
6724 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6725 &time, &nodes, &plylev, &mvleft,
6726 &mvtot, mvname) >= 5) {
6727 /* The stat01: line is from Crafty (9.29+) in response
6728 to the "." command */
6729 programStats.seen_stat = 1;
6730 cps->maybeThinking = TRUE;
6732 if (programStats.got_only_move || !appData.periodicUpdates)
6735 programStats.depth = plylev;
6736 programStats.time = time;
6737 programStats.nodes = nodes;
6738 programStats.moves_left = mvleft;
6739 programStats.nr_moves = mvtot;
6740 strcpy(programStats.move_name, mvname);
6741 programStats.ok_to_send = 1;
6742 programStats.movelist[0] = '\0';
6744 SendProgramStatsToFrontend( cps, &programStats );
6749 } else if (strncmp(message,"++",2) == 0) {
6750 /* Crafty 9.29+ outputs this */
6751 programStats.got_fail = 2;
6754 } else if (strncmp(message,"--",2) == 0) {
6755 /* Crafty 9.29+ outputs this */
6756 programStats.got_fail = 1;
6759 } else if (thinkOutput[0] != NULLCHAR &&
6760 strncmp(message, " ", 4) == 0) {
6761 unsigned message_len;
6764 while (*p && *p == ' ') p++;
6766 message_len = strlen( p );
6768 /* [AS] Avoid buffer overflow */
6769 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6770 strcat(thinkOutput, " ");
6771 strcat(thinkOutput, p);
6774 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6775 strcat(programStats.movelist, " ");
6776 strcat(programStats.movelist, p);
6779 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6780 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6781 DisplayMove(currentMove - 1);
6790 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6791 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6793 ChessProgramStats cpstats;
6795 if (plyext != ' ' && plyext != '\t') {
6799 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6800 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6801 curscore = -curscore;
6804 cpstats.depth = plylev;
6805 cpstats.nodes = nodes;
6806 cpstats.time = time;
6807 cpstats.score = curscore;
6808 cpstats.got_only_move = 0;
6809 cpstats.movelist[0] = '\0';
6811 if (buf1[0] != NULLCHAR) {
6812 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6815 cpstats.ok_to_send = 0;
6816 cpstats.line_is_book = 0;
6817 cpstats.nr_moves = 0;
6818 cpstats.moves_left = 0;
6820 SendProgramStatsToFrontend( cps, &cpstats );
6827 /* Parse a game score from the character string "game", and
6828 record it as the history of the current game. The game
6829 score is NOT assumed to start from the standard position.
6830 The display is not updated in any way.
6833 ParseGameHistory(game)
6837 int fromX, fromY, toX, toY, boardIndex;
6842 if (appData.debugMode)
6843 fprintf(debugFP, "Parsing game history: %s\n", game);
6845 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6846 gameInfo.site = StrSave(appData.icsHost);
6847 gameInfo.date = PGNDate();
6848 gameInfo.round = StrSave("-");
6850 /* Parse out names of players */
6851 while (*game == ' ') game++;
6853 while (*game != ' ') *p++ = *game++;
6855 gameInfo.white = StrSave(buf);
6856 while (*game == ' ') game++;
6858 while (*game != ' ' && *game != '\n') *p++ = *game++;
6860 gameInfo.black = StrSave(buf);
6863 boardIndex = blackPlaysFirst ? 1 : 0;
6866 yyboardindex = boardIndex;
6867 moveType = (ChessMove) yylex();
6869 case IllegalMove: /* maybe suicide chess, etc. */
6870 if (appData.debugMode) {
6871 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6872 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6873 setbuf(debugFP, NULL);
6875 case WhitePromotionChancellor:
6876 case BlackPromotionChancellor:
6877 case WhitePromotionArchbishop:
6878 case BlackPromotionArchbishop:
6879 case WhitePromotionQueen:
6880 case BlackPromotionQueen:
6881 case WhitePromotionRook:
6882 case BlackPromotionRook:
6883 case WhitePromotionBishop:
6884 case BlackPromotionBishop:
6885 case WhitePromotionKnight:
6886 case BlackPromotionKnight:
6887 case WhitePromotionKing:
6888 case BlackPromotionKing:
6890 case WhiteCapturesEnPassant:
6891 case BlackCapturesEnPassant:
6892 case WhiteKingSideCastle:
6893 case WhiteQueenSideCastle:
6894 case BlackKingSideCastle:
6895 case BlackQueenSideCastle:
6896 case WhiteKingSideCastleWild:
6897 case WhiteQueenSideCastleWild:
6898 case BlackKingSideCastleWild:
6899 case BlackQueenSideCastleWild:
6901 case WhiteHSideCastleFR:
6902 case WhiteASideCastleFR:
6903 case BlackHSideCastleFR:
6904 case BlackASideCastleFR:
6906 fromX = currentMoveString[0] - AAA;
6907 fromY = currentMoveString[1] - ONE;
6908 toX = currentMoveString[2] - AAA;
6909 toY = currentMoveString[3] - ONE;
6910 promoChar = currentMoveString[4];
6914 fromX = moveType == WhiteDrop ?
6915 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6916 (int) CharToPiece(ToLower(currentMoveString[0]));
6918 toX = currentMoveString[2] - AAA;
6919 toY = currentMoveString[3] - ONE;
6920 promoChar = NULLCHAR;
6924 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6925 if (appData.debugMode) {
6926 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6927 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6928 setbuf(debugFP, NULL);
6930 DisplayError(buf, 0);
6932 case ImpossibleMove:
6934 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6935 if (appData.debugMode) {
6936 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6937 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6938 setbuf(debugFP, NULL);
6940 DisplayError(buf, 0);
6942 case (ChessMove) 0: /* end of file */
6943 if (boardIndex < backwardMostMove) {
6944 /* Oops, gap. How did that happen? */
6945 DisplayError(_("Gap in move list"), 0);
6948 backwardMostMove = blackPlaysFirst ? 1 : 0;
6949 if (boardIndex > forwardMostMove) {
6950 forwardMostMove = boardIndex;
6954 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6955 strcat(parseList[boardIndex-1], " ");
6956 strcat(parseList[boardIndex-1], yy_text);
6968 case GameUnfinished:
6969 if (gameMode == IcsExamining) {
6970 if (boardIndex < backwardMostMove) {
6971 /* Oops, gap. How did that happen? */
6974 backwardMostMove = blackPlaysFirst ? 1 : 0;
6977 gameInfo.result = moveType;
6978 p = strchr(yy_text, '{');
6979 if (p == NULL) p = strchr(yy_text, '(');
6982 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6984 q = strchr(p, *p == '{' ? '}' : ')');
6985 if (q != NULL) *q = NULLCHAR;
6988 gameInfo.resultDetails = StrSave(p);
6991 if (boardIndex >= forwardMostMove &&
6992 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6993 backwardMostMove = blackPlaysFirst ? 1 : 0;
6996 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6997 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6998 parseList[boardIndex]);
6999 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7000 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7001 /* currentMoveString is set as a side-effect of yylex */
7002 strcpy(moveList[boardIndex], currentMoveString);
7003 strcat(moveList[boardIndex], "\n");
7005 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7006 castlingRights[boardIndex], &epStatus[boardIndex]);
7007 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7008 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7014 if(gameInfo.variant != VariantShogi)
7015 strcat(parseList[boardIndex - 1], "+");
7019 strcat(parseList[boardIndex - 1], "#");
7026 /* Apply a move to the given board */
7028 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7029 int fromX, fromY, toX, toY;
7035 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7037 /* [HGM] compute & store e.p. status and castling rights for new position */
7038 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7041 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7045 if( board[toY][toX] != EmptySquare )
7048 if( board[fromY][fromX] == WhitePawn ) {
7049 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7052 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7053 gameInfo.variant != VariantBerolina || toX < fromX)
7054 *ep = toX | berolina;
7055 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7056 gameInfo.variant != VariantBerolina || toX > fromX)
7060 if( board[fromY][fromX] == BlackPawn ) {
7061 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7063 if( toY-fromY== -2) {
7064 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7065 gameInfo.variant != VariantBerolina || toX < fromX)
7066 *ep = toX | berolina;
7067 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7068 gameInfo.variant != VariantBerolina || toX > fromX)
7073 for(i=0; i<nrCastlingRights; i++) {
7074 if(castling[i] == fromX && castlingRank[i] == fromY ||
7075 castling[i] == toX && castlingRank[i] == toY
7076 ) castling[i] = -1; // revoke for moved or captured piece
7081 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7082 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7083 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7085 if (fromX == toX && fromY == toY) return;
7087 if (fromY == DROP_RANK) {
7089 piece = board[toY][toX] = (ChessSquare) fromX;
7091 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7092 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7093 if(gameInfo.variant == VariantKnightmate)
7094 king += (int) WhiteUnicorn - (int) WhiteKing;
7096 /* Code added by Tord: */
7097 /* FRC castling assumed when king captures friendly rook. */
7098 if (board[fromY][fromX] == WhiteKing &&
7099 board[toY][toX] == WhiteRook) {
7100 board[fromY][fromX] = EmptySquare;
7101 board[toY][toX] = EmptySquare;
7103 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7105 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7107 } else if (board[fromY][fromX] == BlackKing &&
7108 board[toY][toX] == BlackRook) {
7109 board[fromY][fromX] = EmptySquare;
7110 board[toY][toX] = EmptySquare;
7112 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7114 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7116 /* End of code added by Tord */
7118 } else if (board[fromY][fromX] == king
7119 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7120 && toY == fromY && toX > fromX+1) {
7121 board[fromY][fromX] = EmptySquare;
7122 board[toY][toX] = king;
7123 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7124 board[fromY][BOARD_RGHT-1] = EmptySquare;
7125 } else if (board[fromY][fromX] == king
7126 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7127 && toY == fromY && toX < fromX-1) {
7128 board[fromY][fromX] = EmptySquare;
7129 board[toY][toX] = king;
7130 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7131 board[fromY][BOARD_LEFT] = EmptySquare;
7132 } else if (board[fromY][fromX] == WhitePawn
7133 && toY == BOARD_HEIGHT-1
7134 && gameInfo.variant != VariantXiangqi
7136 /* white pawn promotion */
7137 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7138 if (board[toY][toX] == EmptySquare) {
7139 board[toY][toX] = WhiteQueen;
7141 if(gameInfo.variant==VariantBughouse ||
7142 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7143 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7144 board[fromY][fromX] = EmptySquare;
7145 } else if ((fromY == BOARD_HEIGHT-4)
7147 && gameInfo.variant != VariantXiangqi
7148 && gameInfo.variant != VariantBerolina
7149 && (board[fromY][fromX] == WhitePawn)
7150 && (board[toY][toX] == EmptySquare)) {
7151 board[fromY][fromX] = EmptySquare;
7152 board[toY][toX] = WhitePawn;
7153 captured = board[toY - 1][toX];
7154 board[toY - 1][toX] = EmptySquare;
7155 } else if ((fromY == BOARD_HEIGHT-4)
7157 && gameInfo.variant == VariantBerolina
7158 && (board[fromY][fromX] == WhitePawn)
7159 && (board[toY][toX] == EmptySquare)) {
7160 board[fromY][fromX] = EmptySquare;
7161 board[toY][toX] = WhitePawn;
7162 if(oldEP & EP_BEROLIN_A) {
7163 captured = board[fromY][fromX-1];
7164 board[fromY][fromX-1] = EmptySquare;
7165 }else{ captured = board[fromY][fromX+1];
7166 board[fromY][fromX+1] = EmptySquare;
7168 } else if (board[fromY][fromX] == king
7169 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7170 && toY == fromY && toX > fromX+1) {
7171 board[fromY][fromX] = EmptySquare;
7172 board[toY][toX] = king;
7173 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7174 board[fromY][BOARD_RGHT-1] = EmptySquare;
7175 } else if (board[fromY][fromX] == king
7176 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7177 && toY == fromY && toX < fromX-1) {
7178 board[fromY][fromX] = EmptySquare;
7179 board[toY][toX] = king;
7180 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7181 board[fromY][BOARD_LEFT] = EmptySquare;
7182 } else if (fromY == 7 && fromX == 3
7183 && board[fromY][fromX] == BlackKing
7184 && toY == 7 && toX == 5) {
7185 board[fromY][fromX] = EmptySquare;
7186 board[toY][toX] = BlackKing;
7187 board[fromY][7] = EmptySquare;
7188 board[toY][4] = BlackRook;
7189 } else if (fromY == 7 && fromX == 3
7190 && board[fromY][fromX] == BlackKing
7191 && toY == 7 && toX == 1) {
7192 board[fromY][fromX] = EmptySquare;
7193 board[toY][toX] = BlackKing;
7194 board[fromY][0] = EmptySquare;
7195 board[toY][2] = BlackRook;
7196 } else if (board[fromY][fromX] == BlackPawn
7198 && gameInfo.variant != VariantXiangqi
7200 /* black pawn promotion */
7201 board[0][toX] = CharToPiece(ToLower(promoChar));
7202 if (board[0][toX] == EmptySquare) {
7203 board[0][toX] = BlackQueen;
7205 if(gameInfo.variant==VariantBughouse ||
7206 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7207 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7208 board[fromY][fromX] = EmptySquare;
7209 } else if ((fromY == 3)
7211 && gameInfo.variant != VariantXiangqi
7212 && gameInfo.variant != VariantBerolina
7213 && (board[fromY][fromX] == BlackPawn)
7214 && (board[toY][toX] == EmptySquare)) {
7215 board[fromY][fromX] = EmptySquare;
7216 board[toY][toX] = BlackPawn;
7217 captured = board[toY + 1][toX];
7218 board[toY + 1][toX] = EmptySquare;
7219 } else if ((fromY == 3)
7221 && gameInfo.variant == VariantBerolina
7222 && (board[fromY][fromX] == BlackPawn)
7223 && (board[toY][toX] == EmptySquare)) {
7224 board[fromY][fromX] = EmptySquare;
7225 board[toY][toX] = BlackPawn;
7226 if(oldEP & EP_BEROLIN_A) {
7227 captured = board[fromY][fromX-1];
7228 board[fromY][fromX-1] = EmptySquare;
7229 }else{ captured = board[fromY][fromX+1];
7230 board[fromY][fromX+1] = EmptySquare;
7233 board[toY][toX] = board[fromY][fromX];
7234 board[fromY][fromX] = EmptySquare;
7237 /* [HGM] now we promote for Shogi, if needed */
7238 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7239 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7242 if (gameInfo.holdingsWidth != 0) {
7244 /* !!A lot more code needs to be written to support holdings */
7245 /* [HGM] OK, so I have written it. Holdings are stored in the */
7246 /* penultimate board files, so they are automaticlly stored */
7247 /* in the game history. */
7248 if (fromY == DROP_RANK) {
7249 /* Delete from holdings, by decreasing count */
7250 /* and erasing image if necessary */
7252 if(p < (int) BlackPawn) { /* white drop */
7253 p -= (int)WhitePawn;
7254 if(p >= gameInfo.holdingsSize) p = 0;
7255 if(--board[p][BOARD_WIDTH-2] == 0)
7256 board[p][BOARD_WIDTH-1] = EmptySquare;
7257 } else { /* black drop */
7258 p -= (int)BlackPawn;
7259 if(p >= gameInfo.holdingsSize) p = 0;
7260 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7261 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7264 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7265 && gameInfo.variant != VariantBughouse ) {
7266 /* [HGM] holdings: Add to holdings, if holdings exist */
7267 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7268 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7269 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7272 if (p >= (int) BlackPawn) {
7273 p -= (int)BlackPawn;
7274 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7275 /* in Shogi restore piece to its original first */
7276 captured = (ChessSquare) (DEMOTED captured);
7279 p = PieceToNumber((ChessSquare)p);
7280 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7281 board[p][BOARD_WIDTH-2]++;
7282 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7284 p -= (int)WhitePawn;
7285 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7286 captured = (ChessSquare) (DEMOTED captured);
7289 p = PieceToNumber((ChessSquare)p);
7290 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7291 board[BOARD_HEIGHT-1-p][1]++;
7292 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7296 } else if (gameInfo.variant == VariantAtomic) {
7297 if (captured != EmptySquare) {
7299 for (y = toY-1; y <= toY+1; y++) {
7300 for (x = toX-1; x <= toX+1; x++) {
7301 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7302 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7303 board[y][x] = EmptySquare;
7307 board[toY][toX] = EmptySquare;
7310 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7311 /* [HGM] Shogi promotions */
7312 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7315 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7316 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7317 // [HGM] superchess: take promotion piece out of holdings
7318 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7319 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7320 if(!--board[k][BOARD_WIDTH-2])
7321 board[k][BOARD_WIDTH-1] = EmptySquare;
7323 if(!--board[BOARD_HEIGHT-1-k][1])
7324 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7330 /* Updates forwardMostMove */
7332 MakeMove(fromX, fromY, toX, toY, promoChar)
7333 int fromX, fromY, toX, toY;
7336 // forwardMostMove++; // [HGM] bare: moved downstream
7338 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7339 int timeLeft; static int lastLoadFlag=0; int king, piece;
7340 piece = boards[forwardMostMove][fromY][fromX];
7341 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7342 if(gameInfo.variant == VariantKnightmate)
7343 king += (int) WhiteUnicorn - (int) WhiteKing;
7344 if(forwardMostMove == 0) {
7346 fprintf(serverMoves, "%s;", second.tidy);
7347 fprintf(serverMoves, "%s;", first.tidy);
7348 if(!blackPlaysFirst)
7349 fprintf(serverMoves, "%s;", second.tidy);
7350 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7351 lastLoadFlag = loadFlag;
7353 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7354 // print castling suffix
7355 if( toY == fromY && piece == king ) {
7357 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7359 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7362 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7363 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7364 boards[forwardMostMove][toY][toX] == EmptySquare
7366 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7368 if(promoChar != NULLCHAR)
7369 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7371 fprintf(serverMoves, "/%d/%d",
7372 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7373 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7374 else timeLeft = blackTimeRemaining/1000;
7375 fprintf(serverMoves, "/%d", timeLeft);
7377 fflush(serverMoves);
7380 if (forwardMostMove+1 >= MAX_MOVES) {
7381 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7386 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7387 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7388 if (commentList[forwardMostMove+1] != NULL) {
7389 free(commentList[forwardMostMove+1]);
7390 commentList[forwardMostMove+1] = NULL;
7392 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7393 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7394 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7395 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7396 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7397 gameInfo.result = GameUnfinished;
7398 if (gameInfo.resultDetails != NULL) {
7399 free(gameInfo.resultDetails);
7400 gameInfo.resultDetails = NULL;
7402 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7403 moveList[forwardMostMove - 1]);
7404 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7405 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7406 fromY, fromX, toY, toX, promoChar,
7407 parseList[forwardMostMove - 1]);
7408 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7409 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7410 castlingRights[forwardMostMove]) ) {
7416 if(gameInfo.variant != VariantShogi)
7417 strcat(parseList[forwardMostMove - 1], "+");
7421 strcat(parseList[forwardMostMove - 1], "#");
7424 if (appData.debugMode) {
7425 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7430 /* Updates currentMove if not pausing */
7432 ShowMove(fromX, fromY, toX, toY)
7434 int instant = (gameMode == PlayFromGameFile) ?
7435 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7436 if(appData.noGUI) return;
7437 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7439 if (forwardMostMove == currentMove + 1) {
7440 AnimateMove(boards[forwardMostMove - 1],
7441 fromX, fromY, toX, toY);
7443 if (appData.highlightLastMove) {
7444 SetHighlights(fromX, fromY, toX, toY);
7447 currentMove = forwardMostMove;
7450 if (instant) return;
7452 DisplayMove(currentMove - 1);
7453 DrawPosition(FALSE, boards[currentMove]);
7454 DisplayBothClocks();
7455 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7458 void SendEgtPath(ChessProgramState *cps)
7459 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7460 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7462 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7465 char c, *q = name+1, *r, *s;
7467 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7468 while(*p && *p != ',') *q++ = *p++;
7470 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7471 strcmp(name, ",nalimov:") == 0 ) {
7472 // take nalimov path from the menu-changeable option first, if it is defined
7473 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7474 SendToProgram(buf,cps); // send egtbpath command for nalimov
7476 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7477 (s = StrStr(appData.egtFormats, name)) != NULL) {
7478 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7479 s = r = StrStr(s, ":") + 1; // beginning of path info
7480 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7481 c = *r; *r = 0; // temporarily null-terminate path info
7482 *--q = 0; // strip of trailig ':' from name
7483 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7485 SendToProgram(buf,cps); // send egtbpath command for this format
7487 if(*p == ',') p++; // read away comma to position for next format name
7492 InitChessProgram(cps, setup)
7493 ChessProgramState *cps;
7494 int setup; /* [HGM] needed to setup FRC opening position */
7496 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7497 if (appData.noChessProgram) return;
7498 hintRequested = FALSE;
7499 bookRequested = FALSE;
7501 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7502 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7503 if(cps->memSize) { /* [HGM] memory */
7504 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7505 SendToProgram(buf, cps);
7507 SendEgtPath(cps); /* [HGM] EGT */
7508 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7509 sprintf(buf, "cores %d\n", appData.smpCores);
7510 SendToProgram(buf, cps);
7513 SendToProgram(cps->initString, cps);
7514 if (gameInfo.variant != VariantNormal &&
7515 gameInfo.variant != VariantLoadable
7516 /* [HGM] also send variant if board size non-standard */
7517 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7519 char *v = VariantName(gameInfo.variant);
7520 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7521 /* [HGM] in protocol 1 we have to assume all variants valid */
7522 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7523 DisplayFatalError(buf, 0, 1);
7527 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7528 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7529 if( gameInfo.variant == VariantXiangqi )
7530 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7531 if( gameInfo.variant == VariantShogi )
7532 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7533 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7534 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7535 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7536 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7537 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7538 if( gameInfo.variant == VariantCourier )
7539 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7540 if( gameInfo.variant == VariantSuper )
7541 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7542 if( gameInfo.variant == VariantGreat )
7543 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7546 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7547 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7548 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7549 if(StrStr(cps->variants, b) == NULL) {
7550 // specific sized variant not known, check if general sizing allowed
7551 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7552 if(StrStr(cps->variants, "boardsize") == NULL) {
7553 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7554 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7555 DisplayFatalError(buf, 0, 1);
7558 /* [HGM] here we really should compare with the maximum supported board size */
7561 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7562 sprintf(buf, "variant %s\n", b);
7563 SendToProgram(buf, cps);
7565 currentlyInitializedVariant = gameInfo.variant;
7567 /* [HGM] send opening position in FRC to first engine */
7569 SendToProgram("force\n", cps);
7571 /* engine is now in force mode! Set flag to wake it up after first move. */
7572 setboardSpoiledMachineBlack = 1;
7576 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7577 SendToProgram(buf, cps);
7579 cps->maybeThinking = FALSE;
7580 cps->offeredDraw = 0;
7581 if (!appData.icsActive) {
7582 SendTimeControl(cps, movesPerSession, timeControl,
7583 timeIncrement, appData.searchDepth,
7586 if (appData.showThinking
7587 // [HGM] thinking: four options require thinking output to be sent
7588 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7590 SendToProgram("post\n", cps);
7592 SendToProgram("hard\n", cps);
7593 if (!appData.ponderNextMove) {
7594 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7595 it without being sure what state we are in first. "hard"
7596 is not a toggle, so that one is OK.
7598 SendToProgram("easy\n", cps);
7601 sprintf(buf, "ping %d\n", ++cps->lastPing);
7602 SendToProgram(buf, cps);
7604 cps->initDone = TRUE;
7609 StartChessProgram(cps)
7610 ChessProgramState *cps;
7615 if (appData.noChessProgram) return;
7616 cps->initDone = FALSE;
7618 if (strcmp(cps->host, "localhost") == 0) {
7619 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7620 } else if (*appData.remoteShell == NULLCHAR) {
7621 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7623 if (*appData.remoteUser == NULLCHAR) {
7624 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7627 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7628 cps->host, appData.remoteUser, cps->program);
7630 err = StartChildProcess(buf, "", &cps->pr);
7634 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7635 DisplayFatalError(buf, err, 1);
7641 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7642 if (cps->protocolVersion > 1) {
7643 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7644 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7645 cps->comboCnt = 0; // and values of combo boxes
7646 SendToProgram(buf, cps);
7648 SendToProgram("xboard\n", cps);
7654 TwoMachinesEventIfReady P((void))
7656 if (first.lastPing != first.lastPong) {
7657 DisplayMessage("", _("Waiting for first chess program"));
7658 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7661 if (second.lastPing != second.lastPong) {
7662 DisplayMessage("", _("Waiting for second chess program"));
7663 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7671 NextMatchGame P((void))
7673 int index; /* [HGM] autoinc: step lod index during match */
7675 if (*appData.loadGameFile != NULLCHAR) {
7676 index = appData.loadGameIndex;
7677 if(index < 0) { // [HGM] autoinc
7678 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7679 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7681 LoadGameFromFile(appData.loadGameFile,
7683 appData.loadGameFile, FALSE);
7684 } else if (*appData.loadPositionFile != NULLCHAR) {
7685 index = appData.loadPositionIndex;
7686 if(index < 0) { // [HGM] autoinc
7687 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7688 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7690 LoadPositionFromFile(appData.loadPositionFile,
7692 appData.loadPositionFile);
7694 TwoMachinesEventIfReady();
7697 void UserAdjudicationEvent( int result )
7699 ChessMove gameResult = GameIsDrawn;
7702 gameResult = WhiteWins;
7704 else if( result < 0 ) {
7705 gameResult = BlackWins;
7708 if( gameMode == TwoMachinesPlay ) {
7709 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7715 GameEnds(result, resultDetails, whosays)
7717 char *resultDetails;
7720 GameMode nextGameMode;
7724 if(endingGame) return; /* [HGM] crash: forbid recursion */
7727 if (appData.debugMode) {
7728 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7729 result, resultDetails ? resultDetails : "(null)", whosays);
7732 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7733 /* If we are playing on ICS, the server decides when the
7734 game is over, but the engine can offer to draw, claim
7738 if (appData.zippyPlay && first.initDone) {
7739 if (result == GameIsDrawn) {
7740 /* In case draw still needs to be claimed */
7741 SendToICS(ics_prefix);
7742 SendToICS("draw\n");
7743 } else if (StrCaseStr(resultDetails, "resign")) {
7744 SendToICS(ics_prefix);
7745 SendToICS("resign\n");
7749 endingGame = 0; /* [HGM] crash */
7753 /* If we're loading the game from a file, stop */
7754 if (whosays == GE_FILE) {
7755 (void) StopLoadGameTimer();
7759 /* Cancel draw offers */
7760 first.offeredDraw = second.offeredDraw = 0;
7762 /* If this is an ICS game, only ICS can really say it's done;
7763 if not, anyone can. */
7764 isIcsGame = (gameMode == IcsPlayingWhite ||
7765 gameMode == IcsPlayingBlack ||
7766 gameMode == IcsObserving ||
7767 gameMode == IcsExamining);
7769 if (!isIcsGame || whosays == GE_ICS) {
7770 /* OK -- not an ICS game, or ICS said it was done */
7772 if (!isIcsGame && !appData.noChessProgram)
7773 SetUserThinkingEnables();
7775 /* [HGM] if a machine claims the game end we verify this claim */
7776 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7777 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7779 ChessMove trueResult = (ChessMove) -1;
7781 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7782 first.twoMachinesColor[0] :
7783 second.twoMachinesColor[0] ;
7785 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7786 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7787 /* [HGM] verify: engine mate claims accepted if they were flagged */
7788 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7790 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7791 /* [HGM] verify: engine mate claims accepted if they were flagged */
7792 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7794 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7795 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7798 // now verify win claims, but not in drop games, as we don't understand those yet
7799 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7800 || gameInfo.variant == VariantGreat) &&
7801 (result == WhiteWins && claimer == 'w' ||
7802 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7803 if (appData.debugMode) {
7804 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7805 result, epStatus[forwardMostMove], forwardMostMove);
7807 if(result != trueResult) {
7808 sprintf(buf, "False win claim: '%s'", resultDetails);
7809 result = claimer == 'w' ? BlackWins : WhiteWins;
7810 resultDetails = buf;
7813 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7814 && (forwardMostMove <= backwardMostMove ||
7815 epStatus[forwardMostMove-1] > EP_DRAWS ||
7816 (claimer=='b')==(forwardMostMove&1))
7818 /* [HGM] verify: draws that were not flagged are false claims */
7819 sprintf(buf, "False draw claim: '%s'", resultDetails);
7820 result = claimer == 'w' ? BlackWins : WhiteWins;
7821 resultDetails = buf;
7823 /* (Claiming a loss is accepted no questions asked!) */
7825 /* [HGM] bare: don't allow bare King to win */
7826 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7827 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7828 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7829 && result != GameIsDrawn)
7830 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7831 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7832 int p = (int)boards[forwardMostMove][i][j] - color;
7833 if(p >= 0 && p <= (int)WhiteKing) k++;
7835 if (appData.debugMode) {
7836 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7837 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7840 result = GameIsDrawn;
7841 sprintf(buf, "%s but bare king", resultDetails);
7842 resultDetails = buf;
7848 if(serverMoves != NULL && !loadFlag) { char c = '=';
7849 if(result==WhiteWins) c = '+';
7850 if(result==BlackWins) c = '-';
7851 if(resultDetails != NULL)
7852 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7854 if (resultDetails != NULL) {
7855 gameInfo.result = result;
7856 gameInfo.resultDetails = StrSave(resultDetails);
7858 /* display last move only if game was not loaded from file */
7859 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7860 DisplayMove(currentMove - 1);
7862 if (forwardMostMove != 0) {
7863 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7864 if (*appData.saveGameFile != NULLCHAR) {
7865 SaveGameToFile(appData.saveGameFile, TRUE);
7866 } else if (appData.autoSaveGames) {
7869 if (*appData.savePositionFile != NULLCHAR) {
7870 SavePositionToFile(appData.savePositionFile);
7875 /* Tell program how game ended in case it is learning */
7876 /* [HGM] Moved this to after saving the PGN, just in case */
7877 /* engine died and we got here through time loss. In that */
7878 /* case we will get a fatal error writing the pipe, which */
7879 /* would otherwise lose us the PGN. */
7880 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7881 /* output during GameEnds should never be fatal anymore */
7882 if (gameMode == MachinePlaysWhite ||
7883 gameMode == MachinePlaysBlack ||
7884 gameMode == TwoMachinesPlay ||
7885 gameMode == IcsPlayingWhite ||
7886 gameMode == IcsPlayingBlack ||
7887 gameMode == BeginningOfGame) {
7889 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7891 if (first.pr != NoProc) {
7892 SendToProgram(buf, &first);
7894 if (second.pr != NoProc &&
7895 gameMode == TwoMachinesPlay) {
7896 SendToProgram(buf, &second);
7901 if (appData.icsActive) {
7902 if (appData.quietPlay &&
7903 (gameMode == IcsPlayingWhite ||
7904 gameMode == IcsPlayingBlack)) {
7905 SendToICS(ics_prefix);
7906 SendToICS("set shout 1\n");
7908 nextGameMode = IcsIdle;
7909 ics_user_moved = FALSE;
7910 /* clean up premove. It's ugly when the game has ended and the
7911 * premove highlights are still on the board.
7915 ClearPremoveHighlights();
7916 DrawPosition(FALSE, boards[currentMove]);
7918 if (whosays == GE_ICS) {
7921 if (gameMode == IcsPlayingWhite)
7923 else if(gameMode == IcsPlayingBlack)
7927 if (gameMode == IcsPlayingBlack)
7929 else if(gameMode == IcsPlayingWhite)
7936 PlayIcsUnfinishedSound();
7939 } else if (gameMode == EditGame ||
7940 gameMode == PlayFromGameFile ||
7941 gameMode == AnalyzeMode ||
7942 gameMode == AnalyzeFile) {
7943 nextGameMode = gameMode;
7945 nextGameMode = EndOfGame;
7950 nextGameMode = gameMode;
7953 if (appData.noChessProgram) {
7954 gameMode = nextGameMode;
7956 endingGame = 0; /* [HGM] crash */
7961 /* Put first chess program into idle state */
7962 if (first.pr != NoProc &&
7963 (gameMode == MachinePlaysWhite ||
7964 gameMode == MachinePlaysBlack ||
7965 gameMode == TwoMachinesPlay ||
7966 gameMode == IcsPlayingWhite ||
7967 gameMode == IcsPlayingBlack ||
7968 gameMode == BeginningOfGame)) {
7969 SendToProgram("force\n", &first);
7970 if (first.usePing) {
7972 sprintf(buf, "ping %d\n", ++first.lastPing);
7973 SendToProgram(buf, &first);
7976 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7977 /* Kill off first chess program */
7978 if (first.isr != NULL)
7979 RemoveInputSource(first.isr);
7982 if (first.pr != NoProc) {
7984 DoSleep( appData.delayBeforeQuit );
7985 SendToProgram("quit\n", &first);
7986 DoSleep( appData.delayAfterQuit );
7987 DestroyChildProcess(first.pr, first.useSigterm);
7992 /* Put second chess program into idle state */
7993 if (second.pr != NoProc &&
7994 gameMode == TwoMachinesPlay) {
7995 SendToProgram("force\n", &second);
7996 if (second.usePing) {
7998 sprintf(buf, "ping %d\n", ++second.lastPing);
7999 SendToProgram(buf, &second);
8002 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8003 /* Kill off second chess program */
8004 if (second.isr != NULL)
8005 RemoveInputSource(second.isr);
8008 if (second.pr != NoProc) {
8009 DoSleep( appData.delayBeforeQuit );
8010 SendToProgram("quit\n", &second);
8011 DoSleep( appData.delayAfterQuit );
8012 DestroyChildProcess(second.pr, second.useSigterm);
8017 if (matchMode && gameMode == TwoMachinesPlay) {
8020 if (first.twoMachinesColor[0] == 'w') {
8027 if (first.twoMachinesColor[0] == 'b') {
8036 if (matchGame < appData.matchGames) {
8038 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8039 tmp = first.twoMachinesColor;
8040 first.twoMachinesColor = second.twoMachinesColor;
8041 second.twoMachinesColor = tmp;
8043 gameMode = nextGameMode;
8045 if(appData.matchPause>10000 || appData.matchPause<10)
8046 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8047 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8048 endingGame = 0; /* [HGM] crash */
8052 gameMode = nextGameMode;
8053 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8054 first.tidy, second.tidy,
8055 first.matchWins, second.matchWins,
8056 appData.matchGames - (first.matchWins + second.matchWins));
8057 DisplayFatalError(buf, 0, 0);
8060 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8061 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8063 gameMode = nextGameMode;
8065 endingGame = 0; /* [HGM] crash */
8068 /* Assumes program was just initialized (initString sent).
8069 Leaves program in force mode. */
8071 FeedMovesToProgram(cps, upto)
8072 ChessProgramState *cps;
8077 if (appData.debugMode)
8078 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8079 startedFromSetupPosition ? "position and " : "",
8080 backwardMostMove, upto, cps->which);
8081 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8082 // [HGM] variantswitch: make engine aware of new variant
8083 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8084 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8085 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8086 SendToProgram(buf, cps);
8087 currentlyInitializedVariant = gameInfo.variant;
8089 SendToProgram("force\n", cps);
8090 if (startedFromSetupPosition) {
8091 SendBoard(cps, backwardMostMove);
8092 if (appData.debugMode) {
8093 fprintf(debugFP, "feedMoves\n");
8096 for (i = backwardMostMove; i < upto; i++) {
8097 SendMoveToProgram(i, cps);
8103 ResurrectChessProgram()
8105 /* The chess program may have exited.
8106 If so, restart it and feed it all the moves made so far. */
8108 if (appData.noChessProgram || first.pr != NoProc) return;
8110 StartChessProgram(&first);
8111 InitChessProgram(&first, FALSE);
8112 FeedMovesToProgram(&first, currentMove);
8114 if (!first.sendTime) {
8115 /* can't tell gnuchess what its clock should read,
8116 so we bow to its notion. */
8118 timeRemaining[0][currentMove] = whiteTimeRemaining;
8119 timeRemaining[1][currentMove] = blackTimeRemaining;
8122 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8123 appData.icsEngineAnalyze) && first.analysisSupport) {
8124 SendToProgram("analyze\n", &first);
8125 first.analyzing = TRUE;
8138 if (appData.debugMode) {
8139 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8140 redraw, init, gameMode);
8142 pausing = pauseExamInvalid = FALSE;
8143 startedFromSetupPosition = blackPlaysFirst = FALSE;
8145 whiteFlag = blackFlag = FALSE;
8146 userOfferedDraw = FALSE;
8147 hintRequested = bookRequested = FALSE;
8148 first.maybeThinking = FALSE;
8149 second.maybeThinking = FALSE;
8150 first.bookSuspend = FALSE; // [HGM] book
8151 second.bookSuspend = FALSE;
8152 thinkOutput[0] = NULLCHAR;
8153 lastHint[0] = NULLCHAR;
8154 ClearGameInfo(&gameInfo);
8155 gameInfo.variant = StringToVariant(appData.variant);
8156 ics_user_moved = ics_clock_paused = FALSE;
8157 ics_getting_history = H_FALSE;
8159 white_holding[0] = black_holding[0] = NULLCHAR;
8160 ClearProgramStats();
8161 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8165 flipView = appData.flipView;
8166 ClearPremoveHighlights();
8168 alarmSounded = FALSE;
8170 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8171 if(appData.serverMovesName != NULL) {
8172 /* [HGM] prepare to make moves file for broadcasting */
8173 clock_t t = clock();
8174 if(serverMoves != NULL) fclose(serverMoves);
8175 serverMoves = fopen(appData.serverMovesName, "r");
8176 if(serverMoves != NULL) {
8177 fclose(serverMoves);
8178 /* delay 15 sec before overwriting, so all clients can see end */
8179 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8181 serverMoves = fopen(appData.serverMovesName, "w");
8185 gameMode = BeginningOfGame;
8187 if(appData.icsActive) gameInfo.variant = VariantNormal;
8188 InitPosition(redraw);
8189 for (i = 0; i < MAX_MOVES; i++) {
8190 if (commentList[i] != NULL) {
8191 free(commentList[i]);
8192 commentList[i] = NULL;
8196 timeRemaining[0][0] = whiteTimeRemaining;
8197 timeRemaining[1][0] = blackTimeRemaining;
8198 if (first.pr == NULL) {
8199 StartChessProgram(&first);
8202 InitChessProgram(&first, startedFromSetupPosition);
8205 DisplayMessage("", "");
8206 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8213 if (!AutoPlayOneMove())
8215 if (matchMode || appData.timeDelay == 0)
8217 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8219 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8228 int fromX, fromY, toX, toY;
8230 if (appData.debugMode) {
8231 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8234 if (gameMode != PlayFromGameFile)
8237 if (currentMove >= forwardMostMove) {
8238 gameMode = EditGame;
8241 /* [AS] Clear current move marker at the end of a game */
8242 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8247 toX = moveList[currentMove][2] - AAA;
8248 toY = moveList[currentMove][3] - ONE;
8250 if (moveList[currentMove][1] == '@') {
8251 if (appData.highlightLastMove) {
8252 SetHighlights(-1, -1, toX, toY);
8255 fromX = moveList[currentMove][0] - AAA;
8256 fromY = moveList[currentMove][1] - ONE;
8258 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8260 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8262 if (appData.highlightLastMove) {
8263 SetHighlights(fromX, fromY, toX, toY);
8266 DisplayMove(currentMove);
8267 SendMoveToProgram(currentMove++, &first);
8268 DisplayBothClocks();
8269 DrawPosition(FALSE, boards[currentMove]);
8270 // [HGM] PV info: always display, routine tests if empty
8271 DisplayComment(currentMove - 1, commentList[currentMove]);
8277 LoadGameOneMove(readAhead)
8278 ChessMove readAhead;
8280 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8281 char promoChar = NULLCHAR;
8286 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8287 gameMode != AnalyzeMode && gameMode != Training) {
8292 yyboardindex = forwardMostMove;
8293 if (readAhead != (ChessMove)0) {
8294 moveType = readAhead;
8296 if (gameFileFP == NULL)
8298 moveType = (ChessMove) yylex();
8304 if (appData.debugMode)
8305 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8307 if (*p == '{' || *p == '[' || *p == '(') {
8308 p[strlen(p) - 1] = NULLCHAR;
8312 /* append the comment but don't display it */
8313 while (*p == '\n') p++;
8314 AppendComment(currentMove, p);
8317 case WhiteCapturesEnPassant:
8318 case BlackCapturesEnPassant:
8319 case WhitePromotionChancellor:
8320 case BlackPromotionChancellor:
8321 case WhitePromotionArchbishop:
8322 case BlackPromotionArchbishop:
8323 case WhitePromotionCentaur:
8324 case BlackPromotionCentaur:
8325 case WhitePromotionQueen:
8326 case BlackPromotionQueen:
8327 case WhitePromotionRook:
8328 case BlackPromotionRook:
8329 case WhitePromotionBishop:
8330 case BlackPromotionBishop:
8331 case WhitePromotionKnight:
8332 case BlackPromotionKnight:
8333 case WhitePromotionKing:
8334 case BlackPromotionKing:
8336 case WhiteKingSideCastle:
8337 case WhiteQueenSideCastle:
8338 case BlackKingSideCastle:
8339 case BlackQueenSideCastle:
8340 case WhiteKingSideCastleWild:
8341 case WhiteQueenSideCastleWild:
8342 case BlackKingSideCastleWild:
8343 case BlackQueenSideCastleWild:
8345 case WhiteHSideCastleFR:
8346 case WhiteASideCastleFR:
8347 case BlackHSideCastleFR:
8348 case BlackASideCastleFR:
8350 if (appData.debugMode)
8351 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8352 fromX = currentMoveString[0] - AAA;
8353 fromY = currentMoveString[1] - ONE;
8354 toX = currentMoveString[2] - AAA;
8355 toY = currentMoveString[3] - ONE;
8356 promoChar = currentMoveString[4];
8361 if (appData.debugMode)
8362 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8363 fromX = moveType == WhiteDrop ?
8364 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8365 (int) CharToPiece(ToLower(currentMoveString[0]));
8367 toX = currentMoveString[2] - AAA;
8368 toY = currentMoveString[3] - ONE;
8374 case GameUnfinished:
8375 if (appData.debugMode)
8376 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8377 p = strchr(yy_text, '{');
8378 if (p == NULL) p = strchr(yy_text, '(');
8381 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8383 q = strchr(p, *p == '{' ? '}' : ')');
8384 if (q != NULL) *q = NULLCHAR;
8387 GameEnds(moveType, p, GE_FILE);
8389 if (cmailMsgLoaded) {
8391 flipView = WhiteOnMove(currentMove);
8392 if (moveType == GameUnfinished) flipView = !flipView;
8393 if (appData.debugMode)
8394 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8398 case (ChessMove) 0: /* end of file */
8399 if (appData.debugMode)
8400 fprintf(debugFP, "Parser hit end of file\n");
8401 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8402 EP_UNKNOWN, castlingRights[currentMove]) ) {
8408 if (WhiteOnMove(currentMove)) {
8409 GameEnds(BlackWins, "Black mates", GE_FILE);
8411 GameEnds(WhiteWins, "White mates", GE_FILE);
8415 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8422 if (lastLoadGameStart == GNUChessGame) {
8423 /* GNUChessGames have numbers, but they aren't move numbers */
8424 if (appData.debugMode)
8425 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8426 yy_text, (int) moveType);
8427 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8429 /* else fall thru */
8434 /* Reached start of next game in file */
8435 if (appData.debugMode)
8436 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8437 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8438 EP_UNKNOWN, castlingRights[currentMove]) ) {
8444 if (WhiteOnMove(currentMove)) {
8445 GameEnds(BlackWins, "Black mates", GE_FILE);
8447 GameEnds(WhiteWins, "White mates", GE_FILE);
8451 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8457 case PositionDiagram: /* should not happen; ignore */
8458 case ElapsedTime: /* ignore */
8459 case NAG: /* ignore */
8460 if (appData.debugMode)
8461 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8462 yy_text, (int) moveType);
8463 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8466 if (appData.testLegality) {
8467 if (appData.debugMode)
8468 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8469 sprintf(move, _("Illegal move: %d.%s%s"),
8470 (forwardMostMove / 2) + 1,
8471 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8472 DisplayError(move, 0);
8475 if (appData.debugMode)
8476 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8477 yy_text, currentMoveString);
8478 fromX = currentMoveString[0] - AAA;
8479 fromY = currentMoveString[1] - ONE;
8480 toX = currentMoveString[2] - AAA;
8481 toY = currentMoveString[3] - ONE;
8482 promoChar = currentMoveString[4];
8487 if (appData.debugMode)
8488 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8489 sprintf(move, _("Ambiguous move: %d.%s%s"),
8490 (forwardMostMove / 2) + 1,
8491 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8492 DisplayError(move, 0);
8497 case ImpossibleMove:
8498 if (appData.debugMode)
8499 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8500 sprintf(move, _("Illegal move: %d.%s%s"),
8501 (forwardMostMove / 2) + 1,
8502 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8503 DisplayError(move, 0);
8509 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8510 DrawPosition(FALSE, boards[currentMove]);
8511 DisplayBothClocks();
8512 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8513 DisplayComment(currentMove - 1, commentList[currentMove]);
8515 (void) StopLoadGameTimer();
8517 cmailOldMove = forwardMostMove;
8520 /* currentMoveString is set as a side-effect of yylex */
8521 strcat(currentMoveString, "\n");
8522 strcpy(moveList[forwardMostMove], currentMoveString);
8524 thinkOutput[0] = NULLCHAR;
8525 MakeMove(fromX, fromY, toX, toY, promoChar);
8526 currentMove = forwardMostMove;
8531 /* Load the nth game from the given file */
8533 LoadGameFromFile(filename, n, title, useList)
8537 /*Boolean*/ int useList;
8542 if (strcmp(filename, "-") == 0) {
8546 f = fopen(filename, "rb");
8548 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8549 DisplayError(buf, errno);
8553 if (fseek(f, 0, 0) == -1) {
8554 /* f is not seekable; probably a pipe */
8557 if (useList && n == 0) {
8558 int error = GameListBuild(f);
8560 DisplayError(_("Cannot build game list"), error);
8561 } else if (!ListEmpty(&gameList) &&
8562 ((ListGame *) gameList.tailPred)->number > 1) {
8563 GameListPopUp(f, title);
8570 return LoadGame(f, n, title, FALSE);
8575 MakeRegisteredMove()
8577 int fromX, fromY, toX, toY;
8579 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8580 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8583 if (appData.debugMode)
8584 fprintf(debugFP, "Restoring %s for game %d\n",
8585 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8587 thinkOutput[0] = NULLCHAR;
8588 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8589 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8590 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8591 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8592 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8593 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8594 MakeMove(fromX, fromY, toX, toY, promoChar);
8595 ShowMove(fromX, fromY, toX, toY);
8597 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8598 EP_UNKNOWN, castlingRights[currentMove]) ) {
8605 if (WhiteOnMove(currentMove)) {
8606 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8608 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8613 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8620 if (WhiteOnMove(currentMove)) {
8621 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8623 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8628 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8639 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8641 CmailLoadGame(f, gameNumber, title, useList)
8649 if (gameNumber > nCmailGames) {
8650 DisplayError(_("No more games in this message"), 0);
8653 if (f == lastLoadGameFP) {
8654 int offset = gameNumber - lastLoadGameNumber;
8656 cmailMsg[0] = NULLCHAR;
8657 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8658 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8659 nCmailMovesRegistered--;
8661 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8662 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8663 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8666 if (! RegisterMove()) return FALSE;
8670 retVal = LoadGame(f, gameNumber, title, useList);
8672 /* Make move registered during previous look at this game, if any */
8673 MakeRegisteredMove();
8675 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8676 commentList[currentMove]
8677 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8678 DisplayComment(currentMove - 1, commentList[currentMove]);
8684 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8689 int gameNumber = lastLoadGameNumber + offset;
8690 if (lastLoadGameFP == NULL) {
8691 DisplayError(_("No game has been loaded yet"), 0);
8694 if (gameNumber <= 0) {
8695 DisplayError(_("Can't back up any further"), 0);
8698 if (cmailMsgLoaded) {
8699 return CmailLoadGame(lastLoadGameFP, gameNumber,
8700 lastLoadGameTitle, lastLoadGameUseList);
8702 return LoadGame(lastLoadGameFP, gameNumber,
8703 lastLoadGameTitle, lastLoadGameUseList);
8709 /* Load the nth game from open file f */
8711 LoadGame(f, gameNumber, title, useList)
8719 int gn = gameNumber;
8720 ListGame *lg = NULL;
8723 GameMode oldGameMode;
8724 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8726 if (appData.debugMode)
8727 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8729 if (gameMode == Training )
8730 SetTrainingModeOff();
8732 oldGameMode = gameMode;
8733 if (gameMode != BeginningOfGame) {
8738 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8739 fclose(lastLoadGameFP);
8743 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8746 fseek(f, lg->offset, 0);
8747 GameListHighlight(gameNumber);
8751 DisplayError(_("Game number out of range"), 0);
8756 if (fseek(f, 0, 0) == -1) {
8757 if (f == lastLoadGameFP ?
8758 gameNumber == lastLoadGameNumber + 1 :
8762 DisplayError(_("Can't seek on game file"), 0);
8768 lastLoadGameNumber = gameNumber;
8769 strcpy(lastLoadGameTitle, title);
8770 lastLoadGameUseList = useList;
8774 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8775 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8776 lg->gameInfo.black);
8778 } else if (*title != NULLCHAR) {
8779 if (gameNumber > 1) {
8780 sprintf(buf, "%s %d", title, gameNumber);
8783 DisplayTitle(title);
8787 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8788 gameMode = PlayFromGameFile;
8792 currentMove = forwardMostMove = backwardMostMove = 0;
8793 CopyBoard(boards[0], initialPosition);
8797 * Skip the first gn-1 games in the file.
8798 * Also skip over anything that precedes an identifiable
8799 * start of game marker, to avoid being confused by
8800 * garbage at the start of the file. Currently
8801 * recognized start of game markers are the move number "1",
8802 * the pattern "gnuchess .* game", the pattern
8803 * "^[#;%] [^ ]* game file", and a PGN tag block.
8804 * A game that starts with one of the latter two patterns
8805 * will also have a move number 1, possibly
8806 * following a position diagram.
8807 * 5-4-02: Let's try being more lenient and allowing a game to
8808 * start with an unnumbered move. Does that break anything?
8810 cm = lastLoadGameStart = (ChessMove) 0;
8812 yyboardindex = forwardMostMove;
8813 cm = (ChessMove) yylex();
8816 if (cmailMsgLoaded) {
8817 nCmailGames = CMAIL_MAX_GAMES - gn;
8820 DisplayError(_("Game not found in file"), 0);
8827 lastLoadGameStart = cm;
8831 switch (lastLoadGameStart) {
8838 gn--; /* count this game */
8839 lastLoadGameStart = cm;
8848 switch (lastLoadGameStart) {
8853 gn--; /* count this game */
8854 lastLoadGameStart = cm;
8857 lastLoadGameStart = cm; /* game counted already */
8865 yyboardindex = forwardMostMove;
8866 cm = (ChessMove) yylex();
8867 } while (cm == PGNTag || cm == Comment);
8874 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8875 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8876 != CMAIL_OLD_RESULT) {
8878 cmailResult[ CMAIL_MAX_GAMES
8879 - gn - 1] = CMAIL_OLD_RESULT;
8885 /* Only a NormalMove can be at the start of a game
8886 * without a position diagram. */
8887 if (lastLoadGameStart == (ChessMove) 0) {
8889 lastLoadGameStart = MoveNumberOne;
8898 if (appData.debugMode)
8899 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8901 if (cm == XBoardGame) {
8902 /* Skip any header junk before position diagram and/or move 1 */
8904 yyboardindex = forwardMostMove;
8905 cm = (ChessMove) yylex();
8907 if (cm == (ChessMove) 0 ||
8908 cm == GNUChessGame || cm == XBoardGame) {
8909 /* Empty game; pretend end-of-file and handle later */
8914 if (cm == MoveNumberOne || cm == PositionDiagram ||
8915 cm == PGNTag || cm == Comment)
8918 } else if (cm == GNUChessGame) {
8919 if (gameInfo.event != NULL) {
8920 free(gameInfo.event);
8922 gameInfo.event = StrSave(yy_text);
8925 startedFromSetupPosition = FALSE;
8926 while (cm == PGNTag) {
8927 if (appData.debugMode)
8928 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8929 err = ParsePGNTag(yy_text, &gameInfo);
8930 if (!err) numPGNTags++;
8932 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8933 if(gameInfo.variant != oldVariant) {
8934 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8936 oldVariant = gameInfo.variant;
8937 if (appData.debugMode)
8938 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8942 if (gameInfo.fen != NULL) {
8943 Board initial_position;
8944 startedFromSetupPosition = TRUE;
8945 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8947 DisplayError(_("Bad FEN position in file"), 0);
8950 CopyBoard(boards[0], initial_position);
8951 if (blackPlaysFirst) {
8952 currentMove = forwardMostMove = backwardMostMove = 1;
8953 CopyBoard(boards[1], initial_position);
8954 strcpy(moveList[0], "");
8955 strcpy(parseList[0], "");
8956 timeRemaining[0][1] = whiteTimeRemaining;
8957 timeRemaining[1][1] = blackTimeRemaining;
8958 if (commentList[0] != NULL) {
8959 commentList[1] = commentList[0];
8960 commentList[0] = NULL;
8963 currentMove = forwardMostMove = backwardMostMove = 0;
8965 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8967 initialRulePlies = FENrulePlies;
8968 epStatus[forwardMostMove] = FENepStatus;
8969 for( i=0; i< nrCastlingRights; i++ )
8970 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8972 yyboardindex = forwardMostMove;
8974 gameInfo.fen = NULL;
8977 yyboardindex = forwardMostMove;
8978 cm = (ChessMove) yylex();
8980 /* Handle comments interspersed among the tags */
8981 while (cm == Comment) {
8983 if (appData.debugMode)
8984 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8986 if (*p == '{' || *p == '[' || *p == '(') {
8987 p[strlen(p) - 1] = NULLCHAR;
8990 while (*p == '\n') p++;
8991 AppendComment(currentMove, p);
8992 yyboardindex = forwardMostMove;
8993 cm = (ChessMove) yylex();
8997 /* don't rely on existence of Event tag since if game was
8998 * pasted from clipboard the Event tag may not exist
9000 if (numPGNTags > 0){
9002 if (gameInfo.variant == VariantNormal) {
9003 gameInfo.variant = StringToVariant(gameInfo.event);
9006 if( appData.autoDisplayTags ) {
9007 tags = PGNTags(&gameInfo);
9008 TagsPopUp(tags, CmailMsg());
9013 /* Make something up, but don't display it now */
9018 if (cm == PositionDiagram) {
9021 Board initial_position;
9023 if (appData.debugMode)
9024 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9026 if (!startedFromSetupPosition) {
9028 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9029 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9039 initial_position[i][j++] = CharToPiece(*p);
9042 while (*p == ' ' || *p == '\t' ||
9043 *p == '\n' || *p == '\r') p++;
9045 if (strncmp(p, "black", strlen("black"))==0)
9046 blackPlaysFirst = TRUE;
9048 blackPlaysFirst = FALSE;
9049 startedFromSetupPosition = TRUE;
9051 CopyBoard(boards[0], initial_position);
9052 if (blackPlaysFirst) {
9053 currentMove = forwardMostMove = backwardMostMove = 1;
9054 CopyBoard(boards[1], initial_position);
9055 strcpy(moveList[0], "");
9056 strcpy(parseList[0], "");
9057 timeRemaining[0][1] = whiteTimeRemaining;
9058 timeRemaining[1][1] = blackTimeRemaining;
9059 if (commentList[0] != NULL) {
9060 commentList[1] = commentList[0];
9061 commentList[0] = NULL;
9064 currentMove = forwardMostMove = backwardMostMove = 0;
9067 yyboardindex = forwardMostMove;
9068 cm = (ChessMove) yylex();
9071 if (first.pr == NoProc) {
9072 StartChessProgram(&first);
9074 InitChessProgram(&first, FALSE);
9075 SendToProgram("force\n", &first);
9076 if (startedFromSetupPosition) {
9077 SendBoard(&first, forwardMostMove);
9078 if (appData.debugMode) {
9079 fprintf(debugFP, "Load Game\n");
9081 DisplayBothClocks();
9084 /* [HGM] server: flag to write setup moves in broadcast file as one */
9085 loadFlag = appData.suppressLoadMoves;
9087 while (cm == Comment) {
9089 if (appData.debugMode)
9090 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9092 if (*p == '{' || *p == '[' || *p == '(') {
9093 p[strlen(p) - 1] = NULLCHAR;
9096 while (*p == '\n') p++;
9097 AppendComment(currentMove, p);
9098 yyboardindex = forwardMostMove;
9099 cm = (ChessMove) yylex();
9102 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9103 cm == WhiteWins || cm == BlackWins ||
9104 cm == GameIsDrawn || cm == GameUnfinished) {
9105 DisplayMessage("", _("No moves in game"));
9106 if (cmailMsgLoaded) {
9107 if (appData.debugMode)
9108 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9112 DrawPosition(FALSE, boards[currentMove]);
9113 DisplayBothClocks();
9114 gameMode = EditGame;
9121 // [HGM] PV info: routine tests if comment empty
9122 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9123 DisplayComment(currentMove - 1, commentList[currentMove]);
9125 if (!matchMode && appData.timeDelay != 0)
9126 DrawPosition(FALSE, boards[currentMove]);
9128 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9129 programStats.ok_to_send = 1;
9132 /* if the first token after the PGN tags is a move
9133 * and not move number 1, retrieve it from the parser
9135 if (cm != MoveNumberOne)
9136 LoadGameOneMove(cm);
9138 /* load the remaining moves from the file */
9139 while (LoadGameOneMove((ChessMove)0)) {
9140 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9141 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9144 /* rewind to the start of the game */
9145 currentMove = backwardMostMove;
9147 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9149 if (oldGameMode == AnalyzeFile ||
9150 oldGameMode == AnalyzeMode) {
9154 if (matchMode || appData.timeDelay == 0) {
9156 gameMode = EditGame;
9158 } else if (appData.timeDelay > 0) {
9162 if (appData.debugMode)
9163 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9165 loadFlag = 0; /* [HGM] true game starts */
9169 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9171 ReloadPosition(offset)
9174 int positionNumber = lastLoadPositionNumber + offset;
9175 if (lastLoadPositionFP == NULL) {
9176 DisplayError(_("No position has been loaded yet"), 0);
9179 if (positionNumber <= 0) {
9180 DisplayError(_("Can't back up any further"), 0);
9183 return LoadPosition(lastLoadPositionFP, positionNumber,
9184 lastLoadPositionTitle);
9187 /* Load the nth position from the given file */
9189 LoadPositionFromFile(filename, n, title)
9197 if (strcmp(filename, "-") == 0) {
9198 return LoadPosition(stdin, n, "stdin");
9200 f = fopen(filename, "rb");
9202 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9203 DisplayError(buf, errno);
9206 return LoadPosition(f, n, title);
9211 /* Load the nth position from the given open file, and close it */
9213 LoadPosition(f, positionNumber, title)
9218 char *p, line[MSG_SIZ];
9219 Board initial_position;
9220 int i, j, fenMode, pn;
9222 if (gameMode == Training )
9223 SetTrainingModeOff();
9225 if (gameMode != BeginningOfGame) {
9228 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9229 fclose(lastLoadPositionFP);
9231 if (positionNumber == 0) positionNumber = 1;
9232 lastLoadPositionFP = f;
9233 lastLoadPositionNumber = positionNumber;
9234 strcpy(lastLoadPositionTitle, title);
9235 if (first.pr == NoProc) {
9236 StartChessProgram(&first);
9237 InitChessProgram(&first, FALSE);
9239 pn = positionNumber;
9240 if (positionNumber < 0) {
9241 /* Negative position number means to seek to that byte offset */
9242 if (fseek(f, -positionNumber, 0) == -1) {
9243 DisplayError(_("Can't seek on position file"), 0);
9248 if (fseek(f, 0, 0) == -1) {
9249 if (f == lastLoadPositionFP ?
9250 positionNumber == lastLoadPositionNumber + 1 :
9251 positionNumber == 1) {
9254 DisplayError(_("Can't seek on position file"), 0);
9259 /* See if this file is FEN or old-style xboard */
9260 if (fgets(line, MSG_SIZ, f) == NULL) {
9261 DisplayError(_("Position not found in file"), 0);
9270 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9271 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9272 case '1': case '2': case '3': case '4': case '5': case '6':
9273 case '7': case '8': case '9':
9274 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9275 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9276 case 'C': case 'W': case 'c': case 'w':
9281 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9282 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9286 if (fenMode || line[0] == '#') pn--;
9288 /* skip positions before number pn */
9289 if (fgets(line, MSG_SIZ, f) == NULL) {
9291 DisplayError(_("Position not found in file"), 0);
9294 if (fenMode || line[0] == '#') pn--;
9299 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9300 DisplayError(_("Bad FEN position in file"), 0);
9304 (void) fgets(line, MSG_SIZ, f);
9305 (void) fgets(line, MSG_SIZ, f);
9307 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9308 (void) fgets(line, MSG_SIZ, f);
9309 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9312 initial_position[i][j++] = CharToPiece(*p);
9316 blackPlaysFirst = FALSE;
9318 (void) fgets(line, MSG_SIZ, f);
9319 if (strncmp(line, "black", strlen("black"))==0)
9320 blackPlaysFirst = TRUE;
9323 startedFromSetupPosition = TRUE;
9325 SendToProgram("force\n", &first);
9326 CopyBoard(boards[0], initial_position);
9327 if (blackPlaysFirst) {
9328 currentMove = forwardMostMove = backwardMostMove = 1;
9329 strcpy(moveList[0], "");
9330 strcpy(parseList[0], "");
9331 CopyBoard(boards[1], initial_position);
9332 DisplayMessage("", _("Black to play"));
9334 currentMove = forwardMostMove = backwardMostMove = 0;
9335 DisplayMessage("", _("White to play"));
9337 /* [HGM] copy FEN attributes as well */
9339 initialRulePlies = FENrulePlies;
9340 epStatus[forwardMostMove] = FENepStatus;
9341 for( i=0; i< nrCastlingRights; i++ )
9342 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9344 SendBoard(&first, forwardMostMove);
9345 if (appData.debugMode) {
9347 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9348 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9349 fprintf(debugFP, "Load Position\n");
9352 if (positionNumber > 1) {
9353 sprintf(line, "%s %d", title, positionNumber);
9356 DisplayTitle(title);
9358 gameMode = EditGame;
9361 timeRemaining[0][1] = whiteTimeRemaining;
9362 timeRemaining[1][1] = blackTimeRemaining;
9363 DrawPosition(FALSE, boards[currentMove]);
9370 CopyPlayerNameIntoFileName(dest, src)
9373 while (*src != NULLCHAR && *src != ',') {
9378 *(*dest)++ = *src++;
9383 char *DefaultFileName(ext)
9386 static char def[MSG_SIZ];
9389 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9391 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9393 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9402 /* Save the current game to the given file */
9404 SaveGameToFile(filename, append)
9411 if (strcmp(filename, "-") == 0) {
9412 return SaveGame(stdout, 0, NULL);
9414 f = fopen(filename, append ? "a" : "w");
9416 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9417 DisplayError(buf, errno);
9420 return SaveGame(f, 0, NULL);
9429 static char buf[MSG_SIZ];
9432 p = strchr(str, ' ');
9433 if (p == NULL) return str;
9434 strncpy(buf, str, p - str);
9435 buf[p - str] = NULLCHAR;
9439 #define PGN_MAX_LINE 75
9441 #define PGN_SIDE_WHITE 0
9442 #define PGN_SIDE_BLACK 1
9445 static int FindFirstMoveOutOfBook( int side )
9449 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9450 int index = backwardMostMove;
9451 int has_book_hit = 0;
9453 if( (index % 2) != side ) {
9457 while( index < forwardMostMove ) {
9458 /* Check to see if engine is in book */
9459 int depth = pvInfoList[index].depth;
9460 int score = pvInfoList[index].score;
9466 else if( score == 0 && depth == 63 ) {
9467 in_book = 1; /* Zappa */
9469 else if( score == 2 && depth == 99 ) {
9470 in_book = 1; /* Abrok */
9473 has_book_hit += in_book;
9489 void GetOutOfBookInfo( char * buf )
9493 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9495 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9496 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9500 if( oob[0] >= 0 || oob[1] >= 0 ) {
9501 for( i=0; i<2; i++ ) {
9505 if( i > 0 && oob[0] >= 0 ) {
9509 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9510 sprintf( buf+strlen(buf), "%s%.2f",
9511 pvInfoList[idx].score >= 0 ? "+" : "",
9512 pvInfoList[idx].score / 100.0 );
9518 /* Save game in PGN style and close the file */
9523 int i, offset, linelen, newblock;
9527 int movelen, numlen, blank;
9528 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9530 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9532 tm = time((time_t *) NULL);
9534 PrintPGNTags(f, &gameInfo);
9536 if (backwardMostMove > 0 || startedFromSetupPosition) {
9537 char *fen = PositionToFEN(backwardMostMove, NULL);
9538 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9539 fprintf(f, "\n{--------------\n");
9540 PrintPosition(f, backwardMostMove);
9541 fprintf(f, "--------------}\n");
9545 /* [AS] Out of book annotation */
9546 if( appData.saveOutOfBookInfo ) {
9549 GetOutOfBookInfo( buf );
9551 if( buf[0] != '\0' ) {
9552 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9559 i = backwardMostMove;
9563 while (i < forwardMostMove) {
9564 /* Print comments preceding this move */
9565 if (commentList[i] != NULL) {
9566 if (linelen > 0) fprintf(f, "\n");
9567 fprintf(f, "{\n%s}\n", commentList[i]);
9572 /* Format move number */
9574 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9577 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9579 numtext[0] = NULLCHAR;
9582 numlen = strlen(numtext);
9585 /* Print move number */
9586 blank = linelen > 0 && numlen > 0;
9587 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9596 fprintf(f, numtext);
9600 strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9601 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9602 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9603 int p = movelen - 1;
9604 if(move_buffer[p] == ' ') p--;
9605 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9606 while(p && move_buffer[--p] != '(');
9607 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9612 blank = linelen > 0 && movelen > 0;
9613 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9622 fprintf(f, move_buffer);
9625 /* [AS] Add PV info if present */
9626 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9627 /* [HGM] add time */
9628 char buf[MSG_SIZ]; int seconds = 0;
9631 if(i >= backwardMostMove) {
9633 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9634 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9636 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9637 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9639 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9641 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9644 if( seconds <= 0) buf[0] = 0; else
9645 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9646 seconds = (seconds + 4)/10; // round to full seconds
9647 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9648 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9651 sprintf( move_buffer, "{%s%.2f/%d%s}",
9652 pvInfoList[i].score >= 0 ? "+" : "",
9653 pvInfoList[i].score / 100.0,
9654 pvInfoList[i].depth,
9657 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9659 /* Print score/depth */
9660 blank = linelen > 0 && movelen > 0;
9661 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9670 fprintf(f, move_buffer);
9677 /* Start a new line */
9678 if (linelen > 0) fprintf(f, "\n");
9680 /* Print comments after last move */
9681 if (commentList[i] != NULL) {
9682 fprintf(f, "{\n%s}\n", commentList[i]);
9686 if (gameInfo.resultDetails != NULL &&
9687 gameInfo.resultDetails[0] != NULLCHAR) {
9688 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9689 PGNResult(gameInfo.result));
9691 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9698 /* Save game in old style and close the file */
9706 tm = time((time_t *) NULL);
9708 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9711 if (backwardMostMove > 0 || startedFromSetupPosition) {
9712 fprintf(f, "\n[--------------\n");
9713 PrintPosition(f, backwardMostMove);
9714 fprintf(f, "--------------]\n");
9719 i = backwardMostMove;
9720 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9722 while (i < forwardMostMove) {
9723 if (commentList[i] != NULL) {
9724 fprintf(f, "[%s]\n", commentList[i]);
9728 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9731 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9733 if (commentList[i] != NULL) {
9737 if (i >= forwardMostMove) {
9741 fprintf(f, "%s\n", parseList[i]);
9746 if (commentList[i] != NULL) {
9747 fprintf(f, "[%s]\n", commentList[i]);
9750 /* This isn't really the old style, but it's close enough */
9751 if (gameInfo.resultDetails != NULL &&
9752 gameInfo.resultDetails[0] != NULLCHAR) {
9753 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9754 gameInfo.resultDetails);
9756 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9763 /* Save the current game to open file f and close the file */
9765 SaveGame(f, dummy, dummy2)
9770 if (gameMode == EditPosition) EditPositionDone();
9771 if (appData.oldSaveStyle)
9772 return SaveGameOldStyle(f);
9774 return SaveGamePGN(f);
9777 /* Save the current position to the given file */
9779 SavePositionToFile(filename)
9785 if (strcmp(filename, "-") == 0) {
9786 return SavePosition(stdout, 0, NULL);
9788 f = fopen(filename, "a");
9790 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9791 DisplayError(buf, errno);
9794 SavePosition(f, 0, NULL);
9800 /* Save the current position to the given open file and close the file */
9802 SavePosition(f, dummy, dummy2)
9810 if (appData.oldSaveStyle) {
9811 tm = time((time_t *) NULL);
9813 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9815 fprintf(f, "[--------------\n");
9816 PrintPosition(f, currentMove);
9817 fprintf(f, "--------------]\n");
9819 fen = PositionToFEN(currentMove, NULL);
9820 fprintf(f, "%s\n", fen);
9828 ReloadCmailMsgEvent(unregister)
9832 static char *inFilename = NULL;
9833 static char *outFilename;
9835 struct stat inbuf, outbuf;
9838 /* Any registered moves are unregistered if unregister is set, */
9839 /* i.e. invoked by the signal handler */
9841 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9842 cmailMoveRegistered[i] = FALSE;
9843 if (cmailCommentList[i] != NULL) {
9844 free(cmailCommentList[i]);
9845 cmailCommentList[i] = NULL;
9848 nCmailMovesRegistered = 0;
9851 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9852 cmailResult[i] = CMAIL_NOT_RESULT;
9856 if (inFilename == NULL) {
9857 /* Because the filenames are static they only get malloced once */
9858 /* and they never get freed */
9859 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9860 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9862 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9863 sprintf(outFilename, "%s.out", appData.cmailGameName);
9866 status = stat(outFilename, &outbuf);
9868 cmailMailedMove = FALSE;
9870 status = stat(inFilename, &inbuf);
9871 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9874 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9875 counts the games, notes how each one terminated, etc.
9877 It would be nice to remove this kludge and instead gather all
9878 the information while building the game list. (And to keep it
9879 in the game list nodes instead of having a bunch of fixed-size
9880 parallel arrays.) Note this will require getting each game's
9881 termination from the PGN tags, as the game list builder does
9882 not process the game moves. --mann
9884 cmailMsgLoaded = TRUE;
9885 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9887 /* Load first game in the file or popup game menu */
9888 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9898 char string[MSG_SIZ];
9900 if ( cmailMailedMove
9901 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9902 return TRUE; /* Allow free viewing */
9905 /* Unregister move to ensure that we don't leave RegisterMove */
9906 /* with the move registered when the conditions for registering no */
9908 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9909 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9910 nCmailMovesRegistered --;
9912 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9914 free(cmailCommentList[lastLoadGameNumber - 1]);
9915 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9919 if (cmailOldMove == -1) {
9920 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9924 if (currentMove > cmailOldMove + 1) {
9925 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9929 if (currentMove < cmailOldMove) {
9930 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9934 if (forwardMostMove > currentMove) {
9935 /* Silently truncate extra moves */
9939 if ( (currentMove == cmailOldMove + 1)
9940 || ( (currentMove == cmailOldMove)
9941 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9942 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9943 if (gameInfo.result != GameUnfinished) {
9944 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9947 if (commentList[currentMove] != NULL) {
9948 cmailCommentList[lastLoadGameNumber - 1]
9949 = StrSave(commentList[currentMove]);
9951 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9953 if (appData.debugMode)
9954 fprintf(debugFP, "Saving %s for game %d\n",
9955 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9958 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9960 f = fopen(string, "w");
9961 if (appData.oldSaveStyle) {
9962 SaveGameOldStyle(f); /* also closes the file */
9964 sprintf(string, "%s.pos.out", appData.cmailGameName);
9965 f = fopen(string, "w");
9966 SavePosition(f, 0, NULL); /* also closes the file */
9968 fprintf(f, "{--------------\n");
9969 PrintPosition(f, currentMove);
9970 fprintf(f, "--------------}\n\n");
9972 SaveGame(f, 0, NULL); /* also closes the file*/
9975 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9976 nCmailMovesRegistered ++;
9977 } else if (nCmailGames == 1) {
9978 DisplayError(_("You have not made a move yet"), 0);
9989 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9990 FILE *commandOutput;
9991 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9992 int nBytes = 0; /* Suppress warnings on uninitialized variables */
9998 if (! cmailMsgLoaded) {
9999 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10003 if (nCmailGames == nCmailResults) {
10004 DisplayError(_("No unfinished games"), 0);
10008 #if CMAIL_PROHIBIT_REMAIL
10009 if (cmailMailedMove) {
10010 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);
10011 DisplayError(msg, 0);
10016 if (! (cmailMailedMove || RegisterMove())) return;
10018 if ( cmailMailedMove
10019 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10020 sprintf(string, partCommandString,
10021 appData.debugMode ? " -v" : "", appData.cmailGameName);
10022 commandOutput = popen(string, "r");
10024 if (commandOutput == NULL) {
10025 DisplayError(_("Failed to invoke cmail"), 0);
10027 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10028 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10030 if (nBuffers > 1) {
10031 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10032 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10033 nBytes = MSG_SIZ - 1;
10035 (void) memcpy(msg, buffer, nBytes);
10037 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10039 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10040 cmailMailedMove = TRUE; /* Prevent >1 moves */
10043 for (i = 0; i < nCmailGames; i ++) {
10044 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10049 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10051 sprintf(buffer, "%s/%s.%s.archive",
10053 appData.cmailGameName,
10055 LoadGameFromFile(buffer, 1, buffer, FALSE);
10056 cmailMsgLoaded = FALSE;
10060 DisplayInformation(msg);
10061 pclose(commandOutput);
10064 if ((*cmailMsg) != '\0') {
10065 DisplayInformation(cmailMsg);
10070 #endif /* !WIN32 */
10079 int prependComma = 0;
10081 char string[MSG_SIZ]; /* Space for game-list */
10084 if (!cmailMsgLoaded) return "";
10086 if (cmailMailedMove) {
10087 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10089 /* Create a list of games left */
10090 sprintf(string, "[");
10091 for (i = 0; i < nCmailGames; i ++) {
10092 if (! ( cmailMoveRegistered[i]
10093 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10094 if (prependComma) {
10095 sprintf(number, ",%d", i + 1);
10097 sprintf(number, "%d", i + 1);
10101 strcat(string, number);
10104 strcat(string, "]");
10106 if (nCmailMovesRegistered + nCmailResults == 0) {
10107 switch (nCmailGames) {
10110 _("Still need to make move for game\n"));
10115 _("Still need to make moves for both games\n"));
10120 _("Still need to make moves for all %d games\n"),
10125 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10128 _("Still need to make a move for game %s\n"),
10133 if (nCmailResults == nCmailGames) {
10134 sprintf(cmailMsg, _("No unfinished games\n"));
10136 sprintf(cmailMsg, _("Ready to send mail\n"));
10142 _("Still need to make moves for games %s\n"),
10154 if (gameMode == Training)
10155 SetTrainingModeOff();
10158 cmailMsgLoaded = FALSE;
10159 if (appData.icsActive) {
10160 SendToICS(ics_prefix);
10161 SendToICS("refresh\n");
10171 /* Give up on clean exit */
10175 /* Keep trying for clean exit */
10179 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10181 if (telnetISR != NULL) {
10182 RemoveInputSource(telnetISR);
10184 if (icsPR != NoProc) {
10185 DestroyChildProcess(icsPR, TRUE);
10188 /* Save game if resource set and not already saved by GameEnds() */
10189 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10190 && forwardMostMove > 0) {
10191 if (*appData.saveGameFile != NULLCHAR) {
10192 SaveGameToFile(appData.saveGameFile, TRUE);
10193 } else if (appData.autoSaveGames) {
10196 if (*appData.savePositionFile != NULLCHAR) {
10197 SavePositionToFile(appData.savePositionFile);
10200 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10202 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10203 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10205 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10206 /* make sure this other one finishes before killing it! */
10207 if(endingGame) { int count = 0;
10208 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10209 while(endingGame && count++ < 10) DoSleep(1);
10210 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10213 /* Kill off chess programs */
10214 if (first.pr != NoProc) {
10217 DoSleep( appData.delayBeforeQuit );
10218 SendToProgram("quit\n", &first);
10219 DoSleep( appData.delayAfterQuit );
10220 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10222 if (second.pr != NoProc) {
10223 DoSleep( appData.delayBeforeQuit );
10224 SendToProgram("quit\n", &second);
10225 DoSleep( appData.delayAfterQuit );
10226 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10228 if (first.isr != NULL) {
10229 RemoveInputSource(first.isr);
10231 if (second.isr != NULL) {
10232 RemoveInputSource(second.isr);
10235 ShutDownFrontEnd();
10242 if (appData.debugMode)
10243 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10247 if (gameMode == MachinePlaysWhite ||
10248 gameMode == MachinePlaysBlack) {
10251 DisplayBothClocks();
10253 if (gameMode == PlayFromGameFile) {
10254 if (appData.timeDelay >= 0)
10255 AutoPlayGameLoop();
10256 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10257 Reset(FALSE, TRUE);
10258 SendToICS(ics_prefix);
10259 SendToICS("refresh\n");
10260 } else if (currentMove < forwardMostMove) {
10261 ForwardInner(forwardMostMove);
10263 pauseExamInvalid = FALSE;
10265 switch (gameMode) {
10269 pauseExamForwardMostMove = forwardMostMove;
10270 pauseExamInvalid = FALSE;
10273 case IcsPlayingWhite:
10274 case IcsPlayingBlack:
10278 case PlayFromGameFile:
10279 (void) StopLoadGameTimer();
10283 case BeginningOfGame:
10284 if (appData.icsActive) return;
10285 /* else fall through */
10286 case MachinePlaysWhite:
10287 case MachinePlaysBlack:
10288 case TwoMachinesPlay:
10289 if (forwardMostMove == 0)
10290 return; /* don't pause if no one has moved */
10291 if ((gameMode == MachinePlaysWhite &&
10292 !WhiteOnMove(forwardMostMove)) ||
10293 (gameMode == MachinePlaysBlack &&
10294 WhiteOnMove(forwardMostMove))) {
10307 char title[MSG_SIZ];
10309 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10310 strcpy(title, _("Edit comment"));
10312 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10313 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10314 parseList[currentMove - 1]);
10317 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10324 char *tags = PGNTags(&gameInfo);
10325 EditTagsPopUp(tags);
10332 if (appData.noChessProgram || gameMode == AnalyzeMode)
10335 if (gameMode != AnalyzeFile) {
10336 if (!appData.icsEngineAnalyze) {
10338 if (gameMode != EditGame) return;
10340 ResurrectChessProgram();
10341 SendToProgram("analyze\n", &first);
10342 first.analyzing = TRUE;
10343 /*first.maybeThinking = TRUE;*/
10344 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10345 AnalysisPopUp(_("Analysis"),
10346 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10348 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10353 StartAnalysisClock();
10354 GetTimeMark(&lastNodeCountTime);
10361 if (appData.noChessProgram || gameMode == AnalyzeFile)
10364 if (gameMode != AnalyzeMode) {
10366 if (gameMode != EditGame) return;
10367 ResurrectChessProgram();
10368 SendToProgram("analyze\n", &first);
10369 first.analyzing = TRUE;
10370 /*first.maybeThinking = TRUE;*/
10371 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10372 AnalysisPopUp(_("Analysis"),
10373 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10375 gameMode = AnalyzeFile;
10380 StartAnalysisClock();
10381 GetTimeMark(&lastNodeCountTime);
10386 MachineWhiteEvent()
10389 char *bookHit = NULL;
10391 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10395 if (gameMode == PlayFromGameFile ||
10396 gameMode == TwoMachinesPlay ||
10397 gameMode == Training ||
10398 gameMode == AnalyzeMode ||
10399 gameMode == EndOfGame)
10402 if (gameMode == EditPosition)
10403 EditPositionDone();
10405 if (!WhiteOnMove(currentMove)) {
10406 DisplayError(_("It is not White's turn"), 0);
10410 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10413 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10414 gameMode == AnalyzeFile)
10417 ResurrectChessProgram(); /* in case it isn't running */
10418 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10419 gameMode = MachinePlaysWhite;
10422 gameMode = MachinePlaysWhite;
10426 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10428 if (first.sendName) {
10429 sprintf(buf, "name %s\n", gameInfo.black);
10430 SendToProgram(buf, &first);
10432 if (first.sendTime) {
10433 if (first.useColors) {
10434 SendToProgram("black\n", &first); /*gnu kludge*/
10436 SendTimeRemaining(&first, TRUE);
10438 if (first.useColors) {
10439 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10441 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10442 SetMachineThinkingEnables();
10443 first.maybeThinking = TRUE;
10446 if (appData.autoFlipView && !flipView) {
10447 flipView = !flipView;
10448 DrawPosition(FALSE, NULL);
10449 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10452 if(bookHit) { // [HGM] book: simulate book reply
10453 static char bookMove[MSG_SIZ]; // a bit generous?
10455 programStats.nodes = programStats.depth = programStats.time =
10456 programStats.score = programStats.got_only_move = 0;
10457 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10459 strcpy(bookMove, "move ");
10460 strcat(bookMove, bookHit);
10461 HandleMachineMove(bookMove, &first);
10466 MachineBlackEvent()
10469 char *bookHit = NULL;
10471 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10475 if (gameMode == PlayFromGameFile ||
10476 gameMode == TwoMachinesPlay ||
10477 gameMode == Training ||
10478 gameMode == AnalyzeMode ||
10479 gameMode == EndOfGame)
10482 if (gameMode == EditPosition)
10483 EditPositionDone();
10485 if (WhiteOnMove(currentMove)) {
10486 DisplayError(_("It is not Black's turn"), 0);
10490 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10493 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10494 gameMode == AnalyzeFile)
10497 ResurrectChessProgram(); /* in case it isn't running */
10498 gameMode = MachinePlaysBlack;
10502 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10504 if (first.sendName) {
10505 sprintf(buf, "name %s\n", gameInfo.white);
10506 SendToProgram(buf, &first);
10508 if (first.sendTime) {
10509 if (first.useColors) {
10510 SendToProgram("white\n", &first); /*gnu kludge*/
10512 SendTimeRemaining(&first, FALSE);
10514 if (first.useColors) {
10515 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10517 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10518 SetMachineThinkingEnables();
10519 first.maybeThinking = TRUE;
10522 if (appData.autoFlipView && flipView) {
10523 flipView = !flipView;
10524 DrawPosition(FALSE, NULL);
10525 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10527 if(bookHit) { // [HGM] book: simulate book reply
10528 static char bookMove[MSG_SIZ]; // a bit generous?
10530 programStats.nodes = programStats.depth = programStats.time =
10531 programStats.score = programStats.got_only_move = 0;
10532 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10534 strcpy(bookMove, "move ");
10535 strcat(bookMove, bookHit);
10536 HandleMachineMove(bookMove, &first);
10542 DisplayTwoMachinesTitle()
10545 if (appData.matchGames > 0) {
10546 if (first.twoMachinesColor[0] == 'w') {
10547 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10548 gameInfo.white, gameInfo.black,
10549 first.matchWins, second.matchWins,
10550 matchGame - 1 - (first.matchWins + second.matchWins));
10552 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10553 gameInfo.white, gameInfo.black,
10554 second.matchWins, first.matchWins,
10555 matchGame - 1 - (first.matchWins + second.matchWins));
10558 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10564 TwoMachinesEvent P((void))
10568 ChessProgramState *onmove;
10569 char *bookHit = NULL;
10571 if (appData.noChessProgram) return;
10573 switch (gameMode) {
10574 case TwoMachinesPlay:
10576 case MachinePlaysWhite:
10577 case MachinePlaysBlack:
10578 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10579 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10583 case BeginningOfGame:
10584 case PlayFromGameFile:
10587 if (gameMode != EditGame) return;
10590 EditPositionDone();
10601 forwardMostMove = currentMove;
10602 ResurrectChessProgram(); /* in case first program isn't running */
10604 if (second.pr == NULL) {
10605 StartChessProgram(&second);
10606 if (second.protocolVersion == 1) {
10607 TwoMachinesEventIfReady();
10609 /* kludge: allow timeout for initial "feature" command */
10611 DisplayMessage("", _("Starting second chess program"));
10612 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10616 DisplayMessage("", "");
10617 InitChessProgram(&second, FALSE);
10618 SendToProgram("force\n", &second);
10619 if (startedFromSetupPosition) {
10620 SendBoard(&second, backwardMostMove);
10621 if (appData.debugMode) {
10622 fprintf(debugFP, "Two Machines\n");
10625 for (i = backwardMostMove; i < forwardMostMove; i++) {
10626 SendMoveToProgram(i, &second);
10629 gameMode = TwoMachinesPlay;
10633 DisplayTwoMachinesTitle();
10635 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10641 SendToProgram(first.computerString, &first);
10642 if (first.sendName) {
10643 sprintf(buf, "name %s\n", second.tidy);
10644 SendToProgram(buf, &first);
10646 SendToProgram(second.computerString, &second);
10647 if (second.sendName) {
10648 sprintf(buf, "name %s\n", first.tidy);
10649 SendToProgram(buf, &second);
10653 if (!first.sendTime || !second.sendTime) {
10654 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10655 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10657 if (onmove->sendTime) {
10658 if (onmove->useColors) {
10659 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10661 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10663 if (onmove->useColors) {
10664 SendToProgram(onmove->twoMachinesColor, onmove);
10666 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10667 // SendToProgram("go\n", onmove);
10668 onmove->maybeThinking = TRUE;
10669 SetMachineThinkingEnables();
10673 if(bookHit) { // [HGM] book: simulate book reply
10674 static char bookMove[MSG_SIZ]; // a bit generous?
10676 programStats.nodes = programStats.depth = programStats.time =
10677 programStats.score = programStats.got_only_move = 0;
10678 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10680 strcpy(bookMove, "move ");
10681 strcat(bookMove, bookHit);
10682 HandleMachineMove(bookMove, &first);
10689 if (gameMode == Training) {
10690 SetTrainingModeOff();
10691 gameMode = PlayFromGameFile;
10692 DisplayMessage("", _("Training mode off"));
10694 gameMode = Training;
10695 animateTraining = appData.animate;
10697 /* make sure we are not already at the end of the game */
10698 if (currentMove < forwardMostMove) {
10699 SetTrainingModeOn();
10700 DisplayMessage("", _("Training mode on"));
10702 gameMode = PlayFromGameFile;
10703 DisplayError(_("Already at end of game"), 0);
10712 if (!appData.icsActive) return;
10713 switch (gameMode) {
10714 case IcsPlayingWhite:
10715 case IcsPlayingBlack:
10718 case BeginningOfGame:
10726 EditPositionDone();
10739 gameMode = IcsIdle;
10750 switch (gameMode) {
10752 SetTrainingModeOff();
10754 case MachinePlaysWhite:
10755 case MachinePlaysBlack:
10756 case BeginningOfGame:
10757 SendToProgram("force\n", &first);
10758 SetUserThinkingEnables();
10760 case PlayFromGameFile:
10761 (void) StopLoadGameTimer();
10762 if (gameFileFP != NULL) {
10767 EditPositionDone();
10772 SendToProgram("force\n", &first);
10774 case TwoMachinesPlay:
10775 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10776 ResurrectChessProgram();
10777 SetUserThinkingEnables();
10780 ResurrectChessProgram();
10782 case IcsPlayingBlack:
10783 case IcsPlayingWhite:
10784 DisplayError(_("Warning: You are still playing a game"), 0);
10787 DisplayError(_("Warning: You are still observing a game"), 0);
10790 DisplayError(_("Warning: You are still examining a game"), 0);
10801 first.offeredDraw = second.offeredDraw = 0;
10803 if (gameMode == PlayFromGameFile) {
10804 whiteTimeRemaining = timeRemaining[0][currentMove];
10805 blackTimeRemaining = timeRemaining[1][currentMove];
10809 if (gameMode == MachinePlaysWhite ||
10810 gameMode == MachinePlaysBlack ||
10811 gameMode == TwoMachinesPlay ||
10812 gameMode == EndOfGame) {
10813 i = forwardMostMove;
10814 while (i > currentMove) {
10815 SendToProgram("undo\n", &first);
10818 whiteTimeRemaining = timeRemaining[0][currentMove];
10819 blackTimeRemaining = timeRemaining[1][currentMove];
10820 DisplayBothClocks();
10821 if (whiteFlag || blackFlag) {
10822 whiteFlag = blackFlag = 0;
10827 gameMode = EditGame;
10834 EditPositionEvent()
10836 if (gameMode == EditPosition) {
10842 if (gameMode != EditGame) return;
10844 gameMode = EditPosition;
10847 if (currentMove > 0)
10848 CopyBoard(boards[0], boards[currentMove]);
10850 blackPlaysFirst = !WhiteOnMove(currentMove);
10852 currentMove = forwardMostMove = backwardMostMove = 0;
10853 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10860 /* [DM] icsEngineAnalyze - possible call from other functions */
10861 if (appData.icsEngineAnalyze) {
10862 appData.icsEngineAnalyze = FALSE;
10864 DisplayMessage("",_("Close ICS engine analyze..."));
10866 if (first.analysisSupport && first.analyzing) {
10867 SendToProgram("exit\n", &first);
10868 first.analyzing = FALSE;
10871 thinkOutput[0] = NULLCHAR;
10877 startedFromSetupPosition = TRUE;
10878 InitChessProgram(&first, FALSE);
10879 SendToProgram("force\n", &first);
10880 if (blackPlaysFirst) {
10881 strcpy(moveList[0], "");
10882 strcpy(parseList[0], "");
10883 currentMove = forwardMostMove = backwardMostMove = 1;
10884 CopyBoard(boards[1], boards[0]);
10885 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10887 epStatus[1] = epStatus[0];
10888 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10891 currentMove = forwardMostMove = backwardMostMove = 0;
10893 SendBoard(&first, forwardMostMove);
10894 if (appData.debugMode) {
10895 fprintf(debugFP, "EditPosDone\n");
10898 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10899 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10900 gameMode = EditGame;
10902 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10903 ClearHighlights(); /* [AS] */
10906 /* Pause for `ms' milliseconds */
10907 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10917 } while (SubtractTimeMarks(&m2, &m1) < ms);
10920 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10922 SendMultiLineToICS(buf)
10925 char temp[MSG_SIZ+1], *p;
10932 strncpy(temp, buf, len);
10937 if (*p == '\n' || *p == '\r')
10942 strcat(temp, "\n");
10944 SendToPlayer(temp, strlen(temp));
10948 SetWhiteToPlayEvent()
10950 if (gameMode == EditPosition) {
10951 blackPlaysFirst = FALSE;
10952 DisplayBothClocks(); /* works because currentMove is 0 */
10953 } else if (gameMode == IcsExamining) {
10954 SendToICS(ics_prefix);
10955 SendToICS("tomove white\n");
10960 SetBlackToPlayEvent()
10962 if (gameMode == EditPosition) {
10963 blackPlaysFirst = TRUE;
10964 currentMove = 1; /* kludge */
10965 DisplayBothClocks();
10967 } else if (gameMode == IcsExamining) {
10968 SendToICS(ics_prefix);
10969 SendToICS("tomove black\n");
10974 EditPositionMenuEvent(selection, x, y)
10975 ChessSquare selection;
10979 ChessSquare piece = boards[0][y][x];
10981 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10983 switch (selection) {
10985 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10986 SendToICS(ics_prefix);
10987 SendToICS("bsetup clear\n");
10988 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10989 SendToICS(ics_prefix);
10990 SendToICS("clearboard\n");
10992 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10993 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10994 for (y = 0; y < BOARD_HEIGHT; y++) {
10995 if (gameMode == IcsExamining) {
10996 if (boards[currentMove][y][x] != EmptySquare) {
10997 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11002 boards[0][y][x] = p;
11007 if (gameMode == EditPosition) {
11008 DrawPosition(FALSE, boards[0]);
11013 SetWhiteToPlayEvent();
11017 SetBlackToPlayEvent();
11021 if (gameMode == IcsExamining) {
11022 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11025 boards[0][y][x] = EmptySquare;
11026 DrawPosition(FALSE, boards[0]);
11031 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11032 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11033 selection = (ChessSquare) (PROMOTED piece);
11034 } else if(piece == EmptySquare) selection = WhiteSilver;
11035 else selection = (ChessSquare)((int)piece - 1);
11039 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11040 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11041 selection = (ChessSquare) (DEMOTED piece);
11042 } else if(piece == EmptySquare) selection = BlackSilver;
11043 else selection = (ChessSquare)((int)piece + 1);
11048 if(gameInfo.variant == VariantShatranj ||
11049 gameInfo.variant == VariantXiangqi ||
11050 gameInfo.variant == VariantCourier )
11051 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11056 if(gameInfo.variant == VariantXiangqi)
11057 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11058 if(gameInfo.variant == VariantKnightmate)
11059 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11062 if (gameMode == IcsExamining) {
11063 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11064 PieceToChar(selection), AAA + x, ONE + y);
11067 boards[0][y][x] = selection;
11068 DrawPosition(FALSE, boards[0]);
11076 DropMenuEvent(selection, x, y)
11077 ChessSquare selection;
11080 ChessMove moveType;
11082 switch (gameMode) {
11083 case IcsPlayingWhite:
11084 case MachinePlaysBlack:
11085 if (!WhiteOnMove(currentMove)) {
11086 DisplayMoveError(_("It is Black's turn"));
11089 moveType = WhiteDrop;
11091 case IcsPlayingBlack:
11092 case MachinePlaysWhite:
11093 if (WhiteOnMove(currentMove)) {
11094 DisplayMoveError(_("It is White's turn"));
11097 moveType = BlackDrop;
11100 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11106 if (moveType == BlackDrop && selection < BlackPawn) {
11107 selection = (ChessSquare) ((int) selection
11108 + (int) BlackPawn - (int) WhitePawn);
11110 if (boards[currentMove][y][x] != EmptySquare) {
11111 DisplayMoveError(_("That square is occupied"));
11115 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11121 /* Accept a pending offer of any kind from opponent */
11123 if (appData.icsActive) {
11124 SendToICS(ics_prefix);
11125 SendToICS("accept\n");
11126 } else if (cmailMsgLoaded) {
11127 if (currentMove == cmailOldMove &&
11128 commentList[cmailOldMove] != NULL &&
11129 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11130 "Black offers a draw" : "White offers a draw")) {
11132 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11133 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11135 DisplayError(_("There is no pending offer on this move"), 0);
11136 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11139 /* Not used for offers from chess program */
11146 /* Decline a pending offer of any kind from opponent */
11148 if (appData.icsActive) {
11149 SendToICS(ics_prefix);
11150 SendToICS("decline\n");
11151 } else if (cmailMsgLoaded) {
11152 if (currentMove == cmailOldMove &&
11153 commentList[cmailOldMove] != NULL &&
11154 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11155 "Black offers a draw" : "White offers a draw")) {
11157 AppendComment(cmailOldMove, "Draw declined");
11158 DisplayComment(cmailOldMove - 1, "Draw declined");
11161 DisplayError(_("There is no pending offer on this move"), 0);
11164 /* Not used for offers from chess program */
11171 /* Issue ICS rematch command */
11172 if (appData.icsActive) {
11173 SendToICS(ics_prefix);
11174 SendToICS("rematch\n");
11181 /* Call your opponent's flag (claim a win on time) */
11182 if (appData.icsActive) {
11183 SendToICS(ics_prefix);
11184 SendToICS("flag\n");
11186 switch (gameMode) {
11189 case MachinePlaysWhite:
11192 GameEnds(GameIsDrawn, "Both players ran out of time",
11195 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11197 DisplayError(_("Your opponent is not out of time"), 0);
11200 case MachinePlaysBlack:
11203 GameEnds(GameIsDrawn, "Both players ran out of time",
11206 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11208 DisplayError(_("Your opponent is not out of time"), 0);
11218 /* Offer draw or accept pending draw offer from opponent */
11220 if (appData.icsActive) {
11221 /* Note: tournament rules require draw offers to be
11222 made after you make your move but before you punch
11223 your clock. Currently ICS doesn't let you do that;
11224 instead, you immediately punch your clock after making
11225 a move, but you can offer a draw at any time. */
11227 SendToICS(ics_prefix);
11228 SendToICS("draw\n");
11229 } else if (cmailMsgLoaded) {
11230 if (currentMove == cmailOldMove &&
11231 commentList[cmailOldMove] != NULL &&
11232 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11233 "Black offers a draw" : "White offers a draw")) {
11234 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11235 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11236 } else if (currentMove == cmailOldMove + 1) {
11237 char *offer = WhiteOnMove(cmailOldMove) ?
11238 "White offers a draw" : "Black offers a draw";
11239 AppendComment(currentMove, offer);
11240 DisplayComment(currentMove - 1, offer);
11241 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11243 DisplayError(_("You must make your move before offering a draw"), 0);
11244 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11246 } else if (first.offeredDraw) {
11247 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11249 if (first.sendDrawOffers) {
11250 SendToProgram("draw\n", &first);
11251 userOfferedDraw = TRUE;
11259 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11261 if (appData.icsActive) {
11262 SendToICS(ics_prefix);
11263 SendToICS("adjourn\n");
11265 /* Currently GNU Chess doesn't offer or accept Adjourns */
11273 /* Offer Abort or accept pending Abort offer from opponent */
11275 if (appData.icsActive) {
11276 SendToICS(ics_prefix);
11277 SendToICS("abort\n");
11279 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11286 /* Resign. You can do this even if it's not your turn. */
11288 if (appData.icsActive) {
11289 SendToICS(ics_prefix);
11290 SendToICS("resign\n");
11292 switch (gameMode) {
11293 case MachinePlaysWhite:
11294 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11296 case MachinePlaysBlack:
11297 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11300 if (cmailMsgLoaded) {
11302 if (WhiteOnMove(cmailOldMove)) {
11303 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11305 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11307 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11318 StopObservingEvent()
11320 /* Stop observing current games */
11321 SendToICS(ics_prefix);
11322 SendToICS("unobserve\n");
11326 StopExaminingEvent()
11328 /* Stop observing current game */
11329 SendToICS(ics_prefix);
11330 SendToICS("unexamine\n");
11334 ForwardInner(target)
11339 if (appData.debugMode)
11340 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11341 target, currentMove, forwardMostMove);
11343 if (gameMode == EditPosition)
11346 if (gameMode == PlayFromGameFile && !pausing)
11349 if (gameMode == IcsExamining && pausing)
11350 limit = pauseExamForwardMostMove;
11352 limit = forwardMostMove;
11354 if (target > limit) target = limit;
11356 if (target > 0 && moveList[target - 1][0]) {
11357 int fromX, fromY, toX, toY;
11358 toX = moveList[target - 1][2] - AAA;
11359 toY = moveList[target - 1][3] - ONE;
11360 if (moveList[target - 1][1] == '@') {
11361 if (appData.highlightLastMove) {
11362 SetHighlights(-1, -1, toX, toY);
11365 fromX = moveList[target - 1][0] - AAA;
11366 fromY = moveList[target - 1][1] - ONE;
11367 if (target == currentMove + 1) {
11368 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11370 if (appData.highlightLastMove) {
11371 SetHighlights(fromX, fromY, toX, toY);
11375 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11376 gameMode == Training || gameMode == PlayFromGameFile ||
11377 gameMode == AnalyzeFile) {
11378 while (currentMove < target) {
11379 SendMoveToProgram(currentMove++, &first);
11382 currentMove = target;
11385 if (gameMode == EditGame || gameMode == EndOfGame) {
11386 whiteTimeRemaining = timeRemaining[0][currentMove];
11387 blackTimeRemaining = timeRemaining[1][currentMove];
11389 DisplayBothClocks();
11390 DisplayMove(currentMove - 1);
11391 DrawPosition(FALSE, boards[currentMove]);
11392 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11393 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11394 DisplayComment(currentMove - 1, commentList[currentMove]);
11402 if (gameMode == IcsExamining && !pausing) {
11403 SendToICS(ics_prefix);
11404 SendToICS("forward\n");
11406 ForwardInner(currentMove + 1);
11413 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11414 /* to optimze, we temporarily turn off analysis mode while we feed
11415 * the remaining moves to the engine. Otherwise we get analysis output
11418 if (first.analysisSupport) {
11419 SendToProgram("exit\nforce\n", &first);
11420 first.analyzing = FALSE;
11424 if (gameMode == IcsExamining && !pausing) {
11425 SendToICS(ics_prefix);
11426 SendToICS("forward 999999\n");
11428 ForwardInner(forwardMostMove);
11431 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11432 /* we have fed all the moves, so reactivate analysis mode */
11433 SendToProgram("analyze\n", &first);
11434 first.analyzing = TRUE;
11435 /*first.maybeThinking = TRUE;*/
11436 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11441 BackwardInner(target)
11444 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11446 if (appData.debugMode)
11447 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11448 target, currentMove, forwardMostMove);
11450 if (gameMode == EditPosition) return;
11451 if (currentMove <= backwardMostMove) {
11453 DrawPosition(full_redraw, boards[currentMove]);
11456 if (gameMode == PlayFromGameFile && !pausing)
11459 if (moveList[target][0]) {
11460 int fromX, fromY, toX, toY;
11461 toX = moveList[target][2] - AAA;
11462 toY = moveList[target][3] - ONE;
11463 if (moveList[target][1] == '@') {
11464 if (appData.highlightLastMove) {
11465 SetHighlights(-1, -1, toX, toY);
11468 fromX = moveList[target][0] - AAA;
11469 fromY = moveList[target][1] - ONE;
11470 if (target == currentMove - 1) {
11471 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11473 if (appData.highlightLastMove) {
11474 SetHighlights(fromX, fromY, toX, toY);
11478 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11479 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11480 while (currentMove > target) {
11481 SendToProgram("undo\n", &first);
11485 currentMove = target;
11488 if (gameMode == EditGame || gameMode == EndOfGame) {
11489 whiteTimeRemaining = timeRemaining[0][currentMove];
11490 blackTimeRemaining = timeRemaining[1][currentMove];
11492 DisplayBothClocks();
11493 DisplayMove(currentMove - 1);
11494 DrawPosition(full_redraw, boards[currentMove]);
11495 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11496 // [HGM] PV info: routine tests if comment empty
11497 DisplayComment(currentMove - 1, commentList[currentMove]);
11503 if (gameMode == IcsExamining && !pausing) {
11504 SendToICS(ics_prefix);
11505 SendToICS("backward\n");
11507 BackwardInner(currentMove - 1);
11514 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11515 /* to optimze, we temporarily turn off analysis mode while we undo
11516 * all the moves. Otherwise we get analysis output after each undo.
11518 if (first.analysisSupport) {
11519 SendToProgram("exit\nforce\n", &first);
11520 first.analyzing = FALSE;
11524 if (gameMode == IcsExamining && !pausing) {
11525 SendToICS(ics_prefix);
11526 SendToICS("backward 999999\n");
11528 BackwardInner(backwardMostMove);
11531 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11532 /* we have fed all the moves, so reactivate analysis mode */
11533 SendToProgram("analyze\n", &first);
11534 first.analyzing = TRUE;
11535 /*first.maybeThinking = TRUE;*/
11536 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11543 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11544 if (to >= forwardMostMove) to = forwardMostMove;
11545 if (to <= backwardMostMove) to = backwardMostMove;
11546 if (to < currentMove) {
11556 if (gameMode != IcsExamining) {
11557 DisplayError(_("You are not examining a game"), 0);
11561 DisplayError(_("You can't revert while pausing"), 0);
11564 SendToICS(ics_prefix);
11565 SendToICS("revert\n");
11571 switch (gameMode) {
11572 case MachinePlaysWhite:
11573 case MachinePlaysBlack:
11574 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11575 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11578 if (forwardMostMove < 2) return;
11579 currentMove = forwardMostMove = forwardMostMove - 2;
11580 whiteTimeRemaining = timeRemaining[0][currentMove];
11581 blackTimeRemaining = timeRemaining[1][currentMove];
11582 DisplayBothClocks();
11583 DisplayMove(currentMove - 1);
11584 ClearHighlights();/*!! could figure this out*/
11585 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11586 SendToProgram("remove\n", &first);
11587 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11590 case BeginningOfGame:
11594 case IcsPlayingWhite:
11595 case IcsPlayingBlack:
11596 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11597 SendToICS(ics_prefix);
11598 SendToICS("takeback 2\n");
11600 SendToICS(ics_prefix);
11601 SendToICS("takeback 1\n");
11610 ChessProgramState *cps;
11612 switch (gameMode) {
11613 case MachinePlaysWhite:
11614 if (!WhiteOnMove(forwardMostMove)) {
11615 DisplayError(_("It is your turn"), 0);
11620 case MachinePlaysBlack:
11621 if (WhiteOnMove(forwardMostMove)) {
11622 DisplayError(_("It is your turn"), 0);
11627 case TwoMachinesPlay:
11628 if (WhiteOnMove(forwardMostMove) ==
11629 (first.twoMachinesColor[0] == 'w')) {
11635 case BeginningOfGame:
11639 SendToProgram("?\n", cps);
11643 TruncateGameEvent()
11646 if (gameMode != EditGame) return;
11653 if (forwardMostMove > currentMove) {
11654 if (gameInfo.resultDetails != NULL) {
11655 free(gameInfo.resultDetails);
11656 gameInfo.resultDetails = NULL;
11657 gameInfo.result = GameUnfinished;
11659 forwardMostMove = currentMove;
11660 HistorySet(parseList, backwardMostMove, forwardMostMove,
11668 if (appData.noChessProgram) return;
11669 switch (gameMode) {
11670 case MachinePlaysWhite:
11671 if (WhiteOnMove(forwardMostMove)) {
11672 DisplayError(_("Wait until your turn"), 0);
11676 case BeginningOfGame:
11677 case MachinePlaysBlack:
11678 if (!WhiteOnMove(forwardMostMove)) {
11679 DisplayError(_("Wait until your turn"), 0);
11684 DisplayError(_("No hint available"), 0);
11687 SendToProgram("hint\n", &first);
11688 hintRequested = TRUE;
11694 if (appData.noChessProgram) return;
11695 switch (gameMode) {
11696 case MachinePlaysWhite:
11697 if (WhiteOnMove(forwardMostMove)) {
11698 DisplayError(_("Wait until your turn"), 0);
11702 case BeginningOfGame:
11703 case MachinePlaysBlack:
11704 if (!WhiteOnMove(forwardMostMove)) {
11705 DisplayError(_("Wait until your turn"), 0);
11710 EditPositionDone();
11712 case TwoMachinesPlay:
11717 SendToProgram("bk\n", &first);
11718 bookOutput[0] = NULLCHAR;
11719 bookRequested = TRUE;
11725 char *tags = PGNTags(&gameInfo);
11726 TagsPopUp(tags, CmailMsg());
11730 /* end button procedures */
11733 PrintPosition(fp, move)
11739 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11740 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11741 char c = PieceToChar(boards[move][i][j]);
11742 fputc(c == 'x' ? '.' : c, fp);
11743 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11746 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11747 fprintf(fp, "white to play\n");
11749 fprintf(fp, "black to play\n");
11756 if (gameInfo.white != NULL) {
11757 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11763 /* Find last component of program's own name, using some heuristics */
11765 TidyProgramName(prog, host, buf)
11766 char *prog, *host, buf[MSG_SIZ];
11769 int local = (strcmp(host, "localhost") == 0);
11770 while (!local && (p = strchr(prog, ';')) != NULL) {
11772 while (*p == ' ') p++;
11775 if (*prog == '"' || *prog == '\'') {
11776 q = strchr(prog + 1, *prog);
11778 q = strchr(prog, ' ');
11780 if (q == NULL) q = prog + strlen(prog);
11782 while (p >= prog && *p != '/' && *p != '\\') p--;
11784 if(p == prog && *p == '"') p++;
11785 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11786 memcpy(buf, p, q - p);
11787 buf[q - p] = NULLCHAR;
11795 TimeControlTagValue()
11798 if (!appData.clockMode) {
11800 } else if (movesPerSession > 0) {
11801 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11802 } else if (timeIncrement == 0) {
11803 sprintf(buf, "%ld", timeControl/1000);
11805 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11807 return StrSave(buf);
11813 /* This routine is used only for certain modes */
11814 VariantClass v = gameInfo.variant;
11815 ClearGameInfo(&gameInfo);
11816 gameInfo.variant = v;
11818 switch (gameMode) {
11819 case MachinePlaysWhite:
11820 gameInfo.event = StrSave( appData.pgnEventHeader );
11821 gameInfo.site = StrSave(HostName());
11822 gameInfo.date = PGNDate();
11823 gameInfo.round = StrSave("-");
11824 gameInfo.white = StrSave(first.tidy);
11825 gameInfo.black = StrSave(UserName());
11826 gameInfo.timeControl = TimeControlTagValue();
11829 case MachinePlaysBlack:
11830 gameInfo.event = StrSave( appData.pgnEventHeader );
11831 gameInfo.site = StrSave(HostName());
11832 gameInfo.date = PGNDate();
11833 gameInfo.round = StrSave("-");
11834 gameInfo.white = StrSave(UserName());
11835 gameInfo.black = StrSave(first.tidy);
11836 gameInfo.timeControl = TimeControlTagValue();
11839 case TwoMachinesPlay:
11840 gameInfo.event = StrSave( appData.pgnEventHeader );
11841 gameInfo.site = StrSave(HostName());
11842 gameInfo.date = PGNDate();
11843 if (matchGame > 0) {
11845 sprintf(buf, "%d", matchGame);
11846 gameInfo.round = StrSave(buf);
11848 gameInfo.round = StrSave("-");
11850 if (first.twoMachinesColor[0] == 'w') {
11851 gameInfo.white = StrSave(first.tidy);
11852 gameInfo.black = StrSave(second.tidy);
11854 gameInfo.white = StrSave(second.tidy);
11855 gameInfo.black = StrSave(first.tidy);
11857 gameInfo.timeControl = TimeControlTagValue();
11861 gameInfo.event = StrSave("Edited game");
11862 gameInfo.site = StrSave(HostName());
11863 gameInfo.date = PGNDate();
11864 gameInfo.round = StrSave("-");
11865 gameInfo.white = StrSave("-");
11866 gameInfo.black = StrSave("-");
11870 gameInfo.event = StrSave("Edited position");
11871 gameInfo.site = StrSave(HostName());
11872 gameInfo.date = PGNDate();
11873 gameInfo.round = StrSave("-");
11874 gameInfo.white = StrSave("-");
11875 gameInfo.black = StrSave("-");
11878 case IcsPlayingWhite:
11879 case IcsPlayingBlack:
11884 case PlayFromGameFile:
11885 gameInfo.event = StrSave("Game from non-PGN file");
11886 gameInfo.site = StrSave(HostName());
11887 gameInfo.date = PGNDate();
11888 gameInfo.round = StrSave("-");
11889 gameInfo.white = StrSave("?");
11890 gameInfo.black = StrSave("?");
11899 ReplaceComment(index, text)
11905 while (*text == '\n') text++;
11906 len = strlen(text);
11907 while (len > 0 && text[len - 1] == '\n') len--;
11909 if (commentList[index] != NULL)
11910 free(commentList[index]);
11913 commentList[index] = NULL;
11916 commentList[index] = (char *) malloc(len + 2);
11917 strncpy(commentList[index], text, len);
11918 commentList[index][len] = '\n';
11919 commentList[index][len + 1] = NULLCHAR;
11932 if (ch == '\r') continue;
11934 } while (ch != '\0');
11938 AppendComment(index, text)
11945 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11948 while (*text == '\n') text++;
11949 len = strlen(text);
11950 while (len > 0 && text[len - 1] == '\n') len--;
11952 if (len == 0) return;
11954 if (commentList[index] != NULL) {
11955 old = commentList[index];
11956 oldlen = strlen(old);
11957 commentList[index] = (char *) malloc(oldlen + len + 2);
11958 strcpy(commentList[index], old);
11960 strncpy(&commentList[index][oldlen], text, len);
11961 commentList[index][oldlen + len] = '\n';
11962 commentList[index][oldlen + len + 1] = NULLCHAR;
11964 commentList[index] = (char *) malloc(len + 2);
11965 strncpy(commentList[index], text, len);
11966 commentList[index][len] = '\n';
11967 commentList[index][len + 1] = NULLCHAR;
11971 static char * FindStr( char * text, char * sub_text )
11973 char * result = strstr( text, sub_text );
11975 if( result != NULL ) {
11976 result += strlen( sub_text );
11982 /* [AS] Try to extract PV info from PGN comment */
11983 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11984 char *GetInfoFromComment( int index, char * text )
11988 if( text != NULL && index > 0 ) {
11991 int time = -1, sec = 0, deci;
11992 char * s_eval = FindStr( text, "[%eval " );
11993 char * s_emt = FindStr( text, "[%emt " );
11995 if( s_eval != NULL || s_emt != NULL ) {
11999 if( s_eval != NULL ) {
12000 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12004 if( delim != ']' ) {
12009 if( s_emt != NULL ) {
12013 /* We expect something like: [+|-]nnn.nn/dd */
12016 sep = strchr( text, '/' );
12017 if( sep == NULL || sep < (text+4) ) {
12021 time = -1; sec = -1; deci = -1;
12022 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12023 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12024 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12025 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12029 if( score_lo < 0 || score_lo >= 100 ) {
12033 if(sec >= 0) time = 600*time + 10*sec; else
12034 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12036 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12038 /* [HGM] PV time: now locate end of PV info */
12039 while( *++sep >= '0' && *sep <= '9'); // strip depth
12041 while( *++sep >= '0' && *sep <= '9'); // strip time
12043 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12045 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12046 while(*sep == ' ') sep++;
12057 pvInfoList[index-1].depth = depth;
12058 pvInfoList[index-1].score = score;
12059 pvInfoList[index-1].time = 10*time; // centi-sec
12065 SendToProgram(message, cps)
12067 ChessProgramState *cps;
12069 int count, outCount, error;
12072 if (cps->pr == NULL) return;
12075 if (appData.debugMode) {
12078 fprintf(debugFP, "%ld >%-6s: %s",
12079 SubtractTimeMarks(&now, &programStartTime),
12080 cps->which, message);
12083 count = strlen(message);
12084 outCount = OutputToProcess(cps->pr, message, count, &error);
12085 if (outCount < count && !exiting
12086 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12087 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12088 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12089 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12090 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12091 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12093 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12095 gameInfo.resultDetails = buf;
12097 DisplayFatalError(buf, error, 1);
12102 ReceiveFromProgram(isr, closure, message, count, error)
12103 InputSourceRef isr;
12111 ChessProgramState *cps = (ChessProgramState *)closure;
12113 if (isr != cps->isr) return; /* Killed intentionally */
12117 _("Error: %s chess program (%s) exited unexpectedly"),
12118 cps->which, cps->program);
12119 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12120 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12121 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12122 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12124 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12126 gameInfo.resultDetails = buf;
12128 RemoveInputSource(cps->isr);
12129 DisplayFatalError(buf, 0, 1);
12132 _("Error reading from %s chess program (%s)"),
12133 cps->which, cps->program);
12134 RemoveInputSource(cps->isr);
12136 /* [AS] Program is misbehaving badly... kill it */
12137 if( count == -2 ) {
12138 DestroyChildProcess( cps->pr, 9 );
12142 DisplayFatalError(buf, error, 1);
12147 if ((end_str = strchr(message, '\r')) != NULL)
12148 *end_str = NULLCHAR;
12149 if ((end_str = strchr(message, '\n')) != NULL)
12150 *end_str = NULLCHAR;
12152 if (appData.debugMode) {
12153 TimeMark now; int print = 1;
12154 char *quote = ""; char c; int i;
12156 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12157 char start = message[0];
12158 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12159 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12160 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12161 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12162 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12163 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12164 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12165 { quote = "# "; print = (appData.engineComments == 2); }
12166 message[0] = start; // restore original message
12170 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12171 SubtractTimeMarks(&now, &programStartTime), cps->which,
12177 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12178 if (appData.icsEngineAnalyze) {
12179 if (strstr(message, "whisper") != NULL ||
12180 strstr(message, "kibitz") != NULL ||
12181 strstr(message, "tellics") != NULL) return;
12184 HandleMachineMove(message, cps);
12189 SendTimeControl(cps, mps, tc, inc, sd, st)
12190 ChessProgramState *cps;
12191 int mps, inc, sd, st;
12197 if( timeControl_2 > 0 ) {
12198 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12199 tc = timeControl_2;
12202 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12203 inc /= cps->timeOdds;
12204 st /= cps->timeOdds;
12206 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12209 /* Set exact time per move, normally using st command */
12210 if (cps->stKludge) {
12211 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12213 if (seconds == 0) {
12214 sprintf(buf, "level 1 %d\n", st/60);
12216 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12219 sprintf(buf, "st %d\n", st);
12222 /* Set conventional or incremental time control, using level command */
12223 if (seconds == 0) {
12224 /* Note old gnuchess bug -- minutes:seconds used to not work.
12225 Fixed in later versions, but still avoid :seconds
12226 when seconds is 0. */
12227 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12229 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12230 seconds, inc/1000);
12233 SendToProgram(buf, cps);
12235 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12236 /* Orthogonally, limit search to given depth */
12238 if (cps->sdKludge) {
12239 sprintf(buf, "depth\n%d\n", sd);
12241 sprintf(buf, "sd %d\n", sd);
12243 SendToProgram(buf, cps);
12246 if(cps->nps > 0) { /* [HGM] nps */
12247 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12249 sprintf(buf, "nps %d\n", cps->nps);
12250 SendToProgram(buf, cps);
12255 ChessProgramState *WhitePlayer()
12256 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12258 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12259 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12265 SendTimeRemaining(cps, machineWhite)
12266 ChessProgramState *cps;
12267 int /*boolean*/ machineWhite;
12269 char message[MSG_SIZ];
12272 /* Note: this routine must be called when the clocks are stopped
12273 or when they have *just* been set or switched; otherwise
12274 it will be off by the time since the current tick started.
12276 if (machineWhite) {
12277 time = whiteTimeRemaining / 10;
12278 otime = blackTimeRemaining / 10;
12280 time = blackTimeRemaining / 10;
12281 otime = whiteTimeRemaining / 10;
12283 /* [HGM] translate opponent's time by time-odds factor */
12284 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12285 if (appData.debugMode) {
12286 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12289 if (time <= 0) time = 1;
12290 if (otime <= 0) otime = 1;
12292 sprintf(message, "time %ld\n", time);
12293 SendToProgram(message, cps);
12295 sprintf(message, "otim %ld\n", otime);
12296 SendToProgram(message, cps);
12300 BoolFeature(p, name, loc, cps)
12304 ChessProgramState *cps;
12307 int len = strlen(name);
12309 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12311 sscanf(*p, "%d", &val);
12313 while (**p && **p != ' ') (*p)++;
12314 sprintf(buf, "accepted %s\n", name);
12315 SendToProgram(buf, cps);
12322 IntFeature(p, name, loc, cps)
12326 ChessProgramState *cps;
12329 int len = strlen(name);
12330 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12332 sscanf(*p, "%d", loc);
12333 while (**p && **p != ' ') (*p)++;
12334 sprintf(buf, "accepted %s\n", name);
12335 SendToProgram(buf, cps);
12342 StringFeature(p, name, loc, cps)
12346 ChessProgramState *cps;
12349 int len = strlen(name);
12350 if (strncmp((*p), name, len) == 0
12351 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12353 sscanf(*p, "%[^\"]", loc);
12354 while (**p && **p != '\"') (*p)++;
12355 if (**p == '\"') (*p)++;
12356 sprintf(buf, "accepted %s\n", name);
12357 SendToProgram(buf, cps);
12364 ParseOption(Option *opt, ChessProgramState *cps)
12365 // [HGM] options: process the string that defines an engine option, and determine
12366 // name, type, default value, and allowed value range
12368 char *p, *q, buf[MSG_SIZ];
12369 int n, min = (-1)<<31, max = 1<<31, def;
12371 if(p = strstr(opt->name, " -spin ")) {
12372 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12373 if(max < min) max = min; // enforce consistency
12374 if(def < min) def = min;
12375 if(def > max) def = max;
12380 } else if(p = strstr(opt->name, " -string ")) {
12381 opt->textValue = p+9;
12382 opt->type = TextBox;
12383 } else if(p = strstr(opt->name, " -check ")) {
12384 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12385 opt->value = (def != 0);
12386 opt->type = CheckBox;
12387 } else if(p = strstr(opt->name, " -combo ")) {
12388 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12389 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12390 opt->value = n = 0;
12391 while(q = StrStr(q, " /// ")) {
12392 n++; *q = 0; // count choices, and null-terminate each of them
12394 if(*q == '*') { // remember default, which is marked with * prefix
12398 cps->comboList[cps->comboCnt++] = q;
12400 cps->comboList[cps->comboCnt++] = NULL;
12402 opt->type = ComboBox;
12403 } else if(p = strstr(opt->name, " -button")) {
12404 opt->type = Button;
12405 } else if(p = strstr(opt->name, " -save")) {
12406 opt->type = SaveButton;
12407 } else return FALSE;
12408 *p = 0; // terminate option name
12409 // now look if the command-line options define a setting for this engine option.
12410 if(cps->optionSettings && cps->optionSettings[0])
12411 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12412 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12413 sprintf(buf, "option %s", p);
12414 if(p = strstr(buf, ",")) *p = 0;
12416 SendToProgram(buf, cps);
12422 FeatureDone(cps, val)
12423 ChessProgramState* cps;
12426 DelayedEventCallback cb = GetDelayedEvent();
12427 if ((cb == InitBackEnd3 && cps == &first) ||
12428 (cb == TwoMachinesEventIfReady && cps == &second)) {
12429 CancelDelayedEvent();
12430 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12432 cps->initDone = val;
12435 /* Parse feature command from engine */
12437 ParseFeatures(args, cps)
12439 ChessProgramState *cps;
12447 while (*p == ' ') p++;
12448 if (*p == NULLCHAR) return;
12450 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12451 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12452 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12453 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12454 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12455 if (BoolFeature(&p, "reuse", &val, cps)) {
12456 /* Engine can disable reuse, but can't enable it if user said no */
12457 if (!val) cps->reuse = FALSE;
12460 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12461 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12462 if (gameMode == TwoMachinesPlay) {
12463 DisplayTwoMachinesTitle();
12469 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12470 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12471 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12472 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12473 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12474 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12475 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12476 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12477 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12478 if (IntFeature(&p, "done", &val, cps)) {
12479 FeatureDone(cps, val);
12482 /* Added by Tord: */
12483 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12484 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12485 /* End of additions by Tord */
12487 /* [HGM] added features: */
12488 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12489 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12490 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12491 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12492 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12493 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12494 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12495 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12496 if(cps->nrOptions >= MAX_OPTIONS) {
12498 sprintf(buf, "%s engine has too many options\n", cps->which);
12499 DisplayError(buf, 0);
12503 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12504 /* End of additions by HGM */
12506 /* unknown feature: complain and skip */
12508 while (*q && *q != '=') q++;
12509 sprintf(buf, "rejected %.*s\n", q-p, p);
12510 SendToProgram(buf, cps);
12516 while (*p && *p != '\"') p++;
12517 if (*p == '\"') p++;
12519 while (*p && *p != ' ') p++;
12527 PeriodicUpdatesEvent(newState)
12530 if (newState == appData.periodicUpdates)
12533 appData.periodicUpdates=newState;
12535 /* Display type changes, so update it now */
12538 /* Get the ball rolling again... */
12540 AnalysisPeriodicEvent(1);
12541 StartAnalysisClock();
12546 PonderNextMoveEvent(newState)
12549 if (newState == appData.ponderNextMove) return;
12550 if (gameMode == EditPosition) EditPositionDone();
12552 SendToProgram("hard\n", &first);
12553 if (gameMode == TwoMachinesPlay) {
12554 SendToProgram("hard\n", &second);
12557 SendToProgram("easy\n", &first);
12558 thinkOutput[0] = NULLCHAR;
12559 if (gameMode == TwoMachinesPlay) {
12560 SendToProgram("easy\n", &second);
12563 appData.ponderNextMove = newState;
12567 NewSettingEvent(option, command, value)
12573 if (gameMode == EditPosition) EditPositionDone();
12574 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12575 SendToProgram(buf, &first);
12576 if (gameMode == TwoMachinesPlay) {
12577 SendToProgram(buf, &second);
12582 ShowThinkingEvent()
12583 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12585 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12586 int newState = appData.showThinking
12587 // [HGM] thinking: other features now need thinking output as well
12588 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12590 if (oldState == newState) return;
12591 oldState = newState;
12592 if (gameMode == EditPosition) EditPositionDone();
12594 SendToProgram("post\n", &first);
12595 if (gameMode == TwoMachinesPlay) {
12596 SendToProgram("post\n", &second);
12599 SendToProgram("nopost\n", &first);
12600 thinkOutput[0] = NULLCHAR;
12601 if (gameMode == TwoMachinesPlay) {
12602 SendToProgram("nopost\n", &second);
12605 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12609 AskQuestionEvent(title, question, replyPrefix, which)
12610 char *title; char *question; char *replyPrefix; char *which;
12612 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12613 if (pr == NoProc) return;
12614 AskQuestion(title, question, replyPrefix, pr);
12618 DisplayMove(moveNumber)
12621 char message[MSG_SIZ];
12623 char cpThinkOutput[MSG_SIZ];
12625 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12627 if (moveNumber == forwardMostMove - 1 ||
12628 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12630 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12632 if (strchr(cpThinkOutput, '\n')) {
12633 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12636 *cpThinkOutput = NULLCHAR;
12639 /* [AS] Hide thinking from human user */
12640 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12641 *cpThinkOutput = NULLCHAR;
12642 if( thinkOutput[0] != NULLCHAR ) {
12645 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12646 cpThinkOutput[i] = '.';
12648 cpThinkOutput[i] = NULLCHAR;
12649 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12653 if (moveNumber == forwardMostMove - 1 &&
12654 gameInfo.resultDetails != NULL) {
12655 if (gameInfo.resultDetails[0] == NULLCHAR) {
12656 sprintf(res, " %s", PGNResult(gameInfo.result));
12658 sprintf(res, " {%s} %s",
12659 gameInfo.resultDetails, PGNResult(gameInfo.result));
12665 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12666 DisplayMessage(res, cpThinkOutput);
12668 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12669 WhiteOnMove(moveNumber) ? " " : ".. ",
12670 parseList[moveNumber], res);
12671 DisplayMessage(message, cpThinkOutput);
12676 DisplayAnalysisText(text)
12681 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12682 || appData.icsEngineAnalyze) {
12683 sprintf(buf, "Analysis (%s)", first.tidy);
12684 AnalysisPopUp(buf, text);
12692 while (*str && isspace(*str)) ++str;
12693 while (*str && !isspace(*str)) ++str;
12694 if (!*str) return 1;
12695 while (*str && isspace(*str)) ++str;
12696 if (!*str) return 1;
12704 char lst[MSG_SIZ / 2];
12706 static char *xtra[] = { "", " (--)", " (++)" };
12709 if (programStats.time == 0) {
12710 programStats.time = 1;
12713 if (programStats.got_only_move) {
12714 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12716 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12718 nps = (u64ToDouble(programStats.nodes) /
12719 ((double)programStats.time /100.0));
12721 cs = programStats.time % 100;
12722 s = programStats.time / 100;
12728 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12729 if (programStats.move_name[0] != NULLCHAR) {
12730 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12731 programStats.depth,
12732 programStats.nr_moves-programStats.moves_left,
12733 programStats.nr_moves, programStats.move_name,
12734 ((float)programStats.score)/100.0, lst,
12735 only_one_move(lst)?
12736 xtra[programStats.got_fail] : "",
12737 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12739 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12740 programStats.depth,
12741 programStats.nr_moves-programStats.moves_left,
12742 programStats.nr_moves, ((float)programStats.score)/100.0,
12744 only_one_move(lst)?
12745 xtra[programStats.got_fail] : "",
12746 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12749 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12750 programStats.depth,
12751 ((float)programStats.score)/100.0,
12753 only_one_move(lst)?
12754 xtra[programStats.got_fail] : "",
12755 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12758 DisplayAnalysisText(buf);
12762 DisplayComment(moveNumber, text)
12766 char title[MSG_SIZ];
12767 char buf[8000]; // comment can be long!
12770 if( appData.autoDisplayComment ) {
12771 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12772 strcpy(title, "Comment");
12774 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12775 WhiteOnMove(moveNumber) ? " " : ".. ",
12776 parseList[moveNumber]);
12778 // [HGM] PV info: display PV info together with (or as) comment
12779 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12780 if(text == NULL) text = "";
12781 score = pvInfoList[moveNumber].score;
12782 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12783 depth, (pvInfoList[moveNumber].time+50)/100, text);
12786 } else title[0] = 0;
12789 CommentPopUp(title, text);
12792 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12793 * might be busy thinking or pondering. It can be omitted if your
12794 * gnuchess is configured to stop thinking immediately on any user
12795 * input. However, that gnuchess feature depends on the FIONREAD
12796 * ioctl, which does not work properly on some flavors of Unix.
12800 ChessProgramState *cps;
12803 if (!cps->useSigint) return;
12804 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12805 switch (gameMode) {
12806 case MachinePlaysWhite:
12807 case MachinePlaysBlack:
12808 case TwoMachinesPlay:
12809 case IcsPlayingWhite:
12810 case IcsPlayingBlack:
12813 /* Skip if we know it isn't thinking */
12814 if (!cps->maybeThinking) return;
12815 if (appData.debugMode)
12816 fprintf(debugFP, "Interrupting %s\n", cps->which);
12817 InterruptChildProcess(cps->pr);
12818 cps->maybeThinking = FALSE;
12823 #endif /*ATTENTION*/
12829 if (whiteTimeRemaining <= 0) {
12832 if (appData.icsActive) {
12833 if (appData.autoCallFlag &&
12834 gameMode == IcsPlayingBlack && !blackFlag) {
12835 SendToICS(ics_prefix);
12836 SendToICS("flag\n");
12840 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12842 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12843 if (appData.autoCallFlag) {
12844 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12851 if (blackTimeRemaining <= 0) {
12854 if (appData.icsActive) {
12855 if (appData.autoCallFlag &&
12856 gameMode == IcsPlayingWhite && !whiteFlag) {
12857 SendToICS(ics_prefix);
12858 SendToICS("flag\n");
12862 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12864 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12865 if (appData.autoCallFlag) {
12866 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12879 if (!appData.clockMode || appData.icsActive ||
12880 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12883 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12885 if ( !WhiteOnMove(forwardMostMove) )
12886 /* White made time control */
12887 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12888 /* [HGM] time odds: correct new time quota for time odds! */
12889 / WhitePlayer()->timeOdds;
12891 /* Black made time control */
12892 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12893 / WhitePlayer()->other->timeOdds;
12897 DisplayBothClocks()
12899 int wom = gameMode == EditPosition ?
12900 !blackPlaysFirst : WhiteOnMove(currentMove);
12901 DisplayWhiteClock(whiteTimeRemaining, wom);
12902 DisplayBlackClock(blackTimeRemaining, !wom);
12906 /* Timekeeping seems to be a portability nightmare. I think everyone
12907 has ftime(), but I'm really not sure, so I'm including some ifdefs
12908 to use other calls if you don't. Clocks will be less accurate if
12909 you have neither ftime nor gettimeofday.
12912 /* VS 2008 requires the #include outside of the function */
12913 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12914 #include <sys/timeb.h>
12917 /* Get the current time as a TimeMark */
12922 #if HAVE_GETTIMEOFDAY
12924 struct timeval timeVal;
12925 struct timezone timeZone;
12927 gettimeofday(&timeVal, &timeZone);
12928 tm->sec = (long) timeVal.tv_sec;
12929 tm->ms = (int) (timeVal.tv_usec / 1000L);
12931 #else /*!HAVE_GETTIMEOFDAY*/
12934 // include <sys/timeb.h> / moved to just above start of function
12935 struct timeb timeB;
12938 tm->sec = (long) timeB.time;
12939 tm->ms = (int) timeB.millitm;
12941 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12942 tm->sec = (long) time(NULL);
12948 /* Return the difference in milliseconds between two
12949 time marks. We assume the difference will fit in a long!
12952 SubtractTimeMarks(tm2, tm1)
12953 TimeMark *tm2, *tm1;
12955 return 1000L*(tm2->sec - tm1->sec) +
12956 (long) (tm2->ms - tm1->ms);
12961 * Code to manage the game clocks.
12963 * In tournament play, black starts the clock and then white makes a move.
12964 * We give the human user a slight advantage if he is playing white---the
12965 * clocks don't run until he makes his first move, so it takes zero time.
12966 * Also, we don't account for network lag, so we could get out of sync
12967 * with GNU Chess's clock -- but then, referees are always right.
12970 static TimeMark tickStartTM;
12971 static long intendedTickLength;
12974 NextTickLength(timeRemaining)
12975 long timeRemaining;
12977 long nominalTickLength, nextTickLength;
12979 if (timeRemaining > 0L && timeRemaining <= 10000L)
12980 nominalTickLength = 100L;
12982 nominalTickLength = 1000L;
12983 nextTickLength = timeRemaining % nominalTickLength;
12984 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12986 return nextTickLength;
12989 /* Adjust clock one minute up or down */
12991 AdjustClock(Boolean which, int dir)
12993 if(which) blackTimeRemaining += 60000*dir;
12994 else whiteTimeRemaining += 60000*dir;
12995 DisplayBothClocks();
12998 /* Stop clocks and reset to a fresh time control */
13002 (void) StopClockTimer();
13003 if (appData.icsActive) {
13004 whiteTimeRemaining = blackTimeRemaining = 0;
13005 } else { /* [HGM] correct new time quote for time odds */
13006 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13007 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13009 if (whiteFlag || blackFlag) {
13011 whiteFlag = blackFlag = FALSE;
13013 DisplayBothClocks();
13016 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13018 /* Decrement running clock by amount of time that has passed */
13022 long timeRemaining;
13023 long lastTickLength, fudge;
13026 if (!appData.clockMode) return;
13027 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13031 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13033 /* Fudge if we woke up a little too soon */
13034 fudge = intendedTickLength - lastTickLength;
13035 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13037 if (WhiteOnMove(forwardMostMove)) {
13038 if(whiteNPS >= 0) lastTickLength = 0;
13039 timeRemaining = whiteTimeRemaining -= lastTickLength;
13040 DisplayWhiteClock(whiteTimeRemaining - fudge,
13041 WhiteOnMove(currentMove));
13043 if(blackNPS >= 0) lastTickLength = 0;
13044 timeRemaining = blackTimeRemaining -= lastTickLength;
13045 DisplayBlackClock(blackTimeRemaining - fudge,
13046 !WhiteOnMove(currentMove));
13049 if (CheckFlags()) return;
13052 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13053 StartClockTimer(intendedTickLength);
13055 /* if the time remaining has fallen below the alarm threshold, sound the
13056 * alarm. if the alarm has sounded and (due to a takeback or time control
13057 * with increment) the time remaining has increased to a level above the
13058 * threshold, reset the alarm so it can sound again.
13061 if (appData.icsActive && appData.icsAlarm) {
13063 /* make sure we are dealing with the user's clock */
13064 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13065 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13068 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13069 alarmSounded = FALSE;
13070 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13072 alarmSounded = TRUE;
13078 /* A player has just moved, so stop the previously running
13079 clock and (if in clock mode) start the other one.
13080 We redisplay both clocks in case we're in ICS mode, because
13081 ICS gives us an update to both clocks after every move.
13082 Note that this routine is called *after* forwardMostMove
13083 is updated, so the last fractional tick must be subtracted
13084 from the color that is *not* on move now.
13089 long lastTickLength;
13091 int flagged = FALSE;
13095 if (StopClockTimer() && appData.clockMode) {
13096 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13097 if (WhiteOnMove(forwardMostMove)) {
13098 if(blackNPS >= 0) lastTickLength = 0;
13099 blackTimeRemaining -= lastTickLength;
13100 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13101 // if(pvInfoList[forwardMostMove-1].time == -1)
13102 pvInfoList[forwardMostMove-1].time = // use GUI time
13103 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13105 if(whiteNPS >= 0) lastTickLength = 0;
13106 whiteTimeRemaining -= lastTickLength;
13107 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13108 // if(pvInfoList[forwardMostMove-1].time == -1)
13109 pvInfoList[forwardMostMove-1].time =
13110 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13112 flagged = CheckFlags();
13114 CheckTimeControl();
13116 if (flagged || !appData.clockMode) return;
13118 switch (gameMode) {
13119 case MachinePlaysBlack:
13120 case MachinePlaysWhite:
13121 case BeginningOfGame:
13122 if (pausing) return;
13126 case PlayFromGameFile:
13135 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13136 whiteTimeRemaining : blackTimeRemaining);
13137 StartClockTimer(intendedTickLength);
13141 /* Stop both clocks */
13145 long lastTickLength;
13148 if (!StopClockTimer()) return;
13149 if (!appData.clockMode) return;
13153 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13154 if (WhiteOnMove(forwardMostMove)) {
13155 if(whiteNPS >= 0) lastTickLength = 0;
13156 whiteTimeRemaining -= lastTickLength;
13157 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13159 if(blackNPS >= 0) lastTickLength = 0;
13160 blackTimeRemaining -= lastTickLength;
13161 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13166 /* Start clock of player on move. Time may have been reset, so
13167 if clock is already running, stop and restart it. */
13171 (void) StopClockTimer(); /* in case it was running already */
13172 DisplayBothClocks();
13173 if (CheckFlags()) return;
13175 if (!appData.clockMode) return;
13176 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13178 GetTimeMark(&tickStartTM);
13179 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13180 whiteTimeRemaining : blackTimeRemaining);
13182 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13183 whiteNPS = blackNPS = -1;
13184 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13185 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13186 whiteNPS = first.nps;
13187 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13188 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13189 blackNPS = first.nps;
13190 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13191 whiteNPS = second.nps;
13192 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13193 blackNPS = second.nps;
13194 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13196 StartClockTimer(intendedTickLength);
13203 long second, minute, hour, day;
13205 static char buf[32];
13207 if (ms > 0 && ms <= 9900) {
13208 /* convert milliseconds to tenths, rounding up */
13209 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13211 sprintf(buf, " %03.1f ", tenths/10.0);
13215 /* convert milliseconds to seconds, rounding up */
13216 /* use floating point to avoid strangeness of integer division
13217 with negative dividends on many machines */
13218 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13225 day = second / (60 * 60 * 24);
13226 second = second % (60 * 60 * 24);
13227 hour = second / (60 * 60);
13228 second = second % (60 * 60);
13229 minute = second / 60;
13230 second = second % 60;
13233 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13234 sign, day, hour, minute, second);
13236 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13238 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13245 * This is necessary because some C libraries aren't ANSI C compliant yet.
13248 StrStr(string, match)
13249 char *string, *match;
13253 length = strlen(match);
13255 for (i = strlen(string) - length; i >= 0; i--, string++)
13256 if (!strncmp(match, string, length))
13263 StrCaseStr(string, match)
13264 char *string, *match;
13268 length = strlen(match);
13270 for (i = strlen(string) - length; i >= 0; i--, string++) {
13271 for (j = 0; j < length; j++) {
13272 if (ToLower(match[j]) != ToLower(string[j]))
13275 if (j == length) return string;
13289 c1 = ToLower(*s1++);
13290 c2 = ToLower(*s2++);
13291 if (c1 > c2) return 1;
13292 if (c1 < c2) return -1;
13293 if (c1 == NULLCHAR) return 0;
13302 return isupper(c) ? tolower(c) : c;
13310 return islower(c) ? toupper(c) : c;
13312 #endif /* !_amigados */
13320 if ((ret = (char *) malloc(strlen(s) + 1))) {
13327 StrSavePtr(s, savePtr)
13328 char *s, **savePtr;
13333 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13334 strcpy(*savePtr, s);
13346 clock = time((time_t *)NULL);
13347 tm = localtime(&clock);
13348 sprintf(buf, "%04d.%02d.%02d",
13349 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13350 return StrSave(buf);
13355 PositionToFEN(move, overrideCastling)
13357 char *overrideCastling;
13359 int i, j, fromX, fromY, toX, toY;
13366 whiteToPlay = (gameMode == EditPosition) ?
13367 !blackPlaysFirst : (move % 2 == 0);
13370 /* Piece placement data */
13371 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13373 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13374 if (boards[move][i][j] == EmptySquare) {
13376 } else { ChessSquare piece = boards[move][i][j];
13377 if (emptycount > 0) {
13378 if(emptycount<10) /* [HGM] can be >= 10 */
13379 *p++ = '0' + emptycount;
13380 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13383 if(PieceToChar(piece) == '+') {
13384 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13386 piece = (ChessSquare)(DEMOTED piece);
13388 *p++ = PieceToChar(piece);
13390 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13391 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13396 if (emptycount > 0) {
13397 if(emptycount<10) /* [HGM] can be >= 10 */
13398 *p++ = '0' + emptycount;
13399 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13406 /* [HGM] print Crazyhouse or Shogi holdings */
13407 if( gameInfo.holdingsWidth ) {
13408 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13410 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13411 piece = boards[move][i][BOARD_WIDTH-1];
13412 if( piece != EmptySquare )
13413 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13414 *p++ = PieceToChar(piece);
13416 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13417 piece = boards[move][BOARD_HEIGHT-i-1][0];
13418 if( piece != EmptySquare )
13419 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13420 *p++ = PieceToChar(piece);
13423 if( q == p ) *p++ = '-';
13429 *p++ = whiteToPlay ? 'w' : 'b';
13432 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13433 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13435 if(nrCastlingRights) {
13437 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13438 /* [HGM] write directly from rights */
13439 if(castlingRights[move][2] >= 0 &&
13440 castlingRights[move][0] >= 0 )
13441 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13442 if(castlingRights[move][2] >= 0 &&
13443 castlingRights[move][1] >= 0 )
13444 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13445 if(castlingRights[move][5] >= 0 &&
13446 castlingRights[move][3] >= 0 )
13447 *p++ = castlingRights[move][3] + AAA;
13448 if(castlingRights[move][5] >= 0 &&
13449 castlingRights[move][4] >= 0 )
13450 *p++ = castlingRights[move][4] + AAA;
13453 /* [HGM] write true castling rights */
13454 if( nrCastlingRights == 6 ) {
13455 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13456 castlingRights[move][2] >= 0 ) *p++ = 'K';
13457 if(castlingRights[move][1] == BOARD_LEFT &&
13458 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13459 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13460 castlingRights[move][5] >= 0 ) *p++ = 'k';
13461 if(castlingRights[move][4] == BOARD_LEFT &&
13462 castlingRights[move][5] >= 0 ) *p++ = 'q';
13465 if (q == p) *p++ = '-'; /* No castling rights */
13469 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13470 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13471 /* En passant target square */
13472 if (move > backwardMostMove) {
13473 fromX = moveList[move - 1][0] - AAA;
13474 fromY = moveList[move - 1][1] - ONE;
13475 toX = moveList[move - 1][2] - AAA;
13476 toY = moveList[move - 1][3] - ONE;
13477 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13478 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13479 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13481 /* 2-square pawn move just happened */
13483 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13494 /* [HGM] find reversible plies */
13495 { int i = 0, j=move;
13497 if (appData.debugMode) { int k;
13498 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13499 for(k=backwardMostMove; k<=forwardMostMove; k++)
13500 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13504 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13505 if( j == backwardMostMove ) i += initialRulePlies;
13506 sprintf(p, "%d ", i);
13507 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13509 /* Fullmove number */
13510 sprintf(p, "%d", (move / 2) + 1);
13512 return StrSave(buf);
13516 ParseFEN(board, blackPlaysFirst, fen)
13518 int *blackPlaysFirst;
13528 /* [HGM] by default clear Crazyhouse holdings, if present */
13529 if(gameInfo.holdingsWidth) {
13530 for(i=0; i<BOARD_HEIGHT; i++) {
13531 board[i][0] = EmptySquare; /* black holdings */
13532 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13533 board[i][1] = (ChessSquare) 0; /* black counts */
13534 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13538 /* Piece placement data */
13539 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13542 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13543 if (*p == '/') p++;
13544 emptycount = gameInfo.boardWidth - j;
13545 while (emptycount--)
13546 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13548 #if(BOARD_SIZE >= 10)
13549 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13550 p++; emptycount=10;
13551 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13552 while (emptycount--)
13553 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13555 } else if (isdigit(*p)) {
13556 emptycount = *p++ - '0';
13557 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13558 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13559 while (emptycount--)
13560 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13561 } else if (*p == '+' || isalpha(*p)) {
13562 if (j >= gameInfo.boardWidth) return FALSE;
13564 piece = CharToPiece(*++p);
13565 if(piece == EmptySquare) return FALSE; /* unknown piece */
13566 piece = (ChessSquare) (PROMOTED piece ); p++;
13567 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13568 } else piece = CharToPiece(*p++);
13570 if(piece==EmptySquare) return FALSE; /* unknown piece */
13571 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13572 piece = (ChessSquare) (PROMOTED piece);
13573 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13576 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13582 while (*p == '/' || *p == ' ') p++;
13584 /* [HGM] look for Crazyhouse holdings here */
13585 while(*p==' ') p++;
13586 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13588 if(*p == '-' ) *p++; /* empty holdings */ else {
13589 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13590 /* if we would allow FEN reading to set board size, we would */
13591 /* have to add holdings and shift the board read so far here */
13592 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13594 if((int) piece >= (int) BlackPawn ) {
13595 i = (int)piece - (int)BlackPawn;
13596 i = PieceToNumber((ChessSquare)i);
13597 if( i >= gameInfo.holdingsSize ) return FALSE;
13598 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13599 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13601 i = (int)piece - (int)WhitePawn;
13602 i = PieceToNumber((ChessSquare)i);
13603 if( i >= gameInfo.holdingsSize ) return FALSE;
13604 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13605 board[i][BOARD_WIDTH-2]++; /* black holdings */
13609 if(*p == ']') *p++;
13612 while(*p == ' ') p++;
13617 *blackPlaysFirst = FALSE;
13620 *blackPlaysFirst = TRUE;
13626 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13627 /* return the extra info in global variiables */
13629 /* set defaults in case FEN is incomplete */
13630 FENepStatus = EP_UNKNOWN;
13631 for(i=0; i<nrCastlingRights; i++ ) {
13632 FENcastlingRights[i] =
13633 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13634 } /* assume possible unless obviously impossible */
13635 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13636 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13637 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13638 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13639 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13640 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13643 while(*p==' ') p++;
13644 if(nrCastlingRights) {
13645 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13646 /* castling indicator present, so default becomes no castlings */
13647 for(i=0; i<nrCastlingRights; i++ ) {
13648 FENcastlingRights[i] = -1;
13651 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13652 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13653 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13654 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13655 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13657 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13658 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13659 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13663 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13664 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13665 FENcastlingRights[2] = whiteKingFile;
13668 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13669 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13670 FENcastlingRights[2] = whiteKingFile;
13673 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13674 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13675 FENcastlingRights[5] = blackKingFile;
13678 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13679 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13680 FENcastlingRights[5] = blackKingFile;
13683 default: /* FRC castlings */
13684 if(c >= 'a') { /* black rights */
13685 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13686 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13687 if(i == BOARD_RGHT) break;
13688 FENcastlingRights[5] = i;
13690 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13691 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13693 FENcastlingRights[3] = c;
13695 FENcastlingRights[4] = c;
13696 } else { /* white rights */
13697 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13698 if(board[0][i] == WhiteKing) break;
13699 if(i == BOARD_RGHT) break;
13700 FENcastlingRights[2] = i;
13701 c -= AAA - 'a' + 'A';
13702 if(board[0][c] >= WhiteKing) break;
13704 FENcastlingRights[0] = c;
13706 FENcastlingRights[1] = c;
13710 if (appData.debugMode) {
13711 fprintf(debugFP, "FEN castling rights:");
13712 for(i=0; i<nrCastlingRights; i++)
13713 fprintf(debugFP, " %d", FENcastlingRights[i]);
13714 fprintf(debugFP, "\n");
13717 while(*p==' ') p++;
13720 /* read e.p. field in games that know e.p. capture */
13721 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13722 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13724 p++; FENepStatus = EP_NONE;
13726 char c = *p++ - AAA;
13728 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13729 if(*p >= '0' && *p <='9') *p++;
13735 if(sscanf(p, "%d", &i) == 1) {
13736 FENrulePlies = i; /* 50-move ply counter */
13737 /* (The move number is still ignored) */
13744 EditPositionPasteFEN(char *fen)
13747 Board initial_position;
13749 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13750 DisplayError(_("Bad FEN position in clipboard"), 0);
13753 int savedBlackPlaysFirst = blackPlaysFirst;
13754 EditPositionEvent();
13755 blackPlaysFirst = savedBlackPlaysFirst;
13756 CopyBoard(boards[0], initial_position);
13757 /* [HGM] copy FEN attributes as well */
13759 initialRulePlies = FENrulePlies;
13760 epStatus[0] = FENepStatus;
13761 for( i=0; i<nrCastlingRights; i++ )
13762 castlingRights[0][i] = FENcastlingRights[i];
13764 EditPositionDone();
13765 DisplayBothClocks();
13766 DrawPosition(FALSE, boards[currentMove]);