2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163 Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187 void DisplayAnalysis P((void));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
223 extern void ConsoleCreate();
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
242 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
246 int opponentKibitzes;
247 int lastSavedGame; /* [HGM] save: ID of game */
248 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
249 extern int chatCount;
252 /* States for ics_getting_history */
254 #define H_REQUESTED 1
255 #define H_GOT_REQ_HEADER 2
256 #define H_GOT_UNREQ_HEADER 3
257 #define H_GETTING_MOVES 4
258 #define H_GOT_UNWANTED_HEADER 5
260 /* whosays values for GameEnds */
269 /* Maximum number of games in a cmail message */
270 #define CMAIL_MAX_GAMES 20
272 /* Different types of move when calling RegisterMove */
274 #define CMAIL_RESIGN 1
276 #define CMAIL_ACCEPT 3
278 /* Different types of result to remember for each game */
279 #define CMAIL_NOT_RESULT 0
280 #define CMAIL_OLD_RESULT 1
281 #define CMAIL_NEW_RESULT 2
283 /* Telnet protocol constants */
294 static char * safeStrCpy( char * dst, const char * src, size_t count )
296 assert( dst != NULL );
297 assert( src != NULL );
300 strncpy( dst, src, count );
301 dst[ count-1 ] = '\0';
305 /* Some compiler can't cast u64 to double
306 * This function do the job for us:
308 * We use the highest bit for cast, this only
309 * works if the highest bit is not
310 * in use (This should not happen)
312 * We used this for all compiler
315 u64ToDouble(u64 value)
318 u64 tmp = value & u64Const(0x7fffffffffffffff);
319 r = (double)(s64)tmp;
320 if (value & u64Const(0x8000000000000000))
321 r += 9.2233720368547758080e18; /* 2^63 */
325 /* Fake up flags for now, as we aren't keeping track of castling
326 availability yet. [HGM] Change of logic: the flag now only
327 indicates the type of castlings allowed by the rule of the game.
328 The actual rights themselves are maintained in the array
329 castlingRights, as part of the game history, and are not probed
335 int flags = F_ALL_CASTLE_OK;
336 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
337 switch (gameInfo.variant) {
339 flags &= ~F_ALL_CASTLE_OK;
340 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
341 flags |= F_IGNORE_CHECK;
343 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
346 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
348 case VariantKriegspiel:
349 flags |= F_KRIEGSPIEL_CAPTURE;
351 case VariantCapaRandom:
352 case VariantFischeRandom:
353 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
354 case VariantNoCastle:
355 case VariantShatranj:
357 flags &= ~F_ALL_CASTLE_OK;
365 FILE *gameFileFP, *debugFP;
368 [AS] Note: sometimes, the sscanf() function is used to parse the input
369 into a fixed-size buffer. Because of this, we must be prepared to
370 receive strings as long as the size of the input buffer, which is currently
371 set to 4K for Windows and 8K for the rest.
372 So, we must either allocate sufficiently large buffers here, or
373 reduce the size of the input buffer in the input reading part.
376 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
377 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
378 char thinkOutput1[MSG_SIZ*10];
380 ChessProgramState first, second;
382 /* premove variables */
385 int premoveFromX = 0;
386 int premoveFromY = 0;
387 int premovePromoChar = 0;
389 Boolean alarmSounded;
390 /* end premove variables */
392 char *ics_prefix = "$";
393 int ics_type = ICS_GENERIC;
395 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
396 int pauseExamForwardMostMove = 0;
397 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
398 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
399 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
400 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
401 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
402 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
403 int whiteFlag = FALSE, blackFlag = FALSE;
404 int userOfferedDraw = FALSE;
405 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
406 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
407 int cmailMoveType[CMAIL_MAX_GAMES];
408 long ics_clock_paused = 0;
409 ProcRef icsPR = NoProc, cmailPR = NoProc;
410 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
411 GameMode gameMode = BeginningOfGame;
412 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
413 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
414 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
415 int hiddenThinkOutputState = 0; /* [AS] */
416 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
417 int adjudicateLossPlies = 6;
418 char white_holding[64], black_holding[64];
419 TimeMark lastNodeCountTime;
420 long lastNodeCount=0;
421 int have_sent_ICS_logon = 0;
423 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
424 long timeControl_2; /* [AS] Allow separate time controls */
425 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
426 long timeRemaining[2][MAX_MOVES];
428 TimeMark programStartTime;
429 char ics_handle[MSG_SIZ];
430 int have_set_title = 0;
432 /* animateTraining preserves the state of appData.animate
433 * when Training mode is activated. This allows the
434 * response to be animated when appData.animate == TRUE and
435 * appData.animateDragging == TRUE.
437 Boolean animateTraining;
443 Board boards[MAX_MOVES];
444 /* [HGM] Following 7 needed for accurate legality tests: */
445 signed char epStatus[MAX_MOVES];
446 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
447 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
448 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
449 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int initialRulePlies, FENrulePlies;
452 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
455 int mute; // mute all sounds
457 ChessSquare FIDEArray[2][BOARD_SIZE] = {
458 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
459 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
460 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
461 BlackKing, BlackBishop, BlackKnight, BlackRook }
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
465 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
466 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
467 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
468 BlackKing, BlackKing, BlackKnight, BlackRook }
471 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
472 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
473 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
474 { BlackRook, BlackMan, BlackBishop, BlackQueen,
475 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
479 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
480 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
481 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
482 BlackKing, BlackBishop, BlackKnight, BlackRook }
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
486 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
487 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
488 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
489 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {
495 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
496 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
497 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
498 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
502 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
503 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
505 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
509 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
512 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
515 ChessSquare GreatArray[2][BOARD_SIZE] = {
516 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
517 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
518 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
519 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
522 ChessSquare JanusArray[2][BOARD_SIZE] = {
523 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
524 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
525 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
526 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
530 ChessSquare GothicArray[2][BOARD_SIZE] = {
531 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
532 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
533 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
534 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
537 #define GothicArray CapablancaArray
541 ChessSquare FalconArray[2][BOARD_SIZE] = {
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
543 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
545 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
548 #define FalconArray CapablancaArray
551 #else // !(BOARD_SIZE>=10)
552 #define XiangqiPosition FIDEArray
553 #define CapablancaArray FIDEArray
554 #define GothicArray FIDEArray
555 #define GreatArray FIDEArray
556 #endif // !(BOARD_SIZE>=10)
559 ChessSquare CourierArray[2][BOARD_SIZE] = {
560 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
561 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
563 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
565 #else // !(BOARD_SIZE>=12)
566 #define CourierArray CapablancaArray
567 #endif // !(BOARD_SIZE>=12)
570 Board initialPosition;
573 /* Convert str to a rating. Checks for special cases of "----",
575 "++++", etc. Also strips ()'s */
577 string_to_rating(str)
580 while(*str && !isdigit(*str)) ++str;
582 return 0; /* One of the special "no rating" cases */
590 /* Init programStats */
591 programStats.movelist[0] = 0;
592 programStats.depth = 0;
593 programStats.nr_moves = 0;
594 programStats.moves_left = 0;
595 programStats.nodes = 0;
596 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
597 programStats.score = 0;
598 programStats.got_only_move = 0;
599 programStats.got_fail = 0;
600 programStats.line_is_book = 0;
606 int matched, min, sec;
608 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
610 GetTimeMark(&programStartTime);
611 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
614 programStats.ok_to_send = 1;
615 programStats.seen_stat = 0;
618 * Initialize game list
624 * Internet chess server status
626 if (appData.icsActive) {
627 appData.matchMode = FALSE;
628 appData.matchGames = 0;
630 appData.noChessProgram = !appData.zippyPlay;
632 appData.zippyPlay = FALSE;
633 appData.zippyTalk = FALSE;
634 appData.noChessProgram = TRUE;
636 if (*appData.icsHelper != NULLCHAR) {
637 appData.useTelnet = TRUE;
638 appData.telnetProgram = appData.icsHelper;
641 appData.zippyTalk = appData.zippyPlay = FALSE;
644 /* [AS] Initialize pv info list [HGM] and game state */
648 for( i=0; i<MAX_MOVES; i++ ) {
649 pvInfoList[i].depth = -1;
651 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
656 * Parse timeControl resource
658 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
659 appData.movesPerSession)) {
661 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
662 DisplayFatalError(buf, 0, 2);
666 * Parse searchTime resource
668 if (*appData.searchTime != NULLCHAR) {
669 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
671 searchTime = min * 60;
672 } else if (matched == 2) {
673 searchTime = min * 60 + sec;
676 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
677 DisplayFatalError(buf, 0, 2);
681 /* [AS] Adjudication threshold */
682 adjudicateLossThreshold = appData.adjudicateLossThreshold;
684 first.which = "first";
685 second.which = "second";
686 first.maybeThinking = second.maybeThinking = FALSE;
687 first.pr = second.pr = NoProc;
688 first.isr = second.isr = NULL;
689 first.sendTime = second.sendTime = 2;
690 first.sendDrawOffers = 1;
691 if (appData.firstPlaysBlack) {
692 first.twoMachinesColor = "black\n";
693 second.twoMachinesColor = "white\n";
695 first.twoMachinesColor = "white\n";
696 second.twoMachinesColor = "black\n";
698 first.program = appData.firstChessProgram;
699 second.program = appData.secondChessProgram;
700 first.host = appData.firstHost;
701 second.host = appData.secondHost;
702 first.dir = appData.firstDirectory;
703 second.dir = appData.secondDirectory;
704 first.other = &second;
705 second.other = &first;
706 first.initString = appData.initString;
707 second.initString = appData.secondInitString;
708 first.computerString = appData.firstComputerString;
709 second.computerString = appData.secondComputerString;
710 first.useSigint = second.useSigint = TRUE;
711 first.useSigterm = second.useSigterm = TRUE;
712 first.reuse = appData.reuseFirst;
713 second.reuse = appData.reuseSecond;
714 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
715 second.nps = appData.secondNPS;
716 first.useSetboard = second.useSetboard = FALSE;
717 first.useSAN = second.useSAN = FALSE;
718 first.usePing = second.usePing = FALSE;
719 first.lastPing = second.lastPing = 0;
720 first.lastPong = second.lastPong = 0;
721 first.usePlayother = second.usePlayother = FALSE;
722 first.useColors = second.useColors = TRUE;
723 first.useUsermove = second.useUsermove = FALSE;
724 first.sendICS = second.sendICS = FALSE;
725 first.sendName = second.sendName = appData.icsActive;
726 first.sdKludge = second.sdKludge = FALSE;
727 first.stKludge = second.stKludge = FALSE;
728 TidyProgramName(first.program, first.host, first.tidy);
729 TidyProgramName(second.program, second.host, second.tidy);
730 first.matchWins = second.matchWins = 0;
731 strcpy(first.variants, appData.variant);
732 strcpy(second.variants, appData.variant);
733 first.analysisSupport = second.analysisSupport = 2; /* detect */
734 first.analyzing = second.analyzing = FALSE;
735 first.initDone = second.initDone = FALSE;
737 /* New features added by Tord: */
738 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
739 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
740 /* End of new features added by Tord. */
741 first.fenOverride = appData.fenOverride1;
742 second.fenOverride = appData.fenOverride2;
744 /* [HGM] time odds: set factor for each machine */
745 first.timeOdds = appData.firstTimeOdds;
746 second.timeOdds = appData.secondTimeOdds;
748 if(appData.timeOddsMode) {
749 norm = first.timeOdds;
750 if(norm > second.timeOdds) norm = second.timeOdds;
752 first.timeOdds /= norm;
753 second.timeOdds /= norm;
756 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
757 first.accumulateTC = appData.firstAccumulateTC;
758 second.accumulateTC = appData.secondAccumulateTC;
759 first.maxNrOfSessions = second.maxNrOfSessions = 1;
762 first.debug = second.debug = FALSE;
763 first.supportsNPS = second.supportsNPS = UNKNOWN;
766 first.optionSettings = appData.firstOptions;
767 second.optionSettings = appData.secondOptions;
769 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
770 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
771 first.isUCI = appData.firstIsUCI; /* [AS] */
772 second.isUCI = appData.secondIsUCI; /* [AS] */
773 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
774 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
776 if (appData.firstProtocolVersion > PROTOVER ||
777 appData.firstProtocolVersion < 1) {
779 sprintf(buf, _("protocol version %d not supported"),
780 appData.firstProtocolVersion);
781 DisplayFatalError(buf, 0, 2);
783 first.protocolVersion = appData.firstProtocolVersion;
786 if (appData.secondProtocolVersion > PROTOVER ||
787 appData.secondProtocolVersion < 1) {
789 sprintf(buf, _("protocol version %d not supported"),
790 appData.secondProtocolVersion);
791 DisplayFatalError(buf, 0, 2);
793 second.protocolVersion = appData.secondProtocolVersion;
796 if (appData.icsActive) {
797 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
798 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
799 appData.clockMode = FALSE;
800 first.sendTime = second.sendTime = 0;
804 /* Override some settings from environment variables, for backward
805 compatibility. Unfortunately it's not feasible to have the env
806 vars just set defaults, at least in xboard. Ugh.
808 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
813 if (appData.noChessProgram) {
814 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
815 sprintf(programVersion, "%s", PACKAGE_STRING);
817 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
818 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
819 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
822 if (!appData.icsActive) {
824 /* Check for variants that are supported only in ICS mode,
825 or not at all. Some that are accepted here nevertheless
826 have bugs; see comments below.
828 VariantClass variant = StringToVariant(appData.variant);
830 case VariantBughouse: /* need four players and two boards */
831 case VariantKriegspiel: /* need to hide pieces and move details */
832 /* case VariantFischeRandom: (Fabien: moved below) */
833 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
834 DisplayFatalError(buf, 0, 2);
838 case VariantLoadable:
848 sprintf(buf, _("Unknown variant name %s"), appData.variant);
849 DisplayFatalError(buf, 0, 2);
852 case VariantXiangqi: /* [HGM] repetition rules not implemented */
853 case VariantFairy: /* [HGM] TestLegality definitely off! */
854 case VariantGothic: /* [HGM] should work */
855 case VariantCapablanca: /* [HGM] should work */
856 case VariantCourier: /* [HGM] initial forced moves not implemented */
857 case VariantShogi: /* [HGM] drops not tested for legality */
858 case VariantKnightmate: /* [HGM] should work */
859 case VariantCylinder: /* [HGM] untested */
860 case VariantFalcon: /* [HGM] untested */
861 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
862 offboard interposition not understood */
863 case VariantNormal: /* definitely works! */
864 case VariantWildCastle: /* pieces not automatically shuffled */
865 case VariantNoCastle: /* pieces not automatically shuffled */
866 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
867 case VariantLosers: /* should work except for win condition,
868 and doesn't know captures are mandatory */
869 case VariantSuicide: /* should work except for win condition,
870 and doesn't know captures are mandatory */
871 case VariantGiveaway: /* should work except for win condition,
872 and doesn't know captures are mandatory */
873 case VariantTwoKings: /* should work */
874 case VariantAtomic: /* should work except for win condition */
875 case Variant3Check: /* should work except for win condition */
876 case VariantShatranj: /* should work except for all win conditions */
877 case VariantBerolina: /* might work if TestLegality is off */
878 case VariantCapaRandom: /* should work */
879 case VariantJanus: /* should work */
880 case VariantSuper: /* experimental */
881 case VariantGreat: /* experimental, requires legality testing to be off */
886 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
887 InitEngineUCI( installDir, &second );
890 int NextIntegerFromString( char ** str, long * value )
895 while( *s == ' ' || *s == '\t' ) {
901 if( *s >= '0' && *s <= '9' ) {
902 while( *s >= '0' && *s <= '9' ) {
903 *value = *value * 10 + (*s - '0');
915 int NextTimeControlFromString( char ** str, long * value )
918 int result = NextIntegerFromString( str, &temp );
921 *value = temp * 60; /* Minutes */
924 result = NextIntegerFromString( str, &temp );
925 *value += temp; /* Seconds */
932 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
933 { /* [HGM] routine added to read '+moves/time' for secondary time control */
934 int result = -1; long temp, temp2;
936 if(**str != '+') return -1; // old params remain in force!
938 if( NextTimeControlFromString( str, &temp ) ) return -1;
941 /* time only: incremental or sudden-death time control */
942 if(**str == '+') { /* increment follows; read it */
944 if(result = NextIntegerFromString( str, &temp2)) return -1;
947 *moves = 0; *tc = temp * 1000;
949 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
951 (*str)++; /* classical time control */
952 result = NextTimeControlFromString( str, &temp2);
961 int GetTimeQuota(int movenr)
962 { /* [HGM] get time to add from the multi-session time-control string */
963 int moves=1; /* kludge to force reading of first session */
964 long time, increment;
965 char *s = fullTimeControlString;
967 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
969 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
970 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
971 if(movenr == -1) return time; /* last move before new session */
972 if(!moves) return increment; /* current session is incremental */
973 if(movenr >= 0) movenr -= moves; /* we already finished this session */
974 } while(movenr >= -1); /* try again for next session */
976 return 0; // no new time quota on this move
980 ParseTimeControl(tc, ti, mps)
989 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
992 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
993 else sprintf(buf, "+%s+%d", tc, ti);
996 sprintf(buf, "+%d/%s", mps, tc);
997 else sprintf(buf, "+%s", tc);
999 fullTimeControlString = StrSave(buf);
1001 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1006 /* Parse second time control */
1009 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1017 timeControl_2 = tc2 * 1000;
1027 timeControl = tc1 * 1000;
1030 timeIncrement = ti * 1000; /* convert to ms */
1031 movesPerSession = 0;
1034 movesPerSession = mps;
1042 if (appData.debugMode) {
1043 fprintf(debugFP, "%s\n", programVersion);
1046 if (appData.matchGames > 0) {
1047 appData.matchMode = TRUE;
1048 } else if (appData.matchMode) {
1049 appData.matchGames = 1;
1051 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052 appData.matchGames = appData.sameColorGames;
1053 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1058 if (appData.noChessProgram || first.protocolVersion == 1) {
1061 /* kludge: allow timeout for initial "feature" commands */
1063 DisplayMessage("", _("Starting chess program"));
1064 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1069 InitBackEnd3 P((void))
1071 GameMode initialMode;
1075 InitChessProgram(&first, startedFromSetupPosition);
1078 if (appData.icsActive) {
1080 /* [DM] Make a console window if needed [HGM] merged ifs */
1085 if (*appData.icsCommPort != NULLCHAR) {
1086 sprintf(buf, _("Could not open comm port %s"),
1087 appData.icsCommPort);
1089 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1090 appData.icsHost, appData.icsPort);
1092 DisplayFatalError(buf, err, 1);
1097 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1099 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100 } else if (appData.noChessProgram) {
1106 if (*appData.cmailGameName != NULLCHAR) {
1108 OpenLoopback(&cmailPR);
1110 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1114 DisplayMessage("", "");
1115 if (StrCaseCmp(appData.initialMode, "") == 0) {
1116 initialMode = BeginningOfGame;
1117 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118 initialMode = TwoMachinesPlay;
1119 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120 initialMode = AnalyzeFile;
1121 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122 initialMode = AnalyzeMode;
1123 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124 initialMode = MachinePlaysWhite;
1125 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126 initialMode = MachinePlaysBlack;
1127 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128 initialMode = EditGame;
1129 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130 initialMode = EditPosition;
1131 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132 initialMode = Training;
1134 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135 DisplayFatalError(buf, 0, 2);
1139 if (appData.matchMode) {
1140 /* Set up machine vs. machine match */
1141 if (appData.noChessProgram) {
1142 DisplayFatalError(_("Can't have a match with no chess programs"),
1148 if (*appData.loadGameFile != NULLCHAR) {
1149 int index = appData.loadGameIndex; // [HGM] autoinc
1150 if(index<0) lastIndex = index = 1;
1151 if (!LoadGameFromFile(appData.loadGameFile,
1153 appData.loadGameFile, FALSE)) {
1154 DisplayFatalError(_("Bad game file"), 0, 1);
1157 } else if (*appData.loadPositionFile != NULLCHAR) {
1158 int index = appData.loadPositionIndex; // [HGM] autoinc
1159 if(index<0) lastIndex = index = 1;
1160 if (!LoadPositionFromFile(appData.loadPositionFile,
1162 appData.loadPositionFile)) {
1163 DisplayFatalError(_("Bad position file"), 0, 1);
1168 } else if (*appData.cmailGameName != NULLCHAR) {
1169 /* Set up cmail mode */
1170 ReloadCmailMsgEvent(TRUE);
1172 /* Set up other modes */
1173 if (initialMode == AnalyzeFile) {
1174 if (*appData.loadGameFile == NULLCHAR) {
1175 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1179 if (*appData.loadGameFile != NULLCHAR) {
1180 (void) LoadGameFromFile(appData.loadGameFile,
1181 appData.loadGameIndex,
1182 appData.loadGameFile, TRUE);
1183 } else if (*appData.loadPositionFile != NULLCHAR) {
1184 (void) LoadPositionFromFile(appData.loadPositionFile,
1185 appData.loadPositionIndex,
1186 appData.loadPositionFile);
1187 /* [HGM] try to make self-starting even after FEN load */
1188 /* to allow automatic setup of fairy variants with wtm */
1189 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190 gameMode = BeginningOfGame;
1191 setboardSpoiledMachineBlack = 1;
1193 /* [HGM] loadPos: make that every new game uses the setup */
1194 /* from file as long as we do not switch variant */
1195 if(!blackPlaysFirst) { int i;
1196 startedFromPositionFile = TRUE;
1197 CopyBoard(filePosition, boards[0]);
1198 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1201 if (initialMode == AnalyzeMode) {
1202 if (appData.noChessProgram) {
1203 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1206 if (appData.icsActive) {
1207 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1211 } else if (initialMode == AnalyzeFile) {
1212 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213 ShowThinkingEvent();
1215 AnalysisPeriodicEvent(1);
1216 } else if (initialMode == MachinePlaysWhite) {
1217 if (appData.noChessProgram) {
1218 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1222 if (appData.icsActive) {
1223 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1227 MachineWhiteEvent();
1228 } else if (initialMode == MachinePlaysBlack) {
1229 if (appData.noChessProgram) {
1230 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1239 MachineBlackEvent();
1240 } else if (initialMode == TwoMachinesPlay) {
1241 if (appData.noChessProgram) {
1242 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1252 } else if (initialMode == EditGame) {
1254 } else if (initialMode == EditPosition) {
1255 EditPositionEvent();
1256 } else if (initialMode == Training) {
1257 if (*appData.loadGameFile == NULLCHAR) {
1258 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1267 * Establish will establish a contact to a remote host.port.
1268 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269 * used to talk to the host.
1270 * Returns 0 if okay, error code if not.
1277 if (*appData.icsCommPort != NULLCHAR) {
1278 /* Talk to the host through a serial comm port */
1279 return OpenCommPort(appData.icsCommPort, &icsPR);
1281 } else if (*appData.gateway != NULLCHAR) {
1282 if (*appData.remoteShell == NULLCHAR) {
1283 /* Use the rcmd protocol to run telnet program on a gateway host */
1284 snprintf(buf, sizeof(buf), "%s %s %s",
1285 appData.telnetProgram, appData.icsHost, appData.icsPort);
1286 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1289 /* Use the rsh program to run telnet program on a gateway host */
1290 if (*appData.remoteUser == NULLCHAR) {
1291 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292 appData.gateway, appData.telnetProgram,
1293 appData.icsHost, appData.icsPort);
1295 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296 appData.remoteShell, appData.gateway,
1297 appData.remoteUser, appData.telnetProgram,
1298 appData.icsHost, appData.icsPort);
1300 return StartChildProcess(buf, "", &icsPR);
1303 } else if (appData.useTelnet) {
1304 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1307 /* TCP socket interface differs somewhat between
1308 Unix and NT; handle details in the front end.
1310 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1315 show_bytes(fp, buf, count)
1321 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322 fprintf(fp, "\\%03o", *buf & 0xff);
1331 /* Returns an errno value */
1333 OutputMaybeTelnet(pr, message, count, outError)
1339 char buf[8192], *p, *q, *buflim;
1340 int left, newcount, outcount;
1342 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343 *appData.gateway != NULLCHAR) {
1344 if (appData.debugMode) {
1345 fprintf(debugFP, ">ICS: ");
1346 show_bytes(debugFP, message, count);
1347 fprintf(debugFP, "\n");
1349 return OutputToProcess(pr, message, count, outError);
1352 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1359 if (appData.debugMode) {
1360 fprintf(debugFP, ">ICS: ");
1361 show_bytes(debugFP, buf, newcount);
1362 fprintf(debugFP, "\n");
1364 outcount = OutputToProcess(pr, buf, newcount, outError);
1365 if (outcount < newcount) return -1; /* to be sure */
1372 } else if (((unsigned char) *p) == TN_IAC) {
1373 *q++ = (char) TN_IAC;
1380 if (appData.debugMode) {
1381 fprintf(debugFP, ">ICS: ");
1382 show_bytes(debugFP, buf, newcount);
1383 fprintf(debugFP, "\n");
1385 outcount = OutputToProcess(pr, buf, newcount, outError);
1386 if (outcount < newcount) return -1; /* to be sure */
1391 read_from_player(isr, closure, message, count, error)
1398 int outError, outCount;
1399 static int gotEof = 0;
1401 /* Pass data read from player on to ICS */
1404 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405 if (outCount < count) {
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1408 } else if (count < 0) {
1409 RemoveInputSource(isr);
1410 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411 } else if (gotEof++ > 0) {
1412 RemoveInputSource(isr);
1413 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1419 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420 SendToICS("date\n");
1421 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1427 char buffer[MSG_SIZ];
1430 va_start(args, format);
1431 vsnprintf(buffer, sizeof(buffer), format, args);
1432 buffer[sizeof(buffer)-1] = '\0';
1441 int count, outCount, outError;
1443 if (icsPR == NULL) return;
1446 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447 if (outCount < count) {
1448 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1452 /* This is used for sending logon scripts to the ICS. Sending
1453 without a delay causes problems when using timestamp on ICC
1454 (at least on my machine). */
1456 SendToICSDelayed(s,msdelay)
1460 int count, outCount, outError;
1462 if (icsPR == NULL) return;
1465 if (appData.debugMode) {
1466 fprintf(debugFP, ">ICS: ");
1467 show_bytes(debugFP, s, count);
1468 fprintf(debugFP, "\n");
1470 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1472 if (outCount < count) {
1473 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1478 /* Remove all highlighting escape sequences in s
1479 Also deletes any suffix starting with '('
1482 StripHighlightAndTitle(s)
1485 static char retbuf[MSG_SIZ];
1488 while (*s != NULLCHAR) {
1489 while (*s == '\033') {
1490 while (*s != NULLCHAR && !isalpha(*s)) s++;
1491 if (*s != NULLCHAR) s++;
1493 while (*s != NULLCHAR && *s != '\033') {
1494 if (*s == '(' || *s == '[') {
1505 /* Remove all highlighting escape sequences in s */
1510 static char retbuf[MSG_SIZ];
1513 while (*s != NULLCHAR) {
1514 while (*s == '\033') {
1515 while (*s != NULLCHAR && !isalpha(*s)) s++;
1516 if (*s != NULLCHAR) s++;
1518 while (*s != NULLCHAR && *s != '\033') {
1526 char *variantNames[] = VARIANT_NAMES;
1531 return variantNames[v];
1535 /* Identify a variant from the strings the chess servers use or the
1536 PGN Variant tag names we use. */
1543 VariantClass v = VariantNormal;
1544 int i, found = FALSE;
1549 /* [HGM] skip over optional board-size prefixes */
1550 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552 while( *e++ != '_');
1555 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1559 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560 if (StrCaseStr(e, variantNames[i])) {
1561 v = (VariantClass) i;
1568 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569 || StrCaseStr(e, "wild/fr")
1570 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571 v = VariantFischeRandom;
1572 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573 (i = 1, p = StrCaseStr(e, "w"))) {
1575 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1582 case 0: /* FICS only, actually */
1584 /* Castling legal even if K starts on d-file */
1585 v = VariantWildCastle;
1590 /* Castling illegal even if K & R happen to start in
1591 normal positions. */
1592 v = VariantNoCastle;
1605 /* Castling legal iff K & R start in normal positions */
1611 /* Special wilds for position setup; unclear what to do here */
1612 v = VariantLoadable;
1615 /* Bizarre ICC game */
1616 v = VariantTwoKings;
1619 v = VariantKriegspiel;
1625 v = VariantFischeRandom;
1628 v = VariantCrazyhouse;
1631 v = VariantBughouse;
1637 /* Not quite the same as FICS suicide! */
1638 v = VariantGiveaway;
1644 v = VariantShatranj;
1647 /* Temporary names for future ICC types. The name *will* change in
1648 the next xboard/WinBoard release after ICC defines it. */
1686 v = VariantCapablanca;
1689 v = VariantKnightmate;
1695 v = VariantCylinder;
1701 v = VariantCapaRandom;
1704 v = VariantBerolina;
1716 /* Found "wild" or "w" in the string but no number;
1717 must assume it's normal chess. */
1721 sprintf(buf, _("Unknown wild type %d"), wnum);
1722 DisplayError(buf, 0);
1728 if (appData.debugMode) {
1729 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730 e, wnum, VariantName(v));
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739 advance *index beyond it, and set leftover_start to the new value of
1740 *index; else return FALSE. If pattern contains the character '*', it
1741 matches any sequence of characters not containing '\r', '\n', or the
1742 character following the '*' (if any), and the matched sequence(s) are
1743 copied into star_match.
1746 looking_at(buf, index, pattern)
1751 char *bufp = &buf[*index], *patternp = pattern;
1753 char *matchp = star_match[0];
1756 if (*patternp == NULLCHAR) {
1757 *index = leftover_start = bufp - buf;
1761 if (*bufp == NULLCHAR) return FALSE;
1762 if (*patternp == '*') {
1763 if (*bufp == *(patternp + 1)) {
1765 matchp = star_match[++star_count];
1769 } else if (*bufp == '\n' || *bufp == '\r') {
1771 if (*patternp == NULLCHAR)
1776 *matchp++ = *bufp++;
1780 if (*patternp != *bufp) return FALSE;
1787 SendToPlayer(data, length)
1791 int error, outCount;
1792 outCount = OutputToProcess(NoProc, data, length, &error);
1793 if (outCount < length) {
1794 DisplayFatalError(_("Error writing to display"), error, 1);
1799 PackHolding(packed, holding)
1811 switch (runlength) {
1822 sprintf(q, "%d", runlength);
1834 /* Telnet protocol requests from the front end */
1836 TelnetRequest(ddww, option)
1837 unsigned char ddww, option;
1839 unsigned char msg[3];
1840 int outCount, outError;
1842 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1844 if (appData.debugMode) {
1845 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861 sprintf(buf1, "%d", ddww);
1870 sprintf(buf2, "%d", option);
1873 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1878 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DO, TN_ECHO);
1894 if (!appData.icsActive) return;
1895 TelnetRequest(TN_DONT, TN_ECHO);
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1901 /* put the holdings sent to us by the server on the board holdings area */
1902 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1906 if(gameInfo.holdingsWidth < 2) return;
1908 if( (int)lowestPiece >= BlackPawn ) {
1911 holdingsStartRow = BOARD_HEIGHT-1;
1914 holdingsColumn = BOARD_WIDTH-1;
1915 countsColumn = BOARD_WIDTH-2;
1916 holdingsStartRow = 0;
1920 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921 board[i][holdingsColumn] = EmptySquare;
1922 board[i][countsColumn] = (ChessSquare) 0;
1924 while( (p=*holdings++) != NULLCHAR ) {
1925 piece = CharToPiece( ToUpper(p) );
1926 if(piece == EmptySquare) continue;
1927 /*j = (int) piece - (int) WhitePawn;*/
1928 j = PieceToNumber(piece);
1929 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930 if(j < 0) continue; /* should not happen */
1931 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933 board[holdingsStartRow+j*direction][countsColumn]++;
1940 VariantSwitch(Board board, VariantClass newVariant)
1942 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1944 startedFromPositionFile = FALSE;
1945 if(gameInfo.variant == newVariant) return;
1947 /* [HGM] This routine is called each time an assignment is made to
1948 * gameInfo.variant during a game, to make sure the board sizes
1949 * are set to match the new variant. If that means adding or deleting
1950 * holdings, we shift the playing board accordingly
1951 * This kludge is needed because in ICS observe mode, we get boards
1952 * of an ongoing game without knowing the variant, and learn about the
1953 * latter only later. This can be because of the move list we requested,
1954 * in which case the game history is refilled from the beginning anyway,
1955 * but also when receiving holdings of a crazyhouse game. In the latter
1956 * case we want to add those holdings to the already received position.
1960 if (appData.debugMode) {
1961 fprintf(debugFP, "Switch board from %s to %s\n",
1962 VariantName(gameInfo.variant), VariantName(newVariant));
1963 setbuf(debugFP, NULL);
1965 shuffleOpenings = 0; /* [HGM] shuffle */
1966 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1970 newWidth = 9; newHeight = 9;
1971 gameInfo.holdingsSize = 7;
1972 case VariantBughouse:
1973 case VariantCrazyhouse:
1974 newHoldingsWidth = 2; break;
1978 newHoldingsWidth = 2;
1979 gameInfo.holdingsSize = 8;
1982 case VariantCapablanca:
1983 case VariantCapaRandom:
1986 newHoldingsWidth = gameInfo.holdingsSize = 0;
1989 if(newWidth != gameInfo.boardWidth ||
1990 newHeight != gameInfo.boardHeight ||
1991 newHoldingsWidth != gameInfo.holdingsWidth ) {
1993 /* shift position to new playing area, if needed */
1994 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995 for(i=0; i<BOARD_HEIGHT; i++)
1996 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1999 for(i=0; i<newHeight; i++) {
2000 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2003 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004 for(i=0; i<BOARD_HEIGHT; i++)
2005 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2009 gameInfo.boardWidth = newWidth;
2010 gameInfo.boardHeight = newHeight;
2011 gameInfo.holdingsWidth = newHoldingsWidth;
2012 gameInfo.variant = newVariant;
2013 InitDrawingSizes(-2, 0);
2014 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2015 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2017 DrawPosition(TRUE, boards[currentMove]);
2020 static int loggedOn = FALSE;
2022 /*-- Game start info cache: --*/
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2035 read_from_ics(isr, closure, data, count, error)
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2052 static int started = STARTED_NONE;
2053 static char parse[20000];
2054 static int parse_pos = 0;
2055 static char buf[BUF_SIZE + 1];
2056 static int firstTime = TRUE, intfSet = FALSE;
2057 static ColorClass prevColor = ColorNormal;
2058 static int savingComment = FALSE;
2064 int backup; /* [DM] For zippy color lines */
2066 char talker[MSG_SIZ]; // [HGM] chat
2069 if (appData.debugMode) {
2071 fprintf(debugFP, "<ICS: ");
2072 show_bytes(debugFP, data, count);
2073 fprintf(debugFP, "\n");
2077 if (appData.debugMode) { int f = forwardMostMove;
2078 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2079 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2082 /* If last read ended with a partial line that we couldn't parse,
2083 prepend it to the new read and try again. */
2084 if (leftover_len > 0) {
2085 for (i=0; i<leftover_len; i++)
2086 buf[i] = buf[leftover_start + i];
2089 /* Copy in new characters, removing nulls and \r's */
2090 buf_len = leftover_len;
2091 for (i = 0; i < count; i++) {
2092 if (data[i] != NULLCHAR && data[i] != '\r')
2093 buf[buf_len++] = data[i];
2094 if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2095 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2096 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2097 if(buf_len == 0 || buf[buf_len-1] != ' ')
2098 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2102 buf[buf_len] = NULLCHAR;
2103 next_out = leftover_len;
2107 while (i < buf_len) {
2108 /* Deal with part of the TELNET option negotiation
2109 protocol. We refuse to do anything beyond the
2110 defaults, except that we allow the WILL ECHO option,
2111 which ICS uses to turn off password echoing when we are
2112 directly connected to it. We reject this option
2113 if localLineEditing mode is on (always on in xboard)
2114 and we are talking to port 23, which might be a real
2115 telnet server that will try to keep WILL ECHO on permanently.
2117 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2118 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2119 unsigned char option;
2121 switch ((unsigned char) buf[++i]) {
2123 if (appData.debugMode)
2124 fprintf(debugFP, "\n<WILL ");
2125 switch (option = (unsigned char) buf[++i]) {
2127 if (appData.debugMode)
2128 fprintf(debugFP, "ECHO ");
2129 /* Reply only if this is a change, according
2130 to the protocol rules. */
2131 if (remoteEchoOption) break;
2132 if (appData.localLineEditing &&
2133 atoi(appData.icsPort) == TN_PORT) {
2134 TelnetRequest(TN_DONT, TN_ECHO);
2137 TelnetRequest(TN_DO, TN_ECHO);
2138 remoteEchoOption = TRUE;
2142 if (appData.debugMode)
2143 fprintf(debugFP, "%d ", option);
2144 /* Whatever this is, we don't want it. */
2145 TelnetRequest(TN_DONT, option);
2150 if (appData.debugMode)
2151 fprintf(debugFP, "\n<WONT ");
2152 switch (option = (unsigned char) buf[++i]) {
2154 if (appData.debugMode)
2155 fprintf(debugFP, "ECHO ");
2156 /* Reply only if this is a change, according
2157 to the protocol rules. */
2158 if (!remoteEchoOption) break;
2160 TelnetRequest(TN_DONT, TN_ECHO);
2161 remoteEchoOption = FALSE;
2164 if (appData.debugMode)
2165 fprintf(debugFP, "%d ", (unsigned char) option);
2166 /* Whatever this is, it must already be turned
2167 off, because we never agree to turn on
2168 anything non-default, so according to the
2169 protocol rules, we don't reply. */
2174 if (appData.debugMode)
2175 fprintf(debugFP, "\n<DO ");
2176 switch (option = (unsigned char) buf[++i]) {
2178 /* Whatever this is, we refuse to do it. */
2179 if (appData.debugMode)
2180 fprintf(debugFP, "%d ", option);
2181 TelnetRequest(TN_WONT, option);
2186 if (appData.debugMode)
2187 fprintf(debugFP, "\n<DONT ");
2188 switch (option = (unsigned char) buf[++i]) {
2190 if (appData.debugMode)
2191 fprintf(debugFP, "%d ", option);
2192 /* Whatever this is, we are already not doing
2193 it, because we never agree to do anything
2194 non-default, so according to the protocol
2195 rules, we don't reply. */
2200 if (appData.debugMode)
2201 fprintf(debugFP, "\n<IAC ");
2202 /* Doubled IAC; pass it through */
2206 if (appData.debugMode)
2207 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2208 /* Drop all other telnet commands on the floor */
2211 if (oldi > next_out)
2212 SendToPlayer(&buf[next_out], oldi - next_out);
2218 /* OK, this at least will *usually* work */
2219 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2223 if (loggedOn && !intfSet) {
2224 if (ics_type == ICS_ICC) {
2226 "/set-quietly interface %s\n/set-quietly style 12\n",
2228 if (!appData.noJoin)
2229 strcat(str, "/set-quietly wrap 0\n");
2230 } else if (ics_type == ICS_CHESSNET) {
2231 sprintf(str, "/style 12\n");
2233 strcpy(str, "alias $ @\n$set interface ");
2234 strcat(str, programVersion);
2235 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2237 strcat(str, "$iset nohighlight 1\n");
2239 if (!appData.noJoin)
2240 strcat(str, "$iset nowrap 1\n");
2241 strcat(str, "$iset lock 1\n$style 12\n");
2244 NotifyFrontendLogin();
2248 if (started == STARTED_COMMENT) {
2249 /* Accumulate characters in comment */
2250 parse[parse_pos++] = buf[i];
2251 if (buf[i] == '\n') {
2252 parse[parse_pos] = NULLCHAR;
2253 if(chattingPartner>=0) {
2255 sprintf(mess, "%s%s", talker, parse);
2256 OutputChatMessage(chattingPartner, mess);
2257 chattingPartner = -1;
2259 if(!suppressKibitz) // [HGM] kibitz
2260 AppendComment(forwardMostMove, StripHighlight(parse));
2261 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2262 int nrDigit = 0, nrAlph = 0, i;
2263 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2264 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2265 parse[parse_pos] = NULLCHAR;
2266 // try to be smart: if it does not look like search info, it should go to
2267 // ICS interaction window after all, not to engine-output window.
2268 for(i=0; i<parse_pos; i++) { // count letters and digits
2269 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2270 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2271 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2273 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2274 int depth=0; float score;
2275 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2276 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2277 pvInfoList[forwardMostMove-1].depth = depth;
2278 pvInfoList[forwardMostMove-1].score = 100*score;
2280 OutputKibitz(suppressKibitz, parse);
2283 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2284 SendToPlayer(tmp, strlen(tmp));
2287 started = STARTED_NONE;
2289 /* Don't match patterns against characters in chatter */
2294 if (started == STARTED_CHATTER) {
2295 if (buf[i] != '\n') {
2296 /* Don't match patterns against characters in chatter */
2300 started = STARTED_NONE;
2303 /* Kludge to deal with rcmd protocol */
2304 if (firstTime && looking_at(buf, &i, "\001*")) {
2305 DisplayFatalError(&buf[1], 0, 1);
2311 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2314 if (appData.debugMode)
2315 fprintf(debugFP, "ics_type %d\n", ics_type);
2318 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2319 ics_type = ICS_FICS;
2321 if (appData.debugMode)
2322 fprintf(debugFP, "ics_type %d\n", ics_type);
2325 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2326 ics_type = ICS_CHESSNET;
2328 if (appData.debugMode)
2329 fprintf(debugFP, "ics_type %d\n", ics_type);
2334 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2335 looking_at(buf, &i, "Logging you in as \"*\"") ||
2336 looking_at(buf, &i, "will be \"*\""))) {
2337 strcpy(ics_handle, star_match[0]);
2341 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2343 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2344 DisplayIcsInteractionTitle(buf);
2345 have_set_title = TRUE;
2348 /* skip finger notes */
2349 if (started == STARTED_NONE &&
2350 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2351 (buf[i] == '1' && buf[i+1] == '0')) &&
2352 buf[i+2] == ':' && buf[i+3] == ' ') {
2353 started = STARTED_CHATTER;
2358 /* skip formula vars */
2359 if (started == STARTED_NONE &&
2360 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2361 started = STARTED_CHATTER;
2367 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2368 if (appData.autoKibitz && started == STARTED_NONE &&
2369 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2370 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2371 if(looking_at(buf, &i, "* kibitzes: ") &&
2372 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2373 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2374 suppressKibitz = TRUE;
2375 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2376 && (gameMode == IcsPlayingWhite)) ||
2377 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2378 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2379 started = STARTED_CHATTER; // own kibitz we simply discard
2381 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2382 parse_pos = 0; parse[0] = NULLCHAR;
2383 savingComment = TRUE;
2384 suppressKibitz = gameMode != IcsObserving ? 2 :
2385 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2389 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2390 started = STARTED_CHATTER;
2391 suppressKibitz = TRUE;
2393 } // [HGM] kibitz: end of patch
2395 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2397 // [HGM] chat: intercept tells by users for which we have an open chat window
2399 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2400 looking_at(buf, &i, "* whispers:") ||
2401 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2402 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2404 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2405 chattingPartner = -1;
2407 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2408 for(p=0; p<MAX_CHAT; p++) {
2409 if(channel == atoi(chatPartner[p])) {
2410 talker[0] = '['; strcat(talker, "]");
2411 chattingPartner = p; break;
2414 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2415 for(p=0; p<MAX_CHAT; p++) {
2416 if(!strcmp("WHISPER", chatPartner[p])) {
2417 talker[0] = '['; strcat(talker, "]");
2418 chattingPartner = p; break;
2421 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2422 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2424 chattingPartner = p; break;
2426 if(chattingPartner<0) i = oldi; else {
2427 started = STARTED_COMMENT;
2428 parse_pos = 0; parse[0] = NULLCHAR;
2429 savingComment = TRUE;
2430 suppressKibitz = TRUE;
2432 } // [HGM] chat: end of patch
2434 if (appData.zippyTalk || appData.zippyPlay) {
2435 /* [DM] Backup address for color zippy lines */
2439 if (loggedOn == TRUE)
2440 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2441 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2443 if (ZippyControl(buf, &i) ||
2444 ZippyConverse(buf, &i) ||
2445 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2447 if (!appData.colorize) continue;
2451 } // [DM] 'else { ' deleted
2453 /* Regular tells and says */
2454 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2455 looking_at(buf, &i, "* (your partner) tells you: ") ||
2456 looking_at(buf, &i, "* says: ") ||
2457 /* Don't color "message" or "messages" output */
2458 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2459 looking_at(buf, &i, "*. * at *:*: ") ||
2460 looking_at(buf, &i, "--* (*:*): ") ||
2461 /* Message notifications (same color as tells) */
2462 looking_at(buf, &i, "* has left a message ") ||
2463 looking_at(buf, &i, "* just sent you a message:\n") ||
2464 /* Whispers and kibitzes */
2465 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2466 looking_at(buf, &i, "* kibitzes: ") ||
2468 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2470 if (tkind == 1 && strchr(star_match[0], ':')) {
2471 /* Avoid "tells you:" spoofs in channels */
2474 if (star_match[0][0] == NULLCHAR ||
2475 strchr(star_match[0], ' ') ||
2476 (tkind == 3 && strchr(star_match[1], ' '))) {
2477 /* Reject bogus matches */
2480 if (appData.colorize) {
2481 if (oldi > next_out) {
2482 SendToPlayer(&buf[next_out], oldi - next_out);
2487 Colorize(ColorTell, FALSE);
2488 curColor = ColorTell;
2491 Colorize(ColorKibitz, FALSE);
2492 curColor = ColorKibitz;
2495 p = strrchr(star_match[1], '(');
2502 Colorize(ColorChannel1, FALSE);
2503 curColor = ColorChannel1;
2505 Colorize(ColorChannel, FALSE);
2506 curColor = ColorChannel;
2510 curColor = ColorNormal;
2514 if (started == STARTED_NONE && appData.autoComment &&
2515 (gameMode == IcsObserving ||
2516 gameMode == IcsPlayingWhite ||
2517 gameMode == IcsPlayingBlack)) {
2518 parse_pos = i - oldi;
2519 memcpy(parse, &buf[oldi], parse_pos);
2520 parse[parse_pos] = NULLCHAR;
2521 started = STARTED_COMMENT;
2522 savingComment = TRUE;
2524 started = STARTED_CHATTER;
2525 savingComment = FALSE;
2532 if (looking_at(buf, &i, "* s-shouts: ") ||
2533 looking_at(buf, &i, "* c-shouts: ")) {
2534 if (appData.colorize) {
2535 if (oldi > next_out) {
2536 SendToPlayer(&buf[next_out], oldi - next_out);
2539 Colorize(ColorSShout, FALSE);
2540 curColor = ColorSShout;
2543 started = STARTED_CHATTER;
2547 if (looking_at(buf, &i, "--->")) {
2552 if (looking_at(buf, &i, "* shouts: ") ||
2553 looking_at(buf, &i, "--> ")) {
2554 if (appData.colorize) {
2555 if (oldi > next_out) {
2556 SendToPlayer(&buf[next_out], oldi - next_out);
2559 Colorize(ColorShout, FALSE);
2560 curColor = ColorShout;
2563 started = STARTED_CHATTER;
2567 if (looking_at( buf, &i, "Challenge:")) {
2568 if (appData.colorize) {
2569 if (oldi > next_out) {
2570 SendToPlayer(&buf[next_out], oldi - next_out);
2573 Colorize(ColorChallenge, FALSE);
2574 curColor = ColorChallenge;
2580 if (looking_at(buf, &i, "* offers you") ||
2581 looking_at(buf, &i, "* offers to be") ||
2582 looking_at(buf, &i, "* would like to") ||
2583 looking_at(buf, &i, "* requests to") ||
2584 looking_at(buf, &i, "Your opponent offers") ||
2585 looking_at(buf, &i, "Your opponent requests")) {
2587 if (appData.colorize) {
2588 if (oldi > next_out) {
2589 SendToPlayer(&buf[next_out], oldi - next_out);
2592 Colorize(ColorRequest, FALSE);
2593 curColor = ColorRequest;
2598 if (looking_at(buf, &i, "* (*) seeking")) {
2599 if (appData.colorize) {
2600 if (oldi > next_out) {
2601 SendToPlayer(&buf[next_out], oldi - next_out);
2604 Colorize(ColorSeek, FALSE);
2605 curColor = ColorSeek;
2610 if (looking_at(buf, &i, "\\ ")) {
2611 if (prevColor != ColorNormal) {
2612 if (oldi > next_out) {
2613 SendToPlayer(&buf[next_out], oldi - next_out);
2616 Colorize(prevColor, TRUE);
2617 curColor = prevColor;
2619 if (savingComment) {
2620 parse_pos = i - oldi;
2621 memcpy(parse, &buf[oldi], parse_pos);
2622 parse[parse_pos] = NULLCHAR;
2623 started = STARTED_COMMENT;
2625 started = STARTED_CHATTER;
2630 if (looking_at(buf, &i, "Black Strength :") ||
2631 looking_at(buf, &i, "<<< style 10 board >>>") ||
2632 looking_at(buf, &i, "<10>") ||
2633 looking_at(buf, &i, "#@#")) {
2634 /* Wrong board style */
2636 SendToICS(ics_prefix);
2637 SendToICS("set style 12\n");
2638 SendToICS(ics_prefix);
2639 SendToICS("refresh\n");
2643 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2645 have_sent_ICS_logon = 1;
2649 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2650 (looking_at(buf, &i, "\n<12> ") ||
2651 looking_at(buf, &i, "<12> "))) {
2653 if (oldi > next_out) {
2654 SendToPlayer(&buf[next_out], oldi - next_out);
2657 started = STARTED_BOARD;
2662 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2663 looking_at(buf, &i, "<b1> ")) {
2664 if (oldi > next_out) {
2665 SendToPlayer(&buf[next_out], oldi - next_out);
2668 started = STARTED_HOLDINGS;
2673 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2675 /* Header for a move list -- first line */
2677 switch (ics_getting_history) {
2681 case BeginningOfGame:
2682 /* User typed "moves" or "oldmoves" while we
2683 were idle. Pretend we asked for these
2684 moves and soak them up so user can step
2685 through them and/or save them.
2688 gameMode = IcsObserving;
2691 ics_getting_history = H_GOT_UNREQ_HEADER;
2693 case EditGame: /*?*/
2694 case EditPosition: /*?*/
2695 /* Should above feature work in these modes too? */
2696 /* For now it doesn't */
2697 ics_getting_history = H_GOT_UNWANTED_HEADER;
2700 ics_getting_history = H_GOT_UNWANTED_HEADER;
2705 /* Is this the right one? */
2706 if (gameInfo.white && gameInfo.black &&
2707 strcmp(gameInfo.white, star_match[0]) == 0 &&
2708 strcmp(gameInfo.black, star_match[2]) == 0) {
2710 ics_getting_history = H_GOT_REQ_HEADER;
2713 case H_GOT_REQ_HEADER:
2714 case H_GOT_UNREQ_HEADER:
2715 case H_GOT_UNWANTED_HEADER:
2716 case H_GETTING_MOVES:
2717 /* Should not happen */
2718 DisplayError(_("Error gathering move list: two headers"), 0);
2719 ics_getting_history = H_FALSE;
2723 /* Save player ratings into gameInfo if needed */
2724 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2725 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2726 (gameInfo.whiteRating == -1 ||
2727 gameInfo.blackRating == -1)) {
2729 gameInfo.whiteRating = string_to_rating(star_match[1]);
2730 gameInfo.blackRating = string_to_rating(star_match[3]);
2731 if (appData.debugMode)
2732 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2733 gameInfo.whiteRating, gameInfo.blackRating);
2738 if (looking_at(buf, &i,
2739 "* * match, initial time: * minute*, increment: * second")) {
2740 /* Header for a move list -- second line */
2741 /* Initial board will follow if this is a wild game */
2742 if (gameInfo.event != NULL) free(gameInfo.event);
2743 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2744 gameInfo.event = StrSave(str);
2745 /* [HGM] we switched variant. Translate boards if needed. */
2746 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2750 if (looking_at(buf, &i, "Move ")) {
2751 /* Beginning of a move list */
2752 switch (ics_getting_history) {
2754 /* Normally should not happen */
2755 /* Maybe user hit reset while we were parsing */
2758 /* Happens if we are ignoring a move list that is not
2759 * the one we just requested. Common if the user
2760 * tries to observe two games without turning off
2763 case H_GETTING_MOVES:
2764 /* Should not happen */
2765 DisplayError(_("Error gathering move list: nested"), 0);
2766 ics_getting_history = H_FALSE;
2768 case H_GOT_REQ_HEADER:
2769 ics_getting_history = H_GETTING_MOVES;
2770 started = STARTED_MOVES;
2772 if (oldi > next_out) {
2773 SendToPlayer(&buf[next_out], oldi - next_out);
2776 case H_GOT_UNREQ_HEADER:
2777 ics_getting_history = H_GETTING_MOVES;
2778 started = STARTED_MOVES_NOHIDE;
2781 case H_GOT_UNWANTED_HEADER:
2782 ics_getting_history = H_FALSE;
2788 if (looking_at(buf, &i, "% ") ||
2789 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2790 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2791 savingComment = FALSE;
2794 case STARTED_MOVES_NOHIDE:
2795 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2796 parse[parse_pos + i - oldi] = NULLCHAR;
2797 ParseGameHistory(parse);
2799 if (appData.zippyPlay && first.initDone) {
2800 FeedMovesToProgram(&first, forwardMostMove);
2801 if (gameMode == IcsPlayingWhite) {
2802 if (WhiteOnMove(forwardMostMove)) {
2803 if (first.sendTime) {
2804 if (first.useColors) {
2805 SendToProgram("black\n", &first);
2807 SendTimeRemaining(&first, TRUE);
2809 if (first.useColors) {
2810 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2812 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2813 first.maybeThinking = TRUE;
2815 if (first.usePlayother) {
2816 if (first.sendTime) {
2817 SendTimeRemaining(&first, TRUE);
2819 SendToProgram("playother\n", &first);
2825 } else if (gameMode == IcsPlayingBlack) {
2826 if (!WhiteOnMove(forwardMostMove)) {
2827 if (first.sendTime) {
2828 if (first.useColors) {
2829 SendToProgram("white\n", &first);
2831 SendTimeRemaining(&first, FALSE);
2833 if (first.useColors) {
2834 SendToProgram("black\n", &first);
2836 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2837 first.maybeThinking = TRUE;
2839 if (first.usePlayother) {
2840 if (first.sendTime) {
2841 SendTimeRemaining(&first, FALSE);
2843 SendToProgram("playother\n", &first);
2852 if (gameMode == IcsObserving && ics_gamenum == -1) {
2853 /* Moves came from oldmoves or moves command
2854 while we weren't doing anything else.
2856 currentMove = forwardMostMove;
2857 ClearHighlights();/*!!could figure this out*/
2858 flipView = appData.flipView;
2859 DrawPosition(FALSE, boards[currentMove]);
2860 DisplayBothClocks();
2861 sprintf(str, "%s vs. %s",
2862 gameInfo.white, gameInfo.black);
2866 /* Moves were history of an active game */
2867 if (gameInfo.resultDetails != NULL) {
2868 free(gameInfo.resultDetails);
2869 gameInfo.resultDetails = NULL;
2872 HistorySet(parseList, backwardMostMove,
2873 forwardMostMove, currentMove-1);
2874 DisplayMove(currentMove - 1);
2875 if (started == STARTED_MOVES) next_out = i;
2876 started = STARTED_NONE;
2877 ics_getting_history = H_FALSE;
2880 case STARTED_OBSERVE:
2881 started = STARTED_NONE;
2882 SendToICS(ics_prefix);
2883 SendToICS("refresh\n");
2889 if(bookHit) { // [HGM] book: simulate book reply
2890 static char bookMove[MSG_SIZ]; // a bit generous?
2892 programStats.nodes = programStats.depth = programStats.time =
2893 programStats.score = programStats.got_only_move = 0;
2894 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2896 strcpy(bookMove, "move ");
2897 strcat(bookMove, bookHit);
2898 HandleMachineMove(bookMove, &first);
2903 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2904 started == STARTED_HOLDINGS ||
2905 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2906 /* Accumulate characters in move list or board */
2907 parse[parse_pos++] = buf[i];
2910 /* Start of game messages. Mostly we detect start of game
2911 when the first board image arrives. On some versions
2912 of the ICS, though, we need to do a "refresh" after starting
2913 to observe in order to get the current board right away. */
2914 if (looking_at(buf, &i, "Adding game * to observation list")) {
2915 started = STARTED_OBSERVE;
2919 /* Handle auto-observe */
2920 if (appData.autoObserve &&
2921 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2922 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2924 /* Choose the player that was highlighted, if any. */
2925 if (star_match[0][0] == '\033' ||
2926 star_match[1][0] != '\033') {
2927 player = star_match[0];
2929 player = star_match[2];
2931 sprintf(str, "%sobserve %s\n",
2932 ics_prefix, StripHighlightAndTitle(player));
2935 /* Save ratings from notify string */
2936 strcpy(player1Name, star_match[0]);
2937 player1Rating = string_to_rating(star_match[1]);
2938 strcpy(player2Name, star_match[2]);
2939 player2Rating = string_to_rating(star_match[3]);
2941 if (appData.debugMode)
2943 "Ratings from 'Game notification:' %s %d, %s %d\n",
2944 player1Name, player1Rating,
2945 player2Name, player2Rating);
2950 /* Deal with automatic examine mode after a game,
2951 and with IcsObserving -> IcsExamining transition */
2952 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2953 looking_at(buf, &i, "has made you an examiner of game *")) {
2955 int gamenum = atoi(star_match[0]);
2956 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2957 gamenum == ics_gamenum) {
2958 /* We were already playing or observing this game;
2959 no need to refetch history */
2960 gameMode = IcsExamining;
2962 pauseExamForwardMostMove = forwardMostMove;
2963 } else if (currentMove < forwardMostMove) {
2964 ForwardInner(forwardMostMove);
2967 /* I don't think this case really can happen */
2968 SendToICS(ics_prefix);
2969 SendToICS("refresh\n");
2974 /* Error messages */
2975 // if (ics_user_moved) {
2976 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2977 if (looking_at(buf, &i, "Illegal move") ||
2978 looking_at(buf, &i, "Not a legal move") ||
2979 looking_at(buf, &i, "Your king is in check") ||
2980 looking_at(buf, &i, "It isn't your turn") ||
2981 looking_at(buf, &i, "It is not your move")) {
2983 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2984 currentMove = --forwardMostMove;
2985 DisplayMove(currentMove - 1); /* before DMError */
2986 DrawPosition(FALSE, boards[currentMove]);
2988 DisplayBothClocks();
2990 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2996 if (looking_at(buf, &i, "still have time") ||
2997 looking_at(buf, &i, "not out of time") ||
2998 looking_at(buf, &i, "either player is out of time") ||
2999 looking_at(buf, &i, "has timeseal; checking")) {
3000 /* We must have called his flag a little too soon */
3001 whiteFlag = blackFlag = FALSE;
3005 if (looking_at(buf, &i, "added * seconds to") ||
3006 looking_at(buf, &i, "seconds were added to")) {
3007 /* Update the clocks */
3008 SendToICS(ics_prefix);
3009 SendToICS("refresh\n");
3013 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3014 ics_clock_paused = TRUE;
3019 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3020 ics_clock_paused = FALSE;
3025 /* Grab player ratings from the Creating: message.
3026 Note we have to check for the special case when
3027 the ICS inserts things like [white] or [black]. */
3028 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3029 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3031 0 player 1 name (not necessarily white)
3033 2 empty, white, or black (IGNORED)
3034 3 player 2 name (not necessarily black)
3037 The names/ratings are sorted out when the game
3038 actually starts (below).
3040 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3041 player1Rating = string_to_rating(star_match[1]);
3042 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3043 player2Rating = string_to_rating(star_match[4]);
3045 if (appData.debugMode)
3047 "Ratings from 'Creating:' %s %d, %s %d\n",
3048 player1Name, player1Rating,
3049 player2Name, player2Rating);
3054 /* Improved generic start/end-of-game messages */
3055 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3056 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3057 /* If tkind == 0: */
3058 /* star_match[0] is the game number */
3059 /* [1] is the white player's name */
3060 /* [2] is the black player's name */
3061 /* For end-of-game: */
3062 /* [3] is the reason for the game end */
3063 /* [4] is a PGN end game-token, preceded by " " */
3064 /* For start-of-game: */
3065 /* [3] begins with "Creating" or "Continuing" */
3066 /* [4] is " *" or empty (don't care). */
3067 int gamenum = atoi(star_match[0]);
3068 char *whitename, *blackname, *why, *endtoken;
3069 ChessMove endtype = (ChessMove) 0;
3072 whitename = star_match[1];
3073 blackname = star_match[2];
3074 why = star_match[3];
3075 endtoken = star_match[4];
3077 whitename = star_match[1];
3078 blackname = star_match[3];
3079 why = star_match[5];
3080 endtoken = star_match[6];
3083 /* Game start messages */
3084 if (strncmp(why, "Creating ", 9) == 0 ||
3085 strncmp(why, "Continuing ", 11) == 0) {
3086 gs_gamenum = gamenum;
3087 strcpy(gs_kind, strchr(why, ' ') + 1);
3089 if (appData.zippyPlay) {
3090 ZippyGameStart(whitename, blackname);
3096 /* Game end messages */
3097 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3098 ics_gamenum != gamenum) {
3101 while (endtoken[0] == ' ') endtoken++;
3102 switch (endtoken[0]) {
3105 endtype = GameUnfinished;
3108 endtype = BlackWins;
3111 if (endtoken[1] == '/')
3112 endtype = GameIsDrawn;
3114 endtype = WhiteWins;
3117 GameEnds(endtype, why, GE_ICS);
3119 if (appData.zippyPlay && first.initDone) {
3120 ZippyGameEnd(endtype, why);
3121 if (first.pr == NULL) {
3122 /* Start the next process early so that we'll
3123 be ready for the next challenge */
3124 StartChessProgram(&first);
3126 /* Send "new" early, in case this command takes
3127 a long time to finish, so that we'll be ready
3128 for the next challenge. */
3129 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3136 if (looking_at(buf, &i, "Removing game * from observation") ||
3137 looking_at(buf, &i, "no longer observing game *") ||
3138 looking_at(buf, &i, "Game * (*) has no examiners")) {
3139 if (gameMode == IcsObserving &&
3140 atoi(star_match[0]) == ics_gamenum)
3142 /* icsEngineAnalyze */
3143 if (appData.icsEngineAnalyze) {
3150 ics_user_moved = FALSE;
3155 if (looking_at(buf, &i, "no longer examining game *")) {
3156 if (gameMode == IcsExamining &&
3157 atoi(star_match[0]) == ics_gamenum)
3161 ics_user_moved = FALSE;
3166 /* Advance leftover_start past any newlines we find,
3167 so only partial lines can get reparsed */
3168 if (looking_at(buf, &i, "\n")) {
3169 prevColor = curColor;
3170 if (curColor != ColorNormal) {
3171 if (oldi > next_out) {
3172 SendToPlayer(&buf[next_out], oldi - next_out);
3175 Colorize(ColorNormal, FALSE);
3176 curColor = ColorNormal;
3178 if (started == STARTED_BOARD) {
3179 started = STARTED_NONE;
3180 parse[parse_pos] = NULLCHAR;
3181 ParseBoard12(parse);
3184 /* Send premove here */
3185 if (appData.premove) {
3187 if (currentMove == 0 &&
3188 gameMode == IcsPlayingWhite &&
3189 appData.premoveWhite) {
3190 sprintf(str, "%s%s\n", ics_prefix,
3191 appData.premoveWhiteText);
3192 if (appData.debugMode)
3193 fprintf(debugFP, "Sending premove:\n");
3195 } else if (currentMove == 1 &&
3196 gameMode == IcsPlayingBlack &&
3197 appData.premoveBlack) {
3198 sprintf(str, "%s%s\n", ics_prefix,
3199 appData.premoveBlackText);
3200 if (appData.debugMode)
3201 fprintf(debugFP, "Sending premove:\n");
3203 } else if (gotPremove) {
3205 ClearPremoveHighlights();
3206 if (appData.debugMode)
3207 fprintf(debugFP, "Sending premove:\n");
3208 UserMoveEvent(premoveFromX, premoveFromY,
3209 premoveToX, premoveToY,
3214 /* Usually suppress following prompt */
3215 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3216 if (looking_at(buf, &i, "*% ")) {
3217 savingComment = FALSE;
3221 } else if (started == STARTED_HOLDINGS) {
3223 char new_piece[MSG_SIZ];
3224 started = STARTED_NONE;
3225 parse[parse_pos] = NULLCHAR;
3226 if (appData.debugMode)
3227 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3228 parse, currentMove);
3229 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3230 gamenum == ics_gamenum) {
3231 if (gameInfo.variant == VariantNormal) {
3232 /* [HGM] We seem to switch variant during a game!
3233 * Presumably no holdings were displayed, so we have
3234 * to move the position two files to the right to
3235 * create room for them!
3237 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3238 /* Get a move list just to see the header, which
3239 will tell us whether this is really bug or zh */
3240 if (ics_getting_history == H_FALSE) {
3241 ics_getting_history = H_REQUESTED;
3242 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3246 new_piece[0] = NULLCHAR;
3247 sscanf(parse, "game %d white [%s black [%s <- %s",
3248 &gamenum, white_holding, black_holding,
3250 white_holding[strlen(white_holding)-1] = NULLCHAR;
3251 black_holding[strlen(black_holding)-1] = NULLCHAR;
3252 /* [HGM] copy holdings to board holdings area */
3253 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3254 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3256 if (appData.zippyPlay && first.initDone) {
3257 ZippyHoldings(white_holding, black_holding,
3261 if (tinyLayout || smallLayout) {
3262 char wh[16], bh[16];
3263 PackHolding(wh, white_holding);
3264 PackHolding(bh, black_holding);
3265 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3266 gameInfo.white, gameInfo.black);
3268 sprintf(str, "%s [%s] vs. %s [%s]",
3269 gameInfo.white, white_holding,
3270 gameInfo.black, black_holding);
3273 DrawPosition(FALSE, boards[currentMove]);
3276 /* Suppress following prompt */
3277 if (looking_at(buf, &i, "*% ")) {
3278 savingComment = FALSE;
3285 i++; /* skip unparsed character and loop back */
3288 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3289 started != STARTED_HOLDINGS && i > next_out) {
3290 SendToPlayer(&buf[next_out], i - next_out);
3293 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3295 leftover_len = buf_len - leftover_start;
3296 /* if buffer ends with something we couldn't parse,
3297 reparse it after appending the next read */
3299 } else if (count == 0) {
3300 RemoveInputSource(isr);
3301 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3303 DisplayFatalError(_("Error reading from ICS"), error, 1);
3308 /* Board style 12 looks like this:
3310 <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
3312 * The "<12> " is stripped before it gets to this routine. The two
3313 * trailing 0's (flip state and clock ticking) are later addition, and
3314 * some chess servers may not have them, or may have only the first.
3315 * Additional trailing fields may be added in the future.
3318 #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"
3320 #define RELATION_OBSERVING_PLAYED 0
3321 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3322 #define RELATION_PLAYING_MYMOVE 1
3323 #define RELATION_PLAYING_NOTMYMOVE -1
3324 #define RELATION_EXAMINING 2
3325 #define RELATION_ISOLATED_BOARD -3
3326 #define RELATION_STARTING_POSITION -4 /* FICS only */
3329 ParseBoard12(string)
3332 GameMode newGameMode;
3333 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3334 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3335 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3336 char to_play, board_chars[200];
3337 char move_str[500], str[500], elapsed_time[500];
3338 char black[32], white[32];
3340 int prevMove = currentMove;
3343 int fromX, fromY, toX, toY;
3345 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3346 char *bookHit = NULL; // [HGM] book
3348 fromX = fromY = toX = toY = -1;
3352 if (appData.debugMode)
3353 fprintf(debugFP, _("Parsing board: %s\n"), string);
3355 move_str[0] = NULLCHAR;
3356 elapsed_time[0] = NULLCHAR;
3357 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3359 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3360 if(string[i] == ' ') { ranks++; files = 0; }
3364 for(j = 0; j <i; j++) board_chars[j] = string[j];
3365 board_chars[i] = '\0';
3368 n = sscanf(string, PATTERN, &to_play, &double_push,
3369 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3370 &gamenum, white, black, &relation, &basetime, &increment,
3371 &white_stren, &black_stren, &white_time, &black_time,
3372 &moveNum, str, elapsed_time, move_str, &ics_flip,
3376 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3377 DisplayError(str, 0);
3381 /* Convert the move number to internal form */
3382 moveNum = (moveNum - 1) * 2;
3383 if (to_play == 'B') moveNum++;
3384 if (moveNum >= MAX_MOVES) {
3385 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3391 case RELATION_OBSERVING_PLAYED:
3392 case RELATION_OBSERVING_STATIC:
3393 if (gamenum == -1) {
3394 /* Old ICC buglet */
3395 relation = RELATION_OBSERVING_STATIC;
3397 newGameMode = IcsObserving;
3399 case RELATION_PLAYING_MYMOVE:
3400 case RELATION_PLAYING_NOTMYMOVE:
3402 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3403 IcsPlayingWhite : IcsPlayingBlack;
3405 case RELATION_EXAMINING:
3406 newGameMode = IcsExamining;
3408 case RELATION_ISOLATED_BOARD:
3410 /* Just display this board. If user was doing something else,
3411 we will forget about it until the next board comes. */
3412 newGameMode = IcsIdle;
3414 case RELATION_STARTING_POSITION:
3415 newGameMode = gameMode;
3419 /* Modify behavior for initial board display on move listing
3422 switch (ics_getting_history) {
3426 case H_GOT_REQ_HEADER:
3427 case H_GOT_UNREQ_HEADER:
3428 /* This is the initial position of the current game */
3429 gamenum = ics_gamenum;
3430 moveNum = 0; /* old ICS bug workaround */
3431 if (to_play == 'B') {
3432 startedFromSetupPosition = TRUE;
3433 blackPlaysFirst = TRUE;
3435 if (forwardMostMove == 0) forwardMostMove = 1;
3436 if (backwardMostMove == 0) backwardMostMove = 1;
3437 if (currentMove == 0) currentMove = 1;
3439 newGameMode = gameMode;
3440 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3442 case H_GOT_UNWANTED_HEADER:
3443 /* This is an initial board that we don't want */
3445 case H_GETTING_MOVES:
3446 /* Should not happen */
3447 DisplayError(_("Error gathering move list: extra board"), 0);
3448 ics_getting_history = H_FALSE;
3452 /* Take action if this is the first board of a new game, or of a
3453 different game than is currently being displayed. */
3454 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3455 relation == RELATION_ISOLATED_BOARD) {
3457 /* Forget the old game and get the history (if any) of the new one */
3458 if (gameMode != BeginningOfGame) {
3462 if (appData.autoRaiseBoard) BoardToTop();
3464 if (gamenum == -1) {
3465 newGameMode = IcsIdle;
3466 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3467 appData.getMoveList) {
3468 /* Need to get game history */
3469 ics_getting_history = H_REQUESTED;
3470 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3474 /* Initially flip the board to have black on the bottom if playing
3475 black or if the ICS flip flag is set, but let the user change
3476 it with the Flip View button. */
3477 flipView = appData.autoFlipView ?
3478 (newGameMode == IcsPlayingBlack) || ics_flip :
3481 /* Done with values from previous mode; copy in new ones */
3482 gameMode = newGameMode;
3484 ics_gamenum = gamenum;
3485 if (gamenum == gs_gamenum) {
3486 int klen = strlen(gs_kind);
3487 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3488 sprintf(str, "ICS %s", gs_kind);
3489 gameInfo.event = StrSave(str);
3491 gameInfo.event = StrSave("ICS game");
3493 gameInfo.site = StrSave(appData.icsHost);
3494 gameInfo.date = PGNDate();
3495 gameInfo.round = StrSave("-");
3496 gameInfo.white = StrSave(white);
3497 gameInfo.black = StrSave(black);
3498 timeControl = basetime * 60 * 1000;
3500 timeIncrement = increment * 1000;
3501 movesPerSession = 0;
3502 gameInfo.timeControl = TimeControlTagValue();
3503 VariantSwitch(board, StringToVariant(gameInfo.event) );
3504 if (appData.debugMode) {
3505 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3506 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3507 setbuf(debugFP, NULL);
3510 gameInfo.outOfBook = NULL;
3512 /* Do we have the ratings? */
3513 if (strcmp(player1Name, white) == 0 &&
3514 strcmp(player2Name, black) == 0) {
3515 if (appData.debugMode)
3516 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3517 player1Rating, player2Rating);
3518 gameInfo.whiteRating = player1Rating;
3519 gameInfo.blackRating = player2Rating;
3520 } else if (strcmp(player2Name, white) == 0 &&
3521 strcmp(player1Name, black) == 0) {
3522 if (appData.debugMode)
3523 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3524 player2Rating, player1Rating);
3525 gameInfo.whiteRating = player2Rating;
3526 gameInfo.blackRating = player1Rating;
3528 player1Name[0] = player2Name[0] = NULLCHAR;
3530 /* Silence shouts if requested */
3531 if (appData.quietPlay &&
3532 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3533 SendToICS(ics_prefix);
3534 SendToICS("set shout 0\n");
3538 /* Deal with midgame name changes */
3540 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3541 if (gameInfo.white) free(gameInfo.white);
3542 gameInfo.white = StrSave(white);
3544 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3545 if (gameInfo.black) free(gameInfo.black);
3546 gameInfo.black = StrSave(black);
3550 /* Throw away game result if anything actually changes in examine mode */
3551 if (gameMode == IcsExamining && !newGame) {
3552 gameInfo.result = GameUnfinished;
3553 if (gameInfo.resultDetails != NULL) {
3554 free(gameInfo.resultDetails);
3555 gameInfo.resultDetails = NULL;
3559 /* In pausing && IcsExamining mode, we ignore boards coming
3560 in if they are in a different variation than we are. */
3561 if (pauseExamInvalid) return;
3562 if (pausing && gameMode == IcsExamining) {
3563 if (moveNum <= pauseExamForwardMostMove) {
3564 pauseExamInvalid = TRUE;
3565 forwardMostMove = pauseExamForwardMostMove;
3570 if (appData.debugMode) {
3571 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3573 /* Parse the board */
3574 for (k = 0; k < ranks; k++) {
3575 for (j = 0; j < files; j++)
3576 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3577 if(gameInfo.holdingsWidth > 1) {
3578 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3579 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3582 CopyBoard(boards[moveNum], board);
3584 startedFromSetupPosition =
3585 !CompareBoards(board, initialPosition);
3586 if(startedFromSetupPosition)
3587 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3590 /* [HGM] Set castling rights. Take the outermost Rooks,
3591 to make it also work for FRC opening positions. Note that board12
3592 is really defective for later FRC positions, as it has no way to
3593 indicate which Rook can castle if they are on the same side of King.
3594 For the initial position we grant rights to the outermost Rooks,
3595 and remember thos rights, and we then copy them on positions
3596 later in an FRC game. This means WB might not recognize castlings with
3597 Rooks that have moved back to their original position as illegal,
3598 but in ICS mode that is not its job anyway.
3600 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3601 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3603 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3604 if(board[0][i] == WhiteRook) j = i;
3605 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3606 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3607 if(board[0][i] == WhiteRook) j = i;
3608 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3609 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3610 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3611 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3612 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3613 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3614 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3616 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3617 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3618 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3619 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3620 if(board[BOARD_HEIGHT-1][k] == bKing)
3621 initialRights[5] = castlingRights[moveNum][5] = k;
3623 r = castlingRights[moveNum][0] = initialRights[0];
3624 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3625 r = castlingRights[moveNum][1] = initialRights[1];
3626 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3627 r = castlingRights[moveNum][3] = initialRights[3];
3628 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3629 r = castlingRights[moveNum][4] = initialRights[4];
3630 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3631 /* wildcastle kludge: always assume King has rights */
3632 r = castlingRights[moveNum][2] = initialRights[2];
3633 r = castlingRights[moveNum][5] = initialRights[5];
3635 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3636 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3639 if (ics_getting_history == H_GOT_REQ_HEADER ||
3640 ics_getting_history == H_GOT_UNREQ_HEADER) {
3641 /* This was an initial position from a move list, not
3642 the current position */
3646 /* Update currentMove and known move number limits */
3647 newMove = newGame || moveNum > forwardMostMove;
3649 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3650 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3651 takeback = forwardMostMove - moveNum;
3652 for (i = 0; i < takeback; i++) {
3653 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3654 SendToProgram("undo\n", &first);
3659 forwardMostMove = backwardMostMove = currentMove = moveNum;
3660 if (gameMode == IcsExamining && moveNum == 0) {
3661 /* Workaround for ICS limitation: we are not told the wild
3662 type when starting to examine a game. But if we ask for
3663 the move list, the move list header will tell us */
3664 ics_getting_history = H_REQUESTED;
3665 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3668 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3669 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3670 forwardMostMove = moveNum;
3671 if (!pausing || currentMove > forwardMostMove)
3672 currentMove = forwardMostMove;
3674 /* New part of history that is not contiguous with old part */
3675 if (pausing && gameMode == IcsExamining) {
3676 pauseExamInvalid = TRUE;
3677 forwardMostMove = pauseExamForwardMostMove;
3680 forwardMostMove = backwardMostMove = currentMove = moveNum;
3681 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3682 ics_getting_history = H_REQUESTED;
3683 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3688 /* Update the clocks */
3689 if (strchr(elapsed_time, '.')) {
3691 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3692 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3694 /* Time is in seconds */
3695 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3696 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3701 if (appData.zippyPlay && newGame &&
3702 gameMode != IcsObserving && gameMode != IcsIdle &&
3703 gameMode != IcsExamining)
3704 ZippyFirstBoard(moveNum, basetime, increment);
3707 /* Put the move on the move list, first converting
3708 to canonical algebraic form. */
3710 if (appData.debugMode) {
3711 if (appData.debugMode) { int f = forwardMostMove;
3712 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3713 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3715 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3716 fprintf(debugFP, "moveNum = %d\n", moveNum);
3717 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3718 setbuf(debugFP, NULL);
3720 if (moveNum <= backwardMostMove) {
3721 /* We don't know what the board looked like before
3723 strcpy(parseList[moveNum - 1], move_str);
3724 strcat(parseList[moveNum - 1], " ");
3725 strcat(parseList[moveNum - 1], elapsed_time);
3726 moveList[moveNum - 1][0] = NULLCHAR;
3727 } else if (strcmp(move_str, "none") == 0) {
3728 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3729 /* Again, we don't know what the board looked like;
3730 this is really the start of the game. */
3731 parseList[moveNum - 1][0] = NULLCHAR;
3732 moveList[moveNum - 1][0] = NULLCHAR;
3733 backwardMostMove = moveNum;
3734 startedFromSetupPosition = TRUE;
3735 fromX = fromY = toX = toY = -1;
3737 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3738 // So we parse the long-algebraic move string in stead of the SAN move
3739 int valid; char buf[MSG_SIZ], *prom;
3741 // str looks something like "Q/a1-a2"; kill the slash
3743 sprintf(buf, "%c%s", str[0], str+2);
3744 else strcpy(buf, str); // might be castling
3745 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3746 strcat(buf, prom); // long move lacks promo specification!
3747 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3748 if(appData.debugMode)
3749 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3750 strcpy(move_str, buf);
3752 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3753 &fromX, &fromY, &toX, &toY, &promoChar)
3754 || ParseOneMove(buf, moveNum - 1, &moveType,
3755 &fromX, &fromY, &toX, &toY, &promoChar);
3756 // end of long SAN patch
3758 (void) CoordsToAlgebraic(boards[moveNum - 1],
3759 PosFlags(moveNum - 1), EP_UNKNOWN,
3760 fromY, fromX, toY, toX, promoChar,
3761 parseList[moveNum-1]);
3762 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3763 castlingRights[moveNum]) ) {
3769 if(gameInfo.variant != VariantShogi)
3770 strcat(parseList[moveNum - 1], "+");
3773 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3774 strcat(parseList[moveNum - 1], "#");
3777 strcat(parseList[moveNum - 1], " ");
3778 strcat(parseList[moveNum - 1], elapsed_time);
3779 /* currentMoveString is set as a side-effect of ParseOneMove */
3780 strcpy(moveList[moveNum - 1], currentMoveString);
3781 strcat(moveList[moveNum - 1], "\n");
3783 /* Move from ICS was illegal!? Punt. */
3784 if (appData.debugMode) {
3785 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3786 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3788 strcpy(parseList[moveNum - 1], move_str);
3789 strcat(parseList[moveNum - 1], " ");
3790 strcat(parseList[moveNum - 1], elapsed_time);
3791 moveList[moveNum - 1][0] = NULLCHAR;
3792 fromX = fromY = toX = toY = -1;
3795 if (appData.debugMode) {
3796 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3797 setbuf(debugFP, NULL);
3801 /* Send move to chess program (BEFORE animating it). */
3802 if (appData.zippyPlay && !newGame && newMove &&
3803 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3805 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3806 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3807 if (moveList[moveNum - 1][0] == NULLCHAR) {
3808 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3810 DisplayError(str, 0);
3812 if (first.sendTime) {
3813 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3815 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3816 if (firstMove && !bookHit) {
3818 if (first.useColors) {
3819 SendToProgram(gameMode == IcsPlayingWhite ?
3821 "black\ngo\n", &first);
3823 SendToProgram("go\n", &first);
3825 first.maybeThinking = TRUE;
3828 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3829 if (moveList[moveNum - 1][0] == NULLCHAR) {
3830 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3831 DisplayError(str, 0);
3833 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3834 SendMoveToProgram(moveNum - 1, &first);
3841 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3842 /* If move comes from a remote source, animate it. If it
3843 isn't remote, it will have already been animated. */
3844 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3845 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3847 if (!pausing && appData.highlightLastMove) {
3848 SetHighlights(fromX, fromY, toX, toY);
3852 /* Start the clocks */
3853 whiteFlag = blackFlag = FALSE;
3854 appData.clockMode = !(basetime == 0 && increment == 0);
3856 ics_clock_paused = TRUE;
3858 } else if (ticking == 1) {
3859 ics_clock_paused = FALSE;
3861 if (gameMode == IcsIdle ||
3862 relation == RELATION_OBSERVING_STATIC ||
3863 relation == RELATION_EXAMINING ||
3865 DisplayBothClocks();
3869 /* Display opponents and material strengths */
3870 if (gameInfo.variant != VariantBughouse &&
3871 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3872 if (tinyLayout || smallLayout) {
3873 if(gameInfo.variant == VariantNormal)
3874 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3875 gameInfo.white, white_stren, gameInfo.black, black_stren,
3876 basetime, increment);
3878 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3879 gameInfo.white, white_stren, gameInfo.black, black_stren,
3880 basetime, increment, (int) gameInfo.variant);
3882 if(gameInfo.variant == VariantNormal)
3883 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3884 gameInfo.white, white_stren, gameInfo.black, black_stren,
3885 basetime, increment);
3887 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3888 gameInfo.white, white_stren, gameInfo.black, black_stren,
3889 basetime, increment, VariantName(gameInfo.variant));
3892 if (appData.debugMode) {
3893 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3898 /* Display the board */
3899 if (!pausing && !appData.noGUI) {
3901 if (appData.premove)
3903 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3904 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3905 ClearPremoveHighlights();
3907 DrawPosition(FALSE, boards[currentMove]);
3908 DisplayMove(moveNum - 1);
3909 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3910 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3911 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3912 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3916 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3918 if(bookHit) { // [HGM] book: simulate book reply
3919 static char bookMove[MSG_SIZ]; // a bit generous?
3921 programStats.nodes = programStats.depth = programStats.time =
3922 programStats.score = programStats.got_only_move = 0;
3923 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3925 strcpy(bookMove, "move ");
3926 strcat(bookMove, bookHit);
3927 HandleMachineMove(bookMove, &first);
3936 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3937 ics_getting_history = H_REQUESTED;
3938 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3944 AnalysisPeriodicEvent(force)
3947 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3948 && !force) || !appData.periodicUpdates)
3951 /* Send . command to Crafty to collect stats */
3952 SendToProgram(".\n", &first);
3954 /* Don't send another until we get a response (this makes
3955 us stop sending to old Crafty's which don't understand
3956 the "." command (sending illegal cmds resets node count & time,
3957 which looks bad)) */
3958 programStats.ok_to_send = 0;
3961 void ics_update_width(new_width)
3964 ics_printf("set width %d\n", new_width);
3968 SendMoveToProgram(moveNum, cps)
3970 ChessProgramState *cps;
3974 if (cps->useUsermove) {
3975 SendToProgram("usermove ", cps);
3979 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3980 int len = space - parseList[moveNum];
3981 memcpy(buf, parseList[moveNum], len);
3983 buf[len] = NULLCHAR;
3985 sprintf(buf, "%s\n", parseList[moveNum]);
3987 SendToProgram(buf, cps);
3989 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3990 AlphaRank(moveList[moveNum], 4);
3991 SendToProgram(moveList[moveNum], cps);
3992 AlphaRank(moveList[moveNum], 4); // and back
3994 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3995 * the engine. It would be nice to have a better way to identify castle
3997 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3998 && cps->useOOCastle) {
3999 int fromX = moveList[moveNum][0] - AAA;
4000 int fromY = moveList[moveNum][1] - ONE;
4001 int toX = moveList[moveNum][2] - AAA;
4002 int toY = moveList[moveNum][3] - ONE;
4003 if((boards[moveNum][fromY][fromX] == WhiteKing
4004 && boards[moveNum][toY][toX] == WhiteRook)
4005 || (boards[moveNum][fromY][fromX] == BlackKing
4006 && boards[moveNum][toY][toX] == BlackRook)) {
4007 if(toX > fromX) SendToProgram("O-O\n", cps);
4008 else SendToProgram("O-O-O\n", cps);
4010 else SendToProgram(moveList[moveNum], cps);
4012 else SendToProgram(moveList[moveNum], cps);
4013 /* End of additions by Tord */
4016 /* [HGM] setting up the opening has brought engine in force mode! */
4017 /* Send 'go' if we are in a mode where machine should play. */
4018 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4019 (gameMode == TwoMachinesPlay ||
4021 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4023 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4024 SendToProgram("go\n", cps);
4025 if (appData.debugMode) {
4026 fprintf(debugFP, "(extra)\n");
4029 setboardSpoiledMachineBlack = 0;
4033 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4035 int fromX, fromY, toX, toY;
4037 char user_move[MSG_SIZ];
4041 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4042 (int)moveType, fromX, fromY, toX, toY);
4043 DisplayError(user_move + strlen("say "), 0);
4045 case WhiteKingSideCastle:
4046 case BlackKingSideCastle:
4047 case WhiteQueenSideCastleWild:
4048 case BlackQueenSideCastleWild:
4050 case WhiteHSideCastleFR:
4051 case BlackHSideCastleFR:
4053 sprintf(user_move, "o-o\n");
4055 case WhiteQueenSideCastle:
4056 case BlackQueenSideCastle:
4057 case WhiteKingSideCastleWild:
4058 case BlackKingSideCastleWild:
4060 case WhiteASideCastleFR:
4061 case BlackASideCastleFR:
4063 sprintf(user_move, "o-o-o\n");
4065 case WhitePromotionQueen:
4066 case BlackPromotionQueen:
4067 case WhitePromotionRook:
4068 case BlackPromotionRook:
4069 case WhitePromotionBishop:
4070 case BlackPromotionBishop:
4071 case WhitePromotionKnight:
4072 case BlackPromotionKnight:
4073 case WhitePromotionKing:
4074 case BlackPromotionKing:
4075 case WhitePromotionChancellor:
4076 case BlackPromotionChancellor:
4077 case WhitePromotionArchbishop:
4078 case BlackPromotionArchbishop:
4079 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4080 sprintf(user_move, "%c%c%c%c=%c\n",
4081 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4082 PieceToChar(WhiteFerz));
4083 else if(gameInfo.variant == VariantGreat)
4084 sprintf(user_move, "%c%c%c%c=%c\n",
4085 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4086 PieceToChar(WhiteMan));
4088 sprintf(user_move, "%c%c%c%c=%c\n",
4089 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4090 PieceToChar(PromoPiece(moveType)));
4094 sprintf(user_move, "%c@%c%c\n",
4095 ToUpper(PieceToChar((ChessSquare) fromX)),
4096 AAA + toX, ONE + toY);
4099 case WhiteCapturesEnPassant:
4100 case BlackCapturesEnPassant:
4101 case IllegalMove: /* could be a variant we don't quite understand */
4102 sprintf(user_move, "%c%c%c%c\n",
4103 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4106 SendToICS(user_move);
4107 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4108 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4112 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4117 if (rf == DROP_RANK) {
4118 sprintf(move, "%c@%c%c\n",
4119 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4121 if (promoChar == 'x' || promoChar == NULLCHAR) {
4122 sprintf(move, "%c%c%c%c\n",
4123 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4125 sprintf(move, "%c%c%c%c%c\n",
4126 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4132 ProcessICSInitScript(f)
4137 while (fgets(buf, MSG_SIZ, f)) {
4138 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4145 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4147 AlphaRank(char *move, int n)
4149 // char *p = move, c; int x, y;
4151 if (appData.debugMode) {
4152 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4156 move[2]>='0' && move[2]<='9' &&
4157 move[3]>='a' && move[3]<='x' ) {
4159 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4160 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4162 if(move[0]>='0' && move[0]<='9' &&
4163 move[1]>='a' && move[1]<='x' &&
4164 move[2]>='0' && move[2]<='9' &&
4165 move[3]>='a' && move[3]<='x' ) {
4166 /* input move, Shogi -> normal */
4167 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4168 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4169 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4170 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4173 move[3]>='0' && move[3]<='9' &&
4174 move[2]>='a' && move[2]<='x' ) {
4176 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4177 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4180 move[0]>='a' && move[0]<='x' &&
4181 move[3]>='0' && move[3]<='9' &&
4182 move[2]>='a' && move[2]<='x' ) {
4183 /* output move, normal -> Shogi */
4184 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4185 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4186 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4187 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4188 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4190 if (appData.debugMode) {
4191 fprintf(debugFP, " out = '%s'\n", move);
4195 /* Parser for moves from gnuchess, ICS, or user typein box */
4197 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4200 ChessMove *moveType;
4201 int *fromX, *fromY, *toX, *toY;
4204 if (appData.debugMode) {
4205 fprintf(debugFP, "move to parse: %s\n", move);
4207 *moveType = yylexstr(moveNum, move);
4209 switch (*moveType) {
4210 case WhitePromotionChancellor:
4211 case BlackPromotionChancellor:
4212 case WhitePromotionArchbishop:
4213 case BlackPromotionArchbishop:
4214 case WhitePromotionQueen:
4215 case BlackPromotionQueen:
4216 case WhitePromotionRook:
4217 case BlackPromotionRook:
4218 case WhitePromotionBishop:
4219 case BlackPromotionBishop:
4220 case WhitePromotionKnight:
4221 case BlackPromotionKnight:
4222 case WhitePromotionKing:
4223 case BlackPromotionKing:
4225 case WhiteCapturesEnPassant:
4226 case BlackCapturesEnPassant:
4227 case WhiteKingSideCastle:
4228 case WhiteQueenSideCastle:
4229 case BlackKingSideCastle:
4230 case BlackQueenSideCastle:
4231 case WhiteKingSideCastleWild:
4232 case WhiteQueenSideCastleWild:
4233 case BlackKingSideCastleWild:
4234 case BlackQueenSideCastleWild:
4235 /* Code added by Tord: */
4236 case WhiteHSideCastleFR:
4237 case WhiteASideCastleFR:
4238 case BlackHSideCastleFR:
4239 case BlackASideCastleFR:
4240 /* End of code added by Tord */
4241 case IllegalMove: /* bug or odd chess variant */
4242 *fromX = currentMoveString[0] - AAA;
4243 *fromY = currentMoveString[1] - ONE;
4244 *toX = currentMoveString[2] - AAA;
4245 *toY = currentMoveString[3] - ONE;
4246 *promoChar = currentMoveString[4];
4247 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4248 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4249 if (appData.debugMode) {
4250 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4252 *fromX = *fromY = *toX = *toY = 0;
4255 if (appData.testLegality) {
4256 return (*moveType != IllegalMove);
4258 return !(fromX == fromY && toX == toY);
4263 *fromX = *moveType == WhiteDrop ?
4264 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4265 (int) CharToPiece(ToLower(currentMoveString[0]));
4267 *toX = currentMoveString[2] - AAA;
4268 *toY = currentMoveString[3] - ONE;
4269 *promoChar = NULLCHAR;
4273 case ImpossibleMove:
4274 case (ChessMove) 0: /* end of file */
4283 if (appData.debugMode) {
4284 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4287 *fromX = *fromY = *toX = *toY = 0;
4288 *promoChar = NULLCHAR;
4293 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4294 // All positions will have equal probability, but the current method will not provide a unique
4295 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4301 int piecesLeft[(int)BlackPawn];
4302 int seed, nrOfShuffles;
4304 void GetPositionNumber()
4305 { // sets global variable seed
4308 seed = appData.defaultFrcPosition;
4309 if(seed < 0) { // randomize based on time for negative FRC position numbers
4310 for(i=0; i<50; i++) seed += random();
4311 seed = random() ^ random() >> 8 ^ random() << 8;
4312 if(seed<0) seed = -seed;
4316 int put(Board board, int pieceType, int rank, int n, int shade)
4317 // put the piece on the (n-1)-th empty squares of the given shade
4321 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4322 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4323 board[rank][i] = (ChessSquare) pieceType;
4324 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4326 piecesLeft[pieceType]--;
4334 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4335 // calculate where the next piece goes, (any empty square), and put it there
4339 i = seed % squaresLeft[shade];
4340 nrOfShuffles *= squaresLeft[shade];
4341 seed /= squaresLeft[shade];
4342 put(board, pieceType, rank, i, shade);
4345 void AddTwoPieces(Board board, int pieceType, int rank)
4346 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4348 int i, n=squaresLeft[ANY], j=n-1, k;
4350 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4351 i = seed % k; // pick one
4354 while(i >= j) i -= j--;
4355 j = n - 1 - j; i += j;
4356 put(board, pieceType, rank, j, ANY);
4357 put(board, pieceType, rank, i, ANY);
4360 void SetUpShuffle(Board board, int number)
4364 GetPositionNumber(); nrOfShuffles = 1;
4366 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4367 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4368 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4370 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4372 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4373 p = (int) board[0][i];
4374 if(p < (int) BlackPawn) piecesLeft[p] ++;
4375 board[0][i] = EmptySquare;
4378 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4379 // shuffles restricted to allow normal castling put KRR first
4380 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4381 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4382 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4383 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4384 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4385 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4386 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4387 put(board, WhiteRook, 0, 0, ANY);
4388 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4391 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4392 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4393 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4394 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4395 while(piecesLeft[p] >= 2) {
4396 AddOnePiece(board, p, 0, LITE);
4397 AddOnePiece(board, p, 0, DARK);
4399 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4402 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4403 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4404 // but we leave King and Rooks for last, to possibly obey FRC restriction
4405 if(p == (int)WhiteRook) continue;
4406 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4407 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4410 // now everything is placed, except perhaps King (Unicorn) and Rooks
4412 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4413 // Last King gets castling rights
4414 while(piecesLeft[(int)WhiteUnicorn]) {
4415 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4416 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4419 while(piecesLeft[(int)WhiteKing]) {
4420 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4421 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4426 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4427 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4430 // Only Rooks can be left; simply place them all
4431 while(piecesLeft[(int)WhiteRook]) {
4432 i = put(board, WhiteRook, 0, 0, ANY);
4433 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4436 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4438 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4441 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4442 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4445 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4448 int SetCharTable( char *table, const char * map )
4449 /* [HGM] moved here from winboard.c because of its general usefulness */
4450 /* Basically a safe strcpy that uses the last character as King */
4452 int result = FALSE; int NrPieces;
4454 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4455 && NrPieces >= 12 && !(NrPieces&1)) {
4456 int i; /* [HGM] Accept even length from 12 to 34 */
4458 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4459 for( i=0; i<NrPieces/2-1; i++ ) {
4461 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4463 table[(int) WhiteKing] = map[NrPieces/2-1];
4464 table[(int) BlackKing] = map[NrPieces-1];
4472 void Prelude(Board board)
4473 { // [HGM] superchess: random selection of exo-pieces
4474 int i, j, k; ChessSquare p;
4475 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4477 GetPositionNumber(); // use FRC position number
4479 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4480 SetCharTable(pieceToChar, appData.pieceToCharTable);
4481 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4482 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4485 j = seed%4; seed /= 4;
4486 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4487 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4488 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4489 j = seed%3 + (seed%3 >= j); seed /= 3;
4490 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4491 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4492 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4493 j = seed%3; seed /= 3;
4494 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4495 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4496 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4497 j = seed%2 + (seed%2 >= j); seed /= 2;
4498 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4499 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4500 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4501 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4502 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4503 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4504 put(board, exoPieces[0], 0, 0, ANY);
4505 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4509 InitPosition(redraw)
4512 ChessSquare (* pieces)[BOARD_SIZE];
4513 int i, j, pawnRow, overrule,
4514 oldx = gameInfo.boardWidth,
4515 oldy = gameInfo.boardHeight,
4516 oldh = gameInfo.holdingsWidth,
4517 oldv = gameInfo.variant;
4519 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4521 /* [AS] Initialize pv info list [HGM] and game status */
4523 for( i=0; i<MAX_MOVES; i++ ) {
4524 pvInfoList[i].depth = 0;
4525 epStatus[i]=EP_NONE;
4526 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4529 initialRulePlies = 0; /* 50-move counter start */
4531 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4532 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4536 /* [HGM] logic here is completely changed. In stead of full positions */
4537 /* the initialized data only consist of the two backranks. The switch */
4538 /* selects which one we will use, which is than copied to the Board */
4539 /* initialPosition, which for the rest is initialized by Pawns and */
4540 /* empty squares. This initial position is then copied to boards[0], */
4541 /* possibly after shuffling, so that it remains available. */
4543 gameInfo.holdingsWidth = 0; /* default board sizes */
4544 gameInfo.boardWidth = 8;
4545 gameInfo.boardHeight = 8;
4546 gameInfo.holdingsSize = 0;
4547 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4548 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4549 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4551 switch (gameInfo.variant) {
4552 case VariantFischeRandom:
4553 shuffleOpenings = TRUE;
4557 case VariantShatranj:
4558 pieces = ShatranjArray;
4559 nrCastlingRights = 0;
4560 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4562 case VariantTwoKings:
4563 pieces = twoKingsArray;
4565 case VariantCapaRandom:
4566 shuffleOpenings = TRUE;
4567 case VariantCapablanca:
4568 pieces = CapablancaArray;
4569 gameInfo.boardWidth = 10;
4570 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4573 pieces = GothicArray;
4574 gameInfo.boardWidth = 10;
4575 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4578 pieces = JanusArray;
4579 gameInfo.boardWidth = 10;
4580 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4581 nrCastlingRights = 6;
4582 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4583 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4584 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4585 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4586 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4587 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4590 pieces = FalconArray;
4591 gameInfo.boardWidth = 10;
4592 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4594 case VariantXiangqi:
4595 pieces = XiangqiArray;
4596 gameInfo.boardWidth = 9;
4597 gameInfo.boardHeight = 10;
4598 nrCastlingRights = 0;
4599 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4602 pieces = ShogiArray;
4603 gameInfo.boardWidth = 9;
4604 gameInfo.boardHeight = 9;
4605 gameInfo.holdingsSize = 7;
4606 nrCastlingRights = 0;
4607 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4609 case VariantCourier:
4610 pieces = CourierArray;
4611 gameInfo.boardWidth = 12;
4612 nrCastlingRights = 0;
4613 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4614 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4616 case VariantKnightmate:
4617 pieces = KnightmateArray;
4618 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4621 pieces = fairyArray;
4622 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4625 pieces = GreatArray;
4626 gameInfo.boardWidth = 10;
4627 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4628 gameInfo.holdingsSize = 8;
4632 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4633 gameInfo.holdingsSize = 8;
4634 startedFromSetupPosition = TRUE;
4636 case VariantCrazyhouse:
4637 case VariantBughouse:
4639 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4640 gameInfo.holdingsSize = 5;
4642 case VariantWildCastle:
4644 /* !!?shuffle with kings guaranteed to be on d or e file */
4645 shuffleOpenings = 1;
4647 case VariantNoCastle:
4649 nrCastlingRights = 0;
4650 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4651 /* !!?unconstrained back-rank shuffle */
4652 shuffleOpenings = 1;
4657 if(appData.NrFiles >= 0) {
4658 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4659 gameInfo.boardWidth = appData.NrFiles;
4661 if(appData.NrRanks >= 0) {
4662 gameInfo.boardHeight = appData.NrRanks;
4664 if(appData.holdingsSize >= 0) {
4665 i = appData.holdingsSize;
4666 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4667 gameInfo.holdingsSize = i;
4669 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4670 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4671 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4673 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4674 if(pawnRow < 1) pawnRow = 1;
4676 /* User pieceToChar list overrules defaults */
4677 if(appData.pieceToCharTable != NULL)
4678 SetCharTable(pieceToChar, appData.pieceToCharTable);
4680 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4682 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4683 s = (ChessSquare) 0; /* account holding counts in guard band */
4684 for( i=0; i<BOARD_HEIGHT; i++ )
4685 initialPosition[i][j] = s;
4687 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4688 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4689 initialPosition[pawnRow][j] = WhitePawn;
4690 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4691 if(gameInfo.variant == VariantXiangqi) {
4693 initialPosition[pawnRow][j] =
4694 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4695 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4696 initialPosition[2][j] = WhiteCannon;
4697 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4701 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4703 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4706 initialPosition[1][j] = WhiteBishop;
4707 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4709 initialPosition[1][j] = WhiteRook;
4710 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4713 if( nrCastlingRights == -1) {
4714 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4715 /* This sets default castling rights from none to normal corners */
4716 /* Variants with other castling rights must set them themselves above */
4717 nrCastlingRights = 6;
4719 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4720 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4721 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4722 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4723 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4724 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4727 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4728 if(gameInfo.variant == VariantGreat) { // promotion commoners
4729 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4730 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4731 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4732 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4734 if (appData.debugMode) {
4735 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4737 if(shuffleOpenings) {
4738 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4739 startedFromSetupPosition = TRUE;
4741 if(startedFromPositionFile) {
4742 /* [HGM] loadPos: use PositionFile for every new game */
4743 CopyBoard(initialPosition, filePosition);
4744 for(i=0; i<nrCastlingRights; i++)
4745 castlingRights[0][i] = initialRights[i] = fileRights[i];
4746 startedFromSetupPosition = TRUE;
4749 CopyBoard(boards[0], initialPosition);
4751 if(oldx != gameInfo.boardWidth ||
4752 oldy != gameInfo.boardHeight ||
4753 oldh != gameInfo.holdingsWidth
4755 || oldv == VariantGothic || // For licensing popups
4756 gameInfo.variant == VariantGothic
4759 || oldv == VariantFalcon ||
4760 gameInfo.variant == VariantFalcon
4763 InitDrawingSizes(-2 ,0);
4766 DrawPosition(TRUE, boards[currentMove]);
4770 SendBoard(cps, moveNum)
4771 ChessProgramState *cps;
4774 char message[MSG_SIZ];
4776 if (cps->useSetboard) {
4777 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4778 sprintf(message, "setboard %s\n", fen);
4779 SendToProgram(message, cps);
4785 /* Kludge to set black to move, avoiding the troublesome and now
4786 * deprecated "black" command.
4788 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4790 SendToProgram("edit\n", cps);
4791 SendToProgram("#\n", cps);
4792 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4793 bp = &boards[moveNum][i][BOARD_LEFT];
4794 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4795 if ((int) *bp < (int) BlackPawn) {
4796 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4798 if(message[0] == '+' || message[0] == '~') {
4799 sprintf(message, "%c%c%c+\n",
4800 PieceToChar((ChessSquare)(DEMOTED *bp)),
4803 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4804 message[1] = BOARD_RGHT - 1 - j + '1';
4805 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4807 SendToProgram(message, cps);
4812 SendToProgram("c\n", cps);
4813 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4814 bp = &boards[moveNum][i][BOARD_LEFT];
4815 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4816 if (((int) *bp != (int) EmptySquare)
4817 && ((int) *bp >= (int) BlackPawn)) {
4818 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4820 if(message[0] == '+' || message[0] == '~') {
4821 sprintf(message, "%c%c%c+\n",
4822 PieceToChar((ChessSquare)(DEMOTED *bp)),
4825 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4826 message[1] = BOARD_RGHT - 1 - j + '1';
4827 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4829 SendToProgram(message, cps);
4834 SendToProgram(".\n", cps);
4836 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4840 IsPromotion(fromX, fromY, toX, toY)
4841 int fromX, fromY, toX, toY;
4843 /* [HGM] add Shogi promotions */
4844 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4847 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4848 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4849 /* [HGM] Note to self: line above also weeds out drops */
4850 piece = boards[currentMove][fromY][fromX];
4851 if(gameInfo.variant == VariantShogi) {
4852 promotionZoneSize = 3;
4853 highestPromotingPiece = (int)WhiteKing;
4854 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4855 and if in normal chess we then allow promotion to King, why not
4856 allow promotion of other piece in Shogi? */
4858 if((int)piece >= BlackPawn) {
4859 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4861 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4863 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4864 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4866 return ( (int)piece <= highestPromotingPiece );
4870 InPalace(row, column)
4872 { /* [HGM] for Xiangqi */
4873 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4874 column < (BOARD_WIDTH + 4)/2 &&
4875 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4880 PieceForSquare (x, y)
4884 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4887 return boards[currentMove][y][x];
4891 OKToStartUserMove(x, y)
4894 ChessSquare from_piece;
4897 if (matchMode) return FALSE;
4898 if (gameMode == EditPosition) return TRUE;
4900 if (x >= 0 && y >= 0)
4901 from_piece = boards[currentMove][y][x];
4903 from_piece = EmptySquare;
4905 if (from_piece == EmptySquare) return FALSE;
4907 white_piece = (int)from_piece >= (int)WhitePawn &&
4908 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4911 case PlayFromGameFile:
4913 case TwoMachinesPlay:
4921 case MachinePlaysWhite:
4922 case IcsPlayingBlack:
4923 if (appData.zippyPlay) return FALSE;
4925 DisplayMoveError(_("You are playing Black"));
4930 case MachinePlaysBlack:
4931 case IcsPlayingWhite:
4932 if (appData.zippyPlay) return FALSE;
4934 DisplayMoveError(_("You are playing White"));
4940 if (!white_piece && WhiteOnMove(currentMove)) {
4941 DisplayMoveError(_("It is White's turn"));
4944 if (white_piece && !WhiteOnMove(currentMove)) {
4945 DisplayMoveError(_("It is Black's turn"));
4948 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4949 /* Editing correspondence game history */
4950 /* Could disallow this or prompt for confirmation */
4953 if (currentMove < forwardMostMove) {
4954 /* Discarding moves */
4955 /* Could prompt for confirmation here,
4956 but I don't think that's such a good idea */
4957 forwardMostMove = currentMove;
4961 case BeginningOfGame:
4962 if (appData.icsActive) return FALSE;
4963 if (!appData.noChessProgram) {
4965 DisplayMoveError(_("You are playing White"));
4972 if (!white_piece && WhiteOnMove(currentMove)) {
4973 DisplayMoveError(_("It is White's turn"));
4976 if (white_piece && !WhiteOnMove(currentMove)) {
4977 DisplayMoveError(_("It is Black's turn"));
4986 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4987 && gameMode != AnalyzeFile && gameMode != Training) {
4988 DisplayMoveError(_("Displayed position is not current"));
4994 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4995 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4996 int lastLoadGameUseList = FALSE;
4997 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4998 ChessMove lastLoadGameStart = (ChessMove) 0;
5001 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5002 int fromX, fromY, toX, toY;
5007 ChessSquare pdown, pup;
5009 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5011 /* [HGM] suppress all moves into holdings area and guard band */
5012 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5013 return ImpossibleMove;
5015 /* [HGM] <sameColor> moved to here from winboard.c */
5016 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5017 pdown = boards[currentMove][fromY][fromX];
5018 pup = boards[currentMove][toY][toX];
5019 if ( gameMode != EditPosition && !captureOwn &&
5020 (WhitePawn <= pdown && pdown < BlackPawn &&
5021 WhitePawn <= pup && pup < BlackPawn ||
5022 BlackPawn <= pdown && pdown < EmptySquare &&
5023 BlackPawn <= pup && pup < EmptySquare
5024 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5025 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5026 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5027 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5028 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5032 /* Check if the user is playing in turn. This is complicated because we
5033 let the user "pick up" a piece before it is his turn. So the piece he
5034 tried to pick up may have been captured by the time he puts it down!
5035 Therefore we use the color the user is supposed to be playing in this
5036 test, not the color of the piece that is currently on the starting
5037 square---except in EditGame mode, where the user is playing both
5038 sides; fortunately there the capture race can't happen. (It can
5039 now happen in IcsExamining mode, but that's just too bad. The user
5040 will get a somewhat confusing message in that case.)
5044 case PlayFromGameFile:
5046 case TwoMachinesPlay:
5050 /* We switched into a game mode where moves are not accepted,
5051 perhaps while the mouse button was down. */
5052 return ImpossibleMove;
5054 case MachinePlaysWhite:
5055 /* User is moving for Black */
5056 if (WhiteOnMove(currentMove)) {
5057 DisplayMoveError(_("It is White's turn"));
5058 return ImpossibleMove;
5062 case MachinePlaysBlack:
5063 /* User is moving for White */
5064 if (!WhiteOnMove(currentMove)) {
5065 DisplayMoveError(_("It is Black's turn"));
5066 return ImpossibleMove;
5072 case BeginningOfGame:
5075 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5076 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5077 /* User is moving for Black */
5078 if (WhiteOnMove(currentMove)) {
5079 DisplayMoveError(_("It is White's turn"));
5080 return ImpossibleMove;
5083 /* User is moving for White */
5084 if (!WhiteOnMove(currentMove)) {
5085 DisplayMoveError(_("It is Black's turn"));
5086 return ImpossibleMove;
5091 case IcsPlayingBlack:
5092 /* User is moving for Black */
5093 if (WhiteOnMove(currentMove)) {
5094 if (!appData.premove) {
5095 DisplayMoveError(_("It is White's turn"));
5096 } else if (toX >= 0 && toY >= 0) {
5099 premoveFromX = fromX;
5100 premoveFromY = fromY;
5101 premovePromoChar = promoChar;
5103 if (appData.debugMode)
5104 fprintf(debugFP, "Got premove: fromX %d,"
5105 "fromY %d, toX %d, toY %d\n",
5106 fromX, fromY, toX, toY);
5108 return ImpossibleMove;
5112 case IcsPlayingWhite:
5113 /* User is moving for White */
5114 if (!WhiteOnMove(currentMove)) {
5115 if (!appData.premove) {
5116 DisplayMoveError(_("It is Black's turn"));
5117 } else if (toX >= 0 && toY >= 0) {
5120 premoveFromX = fromX;
5121 premoveFromY = fromY;
5122 premovePromoChar = promoChar;
5124 if (appData.debugMode)
5125 fprintf(debugFP, "Got premove: fromX %d,"
5126 "fromY %d, toX %d, toY %d\n",
5127 fromX, fromY, toX, toY);
5129 return ImpossibleMove;
5137 /* EditPosition, empty square, or different color piece;
5138 click-click move is possible */
5139 if (toX == -2 || toY == -2) {
5140 boards[0][fromY][fromX] = EmptySquare;
5141 return AmbiguousMove;
5142 } else if (toX >= 0 && toY >= 0) {
5143 boards[0][toY][toX] = boards[0][fromY][fromX];
5144 boards[0][fromY][fromX] = EmptySquare;
5145 return AmbiguousMove;
5147 return ImpossibleMove;
5150 /* [HGM] If move started in holdings, it means a drop */
5151 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5152 if( pup != EmptySquare ) return ImpossibleMove;
5153 if(appData.testLegality) {
5154 /* it would be more logical if LegalityTest() also figured out
5155 * which drops are legal. For now we forbid pawns on back rank.
5156 * Shogi is on its own here...
5158 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5159 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5160 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5162 return WhiteDrop; /* Not needed to specify white or black yet */
5165 userOfferedDraw = FALSE;
5167 /* [HGM] always test for legality, to get promotion info */
5168 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5169 epStatus[currentMove], castlingRights[currentMove],
5170 fromY, fromX, toY, toX, promoChar);
5171 /* [HGM] but possibly ignore an IllegalMove result */
5172 if (appData.testLegality) {
5173 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5174 DisplayMoveError(_("Illegal move"));
5175 return ImpossibleMove;
5178 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5180 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5181 function is made into one that returns an OK move type if FinishMove
5182 should be called. This to give the calling driver routine the
5183 opportunity to finish the userMove input with a promotion popup,
5184 without bothering the user with this for invalid or illegal moves */
5186 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5189 /* Common tail of UserMoveEvent and DropMenuEvent */
5191 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5193 int fromX, fromY, toX, toY;
5194 /*char*/int promoChar;
5197 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5198 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5199 // [HGM] superchess: suppress promotions to non-available piece
5200 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5201 if(WhiteOnMove(currentMove)) {
5202 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5204 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5208 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5209 move type in caller when we know the move is a legal promotion */
5210 if(moveType == NormalMove && promoChar)
5211 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5212 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5213 /* [HGM] convert drag-and-drop piece drops to standard form */
5214 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5215 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5216 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5217 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5218 // fromX = boards[currentMove][fromY][fromX];
5219 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5220 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5221 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5222 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5226 /* [HGM] <popupFix> The following if has been moved here from
5227 UserMoveEvent(). Because it seemed to belon here (why not allow
5228 piece drops in training games?), and because it can only be
5229 performed after it is known to what we promote. */
5230 if (gameMode == Training) {
5231 /* compare the move played on the board to the next move in the
5232 * game. If they match, display the move and the opponent's response.
5233 * If they don't match, display an error message.
5236 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5237 CopyBoard(testBoard, boards[currentMove]);
5238 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5240 if (CompareBoards(testBoard, boards[currentMove+1])) {
5241 ForwardInner(currentMove+1);
5243 /* Autoplay the opponent's response.
5244 * if appData.animate was TRUE when Training mode was entered,
5245 * the response will be animated.
5247 saveAnimate = appData.animate;
5248 appData.animate = animateTraining;
5249 ForwardInner(currentMove+1);
5250 appData.animate = saveAnimate;
5252 /* check for the end of the game */
5253 if (currentMove >= forwardMostMove) {
5254 gameMode = PlayFromGameFile;
5256 SetTrainingModeOff();
5257 DisplayInformation(_("End of game"));
5260 DisplayError(_("Incorrect move"), 0);
5265 /* Ok, now we know that the move is good, so we can kill
5266 the previous line in Analysis Mode */
5267 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5268 forwardMostMove = currentMove;
5271 /* If we need the chess program but it's dead, restart it */
5272 ResurrectChessProgram();
5274 /* A user move restarts a paused game*/
5278 thinkOutput[0] = NULLCHAR;
5280 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5282 if (gameMode == BeginningOfGame) {
5283 if (appData.noChessProgram) {
5284 gameMode = EditGame;
5288 gameMode = MachinePlaysBlack;
5291 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5293 if (first.sendName) {
5294 sprintf(buf, "name %s\n", gameInfo.white);
5295 SendToProgram(buf, &first);
5301 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5302 /* Relay move to ICS or chess engine */
5303 if (appData.icsActive) {
5304 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5305 gameMode == IcsExamining) {
5306 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5310 if (first.sendTime && (gameMode == BeginningOfGame ||
5311 gameMode == MachinePlaysWhite ||
5312 gameMode == MachinePlaysBlack)) {
5313 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5315 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5316 // [HGM] book: if program might be playing, let it use book
5317 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5318 first.maybeThinking = TRUE;
5319 } else SendMoveToProgram(forwardMostMove-1, &first);
5320 if (currentMove == cmailOldMove + 1) {
5321 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5325 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5329 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5330 EP_UNKNOWN, castlingRights[currentMove]) ) {
5336 if (WhiteOnMove(currentMove)) {
5337 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5339 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5343 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5348 case MachinePlaysBlack:
5349 case MachinePlaysWhite:
5350 /* disable certain menu options while machine is thinking */
5351 SetMachineThinkingEnables();
5358 if(bookHit) { // [HGM] book: simulate book reply
5359 static char bookMove[MSG_SIZ]; // a bit generous?
5361 programStats.nodes = programStats.depth = programStats.time =
5362 programStats.score = programStats.got_only_move = 0;
5363 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5365 strcpy(bookMove, "move ");
5366 strcat(bookMove, bookHit);
5367 HandleMachineMove(bookMove, &first);
5373 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5374 int fromX, fromY, toX, toY;
5377 /* [HGM] This routine was added to allow calling of its two logical
5378 parts from other modules in the old way. Before, UserMoveEvent()
5379 automatically called FinishMove() if the move was OK, and returned
5380 otherwise. I separated the two, in order to make it possible to
5381 slip a promotion popup in between. But that it always needs two
5382 calls, to the first part, (now called UserMoveTest() ), and to
5383 FinishMove if the first part succeeded. Calls that do not need
5384 to do anything in between, can call this routine the old way.
5386 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5387 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5388 if(moveType == AmbiguousMove)
5389 DrawPosition(FALSE, boards[currentMove]);
5390 else if(moveType != ImpossibleMove && moveType != Comment)
5391 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5394 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5396 // char * hint = lastHint;
5397 FrontEndProgramStats stats;
5399 stats.which = cps == &first ? 0 : 1;
5400 stats.depth = cpstats->depth;
5401 stats.nodes = cpstats->nodes;
5402 stats.score = cpstats->score;
5403 stats.time = cpstats->time;
5404 stats.pv = cpstats->movelist;
5405 stats.hint = lastHint;
5406 stats.an_move_index = 0;
5407 stats.an_move_count = 0;
5409 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5410 stats.hint = cpstats->move_name;
5411 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5412 stats.an_move_count = cpstats->nr_moves;
5415 SetProgramStats( &stats );
5418 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5419 { // [HGM] book: this routine intercepts moves to simulate book replies
5420 char *bookHit = NULL;
5422 //first determine if the incoming move brings opponent into his book
5423 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5424 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5425 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5426 if(bookHit != NULL && !cps->bookSuspend) {
5427 // make sure opponent is not going to reply after receiving move to book position
5428 SendToProgram("force\n", cps);
5429 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5431 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5432 // now arrange restart after book miss
5434 // after a book hit we never send 'go', and the code after the call to this routine
5435 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5437 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5438 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5439 SendToProgram(buf, cps);
5440 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5441 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5442 SendToProgram("go\n", cps);
5443 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5444 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5445 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5446 SendToProgram("go\n", cps);
5447 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5449 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5453 ChessProgramState *savedState;
5454 void DeferredBookMove(void)
5456 if(savedState->lastPing != savedState->lastPong)
5457 ScheduleDelayedEvent(DeferredBookMove, 10);
5459 HandleMachineMove(savedMessage, savedState);
5463 HandleMachineMove(message, cps)
5465 ChessProgramState *cps;
5467 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5468 char realname[MSG_SIZ];
5469 int fromX, fromY, toX, toY;
5476 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5478 * Kludge to ignore BEL characters
5480 while (*message == '\007') message++;
5483 * [HGM] engine debug message: ignore lines starting with '#' character
5485 if(cps->debug && *message == '#') return;
5488 * Look for book output
5490 if (cps == &first && bookRequested) {
5491 if (message[0] == '\t' || message[0] == ' ') {
5492 /* Part of the book output is here; append it */
5493 strcat(bookOutput, message);
5494 strcat(bookOutput, " \n");
5496 } else if (bookOutput[0] != NULLCHAR) {
5497 /* All of book output has arrived; display it */
5498 char *p = bookOutput;
5499 while (*p != NULLCHAR) {
5500 if (*p == '\t') *p = ' ';
5503 DisplayInformation(bookOutput);
5504 bookRequested = FALSE;
5505 /* Fall through to parse the current output */
5510 * Look for machine move.
5512 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5513 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5515 /* This method is only useful on engines that support ping */
5516 if (cps->lastPing != cps->lastPong) {
5517 if (gameMode == BeginningOfGame) {
5518 /* Extra move from before last new; ignore */
5519 if (appData.debugMode) {
5520 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5523 if (appData.debugMode) {
5524 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5525 cps->which, gameMode);
5528 SendToProgram("undo\n", cps);
5534 case BeginningOfGame:
5535 /* Extra move from before last reset; ignore */
5536 if (appData.debugMode) {
5537 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5544 /* Extra move after we tried to stop. The mode test is
5545 not a reliable way of detecting this problem, but it's
5546 the best we can do on engines that don't support ping.
5548 if (appData.debugMode) {
5549 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5550 cps->which, gameMode);
5552 SendToProgram("undo\n", cps);
5555 case MachinePlaysWhite:
5556 case IcsPlayingWhite:
5557 machineWhite = TRUE;
5560 case MachinePlaysBlack:
5561 case IcsPlayingBlack:
5562 machineWhite = FALSE;
5565 case TwoMachinesPlay:
5566 machineWhite = (cps->twoMachinesColor[0] == 'w');
5569 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5570 if (appData.debugMode) {
5572 "Ignoring move out of turn by %s, gameMode %d"
5573 ", forwardMost %d\n",
5574 cps->which, gameMode, forwardMostMove);
5579 if (appData.debugMode) { int f = forwardMostMove;
5580 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5581 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5583 if(cps->alphaRank) AlphaRank(machineMove, 4);
5584 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5585 &fromX, &fromY, &toX, &toY, &promoChar)) {
5586 /* Machine move could not be parsed; ignore it. */
5587 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5588 machineMove, cps->which);
5589 DisplayError(buf1, 0);
5590 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5591 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5592 if (gameMode == TwoMachinesPlay) {
5593 GameEnds(machineWhite ? BlackWins : WhiteWins,
5599 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5600 /* So we have to redo legality test with true e.p. status here, */
5601 /* to make sure an illegal e.p. capture does not slip through, */
5602 /* to cause a forfeit on a justified illegal-move complaint */
5603 /* of the opponent. */
5604 if( gameMode==TwoMachinesPlay && appData.testLegality
5605 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5608 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5609 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5610 fromY, fromX, toY, toX, promoChar);
5611 if (appData.debugMode) {
5613 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5614 castlingRights[forwardMostMove][i], castlingRank[i]);
5615 fprintf(debugFP, "castling rights\n");
5617 if(moveType == IllegalMove) {
5618 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5619 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5620 GameEnds(machineWhite ? BlackWins : WhiteWins,
5623 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5624 /* [HGM] Kludge to handle engines that send FRC-style castling
5625 when they shouldn't (like TSCP-Gothic) */
5627 case WhiteASideCastleFR:
5628 case BlackASideCastleFR:
5630 currentMoveString[2]++;
5632 case WhiteHSideCastleFR:
5633 case BlackHSideCastleFR:
5635 currentMoveString[2]--;
5637 default: ; // nothing to do, but suppresses warning of pedantic compilers
5640 hintRequested = FALSE;
5641 lastHint[0] = NULLCHAR;
5642 bookRequested = FALSE;
5643 /* Program may be pondering now */
5644 cps->maybeThinking = TRUE;
5645 if (cps->sendTime == 2) cps->sendTime = 1;
5646 if (cps->offeredDraw) cps->offeredDraw--;
5649 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5651 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5653 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5654 char buf[3*MSG_SIZ];
5656 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5657 programStats.score / 100.,
5659 programStats.time / 100.,
5660 (unsigned int)programStats.nodes,
5661 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5662 programStats.movelist);
5664 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5668 /* currentMoveString is set as a side-effect of ParseOneMove */
5669 strcpy(machineMove, currentMoveString);
5670 strcat(machineMove, "\n");
5671 strcpy(moveList[forwardMostMove], machineMove);
5673 /* [AS] Save move info and clear stats for next move */
5674 pvInfoList[ forwardMostMove ].score = programStats.score;
5675 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5676 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5677 ClearProgramStats();
5678 thinkOutput[0] = NULLCHAR;
5679 hiddenThinkOutputState = 0;
5681 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5683 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5684 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5687 while( count < adjudicateLossPlies ) {
5688 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5691 score = -score; /* Flip score for winning side */
5694 if( score > adjudicateLossThreshold ) {
5701 if( count >= adjudicateLossPlies ) {
5702 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5704 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5705 "Xboard adjudication",
5712 if( gameMode == TwoMachinesPlay ) {
5713 // [HGM] some adjudications useful with buggy engines
5714 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5715 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5718 if( appData.testLegality )
5719 { /* [HGM] Some more adjudications for obstinate engines */
5720 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5721 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5722 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5723 static int moveCount = 6;
5725 char *reason = NULL;
5727 /* Count what is on board. */
5728 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5729 { ChessSquare p = boards[forwardMostMove][i][j];
5733 { /* count B,N,R and other of each side */
5736 NrK++; break; // [HGM] atomic: count Kings
5740 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5741 bishopsColor |= 1 << ((i^j)&1);
5746 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5747 bishopsColor |= 1 << ((i^j)&1);
5762 PawnAdvance += m; NrPawns++;
5764 NrPieces += (p != EmptySquare);
5765 NrW += ((int)p < (int)BlackPawn);
5766 if(gameInfo.variant == VariantXiangqi &&
5767 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5768 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5769 NrW -= ((int)p < (int)BlackPawn);
5773 /* Some material-based adjudications that have to be made before stalemate test */
5774 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5775 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5776 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5777 if(appData.checkMates) {
5778 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5779 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5780 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5781 "Xboard adjudication: King destroyed", GE_XBOARD );
5786 /* Bare King in Shatranj (loses) or Losers (wins) */
5787 if( NrW == 1 || NrPieces - NrW == 1) {
5788 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5789 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5790 if(appData.checkMates) {
5791 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5792 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5793 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5794 "Xboard adjudication: Bare king", GE_XBOARD );
5798 if( gameInfo.variant == VariantShatranj && --bare < 0)
5800 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5801 if(appData.checkMates) {
5802 /* but only adjudicate if adjudication enabled */
5803 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5804 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5805 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5806 "Xboard adjudication: Bare king", GE_XBOARD );
5813 // don't wait for engine to announce game end if we can judge ourselves
5814 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5815 castlingRights[forwardMostMove]) ) {
5817 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5818 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5819 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5820 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5823 reason = "Xboard adjudication: 3rd check";
5824 epStatus[forwardMostMove] = EP_CHECKMATE;
5834 reason = "Xboard adjudication: Stalemate";
5835 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5836 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5837 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5838 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5839 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5840 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5841 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5842 EP_CHECKMATE : EP_WINS);
5843 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5844 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5848 reason = "Xboard adjudication: Checkmate";
5849 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5853 switch(i = epStatus[forwardMostMove]) {
5855 result = GameIsDrawn; break;
5857 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5859 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5861 result = (ChessMove) 0;
5863 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5864 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5865 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5866 GameEnds( result, reason, GE_XBOARD );
5870 /* Next absolutely insufficient mating material. */
5871 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5872 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5873 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5874 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5875 { /* KBK, KNK, KK of KBKB with like Bishops */
5877 /* always flag draws, for judging claims */
5878 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5880 if(appData.materialDraws) {
5881 /* but only adjudicate them if adjudication enabled */
5882 SendToProgram("force\n", cps->other); // suppress reply
5883 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5884 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5885 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5890 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5892 ( NrWR == 1 && NrBR == 1 /* KRKR */
5893 || NrWQ==1 && NrBQ==1 /* KQKQ */
5894 || NrWN==2 || NrBN==2 /* KNNK */
5895 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5897 if(--moveCount < 0 && appData.trivialDraws)
5898 { /* if the first 3 moves do not show a tactical win, declare draw */
5899 SendToProgram("force\n", cps->other); // suppress reply
5900 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5901 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5902 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5905 } else moveCount = 6;
5909 if (appData.debugMode) { int i;
5910 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5911 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5912 appData.drawRepeats);
5913 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5914 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5918 /* Check for rep-draws */
5920 for(k = forwardMostMove-2;
5921 k>=backwardMostMove && k>=forwardMostMove-100 &&
5922 epStatus[k] < EP_UNKNOWN &&
5923 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5926 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5927 /* compare castling rights */
5928 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5929 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5930 rights++; /* King lost rights, while rook still had them */
5931 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5932 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5933 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5934 rights++; /* but at least one rook lost them */
5936 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5937 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5939 if( castlingRights[forwardMostMove][5] >= 0 ) {
5940 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5941 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5944 if( rights == 0 && ++count > appData.drawRepeats-2
5945 && appData.drawRepeats > 1) {
5946 /* adjudicate after user-specified nr of repeats */
5947 SendToProgram("force\n", cps->other); // suppress reply
5948 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5949 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5950 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5951 // [HGM] xiangqi: check for forbidden perpetuals
5952 int m, ourPerpetual = 1, hisPerpetual = 1;
5953 for(m=forwardMostMove; m>k; m-=2) {
5954 if(MateTest(boards[m], PosFlags(m),
5955 EP_NONE, castlingRights[m]) != MT_CHECK)
5956 ourPerpetual = 0; // the current mover did not always check
5957 if(MateTest(boards[m-1], PosFlags(m-1),
5958 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5959 hisPerpetual = 0; // the opponent did not always check
5961 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5962 ourPerpetual, hisPerpetual);
5963 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5964 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5965 "Xboard adjudication: perpetual checking", GE_XBOARD );
5968 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5969 break; // (or we would have caught him before). Abort repetition-checking loop.
5970 // Now check for perpetual chases
5971 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5972 hisPerpetual = PerpetualChase(k, forwardMostMove);
5973 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5974 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5975 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5976 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5979 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5980 break; // Abort repetition-checking loop.
5982 // if neither of us is checking or chasing all the time, or both are, it is draw
5984 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5987 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5988 epStatus[forwardMostMove] = EP_REP_DRAW;
5992 /* Now we test for 50-move draws. Determine ply count */
5993 count = forwardMostMove;
5994 /* look for last irreversble move */
5995 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5997 /* if we hit starting position, add initial plies */
5998 if( count == backwardMostMove )
5999 count -= initialRulePlies;
6000 count = forwardMostMove - count;
6002 epStatus[forwardMostMove] = EP_RULE_DRAW;
6003 /* this is used to judge if draw claims are legal */
6004 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6005 SendToProgram("force\n", cps->other); // suppress reply
6006 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6007 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6008 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6012 /* if draw offer is pending, treat it as a draw claim
6013 * when draw condition present, to allow engines a way to
6014 * claim draws before making their move to avoid a race
6015 * condition occurring after their move
6017 if( cps->other->offeredDraw || cps->offeredDraw ) {
6019 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6020 p = "Draw claim: 50-move rule";
6021 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6022 p = "Draw claim: 3-fold repetition";
6023 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6024 p = "Draw claim: insufficient mating material";
6026 SendToProgram("force\n", cps->other); // suppress reply
6027 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6028 GameEnds( GameIsDrawn, p, GE_XBOARD );
6029 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6035 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6036 SendToProgram("force\n", cps->other); // suppress reply
6037 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6038 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6040 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6047 if (gameMode == TwoMachinesPlay) {
6048 /* [HGM] relaying draw offers moved to after reception of move */
6049 /* and interpreting offer as claim if it brings draw condition */
6050 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6051 SendToProgram("draw\n", cps->other);
6053 if (cps->other->sendTime) {
6054 SendTimeRemaining(cps->other,
6055 cps->other->twoMachinesColor[0] == 'w');
6057 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6058 if (firstMove && !bookHit) {
6060 if (cps->other->useColors) {
6061 SendToProgram(cps->other->twoMachinesColor, cps->other);
6063 SendToProgram("go\n", cps->other);
6065 cps->other->maybeThinking = TRUE;
6068 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6070 if (!pausing && appData.ringBellAfterMoves) {
6075 * Reenable menu items that were disabled while
6076 * machine was thinking
6078 if (gameMode != TwoMachinesPlay)
6079 SetUserThinkingEnables();
6081 // [HGM] book: after book hit opponent has received move and is now in force mode
6082 // force the book reply into it, and then fake that it outputted this move by jumping
6083 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6085 static char bookMove[MSG_SIZ]; // a bit generous?
6087 strcpy(bookMove, "move ");
6088 strcat(bookMove, bookHit);
6091 programStats.nodes = programStats.depth = programStats.time =
6092 programStats.score = programStats.got_only_move = 0;
6093 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6095 if(cps->lastPing != cps->lastPong) {
6096 savedMessage = message; // args for deferred call
6098 ScheduleDelayedEvent(DeferredBookMove, 10);
6107 /* Set special modes for chess engines. Later something general
6108 * could be added here; for now there is just one kludge feature,
6109 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6110 * when "xboard" is given as an interactive command.
6112 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6113 cps->useSigint = FALSE;
6114 cps->useSigterm = FALSE;
6116 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6117 ParseFeatures(message+8, cps);
6118 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6121 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6122 * want this, I was asked to put it in, and obliged.
6124 if (!strncmp(message, "setboard ", 9)) {
6125 Board initial_position; int i;
6127 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6129 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6130 DisplayError(_("Bad FEN received from engine"), 0);
6133 Reset(FALSE, FALSE);
6134 CopyBoard(boards[0], initial_position);
6135 initialRulePlies = FENrulePlies;
6136 epStatus[0] = FENepStatus;
6137 for( i=0; i<nrCastlingRights; i++ )
6138 castlingRights[0][i] = FENcastlingRights[i];
6139 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6140 else gameMode = MachinePlaysBlack;
6141 DrawPosition(FALSE, boards[currentMove]);
6147 * Look for communication commands
6149 if (!strncmp(message, "telluser ", 9)) {
6150 DisplayNote(message + 9);
6153 if (!strncmp(message, "tellusererror ", 14)) {
6154 DisplayError(message + 14, 0);
6157 if (!strncmp(message, "tellopponent ", 13)) {
6158 if (appData.icsActive) {
6160 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6164 DisplayNote(message + 13);
6168 if (!strncmp(message, "tellothers ", 11)) {
6169 if (appData.icsActive) {
6171 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6177 if (!strncmp(message, "tellall ", 8)) {
6178 if (appData.icsActive) {
6180 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6184 DisplayNote(message + 8);
6188 if (strncmp(message, "warning", 7) == 0) {
6189 /* Undocumented feature, use tellusererror in new code */
6190 DisplayError(message, 0);
6193 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6194 strcpy(realname, cps->tidy);
6195 strcat(realname, " query");
6196 AskQuestion(realname, buf2, buf1, cps->pr);
6199 /* Commands from the engine directly to ICS. We don't allow these to be
6200 * sent until we are logged on. Crafty kibitzes have been known to
6201 * interfere with the login process.
6204 if (!strncmp(message, "tellics ", 8)) {
6205 SendToICS(message + 8);
6209 if (!strncmp(message, "tellicsnoalias ", 15)) {
6210 SendToICS(ics_prefix);
6211 SendToICS(message + 15);
6215 /* The following are for backward compatibility only */
6216 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6217 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6218 SendToICS(ics_prefix);
6224 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6228 * If the move is illegal, cancel it and redraw the board.
6229 * Also deal with other error cases. Matching is rather loose
6230 * here to accommodate engines written before the spec.
6232 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6233 strncmp(message, "Error", 5) == 0) {
6234 if (StrStr(message, "name") ||
6235 StrStr(message, "rating") || StrStr(message, "?") ||
6236 StrStr(message, "result") || StrStr(message, "board") ||
6237 StrStr(message, "bk") || StrStr(message, "computer") ||
6238 StrStr(message, "variant") || StrStr(message, "hint") ||
6239 StrStr(message, "random") || StrStr(message, "depth") ||
6240 StrStr(message, "accepted")) {
6243 if (StrStr(message, "protover")) {
6244 /* Program is responding to input, so it's apparently done
6245 initializing, and this error message indicates it is
6246 protocol version 1. So we don't need to wait any longer
6247 for it to initialize and send feature commands. */
6248 FeatureDone(cps, 1);
6249 cps->protocolVersion = 1;
6252 cps->maybeThinking = FALSE;
6254 if (StrStr(message, "draw")) {
6255 /* Program doesn't have "draw" command */
6256 cps->sendDrawOffers = 0;
6259 if (cps->sendTime != 1 &&
6260 (StrStr(message, "time") || StrStr(message, "otim"))) {
6261 /* Program apparently doesn't have "time" or "otim" command */
6265 if (StrStr(message, "analyze")) {
6266 cps->analysisSupport = FALSE;
6267 cps->analyzing = FALSE;
6269 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6270 DisplayError(buf2, 0);
6273 if (StrStr(message, "(no matching move)st")) {
6274 /* Special kludge for GNU Chess 4 only */
6275 cps->stKludge = TRUE;
6276 SendTimeControl(cps, movesPerSession, timeControl,
6277 timeIncrement, appData.searchDepth,
6281 if (StrStr(message, "(no matching move)sd")) {
6282 /* Special kludge for GNU Chess 4 only */
6283 cps->sdKludge = TRUE;
6284 SendTimeControl(cps, movesPerSession, timeControl,
6285 timeIncrement, appData.searchDepth,
6289 if (!StrStr(message, "llegal")) {
6292 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6293 gameMode == IcsIdle) return;
6294 if (forwardMostMove <= backwardMostMove) return;
6295 if (pausing) PauseEvent();
6296 if(appData.forceIllegal) {
6297 // [HGM] illegal: machine refused move; force position after move into it
6298 SendToProgram("force\n", cps);
6299 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6300 // we have a real problem now, as SendBoard will use the a2a3 kludge
6301 // when black is to move, while there might be nothing on a2 or black
6302 // might already have the move. So send the board as if white has the move.
6303 // But first we must change the stm of the engine, as it refused the last move
6304 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6305 if(WhiteOnMove(forwardMostMove)) {
6306 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6307 SendBoard(cps, forwardMostMove); // kludgeless board
6309 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6310 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6311 SendBoard(cps, forwardMostMove+1); // kludgeless board
6313 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6314 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6315 gameMode == TwoMachinesPlay)
6316 SendToProgram("go\n", cps);
6319 if (gameMode == PlayFromGameFile) {
6320 /* Stop reading this game file */
6321 gameMode = EditGame;
6324 currentMove = --forwardMostMove;
6325 DisplayMove(currentMove-1); /* before DisplayMoveError */
6327 DisplayBothClocks();
6328 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6329 parseList[currentMove], cps->which);
6330 DisplayMoveError(buf1);
6331 DrawPosition(FALSE, boards[currentMove]);
6333 /* [HGM] illegal-move claim should forfeit game when Xboard */
6334 /* only passes fully legal moves */
6335 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6336 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6337 "False illegal-move claim", GE_XBOARD );
6341 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6342 /* Program has a broken "time" command that
6343 outputs a string not ending in newline.
6349 * If chess program startup fails, exit with an error message.
6350 * Attempts to recover here are futile.
6352 if ((StrStr(message, "unknown host") != NULL)
6353 || (StrStr(message, "No remote directory") != NULL)
6354 || (StrStr(message, "not found") != NULL)
6355 || (StrStr(message, "No such file") != NULL)
6356 || (StrStr(message, "can't alloc") != NULL)
6357 || (StrStr(message, "Permission denied") != NULL)) {
6359 cps->maybeThinking = FALSE;
6360 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6361 cps->which, cps->program, cps->host, message);
6362 RemoveInputSource(cps->isr);
6363 DisplayFatalError(buf1, 0, 1);
6368 * Look for hint output
6370 if (sscanf(message, "Hint: %s", buf1) == 1) {
6371 if (cps == &first && hintRequested) {
6372 hintRequested = FALSE;
6373 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6374 &fromX, &fromY, &toX, &toY, &promoChar)) {
6375 (void) CoordsToAlgebraic(boards[forwardMostMove],
6376 PosFlags(forwardMostMove), EP_UNKNOWN,
6377 fromY, fromX, toY, toX, promoChar, buf1);
6378 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6379 DisplayInformation(buf2);
6381 /* Hint move could not be parsed!? */
6382 snprintf(buf2, sizeof(buf2),
6383 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6385 DisplayError(buf2, 0);
6388 strcpy(lastHint, buf1);
6394 * Ignore other messages if game is not in progress
6396 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6397 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6400 * look for win, lose, draw, or draw offer
6402 if (strncmp(message, "1-0", 3) == 0) {
6403 char *p, *q, *r = "";
6404 p = strchr(message, '{');
6412 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6414 } else if (strncmp(message, "0-1", 3) == 0) {
6415 char *p, *q, *r = "";
6416 p = strchr(message, '{');
6424 /* Kludge for Arasan 4.1 bug */
6425 if (strcmp(r, "Black resigns") == 0) {
6426 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6429 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6431 } else if (strncmp(message, "1/2", 3) == 0) {
6432 char *p, *q, *r = "";
6433 p = strchr(message, '{');
6442 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6445 } else if (strncmp(message, "White resign", 12) == 0) {
6446 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6448 } else if (strncmp(message, "Black resign", 12) == 0) {
6449 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6451 } else if (strncmp(message, "White matches", 13) == 0 ||
6452 strncmp(message, "Black matches", 13) == 0 ) {
6453 /* [HGM] ignore GNUShogi noises */
6455 } else if (strncmp(message, "White", 5) == 0 &&
6456 message[5] != '(' &&
6457 StrStr(message, "Black") == NULL) {
6458 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6460 } else if (strncmp(message, "Black", 5) == 0 &&
6461 message[5] != '(') {
6462 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6464 } else if (strcmp(message, "resign") == 0 ||
6465 strcmp(message, "computer resigns") == 0) {
6467 case MachinePlaysBlack:
6468 case IcsPlayingBlack:
6469 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6471 case MachinePlaysWhite:
6472 case IcsPlayingWhite:
6473 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6475 case TwoMachinesPlay:
6476 if (cps->twoMachinesColor[0] == 'w')
6477 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6479 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6486 } else if (strncmp(message, "opponent mates", 14) == 0) {
6488 case MachinePlaysBlack:
6489 case IcsPlayingBlack:
6490 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6492 case MachinePlaysWhite:
6493 case IcsPlayingWhite:
6494 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6496 case TwoMachinesPlay:
6497 if (cps->twoMachinesColor[0] == 'w')
6498 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6500 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6507 } else if (strncmp(message, "computer mates", 14) == 0) {
6509 case MachinePlaysBlack:
6510 case IcsPlayingBlack:
6511 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6513 case MachinePlaysWhite:
6514 case IcsPlayingWhite:
6515 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6517 case TwoMachinesPlay:
6518 if (cps->twoMachinesColor[0] == 'w')
6519 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6521 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6528 } else if (strncmp(message, "checkmate", 9) == 0) {
6529 if (WhiteOnMove(forwardMostMove)) {
6530 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6532 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6535 } else if (strstr(message, "Draw") != NULL ||
6536 strstr(message, "game is a draw") != NULL) {
6537 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6539 } else if (strstr(message, "offer") != NULL &&
6540 strstr(message, "draw") != NULL) {
6542 if (appData.zippyPlay && first.initDone) {
6543 /* Relay offer to ICS */
6544 SendToICS(ics_prefix);
6545 SendToICS("draw\n");
6548 cps->offeredDraw = 2; /* valid until this engine moves twice */
6549 if (gameMode == TwoMachinesPlay) {
6550 if (cps->other->offeredDraw) {
6551 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6552 /* [HGM] in two-machine mode we delay relaying draw offer */
6553 /* until after we also have move, to see if it is really claim */
6555 } else if (gameMode == MachinePlaysWhite ||
6556 gameMode == MachinePlaysBlack) {
6557 if (userOfferedDraw) {
6558 DisplayInformation(_("Machine accepts your draw offer"));
6559 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6561 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6568 * Look for thinking output
6570 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6571 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6573 int plylev, mvleft, mvtot, curscore, time;
6574 char mvname[MOVE_LEN];
6578 int prefixHint = FALSE;
6579 mvname[0] = NULLCHAR;
6582 case MachinePlaysBlack:
6583 case IcsPlayingBlack:
6584 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6586 case MachinePlaysWhite:
6587 case IcsPlayingWhite:
6588 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6593 case IcsObserving: /* [DM] icsEngineAnalyze */
6594 if (!appData.icsEngineAnalyze) ignore = TRUE;
6596 case TwoMachinesPlay:
6597 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6608 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6609 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6611 if (plyext != ' ' && plyext != '\t') {
6615 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6616 if( cps->scoreIsAbsolute &&
6617 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6619 curscore = -curscore;
6623 programStats.depth = plylev;
6624 programStats.nodes = nodes;
6625 programStats.time = time;
6626 programStats.score = curscore;
6627 programStats.got_only_move = 0;
6629 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6632 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6633 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6634 if(WhiteOnMove(forwardMostMove))
6635 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6636 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6639 /* Buffer overflow protection */
6640 if (buf1[0] != NULLCHAR) {
6641 if (strlen(buf1) >= sizeof(programStats.movelist)
6642 && appData.debugMode) {
6644 "PV is too long; using the first %d bytes.\n",
6645 sizeof(programStats.movelist) - 1);
6648 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6650 sprintf(programStats.movelist, " no PV\n");
6653 if (programStats.seen_stat) {
6654 programStats.ok_to_send = 1;
6657 if (strchr(programStats.movelist, '(') != NULL) {
6658 programStats.line_is_book = 1;
6659 programStats.nr_moves = 0;
6660 programStats.moves_left = 0;
6662 programStats.line_is_book = 0;
6665 SendProgramStatsToFrontend( cps, &programStats );
6668 [AS] Protect the thinkOutput buffer from overflow... this
6669 is only useful if buf1 hasn't overflowed first!
6671 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6673 (gameMode == TwoMachinesPlay ?
6674 ToUpper(cps->twoMachinesColor[0]) : ' '),
6675 ((double) curscore) / 100.0,
6676 prefixHint ? lastHint : "",
6677 prefixHint ? " " : "" );
6679 if( buf1[0] != NULLCHAR ) {
6680 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6682 if( strlen(buf1) > max_len ) {
6683 if( appData.debugMode) {
6684 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6686 buf1[max_len+1] = '\0';
6689 strcat( thinkOutput, buf1 );
6692 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6693 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6694 DisplayMove(currentMove - 1);
6699 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6700 /* crafty (9.25+) says "(only move) <move>"
6701 * if there is only 1 legal move
6703 sscanf(p, "(only move) %s", buf1);
6704 sprintf(thinkOutput, "%s (only move)", buf1);
6705 sprintf(programStats.movelist, "%s (only move)", buf1);
6706 programStats.depth = 1;
6707 programStats.nr_moves = 1;
6708 programStats.moves_left = 1;
6709 programStats.nodes = 1;
6710 programStats.time = 1;
6711 programStats.got_only_move = 1;
6713 /* Not really, but we also use this member to
6714 mean "line isn't going to change" (Crafty
6715 isn't searching, so stats won't change) */
6716 programStats.line_is_book = 1;
6718 SendProgramStatsToFrontend( cps, &programStats );
6720 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6721 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6722 DisplayMove(currentMove - 1);
6726 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6727 &time, &nodes, &plylev, &mvleft,
6728 &mvtot, mvname) >= 5) {
6729 /* The stat01: line is from Crafty (9.29+) in response
6730 to the "." command */
6731 programStats.seen_stat = 1;
6732 cps->maybeThinking = TRUE;
6734 if (programStats.got_only_move || !appData.periodicUpdates)
6737 programStats.depth = plylev;
6738 programStats.time = time;
6739 programStats.nodes = nodes;
6740 programStats.moves_left = mvleft;
6741 programStats.nr_moves = mvtot;
6742 strcpy(programStats.move_name, mvname);
6743 programStats.ok_to_send = 1;
6744 programStats.movelist[0] = '\0';
6746 SendProgramStatsToFrontend( cps, &programStats );
6751 } else if (strncmp(message,"++",2) == 0) {
6752 /* Crafty 9.29+ outputs this */
6753 programStats.got_fail = 2;
6756 } else if (strncmp(message,"--",2) == 0) {
6757 /* Crafty 9.29+ outputs this */
6758 programStats.got_fail = 1;
6761 } else if (thinkOutput[0] != NULLCHAR &&
6762 strncmp(message, " ", 4) == 0) {
6763 unsigned message_len;
6766 while (*p && *p == ' ') p++;
6768 message_len = strlen( p );
6770 /* [AS] Avoid buffer overflow */
6771 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6772 strcat(thinkOutput, " ");
6773 strcat(thinkOutput, p);
6776 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6777 strcat(programStats.movelist, " ");
6778 strcat(programStats.movelist, p);
6781 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6782 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6783 DisplayMove(currentMove - 1);
6792 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6793 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6795 ChessProgramStats cpstats;
6797 if (plyext != ' ' && plyext != '\t') {
6801 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6802 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6803 curscore = -curscore;
6806 cpstats.depth = plylev;
6807 cpstats.nodes = nodes;
6808 cpstats.time = time;
6809 cpstats.score = curscore;
6810 cpstats.got_only_move = 0;
6811 cpstats.movelist[0] = '\0';
6813 if (buf1[0] != NULLCHAR) {
6814 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6817 cpstats.ok_to_send = 0;
6818 cpstats.line_is_book = 0;
6819 cpstats.nr_moves = 0;
6820 cpstats.moves_left = 0;
6822 SendProgramStatsToFrontend( cps, &cpstats );
6829 /* Parse a game score from the character string "game", and
6830 record it as the history of the current game. The game
6831 score is NOT assumed to start from the standard position.
6832 The display is not updated in any way.
6835 ParseGameHistory(game)
6839 int fromX, fromY, toX, toY, boardIndex;
6844 if (appData.debugMode)
6845 fprintf(debugFP, "Parsing game history: %s\n", game);
6847 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6848 gameInfo.site = StrSave(appData.icsHost);
6849 gameInfo.date = PGNDate();
6850 gameInfo.round = StrSave("-");
6852 /* Parse out names of players */
6853 while (*game == ' ') game++;
6855 while (*game != ' ') *p++ = *game++;
6857 gameInfo.white = StrSave(buf);
6858 while (*game == ' ') game++;
6860 while (*game != ' ' && *game != '\n') *p++ = *game++;
6862 gameInfo.black = StrSave(buf);
6865 boardIndex = blackPlaysFirst ? 1 : 0;
6868 yyboardindex = boardIndex;
6869 moveType = (ChessMove) yylex();
6871 case IllegalMove: /* maybe suicide chess, etc. */
6872 if (appData.debugMode) {
6873 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6874 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6875 setbuf(debugFP, NULL);
6877 case WhitePromotionChancellor:
6878 case BlackPromotionChancellor:
6879 case WhitePromotionArchbishop:
6880 case BlackPromotionArchbishop:
6881 case WhitePromotionQueen:
6882 case BlackPromotionQueen:
6883 case WhitePromotionRook:
6884 case BlackPromotionRook:
6885 case WhitePromotionBishop:
6886 case BlackPromotionBishop:
6887 case WhitePromotionKnight:
6888 case BlackPromotionKnight:
6889 case WhitePromotionKing:
6890 case BlackPromotionKing:
6892 case WhiteCapturesEnPassant:
6893 case BlackCapturesEnPassant:
6894 case WhiteKingSideCastle:
6895 case WhiteQueenSideCastle:
6896 case BlackKingSideCastle:
6897 case BlackQueenSideCastle:
6898 case WhiteKingSideCastleWild:
6899 case WhiteQueenSideCastleWild:
6900 case BlackKingSideCastleWild:
6901 case BlackQueenSideCastleWild:
6903 case WhiteHSideCastleFR:
6904 case WhiteASideCastleFR:
6905 case BlackHSideCastleFR:
6906 case BlackASideCastleFR:
6908 fromX = currentMoveString[0] - AAA;
6909 fromY = currentMoveString[1] - ONE;
6910 toX = currentMoveString[2] - AAA;
6911 toY = currentMoveString[3] - ONE;
6912 promoChar = currentMoveString[4];
6916 fromX = moveType == WhiteDrop ?
6917 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6918 (int) CharToPiece(ToLower(currentMoveString[0]));
6920 toX = currentMoveString[2] - AAA;
6921 toY = currentMoveString[3] - ONE;
6922 promoChar = NULLCHAR;
6926 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6927 if (appData.debugMode) {
6928 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6929 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6930 setbuf(debugFP, NULL);
6932 DisplayError(buf, 0);
6934 case ImpossibleMove:
6936 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6937 if (appData.debugMode) {
6938 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6939 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6940 setbuf(debugFP, NULL);
6942 DisplayError(buf, 0);
6944 case (ChessMove) 0: /* end of file */
6945 if (boardIndex < backwardMostMove) {
6946 /* Oops, gap. How did that happen? */
6947 DisplayError(_("Gap in move list"), 0);
6950 backwardMostMove = blackPlaysFirst ? 1 : 0;
6951 if (boardIndex > forwardMostMove) {
6952 forwardMostMove = boardIndex;
6956 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6957 strcat(parseList[boardIndex-1], " ");
6958 strcat(parseList[boardIndex-1], yy_text);
6970 case GameUnfinished:
6971 if (gameMode == IcsExamining) {
6972 if (boardIndex < backwardMostMove) {
6973 /* Oops, gap. How did that happen? */
6976 backwardMostMove = blackPlaysFirst ? 1 : 0;
6979 gameInfo.result = moveType;
6980 p = strchr(yy_text, '{');
6981 if (p == NULL) p = strchr(yy_text, '(');
6984 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6986 q = strchr(p, *p == '{' ? '}' : ')');
6987 if (q != NULL) *q = NULLCHAR;
6990 gameInfo.resultDetails = StrSave(p);
6993 if (boardIndex >= forwardMostMove &&
6994 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6995 backwardMostMove = blackPlaysFirst ? 1 : 0;
6998 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6999 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7000 parseList[boardIndex]);
7001 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7002 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7003 /* currentMoveString is set as a side-effect of yylex */
7004 strcpy(moveList[boardIndex], currentMoveString);
7005 strcat(moveList[boardIndex], "\n");
7007 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7008 castlingRights[boardIndex], &epStatus[boardIndex]);
7009 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7010 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7016 if(gameInfo.variant != VariantShogi)
7017 strcat(parseList[boardIndex - 1], "+");
7021 strcat(parseList[boardIndex - 1], "#");
7028 /* Apply a move to the given board */
7030 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7031 int fromX, fromY, toX, toY;
7037 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7039 /* [HGM] compute & store e.p. status and castling rights for new position */
7040 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7043 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7047 if( board[toY][toX] != EmptySquare )
7050 if( board[fromY][fromX] == WhitePawn ) {
7051 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7054 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7055 gameInfo.variant != VariantBerolina || toX < fromX)
7056 *ep = toX | berolina;
7057 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7058 gameInfo.variant != VariantBerolina || toX > fromX)
7062 if( board[fromY][fromX] == BlackPawn ) {
7063 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7065 if( toY-fromY== -2) {
7066 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7067 gameInfo.variant != VariantBerolina || toX < fromX)
7068 *ep = toX | berolina;
7069 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7070 gameInfo.variant != VariantBerolina || toX > fromX)
7075 for(i=0; i<nrCastlingRights; i++) {
7076 if(castling[i] == fromX && castlingRank[i] == fromY ||
7077 castling[i] == toX && castlingRank[i] == toY
7078 ) castling[i] = -1; // revoke for moved or captured piece
7083 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7084 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7085 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7087 if (fromX == toX && fromY == toY) return;
7089 if (fromY == DROP_RANK) {
7091 piece = board[toY][toX] = (ChessSquare) fromX;
7093 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7094 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7095 if(gameInfo.variant == VariantKnightmate)
7096 king += (int) WhiteUnicorn - (int) WhiteKing;
7098 /* Code added by Tord: */
7099 /* FRC castling assumed when king captures friendly rook. */
7100 if (board[fromY][fromX] == WhiteKing &&
7101 board[toY][toX] == WhiteRook) {
7102 board[fromY][fromX] = EmptySquare;
7103 board[toY][toX] = EmptySquare;
7105 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7107 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7109 } else if (board[fromY][fromX] == BlackKing &&
7110 board[toY][toX] == BlackRook) {
7111 board[fromY][fromX] = EmptySquare;
7112 board[toY][toX] = EmptySquare;
7114 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7116 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7118 /* End of code added by Tord */
7120 } else if (board[fromY][fromX] == king
7121 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7122 && toY == fromY && toX > fromX+1) {
7123 board[fromY][fromX] = EmptySquare;
7124 board[toY][toX] = king;
7125 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7126 board[fromY][BOARD_RGHT-1] = EmptySquare;
7127 } else if (board[fromY][fromX] == king
7128 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7129 && toY == fromY && toX < fromX-1) {
7130 board[fromY][fromX] = EmptySquare;
7131 board[toY][toX] = king;
7132 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7133 board[fromY][BOARD_LEFT] = EmptySquare;
7134 } else if (board[fromY][fromX] == WhitePawn
7135 && toY == BOARD_HEIGHT-1
7136 && gameInfo.variant != VariantXiangqi
7138 /* white pawn promotion */
7139 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7140 if (board[toY][toX] == EmptySquare) {
7141 board[toY][toX] = WhiteQueen;
7143 if(gameInfo.variant==VariantBughouse ||
7144 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7145 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7146 board[fromY][fromX] = EmptySquare;
7147 } else if ((fromY == BOARD_HEIGHT-4)
7149 && gameInfo.variant != VariantXiangqi
7150 && gameInfo.variant != VariantBerolina
7151 && (board[fromY][fromX] == WhitePawn)
7152 && (board[toY][toX] == EmptySquare)) {
7153 board[fromY][fromX] = EmptySquare;
7154 board[toY][toX] = WhitePawn;
7155 captured = board[toY - 1][toX];
7156 board[toY - 1][toX] = EmptySquare;
7157 } else if ((fromY == BOARD_HEIGHT-4)
7159 && gameInfo.variant == VariantBerolina
7160 && (board[fromY][fromX] == WhitePawn)
7161 && (board[toY][toX] == EmptySquare)) {
7162 board[fromY][fromX] = EmptySquare;
7163 board[toY][toX] = WhitePawn;
7164 if(oldEP & EP_BEROLIN_A) {
7165 captured = board[fromY][fromX-1];
7166 board[fromY][fromX-1] = EmptySquare;
7167 }else{ captured = board[fromY][fromX+1];
7168 board[fromY][fromX+1] = EmptySquare;
7170 } else if (board[fromY][fromX] == king
7171 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7172 && toY == fromY && toX > fromX+1) {
7173 board[fromY][fromX] = EmptySquare;
7174 board[toY][toX] = king;
7175 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7176 board[fromY][BOARD_RGHT-1] = EmptySquare;
7177 } else if (board[fromY][fromX] == king
7178 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7179 && toY == fromY && toX < fromX-1) {
7180 board[fromY][fromX] = EmptySquare;
7181 board[toY][toX] = king;
7182 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7183 board[fromY][BOARD_LEFT] = EmptySquare;
7184 } else if (fromY == 7 && fromX == 3
7185 && board[fromY][fromX] == BlackKing
7186 && toY == 7 && toX == 5) {
7187 board[fromY][fromX] = EmptySquare;
7188 board[toY][toX] = BlackKing;
7189 board[fromY][7] = EmptySquare;
7190 board[toY][4] = BlackRook;
7191 } else if (fromY == 7 && fromX == 3
7192 && board[fromY][fromX] == BlackKing
7193 && toY == 7 && toX == 1) {
7194 board[fromY][fromX] = EmptySquare;
7195 board[toY][toX] = BlackKing;
7196 board[fromY][0] = EmptySquare;
7197 board[toY][2] = BlackRook;
7198 } else if (board[fromY][fromX] == BlackPawn
7200 && gameInfo.variant != VariantXiangqi
7202 /* black pawn promotion */
7203 board[0][toX] = CharToPiece(ToLower(promoChar));
7204 if (board[0][toX] == EmptySquare) {
7205 board[0][toX] = BlackQueen;
7207 if(gameInfo.variant==VariantBughouse ||
7208 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7209 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7210 board[fromY][fromX] = EmptySquare;
7211 } else if ((fromY == 3)
7213 && gameInfo.variant != VariantXiangqi
7214 && gameInfo.variant != VariantBerolina
7215 && (board[fromY][fromX] == BlackPawn)
7216 && (board[toY][toX] == EmptySquare)) {
7217 board[fromY][fromX] = EmptySquare;
7218 board[toY][toX] = BlackPawn;
7219 captured = board[toY + 1][toX];
7220 board[toY + 1][toX] = EmptySquare;
7221 } else if ((fromY == 3)
7223 && gameInfo.variant == VariantBerolina
7224 && (board[fromY][fromX] == BlackPawn)
7225 && (board[toY][toX] == EmptySquare)) {
7226 board[fromY][fromX] = EmptySquare;
7227 board[toY][toX] = BlackPawn;
7228 if(oldEP & EP_BEROLIN_A) {
7229 captured = board[fromY][fromX-1];
7230 board[fromY][fromX-1] = EmptySquare;
7231 }else{ captured = board[fromY][fromX+1];
7232 board[fromY][fromX+1] = EmptySquare;
7235 board[toY][toX] = board[fromY][fromX];
7236 board[fromY][fromX] = EmptySquare;
7239 /* [HGM] now we promote for Shogi, if needed */
7240 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7241 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7244 if (gameInfo.holdingsWidth != 0) {
7246 /* !!A lot more code needs to be written to support holdings */
7247 /* [HGM] OK, so I have written it. Holdings are stored in the */
7248 /* penultimate board files, so they are automaticlly stored */
7249 /* in the game history. */
7250 if (fromY == DROP_RANK) {
7251 /* Delete from holdings, by decreasing count */
7252 /* and erasing image if necessary */
7254 if(p < (int) BlackPawn) { /* white drop */
7255 p -= (int)WhitePawn;
7256 if(p >= gameInfo.holdingsSize) p = 0;
7257 if(--board[p][BOARD_WIDTH-2] == 0)
7258 board[p][BOARD_WIDTH-1] = EmptySquare;
7259 } else { /* black drop */
7260 p -= (int)BlackPawn;
7261 if(p >= gameInfo.holdingsSize) p = 0;
7262 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7263 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7266 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7267 && gameInfo.variant != VariantBughouse ) {
7268 /* [HGM] holdings: Add to holdings, if holdings exist */
7269 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7270 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7271 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7274 if (p >= (int) BlackPawn) {
7275 p -= (int)BlackPawn;
7276 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7277 /* in Shogi restore piece to its original first */
7278 captured = (ChessSquare) (DEMOTED captured);
7281 p = PieceToNumber((ChessSquare)p);
7282 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7283 board[p][BOARD_WIDTH-2]++;
7284 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7286 p -= (int)WhitePawn;
7287 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7288 captured = (ChessSquare) (DEMOTED captured);
7291 p = PieceToNumber((ChessSquare)p);
7292 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7293 board[BOARD_HEIGHT-1-p][1]++;
7294 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7298 } else if (gameInfo.variant == VariantAtomic) {
7299 if (captured != EmptySquare) {
7301 for (y = toY-1; y <= toY+1; y++) {
7302 for (x = toX-1; x <= toX+1; x++) {
7303 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7304 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7305 board[y][x] = EmptySquare;
7309 board[toY][toX] = EmptySquare;
7312 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7313 /* [HGM] Shogi promotions */
7314 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7317 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7318 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7319 // [HGM] superchess: take promotion piece out of holdings
7320 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7321 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7322 if(!--board[k][BOARD_WIDTH-2])
7323 board[k][BOARD_WIDTH-1] = EmptySquare;
7325 if(!--board[BOARD_HEIGHT-1-k][1])
7326 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7332 /* Updates forwardMostMove */
7334 MakeMove(fromX, fromY, toX, toY, promoChar)
7335 int fromX, fromY, toX, toY;
7338 // forwardMostMove++; // [HGM] bare: moved downstream
7340 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7341 int timeLeft; static int lastLoadFlag=0; int king, piece;
7342 piece = boards[forwardMostMove][fromY][fromX];
7343 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7344 if(gameInfo.variant == VariantKnightmate)
7345 king += (int) WhiteUnicorn - (int) WhiteKing;
7346 if(forwardMostMove == 0) {
7348 fprintf(serverMoves, "%s;", second.tidy);
7349 fprintf(serverMoves, "%s;", first.tidy);
7350 if(!blackPlaysFirst)
7351 fprintf(serverMoves, "%s;", second.tidy);
7352 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7353 lastLoadFlag = loadFlag;
7355 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7356 // print castling suffix
7357 if( toY == fromY && piece == king ) {
7359 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7361 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7364 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7365 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7366 boards[forwardMostMove][toY][toX] == EmptySquare
7368 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7370 if(promoChar != NULLCHAR)
7371 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7373 fprintf(serverMoves, "/%d/%d",
7374 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7375 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7376 else timeLeft = blackTimeRemaining/1000;
7377 fprintf(serverMoves, "/%d", timeLeft);
7379 fflush(serverMoves);
7382 if (forwardMostMove+1 >= MAX_MOVES) {
7383 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7387 if (commentList[forwardMostMove+1] != NULL) {
7388 free(commentList[forwardMostMove+1]);
7389 commentList[forwardMostMove+1] = NULL;
7391 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7392 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7393 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7394 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7395 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7396 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7397 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7398 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7399 gameInfo.result = GameUnfinished;
7400 if (gameInfo.resultDetails != NULL) {
7401 free(gameInfo.resultDetails);
7402 gameInfo.resultDetails = NULL;
7404 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7405 moveList[forwardMostMove - 1]);
7406 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7407 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7408 fromY, fromX, toY, toX, promoChar,
7409 parseList[forwardMostMove - 1]);
7410 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7411 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7412 castlingRights[forwardMostMove]) ) {
7418 if(gameInfo.variant != VariantShogi)
7419 strcat(parseList[forwardMostMove - 1], "+");
7423 strcat(parseList[forwardMostMove - 1], "#");
7426 if (appData.debugMode) {
7427 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7432 /* Updates currentMove if not pausing */
7434 ShowMove(fromX, fromY, toX, toY)
7436 int instant = (gameMode == PlayFromGameFile) ?
7437 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7438 if(appData.noGUI) return;
7439 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7441 if (forwardMostMove == currentMove + 1) {
7442 AnimateMove(boards[forwardMostMove - 1],
7443 fromX, fromY, toX, toY);
7445 if (appData.highlightLastMove) {
7446 SetHighlights(fromX, fromY, toX, toY);
7449 currentMove = forwardMostMove;
7452 if (instant) return;
7454 DisplayMove(currentMove - 1);
7455 DrawPosition(FALSE, boards[currentMove]);
7456 DisplayBothClocks();
7457 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7460 void SendEgtPath(ChessProgramState *cps)
7461 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7462 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7464 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7467 char c, *q = name+1, *r, *s;
7469 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7470 while(*p && *p != ',') *q++ = *p++;
7472 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7473 strcmp(name, ",nalimov:") == 0 ) {
7474 // take nalimov path from the menu-changeable option first, if it is defined
7475 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7476 SendToProgram(buf,cps); // send egtbpath command for nalimov
7478 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7479 (s = StrStr(appData.egtFormats, name)) != NULL) {
7480 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7481 s = r = StrStr(s, ":") + 1; // beginning of path info
7482 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7483 c = *r; *r = 0; // temporarily null-terminate path info
7484 *--q = 0; // strip of trailig ':' from name
7485 sprintf(buf, "egtpath %s %s\n", name+1, s);
7487 SendToProgram(buf,cps); // send egtbpath command for this format
7489 if(*p == ',') p++; // read away comma to position for next format name
7494 InitChessProgram(cps, setup)
7495 ChessProgramState *cps;
7496 int setup; /* [HGM] needed to setup FRC opening position */
7498 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7499 if (appData.noChessProgram) return;
7500 hintRequested = FALSE;
7501 bookRequested = FALSE;
7503 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7504 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7505 if(cps->memSize) { /* [HGM] memory */
7506 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7507 SendToProgram(buf, cps);
7509 SendEgtPath(cps); /* [HGM] EGT */
7510 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7511 sprintf(buf, "cores %d\n", appData.smpCores);
7512 SendToProgram(buf, cps);
7515 SendToProgram(cps->initString, cps);
7516 if (gameInfo.variant != VariantNormal &&
7517 gameInfo.variant != VariantLoadable
7518 /* [HGM] also send variant if board size non-standard */
7519 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7521 char *v = VariantName(gameInfo.variant);
7522 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7523 /* [HGM] in protocol 1 we have to assume all variants valid */
7524 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7525 DisplayFatalError(buf, 0, 1);
7529 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7530 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7531 if( gameInfo.variant == VariantXiangqi )
7532 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7533 if( gameInfo.variant == VariantShogi )
7534 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7535 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7536 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7537 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7538 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7539 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7540 if( gameInfo.variant == VariantCourier )
7541 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7542 if( gameInfo.variant == VariantSuper )
7543 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7544 if( gameInfo.variant == VariantGreat )
7545 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7548 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7549 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7550 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7551 if(StrStr(cps->variants, b) == NULL) {
7552 // specific sized variant not known, check if general sizing allowed
7553 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7554 if(StrStr(cps->variants, "boardsize") == NULL) {
7555 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7556 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7557 DisplayFatalError(buf, 0, 1);
7560 /* [HGM] here we really should compare with the maximum supported board size */
7563 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7564 sprintf(buf, "variant %s\n", b);
7565 SendToProgram(buf, cps);
7567 currentlyInitializedVariant = gameInfo.variant;
7569 /* [HGM] send opening position in FRC to first engine */
7571 SendToProgram("force\n", cps);
7573 /* engine is now in force mode! Set flag to wake it up after first move. */
7574 setboardSpoiledMachineBlack = 1;
7578 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7579 SendToProgram(buf, cps);
7581 cps->maybeThinking = FALSE;
7582 cps->offeredDraw = 0;
7583 if (!appData.icsActive) {
7584 SendTimeControl(cps, movesPerSession, timeControl,
7585 timeIncrement, appData.searchDepth,
7588 if (appData.showThinking
7589 // [HGM] thinking: four options require thinking output to be sent
7590 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7592 SendToProgram("post\n", cps);
7594 SendToProgram("hard\n", cps);
7595 if (!appData.ponderNextMove) {
7596 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7597 it without being sure what state we are in first. "hard"
7598 is not a toggle, so that one is OK.
7600 SendToProgram("easy\n", cps);
7603 sprintf(buf, "ping %d\n", ++cps->lastPing);
7604 SendToProgram(buf, cps);
7606 cps->initDone = TRUE;
7611 StartChessProgram(cps)
7612 ChessProgramState *cps;
7617 if (appData.noChessProgram) return;
7618 cps->initDone = FALSE;
7620 if (strcmp(cps->host, "localhost") == 0) {
7621 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7622 } else if (*appData.remoteShell == NULLCHAR) {
7623 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7625 if (*appData.remoteUser == NULLCHAR) {
7626 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7629 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7630 cps->host, appData.remoteUser, cps->program);
7632 err = StartChildProcess(buf, "", &cps->pr);
7636 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7637 DisplayFatalError(buf, err, 1);
7643 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7644 if (cps->protocolVersion > 1) {
7645 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7646 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7647 cps->comboCnt = 0; // and values of combo boxes
7648 SendToProgram(buf, cps);
7650 SendToProgram("xboard\n", cps);
7656 TwoMachinesEventIfReady P((void))
7658 if (first.lastPing != first.lastPong) {
7659 DisplayMessage("", _("Waiting for first chess program"));
7660 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7663 if (second.lastPing != second.lastPong) {
7664 DisplayMessage("", _("Waiting for second chess program"));
7665 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7673 NextMatchGame P((void))
7675 int index; /* [HGM] autoinc: step lod index during match */
7677 if (*appData.loadGameFile != NULLCHAR) {
7678 index = appData.loadGameIndex;
7679 if(index < 0) { // [HGM] autoinc
7680 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7681 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7683 LoadGameFromFile(appData.loadGameFile,
7685 appData.loadGameFile, FALSE);
7686 } else if (*appData.loadPositionFile != NULLCHAR) {
7687 index = appData.loadPositionIndex;
7688 if(index < 0) { // [HGM] autoinc
7689 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7690 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7692 LoadPositionFromFile(appData.loadPositionFile,
7694 appData.loadPositionFile);
7696 TwoMachinesEventIfReady();
7699 void UserAdjudicationEvent( int result )
7701 ChessMove gameResult = GameIsDrawn;
7704 gameResult = WhiteWins;
7706 else if( result < 0 ) {
7707 gameResult = BlackWins;
7710 if( gameMode == TwoMachinesPlay ) {
7711 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7716 // [HGM] save: calculate checksum of game to make games easily identifiable
7717 int StringCheckSum(char *s)
7720 if(s==NULL) return 0;
7721 while(*s) i = i*259 + *s++;
7728 for(i=backwardMostMove; i<forwardMostMove; i++) {
7729 sum += pvInfoList[i].depth;
7730 sum += StringCheckSum(parseList[i]);
7731 sum += StringCheckSum(commentList[i]);
7734 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7735 return sum + StringCheckSum(commentList[i]);
7736 } // end of save patch
7739 GameEnds(result, resultDetails, whosays)
7741 char *resultDetails;
7744 GameMode nextGameMode;
7748 if(endingGame) return; /* [HGM] crash: forbid recursion */
7751 if (appData.debugMode) {
7752 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7753 result, resultDetails ? resultDetails : "(null)", whosays);
7756 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7757 /* If we are playing on ICS, the server decides when the
7758 game is over, but the engine can offer to draw, claim
7762 if (appData.zippyPlay && first.initDone) {
7763 if (result == GameIsDrawn) {
7764 /* In case draw still needs to be claimed */
7765 SendToICS(ics_prefix);
7766 SendToICS("draw\n");
7767 } else if (StrCaseStr(resultDetails, "resign")) {
7768 SendToICS(ics_prefix);
7769 SendToICS("resign\n");
7773 endingGame = 0; /* [HGM] crash */
7777 /* If we're loading the game from a file, stop */
7778 if (whosays == GE_FILE) {
7779 (void) StopLoadGameTimer();
7783 /* Cancel draw offers */
7784 first.offeredDraw = second.offeredDraw = 0;
7786 /* If this is an ICS game, only ICS can really say it's done;
7787 if not, anyone can. */
7788 isIcsGame = (gameMode == IcsPlayingWhite ||
7789 gameMode == IcsPlayingBlack ||
7790 gameMode == IcsObserving ||
7791 gameMode == IcsExamining);
7793 if (!isIcsGame || whosays == GE_ICS) {
7794 /* OK -- not an ICS game, or ICS said it was done */
7796 if (!isIcsGame && !appData.noChessProgram)
7797 SetUserThinkingEnables();
7799 /* [HGM] if a machine claims the game end we verify this claim */
7800 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7801 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7803 ChessMove trueResult = (ChessMove) -1;
7805 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7806 first.twoMachinesColor[0] :
7807 second.twoMachinesColor[0] ;
7809 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7810 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7811 /* [HGM] verify: engine mate claims accepted if they were flagged */
7812 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7814 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7815 /* [HGM] verify: engine mate claims accepted if they were flagged */
7816 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7818 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7819 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7822 // now verify win claims, but not in drop games, as we don't understand those yet
7823 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7824 || gameInfo.variant == VariantGreat) &&
7825 (result == WhiteWins && claimer == 'w' ||
7826 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7827 if (appData.debugMode) {
7828 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7829 result, epStatus[forwardMostMove], forwardMostMove);
7831 if(result != trueResult) {
7832 sprintf(buf, "False win claim: '%s'", resultDetails);
7833 result = claimer == 'w' ? BlackWins : WhiteWins;
7834 resultDetails = buf;
7837 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7838 && (forwardMostMove <= backwardMostMove ||
7839 epStatus[forwardMostMove-1] > EP_DRAWS ||
7840 (claimer=='b')==(forwardMostMove&1))
7842 /* [HGM] verify: draws that were not flagged are false claims */
7843 sprintf(buf, "False draw claim: '%s'", resultDetails);
7844 result = claimer == 'w' ? BlackWins : WhiteWins;
7845 resultDetails = buf;
7847 /* (Claiming a loss is accepted no questions asked!) */
7849 /* [HGM] bare: don't allow bare King to win */
7850 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7851 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7852 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7853 && result != GameIsDrawn)
7854 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7855 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7856 int p = (int)boards[forwardMostMove][i][j] - color;
7857 if(p >= 0 && p <= (int)WhiteKing) k++;
7859 if (appData.debugMode) {
7860 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7861 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7864 result = GameIsDrawn;
7865 sprintf(buf, "%s but bare king", resultDetails);
7866 resultDetails = buf;
7872 if(serverMoves != NULL && !loadFlag) { char c = '=';
7873 if(result==WhiteWins) c = '+';
7874 if(result==BlackWins) c = '-';
7875 if(resultDetails != NULL)
7876 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7878 if (resultDetails != NULL) {
7879 gameInfo.result = result;
7880 gameInfo.resultDetails = StrSave(resultDetails);
7882 /* display last move only if game was not loaded from file */
7883 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7884 DisplayMove(currentMove - 1);
7886 if (forwardMostMove != 0) {
7887 if (gameMode != PlayFromGameFile && gameMode != EditGame
7888 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7890 if (*appData.saveGameFile != NULLCHAR) {
7891 SaveGameToFile(appData.saveGameFile, TRUE);
7892 } else if (appData.autoSaveGames) {
7895 if (*appData.savePositionFile != NULLCHAR) {
7896 SavePositionToFile(appData.savePositionFile);
7901 /* Tell program how game ended in case it is learning */
7902 /* [HGM] Moved this to after saving the PGN, just in case */
7903 /* engine died and we got here through time loss. In that */
7904 /* case we will get a fatal error writing the pipe, which */
7905 /* would otherwise lose us the PGN. */
7906 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7907 /* output during GameEnds should never be fatal anymore */
7908 if (gameMode == MachinePlaysWhite ||
7909 gameMode == MachinePlaysBlack ||
7910 gameMode == TwoMachinesPlay ||
7911 gameMode == IcsPlayingWhite ||
7912 gameMode == IcsPlayingBlack ||
7913 gameMode == BeginningOfGame) {
7915 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7917 if (first.pr != NoProc) {
7918 SendToProgram(buf, &first);
7920 if (second.pr != NoProc &&
7921 gameMode == TwoMachinesPlay) {
7922 SendToProgram(buf, &second);
7927 if (appData.icsActive) {
7928 if (appData.quietPlay &&
7929 (gameMode == IcsPlayingWhite ||
7930 gameMode == IcsPlayingBlack)) {
7931 SendToICS(ics_prefix);
7932 SendToICS("set shout 1\n");
7934 nextGameMode = IcsIdle;
7935 ics_user_moved = FALSE;
7936 /* clean up premove. It's ugly when the game has ended and the
7937 * premove highlights are still on the board.
7941 ClearPremoveHighlights();
7942 DrawPosition(FALSE, boards[currentMove]);
7944 if (whosays == GE_ICS) {
7947 if (gameMode == IcsPlayingWhite)
7949 else if(gameMode == IcsPlayingBlack)
7953 if (gameMode == IcsPlayingBlack)
7955 else if(gameMode == IcsPlayingWhite)
7962 PlayIcsUnfinishedSound();
7965 } else if (gameMode == EditGame ||
7966 gameMode == PlayFromGameFile ||
7967 gameMode == AnalyzeMode ||
7968 gameMode == AnalyzeFile) {
7969 nextGameMode = gameMode;
7971 nextGameMode = EndOfGame;
7976 nextGameMode = gameMode;
7979 if (appData.noChessProgram) {
7980 gameMode = nextGameMode;
7982 endingGame = 0; /* [HGM] crash */
7987 /* Put first chess program into idle state */
7988 if (first.pr != NoProc &&
7989 (gameMode == MachinePlaysWhite ||
7990 gameMode == MachinePlaysBlack ||
7991 gameMode == TwoMachinesPlay ||
7992 gameMode == IcsPlayingWhite ||
7993 gameMode == IcsPlayingBlack ||
7994 gameMode == BeginningOfGame)) {
7995 SendToProgram("force\n", &first);
7996 if (first.usePing) {
7998 sprintf(buf, "ping %d\n", ++first.lastPing);
7999 SendToProgram(buf, &first);
8002 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8003 /* Kill off first chess program */
8004 if (first.isr != NULL)
8005 RemoveInputSource(first.isr);
8008 if (first.pr != NoProc) {
8010 DoSleep( appData.delayBeforeQuit );
8011 SendToProgram("quit\n", &first);
8012 DoSleep( appData.delayAfterQuit );
8013 DestroyChildProcess(first.pr, first.useSigterm);
8018 /* Put second chess program into idle state */
8019 if (second.pr != NoProc &&
8020 gameMode == TwoMachinesPlay) {
8021 SendToProgram("force\n", &second);
8022 if (second.usePing) {
8024 sprintf(buf, "ping %d\n", ++second.lastPing);
8025 SendToProgram(buf, &second);
8028 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8029 /* Kill off second chess program */
8030 if (second.isr != NULL)
8031 RemoveInputSource(second.isr);
8034 if (second.pr != NoProc) {
8035 DoSleep( appData.delayBeforeQuit );
8036 SendToProgram("quit\n", &second);
8037 DoSleep( appData.delayAfterQuit );
8038 DestroyChildProcess(second.pr, second.useSigterm);
8043 if (matchMode && gameMode == TwoMachinesPlay) {
8046 if (first.twoMachinesColor[0] == 'w') {
8053 if (first.twoMachinesColor[0] == 'b') {
8062 if (matchGame < appData.matchGames) {
8064 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8065 tmp = first.twoMachinesColor;
8066 first.twoMachinesColor = second.twoMachinesColor;
8067 second.twoMachinesColor = tmp;
8069 gameMode = nextGameMode;
8071 if(appData.matchPause>10000 || appData.matchPause<10)
8072 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8073 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8074 endingGame = 0; /* [HGM] crash */
8078 gameMode = nextGameMode;
8079 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8080 first.tidy, second.tidy,
8081 first.matchWins, second.matchWins,
8082 appData.matchGames - (first.matchWins + second.matchWins));
8083 DisplayFatalError(buf, 0, 0);
8086 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8087 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8089 gameMode = nextGameMode;
8091 endingGame = 0; /* [HGM] crash */
8094 /* Assumes program was just initialized (initString sent).
8095 Leaves program in force mode. */
8097 FeedMovesToProgram(cps, upto)
8098 ChessProgramState *cps;
8103 if (appData.debugMode)
8104 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8105 startedFromSetupPosition ? "position and " : "",
8106 backwardMostMove, upto, cps->which);
8107 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8108 // [HGM] variantswitch: make engine aware of new variant
8109 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8110 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8111 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8112 SendToProgram(buf, cps);
8113 currentlyInitializedVariant = gameInfo.variant;
8115 SendToProgram("force\n", cps);
8116 if (startedFromSetupPosition) {
8117 SendBoard(cps, backwardMostMove);
8118 if (appData.debugMode) {
8119 fprintf(debugFP, "feedMoves\n");
8122 for (i = backwardMostMove; i < upto; i++) {
8123 SendMoveToProgram(i, cps);
8129 ResurrectChessProgram()
8131 /* The chess program may have exited.
8132 If so, restart it and feed it all the moves made so far. */
8134 if (appData.noChessProgram || first.pr != NoProc) return;
8136 StartChessProgram(&first);
8137 InitChessProgram(&first, FALSE);
8138 FeedMovesToProgram(&first, currentMove);
8140 if (!first.sendTime) {
8141 /* can't tell gnuchess what its clock should read,
8142 so we bow to its notion. */
8144 timeRemaining[0][currentMove] = whiteTimeRemaining;
8145 timeRemaining[1][currentMove] = blackTimeRemaining;
8148 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8149 appData.icsEngineAnalyze) && first.analysisSupport) {
8150 SendToProgram("analyze\n", &first);
8151 first.analyzing = TRUE;
8164 if (appData.debugMode) {
8165 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8166 redraw, init, gameMode);
8168 pausing = pauseExamInvalid = FALSE;
8169 startedFromSetupPosition = blackPlaysFirst = FALSE;
8171 whiteFlag = blackFlag = FALSE;
8172 userOfferedDraw = FALSE;
8173 hintRequested = bookRequested = FALSE;
8174 first.maybeThinking = FALSE;
8175 second.maybeThinking = FALSE;
8176 first.bookSuspend = FALSE; // [HGM] book
8177 second.bookSuspend = FALSE;
8178 thinkOutput[0] = NULLCHAR;
8179 lastHint[0] = NULLCHAR;
8180 ClearGameInfo(&gameInfo);
8181 gameInfo.variant = StringToVariant(appData.variant);
8182 ics_user_moved = ics_clock_paused = FALSE;
8183 ics_getting_history = H_FALSE;
8185 white_holding[0] = black_holding[0] = NULLCHAR;
8186 ClearProgramStats();
8187 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8191 flipView = appData.flipView;
8192 ClearPremoveHighlights();
8194 alarmSounded = FALSE;
8196 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8197 if(appData.serverMovesName != NULL) {
8198 /* [HGM] prepare to make moves file for broadcasting */
8199 clock_t t = clock();
8200 if(serverMoves != NULL) fclose(serverMoves);
8201 serverMoves = fopen(appData.serverMovesName, "r");
8202 if(serverMoves != NULL) {
8203 fclose(serverMoves);
8204 /* delay 15 sec before overwriting, so all clients can see end */
8205 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8207 serverMoves = fopen(appData.serverMovesName, "w");
8211 gameMode = BeginningOfGame;
8213 if(appData.icsActive) gameInfo.variant = VariantNormal;
8214 currentMove = forwardMostMove = backwardMostMove = 0;
8215 InitPosition(redraw);
8216 for (i = 0; i < MAX_MOVES; i++) {
8217 if (commentList[i] != NULL) {
8218 free(commentList[i]);
8219 commentList[i] = NULL;
8223 timeRemaining[0][0] = whiteTimeRemaining;
8224 timeRemaining[1][0] = blackTimeRemaining;
8225 if (first.pr == NULL) {
8226 StartChessProgram(&first);
8229 InitChessProgram(&first, startedFromSetupPosition);
8232 DisplayMessage("", "");
8233 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8234 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8241 if (!AutoPlayOneMove())
8243 if (matchMode || appData.timeDelay == 0)
8245 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8247 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8256 int fromX, fromY, toX, toY;
8258 if (appData.debugMode) {
8259 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8262 if (gameMode != PlayFromGameFile)
8265 if (currentMove >= forwardMostMove) {
8266 gameMode = EditGame;
8269 /* [AS] Clear current move marker at the end of a game */
8270 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8275 toX = moveList[currentMove][2] - AAA;
8276 toY = moveList[currentMove][3] - ONE;
8278 if (moveList[currentMove][1] == '@') {
8279 if (appData.highlightLastMove) {
8280 SetHighlights(-1, -1, toX, toY);
8283 fromX = moveList[currentMove][0] - AAA;
8284 fromY = moveList[currentMove][1] - ONE;
8286 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8288 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8290 if (appData.highlightLastMove) {
8291 SetHighlights(fromX, fromY, toX, toY);
8294 DisplayMove(currentMove);
8295 SendMoveToProgram(currentMove++, &first);
8296 DisplayBothClocks();
8297 DrawPosition(FALSE, boards[currentMove]);
8298 // [HGM] PV info: always display, routine tests if empty
8299 DisplayComment(currentMove - 1, commentList[currentMove]);
8305 LoadGameOneMove(readAhead)
8306 ChessMove readAhead;
8308 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8309 char promoChar = NULLCHAR;
8314 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8315 gameMode != AnalyzeMode && gameMode != Training) {
8320 yyboardindex = forwardMostMove;
8321 if (readAhead != (ChessMove)0) {
8322 moveType = readAhead;
8324 if (gameFileFP == NULL)
8326 moveType = (ChessMove) yylex();
8332 if (appData.debugMode)
8333 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8335 if (*p == '{' || *p == '[' || *p == '(') {
8336 p[strlen(p) - 1] = NULLCHAR;
8340 /* append the comment but don't display it */
8341 while (*p == '\n') p++;
8342 AppendComment(currentMove, p);
8345 case WhiteCapturesEnPassant:
8346 case BlackCapturesEnPassant:
8347 case WhitePromotionChancellor:
8348 case BlackPromotionChancellor:
8349 case WhitePromotionArchbishop:
8350 case BlackPromotionArchbishop:
8351 case WhitePromotionCentaur:
8352 case BlackPromotionCentaur:
8353 case WhitePromotionQueen:
8354 case BlackPromotionQueen:
8355 case WhitePromotionRook:
8356 case BlackPromotionRook:
8357 case WhitePromotionBishop:
8358 case BlackPromotionBishop:
8359 case WhitePromotionKnight:
8360 case BlackPromotionKnight:
8361 case WhitePromotionKing:
8362 case BlackPromotionKing:
8364 case WhiteKingSideCastle:
8365 case WhiteQueenSideCastle:
8366 case BlackKingSideCastle:
8367 case BlackQueenSideCastle:
8368 case WhiteKingSideCastleWild:
8369 case WhiteQueenSideCastleWild:
8370 case BlackKingSideCastleWild:
8371 case BlackQueenSideCastleWild:
8373 case WhiteHSideCastleFR:
8374 case WhiteASideCastleFR:
8375 case BlackHSideCastleFR:
8376 case BlackASideCastleFR:
8378 if (appData.debugMode)
8379 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8380 fromX = currentMoveString[0] - AAA;
8381 fromY = currentMoveString[1] - ONE;
8382 toX = currentMoveString[2] - AAA;
8383 toY = currentMoveString[3] - ONE;
8384 promoChar = currentMoveString[4];
8389 if (appData.debugMode)
8390 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8391 fromX = moveType == WhiteDrop ?
8392 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8393 (int) CharToPiece(ToLower(currentMoveString[0]));
8395 toX = currentMoveString[2] - AAA;
8396 toY = currentMoveString[3] - ONE;
8402 case GameUnfinished:
8403 if (appData.debugMode)
8404 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8405 p = strchr(yy_text, '{');
8406 if (p == NULL) p = strchr(yy_text, '(');
8409 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8411 q = strchr(p, *p == '{' ? '}' : ')');
8412 if (q != NULL) *q = NULLCHAR;
8415 GameEnds(moveType, p, GE_FILE);
8417 if (cmailMsgLoaded) {
8419 flipView = WhiteOnMove(currentMove);
8420 if (moveType == GameUnfinished) flipView = !flipView;
8421 if (appData.debugMode)
8422 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8426 case (ChessMove) 0: /* end of file */
8427 if (appData.debugMode)
8428 fprintf(debugFP, "Parser hit end of file\n");
8429 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8430 EP_UNKNOWN, castlingRights[currentMove]) ) {
8436 if (WhiteOnMove(currentMove)) {
8437 GameEnds(BlackWins, "Black mates", GE_FILE);
8439 GameEnds(WhiteWins, "White mates", GE_FILE);
8443 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8450 if (lastLoadGameStart == GNUChessGame) {
8451 /* GNUChessGames have numbers, but they aren't move numbers */
8452 if (appData.debugMode)
8453 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8454 yy_text, (int) moveType);
8455 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8457 /* else fall thru */
8462 /* Reached start of next game in file */
8463 if (appData.debugMode)
8464 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8465 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8466 EP_UNKNOWN, castlingRights[currentMove]) ) {
8472 if (WhiteOnMove(currentMove)) {
8473 GameEnds(BlackWins, "Black mates", GE_FILE);
8475 GameEnds(WhiteWins, "White mates", GE_FILE);
8479 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8485 case PositionDiagram: /* should not happen; ignore */
8486 case ElapsedTime: /* ignore */
8487 case NAG: /* ignore */
8488 if (appData.debugMode)
8489 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8490 yy_text, (int) moveType);
8491 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8494 if (appData.testLegality) {
8495 if (appData.debugMode)
8496 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8497 sprintf(move, _("Illegal move: %d.%s%s"),
8498 (forwardMostMove / 2) + 1,
8499 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8500 DisplayError(move, 0);
8503 if (appData.debugMode)
8504 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8505 yy_text, currentMoveString);
8506 fromX = currentMoveString[0] - AAA;
8507 fromY = currentMoveString[1] - ONE;
8508 toX = currentMoveString[2] - AAA;
8509 toY = currentMoveString[3] - ONE;
8510 promoChar = currentMoveString[4];
8515 if (appData.debugMode)
8516 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8517 sprintf(move, _("Ambiguous move: %d.%s%s"),
8518 (forwardMostMove / 2) + 1,
8519 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8520 DisplayError(move, 0);
8525 case ImpossibleMove:
8526 if (appData.debugMode)
8527 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8528 sprintf(move, _("Illegal move: %d.%s%s"),
8529 (forwardMostMove / 2) + 1,
8530 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8531 DisplayError(move, 0);
8537 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8538 DrawPosition(FALSE, boards[currentMove]);
8539 DisplayBothClocks();
8540 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8541 DisplayComment(currentMove - 1, commentList[currentMove]);
8543 (void) StopLoadGameTimer();
8545 cmailOldMove = forwardMostMove;
8548 /* currentMoveString is set as a side-effect of yylex */
8549 strcat(currentMoveString, "\n");
8550 strcpy(moveList[forwardMostMove], currentMoveString);
8552 thinkOutput[0] = NULLCHAR;
8553 MakeMove(fromX, fromY, toX, toY, promoChar);
8554 currentMove = forwardMostMove;
8559 /* Load the nth game from the given file */
8561 LoadGameFromFile(filename, n, title, useList)
8565 /*Boolean*/ int useList;
8570 if (strcmp(filename, "-") == 0) {
8574 f = fopen(filename, "rb");
8576 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8577 DisplayError(buf, errno);
8581 if (fseek(f, 0, 0) == -1) {
8582 /* f is not seekable; probably a pipe */
8585 if (useList && n == 0) {
8586 int error = GameListBuild(f);
8588 DisplayError(_("Cannot build game list"), error);
8589 } else if (!ListEmpty(&gameList) &&
8590 ((ListGame *) gameList.tailPred)->number > 1) {
8591 GameListPopUp(f, title);
8598 return LoadGame(f, n, title, FALSE);
8603 MakeRegisteredMove()
8605 int fromX, fromY, toX, toY;
8607 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8608 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8611 if (appData.debugMode)
8612 fprintf(debugFP, "Restoring %s for game %d\n",
8613 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8615 thinkOutput[0] = NULLCHAR;
8616 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8617 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8618 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8619 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8620 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8621 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8622 MakeMove(fromX, fromY, toX, toY, promoChar);
8623 ShowMove(fromX, fromY, toX, toY);
8625 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8626 EP_UNKNOWN, castlingRights[currentMove]) ) {
8633 if (WhiteOnMove(currentMove)) {
8634 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8636 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8641 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8648 if (WhiteOnMove(currentMove)) {
8649 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8651 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8656 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8667 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8669 CmailLoadGame(f, gameNumber, title, useList)
8677 if (gameNumber > nCmailGames) {
8678 DisplayError(_("No more games in this message"), 0);
8681 if (f == lastLoadGameFP) {
8682 int offset = gameNumber - lastLoadGameNumber;
8684 cmailMsg[0] = NULLCHAR;
8685 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8686 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8687 nCmailMovesRegistered--;
8689 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8690 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8691 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8694 if (! RegisterMove()) return FALSE;
8698 retVal = LoadGame(f, gameNumber, title, useList);
8700 /* Make move registered during previous look at this game, if any */
8701 MakeRegisteredMove();
8703 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8704 commentList[currentMove]
8705 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8706 DisplayComment(currentMove - 1, commentList[currentMove]);
8712 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8717 int gameNumber = lastLoadGameNumber + offset;
8718 if (lastLoadGameFP == NULL) {
8719 DisplayError(_("No game has been loaded yet"), 0);
8722 if (gameNumber <= 0) {
8723 DisplayError(_("Can't back up any further"), 0);
8726 if (cmailMsgLoaded) {
8727 return CmailLoadGame(lastLoadGameFP, gameNumber,
8728 lastLoadGameTitle, lastLoadGameUseList);
8730 return LoadGame(lastLoadGameFP, gameNumber,
8731 lastLoadGameTitle, lastLoadGameUseList);
8737 /* Load the nth game from open file f */
8739 LoadGame(f, gameNumber, title, useList)
8747 int gn = gameNumber;
8748 ListGame *lg = NULL;
8751 GameMode oldGameMode;
8752 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8754 if (appData.debugMode)
8755 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8757 if (gameMode == Training )
8758 SetTrainingModeOff();
8760 oldGameMode = gameMode;
8761 if (gameMode != BeginningOfGame) {
8766 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8767 fclose(lastLoadGameFP);
8771 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8774 fseek(f, lg->offset, 0);
8775 GameListHighlight(gameNumber);
8779 DisplayError(_("Game number out of range"), 0);
8784 if (fseek(f, 0, 0) == -1) {
8785 if (f == lastLoadGameFP ?
8786 gameNumber == lastLoadGameNumber + 1 :
8790 DisplayError(_("Can't seek on game file"), 0);
8796 lastLoadGameNumber = gameNumber;
8797 strcpy(lastLoadGameTitle, title);
8798 lastLoadGameUseList = useList;
8802 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8803 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8804 lg->gameInfo.black);
8806 } else if (*title != NULLCHAR) {
8807 if (gameNumber > 1) {
8808 sprintf(buf, "%s %d", title, gameNumber);
8811 DisplayTitle(title);
8815 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8816 gameMode = PlayFromGameFile;
8820 currentMove = forwardMostMove = backwardMostMove = 0;
8821 CopyBoard(boards[0], initialPosition);
8825 * Skip the first gn-1 games in the file.
8826 * Also skip over anything that precedes an identifiable
8827 * start of game marker, to avoid being confused by
8828 * garbage at the start of the file. Currently
8829 * recognized start of game markers are the move number "1",
8830 * the pattern "gnuchess .* game", the pattern
8831 * "^[#;%] [^ ]* game file", and a PGN tag block.
8832 * A game that starts with one of the latter two patterns
8833 * will also have a move number 1, possibly
8834 * following a position diagram.
8835 * 5-4-02: Let's try being more lenient and allowing a game to
8836 * start with an unnumbered move. Does that break anything?
8838 cm = lastLoadGameStart = (ChessMove) 0;
8840 yyboardindex = forwardMostMove;
8841 cm = (ChessMove) yylex();
8844 if (cmailMsgLoaded) {
8845 nCmailGames = CMAIL_MAX_GAMES - gn;
8848 DisplayError(_("Game not found in file"), 0);
8855 lastLoadGameStart = cm;
8859 switch (lastLoadGameStart) {
8866 gn--; /* count this game */
8867 lastLoadGameStart = cm;
8876 switch (lastLoadGameStart) {
8881 gn--; /* count this game */
8882 lastLoadGameStart = cm;
8885 lastLoadGameStart = cm; /* game counted already */
8893 yyboardindex = forwardMostMove;
8894 cm = (ChessMove) yylex();
8895 } while (cm == PGNTag || cm == Comment);
8902 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8903 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8904 != CMAIL_OLD_RESULT) {
8906 cmailResult[ CMAIL_MAX_GAMES
8907 - gn - 1] = CMAIL_OLD_RESULT;
8913 /* Only a NormalMove can be at the start of a game
8914 * without a position diagram. */
8915 if (lastLoadGameStart == (ChessMove) 0) {
8917 lastLoadGameStart = MoveNumberOne;
8926 if (appData.debugMode)
8927 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8929 if (cm == XBoardGame) {
8930 /* Skip any header junk before position diagram and/or move 1 */
8932 yyboardindex = forwardMostMove;
8933 cm = (ChessMove) yylex();
8935 if (cm == (ChessMove) 0 ||
8936 cm == GNUChessGame || cm == XBoardGame) {
8937 /* Empty game; pretend end-of-file and handle later */
8942 if (cm == MoveNumberOne || cm == PositionDiagram ||
8943 cm == PGNTag || cm == Comment)
8946 } else if (cm == GNUChessGame) {
8947 if (gameInfo.event != NULL) {
8948 free(gameInfo.event);
8950 gameInfo.event = StrSave(yy_text);
8953 startedFromSetupPosition = FALSE;
8954 while (cm == PGNTag) {
8955 if (appData.debugMode)
8956 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8957 err = ParsePGNTag(yy_text, &gameInfo);
8958 if (!err) numPGNTags++;
8960 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8961 if(gameInfo.variant != oldVariant) {
8962 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8964 oldVariant = gameInfo.variant;
8965 if (appData.debugMode)
8966 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8970 if (gameInfo.fen != NULL) {
8971 Board initial_position;
8972 startedFromSetupPosition = TRUE;
8973 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8975 DisplayError(_("Bad FEN position in file"), 0);
8978 CopyBoard(boards[0], initial_position);
8979 if (blackPlaysFirst) {
8980 currentMove = forwardMostMove = backwardMostMove = 1;
8981 CopyBoard(boards[1], initial_position);
8982 strcpy(moveList[0], "");
8983 strcpy(parseList[0], "");
8984 timeRemaining[0][1] = whiteTimeRemaining;
8985 timeRemaining[1][1] = blackTimeRemaining;
8986 if (commentList[0] != NULL) {
8987 commentList[1] = commentList[0];
8988 commentList[0] = NULL;
8991 currentMove = forwardMostMove = backwardMostMove = 0;
8993 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8995 initialRulePlies = FENrulePlies;
8996 epStatus[forwardMostMove] = FENepStatus;
8997 for( i=0; i< nrCastlingRights; i++ )
8998 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9000 yyboardindex = forwardMostMove;
9002 gameInfo.fen = NULL;
9005 yyboardindex = forwardMostMove;
9006 cm = (ChessMove) yylex();
9008 /* Handle comments interspersed among the tags */
9009 while (cm == Comment) {
9011 if (appData.debugMode)
9012 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9014 if (*p == '{' || *p == '[' || *p == '(') {
9015 p[strlen(p) - 1] = NULLCHAR;
9018 while (*p == '\n') p++;
9019 AppendComment(currentMove, p);
9020 yyboardindex = forwardMostMove;
9021 cm = (ChessMove) yylex();
9025 /* don't rely on existence of Event tag since if game was
9026 * pasted from clipboard the Event tag may not exist
9028 if (numPGNTags > 0){
9030 if (gameInfo.variant == VariantNormal) {
9031 gameInfo.variant = StringToVariant(gameInfo.event);
9034 if( appData.autoDisplayTags ) {
9035 tags = PGNTags(&gameInfo);
9036 TagsPopUp(tags, CmailMsg());
9041 /* Make something up, but don't display it now */
9046 if (cm == PositionDiagram) {
9049 Board initial_position;
9051 if (appData.debugMode)
9052 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9054 if (!startedFromSetupPosition) {
9056 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9057 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9067 initial_position[i][j++] = CharToPiece(*p);
9070 while (*p == ' ' || *p == '\t' ||
9071 *p == '\n' || *p == '\r') p++;
9073 if (strncmp(p, "black", strlen("black"))==0)
9074 blackPlaysFirst = TRUE;
9076 blackPlaysFirst = FALSE;
9077 startedFromSetupPosition = TRUE;
9079 CopyBoard(boards[0], initial_position);
9080 if (blackPlaysFirst) {
9081 currentMove = forwardMostMove = backwardMostMove = 1;
9082 CopyBoard(boards[1], initial_position);
9083 strcpy(moveList[0], "");
9084 strcpy(parseList[0], "");
9085 timeRemaining[0][1] = whiteTimeRemaining;
9086 timeRemaining[1][1] = blackTimeRemaining;
9087 if (commentList[0] != NULL) {
9088 commentList[1] = commentList[0];
9089 commentList[0] = NULL;
9092 currentMove = forwardMostMove = backwardMostMove = 0;
9095 yyboardindex = forwardMostMove;
9096 cm = (ChessMove) yylex();
9099 if (first.pr == NoProc) {
9100 StartChessProgram(&first);
9102 InitChessProgram(&first, FALSE);
9103 SendToProgram("force\n", &first);
9104 if (startedFromSetupPosition) {
9105 SendBoard(&first, forwardMostMove);
9106 if (appData.debugMode) {
9107 fprintf(debugFP, "Load Game\n");
9109 DisplayBothClocks();
9112 /* [HGM] server: flag to write setup moves in broadcast file as one */
9113 loadFlag = appData.suppressLoadMoves;
9115 while (cm == Comment) {
9117 if (appData.debugMode)
9118 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9120 if (*p == '{' || *p == '[' || *p == '(') {
9121 p[strlen(p) - 1] = NULLCHAR;
9124 while (*p == '\n') p++;
9125 AppendComment(currentMove, p);
9126 yyboardindex = forwardMostMove;
9127 cm = (ChessMove) yylex();
9130 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9131 cm == WhiteWins || cm == BlackWins ||
9132 cm == GameIsDrawn || cm == GameUnfinished) {
9133 DisplayMessage("", _("No moves in game"));
9134 if (cmailMsgLoaded) {
9135 if (appData.debugMode)
9136 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9140 DrawPosition(FALSE, boards[currentMove]);
9141 DisplayBothClocks();
9142 gameMode = EditGame;
9149 // [HGM] PV info: routine tests if comment empty
9150 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9151 DisplayComment(currentMove - 1, commentList[currentMove]);
9153 if (!matchMode && appData.timeDelay != 0)
9154 DrawPosition(FALSE, boards[currentMove]);
9156 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9157 programStats.ok_to_send = 1;
9160 /* if the first token after the PGN tags is a move
9161 * and not move number 1, retrieve it from the parser
9163 if (cm != MoveNumberOne)
9164 LoadGameOneMove(cm);
9166 /* load the remaining moves from the file */
9167 while (LoadGameOneMove((ChessMove)0)) {
9168 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9169 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9172 /* rewind to the start of the game */
9173 currentMove = backwardMostMove;
9175 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9177 if (oldGameMode == AnalyzeFile ||
9178 oldGameMode == AnalyzeMode) {
9182 if (matchMode || appData.timeDelay == 0) {
9184 gameMode = EditGame;
9186 } else if (appData.timeDelay > 0) {
9190 if (appData.debugMode)
9191 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9193 loadFlag = 0; /* [HGM] true game starts */
9197 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9199 ReloadPosition(offset)
9202 int positionNumber = lastLoadPositionNumber + offset;
9203 if (lastLoadPositionFP == NULL) {
9204 DisplayError(_("No position has been loaded yet"), 0);
9207 if (positionNumber <= 0) {
9208 DisplayError(_("Can't back up any further"), 0);
9211 return LoadPosition(lastLoadPositionFP, positionNumber,
9212 lastLoadPositionTitle);
9215 /* Load the nth position from the given file */
9217 LoadPositionFromFile(filename, n, title)
9225 if (strcmp(filename, "-") == 0) {
9226 return LoadPosition(stdin, n, "stdin");
9228 f = fopen(filename, "rb");
9230 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9231 DisplayError(buf, errno);
9234 return LoadPosition(f, n, title);
9239 /* Load the nth position from the given open file, and close it */
9241 LoadPosition(f, positionNumber, title)
9246 char *p, line[MSG_SIZ];
9247 Board initial_position;
9248 int i, j, fenMode, pn;
9250 if (gameMode == Training )
9251 SetTrainingModeOff();
9253 if (gameMode != BeginningOfGame) {
9256 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9257 fclose(lastLoadPositionFP);
9259 if (positionNumber == 0) positionNumber = 1;
9260 lastLoadPositionFP = f;
9261 lastLoadPositionNumber = positionNumber;
9262 strcpy(lastLoadPositionTitle, title);
9263 if (first.pr == NoProc) {
9264 StartChessProgram(&first);
9265 InitChessProgram(&first, FALSE);
9267 pn = positionNumber;
9268 if (positionNumber < 0) {
9269 /* Negative position number means to seek to that byte offset */
9270 if (fseek(f, -positionNumber, 0) == -1) {
9271 DisplayError(_("Can't seek on position file"), 0);
9276 if (fseek(f, 0, 0) == -1) {
9277 if (f == lastLoadPositionFP ?
9278 positionNumber == lastLoadPositionNumber + 1 :
9279 positionNumber == 1) {
9282 DisplayError(_("Can't seek on position file"), 0);
9287 /* See if this file is FEN or old-style xboard */
9288 if (fgets(line, MSG_SIZ, f) == NULL) {
9289 DisplayError(_("Position not found in file"), 0);
9292 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9293 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9296 if (fenMode || line[0] == '#') pn--;
9298 /* skip positions before number pn */
9299 if (fgets(line, MSG_SIZ, f) == NULL) {
9301 DisplayError(_("Position not found in file"), 0);
9304 if (fenMode || line[0] == '#') pn--;
9309 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9310 DisplayError(_("Bad FEN position in file"), 0);
9314 (void) fgets(line, MSG_SIZ, f);
9315 (void) fgets(line, MSG_SIZ, f);
9317 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9318 (void) fgets(line, MSG_SIZ, f);
9319 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9322 initial_position[i][j++] = CharToPiece(*p);
9326 blackPlaysFirst = FALSE;
9328 (void) fgets(line, MSG_SIZ, f);
9329 if (strncmp(line, "black", strlen("black"))==0)
9330 blackPlaysFirst = TRUE;
9333 startedFromSetupPosition = TRUE;
9335 SendToProgram("force\n", &first);
9336 CopyBoard(boards[0], initial_position);
9337 if (blackPlaysFirst) {
9338 currentMove = forwardMostMove = backwardMostMove = 1;
9339 strcpy(moveList[0], "");
9340 strcpy(parseList[0], "");
9341 CopyBoard(boards[1], initial_position);
9342 DisplayMessage("", _("Black to play"));
9344 currentMove = forwardMostMove = backwardMostMove = 0;
9345 DisplayMessage("", _("White to play"));
9347 /* [HGM] copy FEN attributes as well */
9349 initialRulePlies = FENrulePlies;
9350 epStatus[forwardMostMove] = FENepStatus;
9351 for( i=0; i< nrCastlingRights; i++ )
9352 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9354 SendBoard(&first, forwardMostMove);
9355 if (appData.debugMode) {
9357 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9358 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9359 fprintf(debugFP, "Load Position\n");
9362 if (positionNumber > 1) {
9363 sprintf(line, "%s %d", title, positionNumber);
9366 DisplayTitle(title);
9368 gameMode = EditGame;
9371 timeRemaining[0][1] = whiteTimeRemaining;
9372 timeRemaining[1][1] = blackTimeRemaining;
9373 DrawPosition(FALSE, boards[currentMove]);
9380 CopyPlayerNameIntoFileName(dest, src)
9383 while (*src != NULLCHAR && *src != ',') {
9388 *(*dest)++ = *src++;
9393 char *DefaultFileName(ext)
9396 static char def[MSG_SIZ];
9399 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9401 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9403 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9412 /* Save the current game to the given file */
9414 SaveGameToFile(filename, append)
9421 if (strcmp(filename, "-") == 0) {
9422 return SaveGame(stdout, 0, NULL);
9424 f = fopen(filename, append ? "a" : "w");
9426 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9427 DisplayError(buf, errno);
9430 return SaveGame(f, 0, NULL);
9439 static char buf[MSG_SIZ];
9442 p = strchr(str, ' ');
9443 if (p == NULL) return str;
9444 strncpy(buf, str, p - str);
9445 buf[p - str] = NULLCHAR;
9449 #define PGN_MAX_LINE 75
9451 #define PGN_SIDE_WHITE 0
9452 #define PGN_SIDE_BLACK 1
9455 static int FindFirstMoveOutOfBook( int side )
9459 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9460 int index = backwardMostMove;
9461 int has_book_hit = 0;
9463 if( (index % 2) != side ) {
9467 while( index < forwardMostMove ) {
9468 /* Check to see if engine is in book */
9469 int depth = pvInfoList[index].depth;
9470 int score = pvInfoList[index].score;
9476 else if( score == 0 && depth == 63 ) {
9477 in_book = 1; /* Zappa */
9479 else if( score == 2 && depth == 99 ) {
9480 in_book = 1; /* Abrok */
9483 has_book_hit += in_book;
9499 void GetOutOfBookInfo( char * buf )
9503 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9505 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9506 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9510 if( oob[0] >= 0 || oob[1] >= 0 ) {
9511 for( i=0; i<2; i++ ) {
9515 if( i > 0 && oob[0] >= 0 ) {
9519 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9520 sprintf( buf+strlen(buf), "%s%.2f",
9521 pvInfoList[idx].score >= 0 ? "+" : "",
9522 pvInfoList[idx].score / 100.0 );
9528 /* Save game in PGN style and close the file */
9533 int i, offset, linelen, newblock;
9537 int movelen, numlen, blank;
9538 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9540 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9542 tm = time((time_t *) NULL);
9544 PrintPGNTags(f, &gameInfo);
9546 if (backwardMostMove > 0 || startedFromSetupPosition) {
9547 char *fen = PositionToFEN(backwardMostMove, NULL);
9548 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9549 fprintf(f, "\n{--------------\n");
9550 PrintPosition(f, backwardMostMove);
9551 fprintf(f, "--------------}\n");
9555 /* [AS] Out of book annotation */
9556 if( appData.saveOutOfBookInfo ) {
9559 GetOutOfBookInfo( buf );
9561 if( buf[0] != '\0' ) {
9562 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9569 i = backwardMostMove;
9573 while (i < forwardMostMove) {
9574 /* Print comments preceding this move */
9575 if (commentList[i] != NULL) {
9576 if (linelen > 0) fprintf(f, "\n");
9577 fprintf(f, "{\n%s}\n", commentList[i]);
9582 /* Format move number */
9584 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9587 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9589 numtext[0] = NULLCHAR;
9592 numlen = strlen(numtext);
9595 /* Print move number */
9596 blank = linelen > 0 && numlen > 0;
9597 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9606 fprintf(f, "%s", numtext);
9610 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9611 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9614 blank = linelen > 0 && movelen > 0;
9615 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9624 fprintf(f, "%s", move_buffer);
9627 /* [AS] Add PV info if present */
9628 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9629 /* [HGM] add time */
9630 char buf[MSG_SIZ]; int seconds = 0;
9632 if(i >= backwardMostMove) {
9634 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9635 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9637 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9638 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9640 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9642 if( seconds <= 0) buf[0] = 0; else
9643 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9644 seconds = (seconds + 4)/10; // round to full seconds
9645 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9646 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9649 sprintf( move_buffer, "{%s%.2f/%d%s}",
9650 pvInfoList[i].score >= 0 ? "+" : "",
9651 pvInfoList[i].score / 100.0,
9652 pvInfoList[i].depth,
9655 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9657 /* Print score/depth */
9658 blank = linelen > 0 && movelen > 0;
9659 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9668 fprintf(f, "%s", move_buffer);
9675 /* Start a new line */
9676 if (linelen > 0) fprintf(f, "\n");
9678 /* Print comments after last move */
9679 if (commentList[i] != NULL) {
9680 fprintf(f, "{\n%s}\n", commentList[i]);
9684 if (gameInfo.resultDetails != NULL &&
9685 gameInfo.resultDetails[0] != NULLCHAR) {
9686 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9687 PGNResult(gameInfo.result));
9689 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9693 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9697 /* Save game in old style and close the file */
9705 tm = time((time_t *) NULL);
9707 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9710 if (backwardMostMove > 0 || startedFromSetupPosition) {
9711 fprintf(f, "\n[--------------\n");
9712 PrintPosition(f, backwardMostMove);
9713 fprintf(f, "--------------]\n");
9718 i = backwardMostMove;
9719 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9721 while (i < forwardMostMove) {
9722 if (commentList[i] != NULL) {
9723 fprintf(f, "[%s]\n", commentList[i]);
9727 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9730 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9732 if (commentList[i] != NULL) {
9736 if (i >= forwardMostMove) {
9740 fprintf(f, "%s\n", parseList[i]);
9745 if (commentList[i] != NULL) {
9746 fprintf(f, "[%s]\n", commentList[i]);
9749 /* This isn't really the old style, but it's close enough */
9750 if (gameInfo.resultDetails != NULL &&
9751 gameInfo.resultDetails[0] != NULLCHAR) {
9752 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9753 gameInfo.resultDetails);
9755 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9762 /* Save the current game to open file f and close the file */
9764 SaveGame(f, dummy, dummy2)
9769 if (gameMode == EditPosition) EditPositionDone();
9770 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
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 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10189 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10191 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10192 /* make sure this other one finishes before killing it! */
10193 if(endingGame) { int count = 0;
10194 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10195 while(endingGame && count++ < 10) DoSleep(1);
10196 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10199 /* Kill off chess programs */
10200 if (first.pr != NoProc) {
10203 DoSleep( appData.delayBeforeQuit );
10204 SendToProgram("quit\n", &first);
10205 DoSleep( appData.delayAfterQuit );
10206 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10208 if (second.pr != NoProc) {
10209 DoSleep( appData.delayBeforeQuit );
10210 SendToProgram("quit\n", &second);
10211 DoSleep( appData.delayAfterQuit );
10212 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10214 if (first.isr != NULL) {
10215 RemoveInputSource(first.isr);
10217 if (second.isr != NULL) {
10218 RemoveInputSource(second.isr);
10221 ShutDownFrontEnd();
10228 if (appData.debugMode)
10229 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10233 if (gameMode == MachinePlaysWhite ||
10234 gameMode == MachinePlaysBlack) {
10237 DisplayBothClocks();
10239 if (gameMode == PlayFromGameFile) {
10240 if (appData.timeDelay >= 0)
10241 AutoPlayGameLoop();
10242 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10243 Reset(FALSE, TRUE);
10244 SendToICS(ics_prefix);
10245 SendToICS("refresh\n");
10246 } else if (currentMove < forwardMostMove) {
10247 ForwardInner(forwardMostMove);
10249 pauseExamInvalid = FALSE;
10251 switch (gameMode) {
10255 pauseExamForwardMostMove = forwardMostMove;
10256 pauseExamInvalid = FALSE;
10259 case IcsPlayingWhite:
10260 case IcsPlayingBlack:
10264 case PlayFromGameFile:
10265 (void) StopLoadGameTimer();
10269 case BeginningOfGame:
10270 if (appData.icsActive) return;
10271 /* else fall through */
10272 case MachinePlaysWhite:
10273 case MachinePlaysBlack:
10274 case TwoMachinesPlay:
10275 if (forwardMostMove == 0)
10276 return; /* don't pause if no one has moved */
10277 if ((gameMode == MachinePlaysWhite &&
10278 !WhiteOnMove(forwardMostMove)) ||
10279 (gameMode == MachinePlaysBlack &&
10280 WhiteOnMove(forwardMostMove))) {
10293 char title[MSG_SIZ];
10295 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10296 strcpy(title, _("Edit comment"));
10298 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10299 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10300 parseList[currentMove - 1]);
10303 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10310 char *tags = PGNTags(&gameInfo);
10311 EditTagsPopUp(tags);
10318 if (appData.noChessProgram || gameMode == AnalyzeMode)
10321 if (gameMode != AnalyzeFile) {
10322 if (!appData.icsEngineAnalyze) {
10324 if (gameMode != EditGame) return;
10326 ResurrectChessProgram();
10327 SendToProgram("analyze\n", &first);
10328 first.analyzing = TRUE;
10329 /*first.maybeThinking = TRUE;*/
10330 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10331 EngineOutputPopUp();
10333 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10338 StartAnalysisClock();
10339 GetTimeMark(&lastNodeCountTime);
10346 if (appData.noChessProgram || gameMode == AnalyzeFile)
10349 if (gameMode != AnalyzeMode) {
10351 if (gameMode != EditGame) return;
10352 ResurrectChessProgram();
10353 SendToProgram("analyze\n", &first);
10354 first.analyzing = TRUE;
10355 /*first.maybeThinking = TRUE;*/
10356 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10357 EngineOutputPopUp();
10359 gameMode = AnalyzeFile;
10364 StartAnalysisClock();
10365 GetTimeMark(&lastNodeCountTime);
10370 MachineWhiteEvent()
10373 char *bookHit = NULL;
10375 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10379 if (gameMode == PlayFromGameFile ||
10380 gameMode == TwoMachinesPlay ||
10381 gameMode == Training ||
10382 gameMode == AnalyzeMode ||
10383 gameMode == EndOfGame)
10386 if (gameMode == EditPosition)
10387 EditPositionDone();
10389 if (!WhiteOnMove(currentMove)) {
10390 DisplayError(_("It is not White's turn"), 0);
10394 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10397 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10398 gameMode == AnalyzeFile)
10401 ResurrectChessProgram(); /* in case it isn't running */
10402 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10403 gameMode = MachinePlaysWhite;
10406 gameMode = MachinePlaysWhite;
10410 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10412 if (first.sendName) {
10413 sprintf(buf, "name %s\n", gameInfo.black);
10414 SendToProgram(buf, &first);
10416 if (first.sendTime) {
10417 if (first.useColors) {
10418 SendToProgram("black\n", &first); /*gnu kludge*/
10420 SendTimeRemaining(&first, TRUE);
10422 if (first.useColors) {
10423 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10425 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10426 SetMachineThinkingEnables();
10427 first.maybeThinking = TRUE;
10431 if (appData.autoFlipView && !flipView) {
10432 flipView = !flipView;
10433 DrawPosition(FALSE, NULL);
10434 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10437 if(bookHit) { // [HGM] book: simulate book reply
10438 static char bookMove[MSG_SIZ]; // a bit generous?
10440 programStats.nodes = programStats.depth = programStats.time =
10441 programStats.score = programStats.got_only_move = 0;
10442 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10444 strcpy(bookMove, "move ");
10445 strcat(bookMove, bookHit);
10446 HandleMachineMove(bookMove, &first);
10451 MachineBlackEvent()
10454 char *bookHit = NULL;
10456 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10460 if (gameMode == PlayFromGameFile ||
10461 gameMode == TwoMachinesPlay ||
10462 gameMode == Training ||
10463 gameMode == AnalyzeMode ||
10464 gameMode == EndOfGame)
10467 if (gameMode == EditPosition)
10468 EditPositionDone();
10470 if (WhiteOnMove(currentMove)) {
10471 DisplayError(_("It is not Black's turn"), 0);
10475 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10478 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10479 gameMode == AnalyzeFile)
10482 ResurrectChessProgram(); /* in case it isn't running */
10483 gameMode = MachinePlaysBlack;
10487 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10489 if (first.sendName) {
10490 sprintf(buf, "name %s\n", gameInfo.white);
10491 SendToProgram(buf, &first);
10493 if (first.sendTime) {
10494 if (first.useColors) {
10495 SendToProgram("white\n", &first); /*gnu kludge*/
10497 SendTimeRemaining(&first, FALSE);
10499 if (first.useColors) {
10500 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10502 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10503 SetMachineThinkingEnables();
10504 first.maybeThinking = TRUE;
10507 if (appData.autoFlipView && flipView) {
10508 flipView = !flipView;
10509 DrawPosition(FALSE, NULL);
10510 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10512 if(bookHit) { // [HGM] book: simulate book reply
10513 static char bookMove[MSG_SIZ]; // a bit generous?
10515 programStats.nodes = programStats.depth = programStats.time =
10516 programStats.score = programStats.got_only_move = 0;
10517 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10519 strcpy(bookMove, "move ");
10520 strcat(bookMove, bookHit);
10521 HandleMachineMove(bookMove, &first);
10527 DisplayTwoMachinesTitle()
10530 if (appData.matchGames > 0) {
10531 if (first.twoMachinesColor[0] == 'w') {
10532 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10533 gameInfo.white, gameInfo.black,
10534 first.matchWins, second.matchWins,
10535 matchGame - 1 - (first.matchWins + second.matchWins));
10537 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10538 gameInfo.white, gameInfo.black,
10539 second.matchWins, first.matchWins,
10540 matchGame - 1 - (first.matchWins + second.matchWins));
10543 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10549 TwoMachinesEvent P((void))
10553 ChessProgramState *onmove;
10554 char *bookHit = NULL;
10556 if (appData.noChessProgram) return;
10558 switch (gameMode) {
10559 case TwoMachinesPlay:
10561 case MachinePlaysWhite:
10562 case MachinePlaysBlack:
10563 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10564 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10568 case BeginningOfGame:
10569 case PlayFromGameFile:
10572 if (gameMode != EditGame) return;
10575 EditPositionDone();
10586 forwardMostMove = currentMove;
10587 ResurrectChessProgram(); /* in case first program isn't running */
10589 if (second.pr == NULL) {
10590 StartChessProgram(&second);
10591 if (second.protocolVersion == 1) {
10592 TwoMachinesEventIfReady();
10594 /* kludge: allow timeout for initial "feature" command */
10596 DisplayMessage("", _("Starting second chess program"));
10597 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10601 DisplayMessage("", "");
10602 InitChessProgram(&second, FALSE);
10603 SendToProgram("force\n", &second);
10604 if (startedFromSetupPosition) {
10605 SendBoard(&second, backwardMostMove);
10606 if (appData.debugMode) {
10607 fprintf(debugFP, "Two Machines\n");
10610 for (i = backwardMostMove; i < forwardMostMove; i++) {
10611 SendMoveToProgram(i, &second);
10614 gameMode = TwoMachinesPlay;
10618 DisplayTwoMachinesTitle();
10620 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10626 SendToProgram(first.computerString, &first);
10627 if (first.sendName) {
10628 sprintf(buf, "name %s\n", second.tidy);
10629 SendToProgram(buf, &first);
10631 SendToProgram(second.computerString, &second);
10632 if (second.sendName) {
10633 sprintf(buf, "name %s\n", first.tidy);
10634 SendToProgram(buf, &second);
10638 if (!first.sendTime || !second.sendTime) {
10639 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10640 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10642 if (onmove->sendTime) {
10643 if (onmove->useColors) {
10644 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10646 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10648 if (onmove->useColors) {
10649 SendToProgram(onmove->twoMachinesColor, onmove);
10651 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10652 // SendToProgram("go\n", onmove);
10653 onmove->maybeThinking = TRUE;
10654 SetMachineThinkingEnables();
10658 if(bookHit) { // [HGM] book: simulate book reply
10659 static char bookMove[MSG_SIZ]; // a bit generous?
10661 programStats.nodes = programStats.depth = programStats.time =
10662 programStats.score = programStats.got_only_move = 0;
10663 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10665 strcpy(bookMove, "move ");
10666 strcat(bookMove, bookHit);
10667 HandleMachineMove(bookMove, &first);
10674 if (gameMode == Training) {
10675 SetTrainingModeOff();
10676 gameMode = PlayFromGameFile;
10677 DisplayMessage("", _("Training mode off"));
10679 gameMode = Training;
10680 animateTraining = appData.animate;
10682 /* make sure we are not already at the end of the game */
10683 if (currentMove < forwardMostMove) {
10684 SetTrainingModeOn();
10685 DisplayMessage("", _("Training mode on"));
10687 gameMode = PlayFromGameFile;
10688 DisplayError(_("Already at end of game"), 0);
10697 if (!appData.icsActive) return;
10698 switch (gameMode) {
10699 case IcsPlayingWhite:
10700 case IcsPlayingBlack:
10703 case BeginningOfGame:
10711 EditPositionDone();
10724 gameMode = IcsIdle;
10735 switch (gameMode) {
10737 SetTrainingModeOff();
10739 case MachinePlaysWhite:
10740 case MachinePlaysBlack:
10741 case BeginningOfGame:
10742 SendToProgram("force\n", &first);
10743 SetUserThinkingEnables();
10745 case PlayFromGameFile:
10746 (void) StopLoadGameTimer();
10747 if (gameFileFP != NULL) {
10752 EditPositionDone();
10757 SendToProgram("force\n", &first);
10759 case TwoMachinesPlay:
10760 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10761 ResurrectChessProgram();
10762 SetUserThinkingEnables();
10765 ResurrectChessProgram();
10767 case IcsPlayingBlack:
10768 case IcsPlayingWhite:
10769 DisplayError(_("Warning: You are still playing a game"), 0);
10772 DisplayError(_("Warning: You are still observing a game"), 0);
10775 DisplayError(_("Warning: You are still examining a game"), 0);
10786 first.offeredDraw = second.offeredDraw = 0;
10788 if (gameMode == PlayFromGameFile) {
10789 whiteTimeRemaining = timeRemaining[0][currentMove];
10790 blackTimeRemaining = timeRemaining[1][currentMove];
10794 if (gameMode == MachinePlaysWhite ||
10795 gameMode == MachinePlaysBlack ||
10796 gameMode == TwoMachinesPlay ||
10797 gameMode == EndOfGame) {
10798 i = forwardMostMove;
10799 while (i > currentMove) {
10800 SendToProgram("undo\n", &first);
10803 whiteTimeRemaining = timeRemaining[0][currentMove];
10804 blackTimeRemaining = timeRemaining[1][currentMove];
10805 DisplayBothClocks();
10806 if (whiteFlag || blackFlag) {
10807 whiteFlag = blackFlag = 0;
10812 gameMode = EditGame;
10819 EditPositionEvent()
10821 if (gameMode == EditPosition) {
10827 if (gameMode != EditGame) return;
10829 gameMode = EditPosition;
10832 if (currentMove > 0)
10833 CopyBoard(boards[0], boards[currentMove]);
10835 blackPlaysFirst = !WhiteOnMove(currentMove);
10837 currentMove = forwardMostMove = backwardMostMove = 0;
10838 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10845 /* [DM] icsEngineAnalyze - possible call from other functions */
10846 if (appData.icsEngineAnalyze) {
10847 appData.icsEngineAnalyze = FALSE;
10849 DisplayMessage("",_("Close ICS engine analyze..."));
10851 if (first.analysisSupport && first.analyzing) {
10852 SendToProgram("exit\n", &first);
10853 first.analyzing = FALSE;
10855 EngineOutputPopDown();
10856 thinkOutput[0] = NULLCHAR;
10862 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10864 startedFromSetupPosition = TRUE;
10865 InitChessProgram(&first, FALSE);
10866 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10867 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10868 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10869 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10870 } else castlingRights[0][2] = -1;
10871 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10872 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10873 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10874 } else castlingRights[0][5] = -1;
10875 SendToProgram("force\n", &first);
10876 if (blackPlaysFirst) {
10877 strcpy(moveList[0], "");
10878 strcpy(parseList[0], "");
10879 currentMove = forwardMostMove = backwardMostMove = 1;
10880 CopyBoard(boards[1], boards[0]);
10881 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10883 epStatus[1] = epStatus[0];
10884 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10887 currentMove = forwardMostMove = backwardMostMove = 0;
10889 SendBoard(&first, forwardMostMove);
10890 if (appData.debugMode) {
10891 fprintf(debugFP, "EditPosDone\n");
10894 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10895 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10896 gameMode = EditGame;
10898 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10899 ClearHighlights(); /* [AS] */
10902 /* Pause for `ms' milliseconds */
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10913 } while (SubtractTimeMarks(&m2, &m1) < ms);
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10918 SendMultiLineToICS(buf)
10921 char temp[MSG_SIZ+1], *p;
10928 strncpy(temp, buf, len);
10933 if (*p == '\n' || *p == '\r')
10938 strcat(temp, "\n");
10940 SendToPlayer(temp, strlen(temp));
10944 SetWhiteToPlayEvent()
10946 if (gameMode == EditPosition) {
10947 blackPlaysFirst = FALSE;
10948 DisplayBothClocks(); /* works because currentMove is 0 */
10949 } else if (gameMode == IcsExamining) {
10950 SendToICS(ics_prefix);
10951 SendToICS("tomove white\n");
10956 SetBlackToPlayEvent()
10958 if (gameMode == EditPosition) {
10959 blackPlaysFirst = TRUE;
10960 currentMove = 1; /* kludge */
10961 DisplayBothClocks();
10963 } else if (gameMode == IcsExamining) {
10964 SendToICS(ics_prefix);
10965 SendToICS("tomove black\n");
10970 EditPositionMenuEvent(selection, x, y)
10971 ChessSquare selection;
10975 ChessSquare piece = boards[0][y][x];
10977 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10979 switch (selection) {
10981 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10982 SendToICS(ics_prefix);
10983 SendToICS("bsetup clear\n");
10984 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10985 SendToICS(ics_prefix);
10986 SendToICS("clearboard\n");
10988 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10989 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10990 for (y = 0; y < BOARD_HEIGHT; y++) {
10991 if (gameMode == IcsExamining) {
10992 if (boards[currentMove][y][x] != EmptySquare) {
10993 sprintf(buf, "%sx@%c%c\n", ics_prefix,
10998 boards[0][y][x] = p;
11003 if (gameMode == EditPosition) {
11004 DrawPosition(FALSE, boards[0]);
11009 SetWhiteToPlayEvent();
11013 SetBlackToPlayEvent();
11017 if (gameMode == IcsExamining) {
11018 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11021 boards[0][y][x] = EmptySquare;
11022 DrawPosition(FALSE, boards[0]);
11027 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11028 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11029 selection = (ChessSquare) (PROMOTED piece);
11030 } else if(piece == EmptySquare) selection = WhiteSilver;
11031 else selection = (ChessSquare)((int)piece - 1);
11035 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11036 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11037 selection = (ChessSquare) (DEMOTED piece);
11038 } else if(piece == EmptySquare) selection = BlackSilver;
11039 else selection = (ChessSquare)((int)piece + 1);
11044 if(gameInfo.variant == VariantShatranj ||
11045 gameInfo.variant == VariantXiangqi ||
11046 gameInfo.variant == VariantCourier )
11047 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11052 if(gameInfo.variant == VariantXiangqi)
11053 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11054 if(gameInfo.variant == VariantKnightmate)
11055 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11058 if (gameMode == IcsExamining) {
11059 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11060 PieceToChar(selection), AAA + x, ONE + y);
11063 boards[0][y][x] = selection;
11064 DrawPosition(FALSE, boards[0]);
11072 DropMenuEvent(selection, x, y)
11073 ChessSquare selection;
11076 ChessMove moveType;
11078 switch (gameMode) {
11079 case IcsPlayingWhite:
11080 case MachinePlaysBlack:
11081 if (!WhiteOnMove(currentMove)) {
11082 DisplayMoveError(_("It is Black's turn"));
11085 moveType = WhiteDrop;
11087 case IcsPlayingBlack:
11088 case MachinePlaysWhite:
11089 if (WhiteOnMove(currentMove)) {
11090 DisplayMoveError(_("It is White's turn"));
11093 moveType = BlackDrop;
11096 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11102 if (moveType == BlackDrop && selection < BlackPawn) {
11103 selection = (ChessSquare) ((int) selection
11104 + (int) BlackPawn - (int) WhitePawn);
11106 if (boards[currentMove][y][x] != EmptySquare) {
11107 DisplayMoveError(_("That square is occupied"));
11111 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11117 /* Accept a pending offer of any kind from opponent */
11119 if (appData.icsActive) {
11120 SendToICS(ics_prefix);
11121 SendToICS("accept\n");
11122 } else if (cmailMsgLoaded) {
11123 if (currentMove == cmailOldMove &&
11124 commentList[cmailOldMove] != NULL &&
11125 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11126 "Black offers a draw" : "White offers a draw")) {
11128 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11129 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11131 DisplayError(_("There is no pending offer on this move"), 0);
11132 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11135 /* Not used for offers from chess program */
11142 /* Decline a pending offer of any kind from opponent */
11144 if (appData.icsActive) {
11145 SendToICS(ics_prefix);
11146 SendToICS("decline\n");
11147 } else if (cmailMsgLoaded) {
11148 if (currentMove == cmailOldMove &&
11149 commentList[cmailOldMove] != NULL &&
11150 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11151 "Black offers a draw" : "White offers a draw")) {
11153 AppendComment(cmailOldMove, "Draw declined");
11154 DisplayComment(cmailOldMove - 1, "Draw declined");
11157 DisplayError(_("There is no pending offer on this move"), 0);
11160 /* Not used for offers from chess program */
11167 /* Issue ICS rematch command */
11168 if (appData.icsActive) {
11169 SendToICS(ics_prefix);
11170 SendToICS("rematch\n");
11177 /* Call your opponent's flag (claim a win on time) */
11178 if (appData.icsActive) {
11179 SendToICS(ics_prefix);
11180 SendToICS("flag\n");
11182 switch (gameMode) {
11185 case MachinePlaysWhite:
11188 GameEnds(GameIsDrawn, "Both players ran out of time",
11191 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11193 DisplayError(_("Your opponent is not out of time"), 0);
11196 case MachinePlaysBlack:
11199 GameEnds(GameIsDrawn, "Both players ran out of time",
11202 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11204 DisplayError(_("Your opponent is not out of time"), 0);
11214 /* Offer draw or accept pending draw offer from opponent */
11216 if (appData.icsActive) {
11217 /* Note: tournament rules require draw offers to be
11218 made after you make your move but before you punch
11219 your clock. Currently ICS doesn't let you do that;
11220 instead, you immediately punch your clock after making
11221 a move, but you can offer a draw at any time. */
11223 SendToICS(ics_prefix);
11224 SendToICS("draw\n");
11225 } else if (cmailMsgLoaded) {
11226 if (currentMove == cmailOldMove &&
11227 commentList[cmailOldMove] != NULL &&
11228 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11229 "Black offers a draw" : "White offers a draw")) {
11230 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11231 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11232 } else if (currentMove == cmailOldMove + 1) {
11233 char *offer = WhiteOnMove(cmailOldMove) ?
11234 "White offers a draw" : "Black offers a draw";
11235 AppendComment(currentMove, offer);
11236 DisplayComment(currentMove - 1, offer);
11237 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11239 DisplayError(_("You must make your move before offering a draw"), 0);
11240 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11242 } else if (first.offeredDraw) {
11243 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11245 if (first.sendDrawOffers) {
11246 SendToProgram("draw\n", &first);
11247 userOfferedDraw = TRUE;
11255 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11257 if (appData.icsActive) {
11258 SendToICS(ics_prefix);
11259 SendToICS("adjourn\n");
11261 /* Currently GNU Chess doesn't offer or accept Adjourns */
11269 /* Offer Abort or accept pending Abort offer from opponent */
11271 if (appData.icsActive) {
11272 SendToICS(ics_prefix);
11273 SendToICS("abort\n");
11275 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11282 /* Resign. You can do this even if it's not your turn. */
11284 if (appData.icsActive) {
11285 SendToICS(ics_prefix);
11286 SendToICS("resign\n");
11288 switch (gameMode) {
11289 case MachinePlaysWhite:
11290 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11292 case MachinePlaysBlack:
11293 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11296 if (cmailMsgLoaded) {
11298 if (WhiteOnMove(cmailOldMove)) {
11299 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11301 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11303 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11314 StopObservingEvent()
11316 /* Stop observing current games */
11317 SendToICS(ics_prefix);
11318 SendToICS("unobserve\n");
11322 StopExaminingEvent()
11324 /* Stop observing current game */
11325 SendToICS(ics_prefix);
11326 SendToICS("unexamine\n");
11330 ForwardInner(target)
11335 if (appData.debugMode)
11336 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11337 target, currentMove, forwardMostMove);
11339 if (gameMode == EditPosition)
11342 if (gameMode == PlayFromGameFile && !pausing)
11345 if (gameMode == IcsExamining && pausing)
11346 limit = pauseExamForwardMostMove;
11348 limit = forwardMostMove;
11350 if (target > limit) target = limit;
11352 if (target > 0 && moveList[target - 1][0]) {
11353 int fromX, fromY, toX, toY;
11354 toX = moveList[target - 1][2] - AAA;
11355 toY = moveList[target - 1][3] - ONE;
11356 if (moveList[target - 1][1] == '@') {
11357 if (appData.highlightLastMove) {
11358 SetHighlights(-1, -1, toX, toY);
11361 fromX = moveList[target - 1][0] - AAA;
11362 fromY = moveList[target - 1][1] - ONE;
11363 if (target == currentMove + 1) {
11364 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11366 if (appData.highlightLastMove) {
11367 SetHighlights(fromX, fromY, toX, toY);
11371 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11372 gameMode == Training || gameMode == PlayFromGameFile ||
11373 gameMode == AnalyzeFile) {
11374 while (currentMove < target) {
11375 SendMoveToProgram(currentMove++, &first);
11378 currentMove = target;
11381 if (gameMode == EditGame || gameMode == EndOfGame) {
11382 whiteTimeRemaining = timeRemaining[0][currentMove];
11383 blackTimeRemaining = timeRemaining[1][currentMove];
11385 DisplayBothClocks();
11386 DisplayMove(currentMove - 1);
11387 DrawPosition(FALSE, boards[currentMove]);
11388 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11389 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11390 DisplayComment(currentMove - 1, commentList[currentMove]);
11398 if (gameMode == IcsExamining && !pausing) {
11399 SendToICS(ics_prefix);
11400 SendToICS("forward\n");
11402 ForwardInner(currentMove + 1);
11409 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11410 /* to optimze, we temporarily turn off analysis mode while we feed
11411 * the remaining moves to the engine. Otherwise we get analysis output
11414 if (first.analysisSupport) {
11415 SendToProgram("exit\nforce\n", &first);
11416 first.analyzing = FALSE;
11420 if (gameMode == IcsExamining && !pausing) {
11421 SendToICS(ics_prefix);
11422 SendToICS("forward 999999\n");
11424 ForwardInner(forwardMostMove);
11427 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11428 /* we have fed all the moves, so reactivate analysis mode */
11429 SendToProgram("analyze\n", &first);
11430 first.analyzing = TRUE;
11431 /*first.maybeThinking = TRUE;*/
11432 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11437 BackwardInner(target)
11440 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11442 if (appData.debugMode)
11443 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11444 target, currentMove, forwardMostMove);
11446 if (gameMode == EditPosition) return;
11447 if (currentMove <= backwardMostMove) {
11449 DrawPosition(full_redraw, boards[currentMove]);
11452 if (gameMode == PlayFromGameFile && !pausing)
11455 if (moveList[target][0]) {
11456 int fromX, fromY, toX, toY;
11457 toX = moveList[target][2] - AAA;
11458 toY = moveList[target][3] - ONE;
11459 if (moveList[target][1] == '@') {
11460 if (appData.highlightLastMove) {
11461 SetHighlights(-1, -1, toX, toY);
11464 fromX = moveList[target][0] - AAA;
11465 fromY = moveList[target][1] - ONE;
11466 if (target == currentMove - 1) {
11467 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11469 if (appData.highlightLastMove) {
11470 SetHighlights(fromX, fromY, toX, toY);
11474 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11475 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11476 while (currentMove > target) {
11477 SendToProgram("undo\n", &first);
11481 currentMove = target;
11484 if (gameMode == EditGame || gameMode == EndOfGame) {
11485 whiteTimeRemaining = timeRemaining[0][currentMove];
11486 blackTimeRemaining = timeRemaining[1][currentMove];
11488 DisplayBothClocks();
11489 DisplayMove(currentMove - 1);
11490 DrawPosition(full_redraw, boards[currentMove]);
11491 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11492 // [HGM] PV info: routine tests if comment empty
11493 DisplayComment(currentMove - 1, commentList[currentMove]);
11499 if (gameMode == IcsExamining && !pausing) {
11500 SendToICS(ics_prefix);
11501 SendToICS("backward\n");
11503 BackwardInner(currentMove - 1);
11510 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11511 /* to optimze, we temporarily turn off analysis mode while we undo
11512 * all the moves. Otherwise we get analysis output after each undo.
11514 if (first.analysisSupport) {
11515 SendToProgram("exit\nforce\n", &first);
11516 first.analyzing = FALSE;
11520 if (gameMode == IcsExamining && !pausing) {
11521 SendToICS(ics_prefix);
11522 SendToICS("backward 999999\n");
11524 BackwardInner(backwardMostMove);
11527 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11528 /* we have fed all the moves, so reactivate analysis mode */
11529 SendToProgram("analyze\n", &first);
11530 first.analyzing = TRUE;
11531 /*first.maybeThinking = TRUE;*/
11532 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11539 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11540 if (to >= forwardMostMove) to = forwardMostMove;
11541 if (to <= backwardMostMove) to = backwardMostMove;
11542 if (to < currentMove) {
11552 if (gameMode != IcsExamining) {
11553 DisplayError(_("You are not examining a game"), 0);
11557 DisplayError(_("You can't revert while pausing"), 0);
11560 SendToICS(ics_prefix);
11561 SendToICS("revert\n");
11567 switch (gameMode) {
11568 case MachinePlaysWhite:
11569 case MachinePlaysBlack:
11570 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11571 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11574 if (forwardMostMove < 2) return;
11575 currentMove = forwardMostMove = forwardMostMove - 2;
11576 whiteTimeRemaining = timeRemaining[0][currentMove];
11577 blackTimeRemaining = timeRemaining[1][currentMove];
11578 DisplayBothClocks();
11579 DisplayMove(currentMove - 1);
11580 ClearHighlights();/*!! could figure this out*/
11581 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11582 SendToProgram("remove\n", &first);
11583 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11586 case BeginningOfGame:
11590 case IcsPlayingWhite:
11591 case IcsPlayingBlack:
11592 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11593 SendToICS(ics_prefix);
11594 SendToICS("takeback 2\n");
11596 SendToICS(ics_prefix);
11597 SendToICS("takeback 1\n");
11606 ChessProgramState *cps;
11608 switch (gameMode) {
11609 case MachinePlaysWhite:
11610 if (!WhiteOnMove(forwardMostMove)) {
11611 DisplayError(_("It is your turn"), 0);
11616 case MachinePlaysBlack:
11617 if (WhiteOnMove(forwardMostMove)) {
11618 DisplayError(_("It is your turn"), 0);
11623 case TwoMachinesPlay:
11624 if (WhiteOnMove(forwardMostMove) ==
11625 (first.twoMachinesColor[0] == 'w')) {
11631 case BeginningOfGame:
11635 SendToProgram("?\n", cps);
11639 TruncateGameEvent()
11642 if (gameMode != EditGame) return;
11649 if (forwardMostMove > currentMove) {
11650 if (gameInfo.resultDetails != NULL) {
11651 free(gameInfo.resultDetails);
11652 gameInfo.resultDetails = NULL;
11653 gameInfo.result = GameUnfinished;
11655 forwardMostMove = currentMove;
11656 HistorySet(parseList, backwardMostMove, forwardMostMove,
11664 if (appData.noChessProgram) return;
11665 switch (gameMode) {
11666 case MachinePlaysWhite:
11667 if (WhiteOnMove(forwardMostMove)) {
11668 DisplayError(_("Wait until your turn"), 0);
11672 case BeginningOfGame:
11673 case MachinePlaysBlack:
11674 if (!WhiteOnMove(forwardMostMove)) {
11675 DisplayError(_("Wait until your turn"), 0);
11680 DisplayError(_("No hint available"), 0);
11683 SendToProgram("hint\n", &first);
11684 hintRequested = TRUE;
11690 if (appData.noChessProgram) return;
11691 switch (gameMode) {
11692 case MachinePlaysWhite:
11693 if (WhiteOnMove(forwardMostMove)) {
11694 DisplayError(_("Wait until your turn"), 0);
11698 case BeginningOfGame:
11699 case MachinePlaysBlack:
11700 if (!WhiteOnMove(forwardMostMove)) {
11701 DisplayError(_("Wait until your turn"), 0);
11706 EditPositionDone();
11708 case TwoMachinesPlay:
11713 SendToProgram("bk\n", &first);
11714 bookOutput[0] = NULLCHAR;
11715 bookRequested = TRUE;
11721 char *tags = PGNTags(&gameInfo);
11722 TagsPopUp(tags, CmailMsg());
11726 /* end button procedures */
11729 PrintPosition(fp, move)
11735 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11736 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11737 char c = PieceToChar(boards[move][i][j]);
11738 fputc(c == 'x' ? '.' : c, fp);
11739 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11742 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11743 fprintf(fp, "white to play\n");
11745 fprintf(fp, "black to play\n");
11752 if (gameInfo.white != NULL) {
11753 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11759 /* Find last component of program's own name, using some heuristics */
11761 TidyProgramName(prog, host, buf)
11762 char *prog, *host, buf[MSG_SIZ];
11765 int local = (strcmp(host, "localhost") == 0);
11766 while (!local && (p = strchr(prog, ';')) != NULL) {
11768 while (*p == ' ') p++;
11771 if (*prog == '"' || *prog == '\'') {
11772 q = strchr(prog + 1, *prog);
11774 q = strchr(prog, ' ');
11776 if (q == NULL) q = prog + strlen(prog);
11778 while (p >= prog && *p != '/' && *p != '\\') p--;
11780 if(p == prog && *p == '"') p++;
11781 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11782 memcpy(buf, p, q - p);
11783 buf[q - p] = NULLCHAR;
11791 TimeControlTagValue()
11794 if (!appData.clockMode) {
11796 } else if (movesPerSession > 0) {
11797 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11798 } else if (timeIncrement == 0) {
11799 sprintf(buf, "%ld", timeControl/1000);
11801 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11803 return StrSave(buf);
11809 /* This routine is used only for certain modes */
11810 VariantClass v = gameInfo.variant;
11811 ClearGameInfo(&gameInfo);
11812 gameInfo.variant = v;
11814 switch (gameMode) {
11815 case MachinePlaysWhite:
11816 gameInfo.event = StrSave( appData.pgnEventHeader );
11817 gameInfo.site = StrSave(HostName());
11818 gameInfo.date = PGNDate();
11819 gameInfo.round = StrSave("-");
11820 gameInfo.white = StrSave(first.tidy);
11821 gameInfo.black = StrSave(UserName());
11822 gameInfo.timeControl = TimeControlTagValue();
11825 case MachinePlaysBlack:
11826 gameInfo.event = StrSave( appData.pgnEventHeader );
11827 gameInfo.site = StrSave(HostName());
11828 gameInfo.date = PGNDate();
11829 gameInfo.round = StrSave("-");
11830 gameInfo.white = StrSave(UserName());
11831 gameInfo.black = StrSave(first.tidy);
11832 gameInfo.timeControl = TimeControlTagValue();
11835 case TwoMachinesPlay:
11836 gameInfo.event = StrSave( appData.pgnEventHeader );
11837 gameInfo.site = StrSave(HostName());
11838 gameInfo.date = PGNDate();
11839 if (matchGame > 0) {
11841 sprintf(buf, "%d", matchGame);
11842 gameInfo.round = StrSave(buf);
11844 gameInfo.round = StrSave("-");
11846 if (first.twoMachinesColor[0] == 'w') {
11847 gameInfo.white = StrSave(first.tidy);
11848 gameInfo.black = StrSave(second.tidy);
11850 gameInfo.white = StrSave(second.tidy);
11851 gameInfo.black = StrSave(first.tidy);
11853 gameInfo.timeControl = TimeControlTagValue();
11857 gameInfo.event = StrSave("Edited game");
11858 gameInfo.site = StrSave(HostName());
11859 gameInfo.date = PGNDate();
11860 gameInfo.round = StrSave("-");
11861 gameInfo.white = StrSave("-");
11862 gameInfo.black = StrSave("-");
11866 gameInfo.event = StrSave("Edited position");
11867 gameInfo.site = StrSave(HostName());
11868 gameInfo.date = PGNDate();
11869 gameInfo.round = StrSave("-");
11870 gameInfo.white = StrSave("-");
11871 gameInfo.black = StrSave("-");
11874 case IcsPlayingWhite:
11875 case IcsPlayingBlack:
11880 case PlayFromGameFile:
11881 gameInfo.event = StrSave("Game from non-PGN file");
11882 gameInfo.site = StrSave(HostName());
11883 gameInfo.date = PGNDate();
11884 gameInfo.round = StrSave("-");
11885 gameInfo.white = StrSave("?");
11886 gameInfo.black = StrSave("?");
11895 ReplaceComment(index, text)
11901 while (*text == '\n') text++;
11902 len = strlen(text);
11903 while (len > 0 && text[len - 1] == '\n') len--;
11905 if (commentList[index] != NULL)
11906 free(commentList[index]);
11909 commentList[index] = NULL;
11912 commentList[index] = (char *) malloc(len + 2);
11913 strncpy(commentList[index], text, len);
11914 commentList[index][len] = '\n';
11915 commentList[index][len + 1] = NULLCHAR;
11928 if (ch == '\r') continue;
11930 } while (ch != '\0');
11934 AppendComment(index, text)
11941 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11944 while (*text == '\n') text++;
11945 len = strlen(text);
11946 while (len > 0 && text[len - 1] == '\n') len--;
11948 if (len == 0) return;
11950 if (commentList[index] != NULL) {
11951 old = commentList[index];
11952 oldlen = strlen(old);
11953 commentList[index] = (char *) malloc(oldlen + len + 2);
11954 strcpy(commentList[index], old);
11956 strncpy(&commentList[index][oldlen], text, len);
11957 commentList[index][oldlen + len] = '\n';
11958 commentList[index][oldlen + len + 1] = NULLCHAR;
11960 commentList[index] = (char *) malloc(len + 2);
11961 strncpy(commentList[index], text, len);
11962 commentList[index][len] = '\n';
11963 commentList[index][len + 1] = NULLCHAR;
11967 static char * FindStr( char * text, char * sub_text )
11969 char * result = strstr( text, sub_text );
11971 if( result != NULL ) {
11972 result += strlen( sub_text );
11978 /* [AS] Try to extract PV info from PGN comment */
11979 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11980 char *GetInfoFromComment( int index, char * text )
11984 if( text != NULL && index > 0 ) {
11987 int time = -1, sec = 0, deci;
11988 char * s_eval = FindStr( text, "[%eval " );
11989 char * s_emt = FindStr( text, "[%emt " );
11991 if( s_eval != NULL || s_emt != NULL ) {
11995 if( s_eval != NULL ) {
11996 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12000 if( delim != ']' ) {
12005 if( s_emt != NULL ) {
12009 /* We expect something like: [+|-]nnn.nn/dd */
12012 sep = strchr( text, '/' );
12013 if( sep == NULL || sep < (text+4) ) {
12017 time = -1; sec = -1; deci = -1;
12018 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12019 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12020 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12021 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12025 if( score_lo < 0 || score_lo >= 100 ) {
12029 if(sec >= 0) time = 600*time + 10*sec; else
12030 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12032 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12034 /* [HGM] PV time: now locate end of PV info */
12035 while( *++sep >= '0' && *sep <= '9'); // strip depth
12037 while( *++sep >= '0' && *sep <= '9'); // strip time
12039 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12041 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12042 while(*sep == ' ') sep++;
12053 pvInfoList[index-1].depth = depth;
12054 pvInfoList[index-1].score = score;
12055 pvInfoList[index-1].time = 10*time; // centi-sec
12061 SendToProgram(message, cps)
12063 ChessProgramState *cps;
12065 int count, outCount, error;
12068 if (cps->pr == NULL) return;
12071 if (appData.debugMode) {
12074 fprintf(debugFP, "%ld >%-6s: %s",
12075 SubtractTimeMarks(&now, &programStartTime),
12076 cps->which, message);
12079 count = strlen(message);
12080 outCount = OutputToProcess(cps->pr, message, count, &error);
12081 if (outCount < count && !exiting
12082 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12083 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12084 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12085 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12086 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12087 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12089 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12091 gameInfo.resultDetails = buf;
12093 DisplayFatalError(buf, error, 1);
12098 ReceiveFromProgram(isr, closure, message, count, error)
12099 InputSourceRef isr;
12107 ChessProgramState *cps = (ChessProgramState *)closure;
12109 if (isr != cps->isr) return; /* Killed intentionally */
12113 _("Error: %s chess program (%s) exited unexpectedly"),
12114 cps->which, cps->program);
12115 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12116 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12117 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12118 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12120 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12122 gameInfo.resultDetails = buf;
12124 RemoveInputSource(cps->isr);
12125 DisplayFatalError(buf, 0, 1);
12128 _("Error reading from %s chess program (%s)"),
12129 cps->which, cps->program);
12130 RemoveInputSource(cps->isr);
12132 /* [AS] Program is misbehaving badly... kill it */
12133 if( count == -2 ) {
12134 DestroyChildProcess( cps->pr, 9 );
12138 DisplayFatalError(buf, error, 1);
12143 if ((end_str = strchr(message, '\r')) != NULL)
12144 *end_str = NULLCHAR;
12145 if ((end_str = strchr(message, '\n')) != NULL)
12146 *end_str = NULLCHAR;
12148 if (appData.debugMode) {
12149 TimeMark now; int print = 1;
12150 char *quote = ""; char c; int i;
12152 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12153 char start = message[0];
12154 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12155 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12156 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12157 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12158 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12159 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12160 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12161 sscanf(message, "pong %c", &c)!=1 && start != '#')
12162 { quote = "# "; print = (appData.engineComments == 2); }
12163 message[0] = start; // restore original message
12167 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12168 SubtractTimeMarks(&now, &programStartTime), cps->which,
12174 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12175 if (appData.icsEngineAnalyze) {
12176 if (strstr(message, "whisper") != NULL ||
12177 strstr(message, "kibitz") != NULL ||
12178 strstr(message, "tellics") != NULL) return;
12181 HandleMachineMove(message, cps);
12186 SendTimeControl(cps, mps, tc, inc, sd, st)
12187 ChessProgramState *cps;
12188 int mps, inc, sd, st;
12194 if( timeControl_2 > 0 ) {
12195 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12196 tc = timeControl_2;
12199 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12200 inc /= cps->timeOdds;
12201 st /= cps->timeOdds;
12203 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12206 /* Set exact time per move, normally using st command */
12207 if (cps->stKludge) {
12208 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12210 if (seconds == 0) {
12211 sprintf(buf, "level 1 %d\n", st/60);
12213 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12216 sprintf(buf, "st %d\n", st);
12219 /* Set conventional or incremental time control, using level command */
12220 if (seconds == 0) {
12221 /* Note old gnuchess bug -- minutes:seconds used to not work.
12222 Fixed in later versions, but still avoid :seconds
12223 when seconds is 0. */
12224 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12226 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12227 seconds, inc/1000);
12230 SendToProgram(buf, cps);
12232 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12233 /* Orthogonally, limit search to given depth */
12235 if (cps->sdKludge) {
12236 sprintf(buf, "depth\n%d\n", sd);
12238 sprintf(buf, "sd %d\n", sd);
12240 SendToProgram(buf, cps);
12243 if(cps->nps > 0) { /* [HGM] nps */
12244 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12246 sprintf(buf, "nps %d\n", cps->nps);
12247 SendToProgram(buf, cps);
12252 ChessProgramState *WhitePlayer()
12253 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12255 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12256 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12262 SendTimeRemaining(cps, machineWhite)
12263 ChessProgramState *cps;
12264 int /*boolean*/ machineWhite;
12266 char message[MSG_SIZ];
12269 /* Note: this routine must be called when the clocks are stopped
12270 or when they have *just* been set or switched; otherwise
12271 it will be off by the time since the current tick started.
12273 if (machineWhite) {
12274 time = whiteTimeRemaining / 10;
12275 otime = blackTimeRemaining / 10;
12277 time = blackTimeRemaining / 10;
12278 otime = whiteTimeRemaining / 10;
12280 /* [HGM] translate opponent's time by time-odds factor */
12281 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12282 if (appData.debugMode) {
12283 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12286 if (time <= 0) time = 1;
12287 if (otime <= 0) otime = 1;
12289 sprintf(message, "time %ld\n", time);
12290 SendToProgram(message, cps);
12292 sprintf(message, "otim %ld\n", otime);
12293 SendToProgram(message, cps);
12297 BoolFeature(p, name, loc, cps)
12301 ChessProgramState *cps;
12304 int len = strlen(name);
12306 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12308 sscanf(*p, "%d", &val);
12310 while (**p && **p != ' ') (*p)++;
12311 sprintf(buf, "accepted %s\n", name);
12312 SendToProgram(buf, cps);
12319 IntFeature(p, name, loc, cps)
12323 ChessProgramState *cps;
12326 int len = strlen(name);
12327 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12329 sscanf(*p, "%d", loc);
12330 while (**p && **p != ' ') (*p)++;
12331 sprintf(buf, "accepted %s\n", name);
12332 SendToProgram(buf, cps);
12339 StringFeature(p, name, loc, cps)
12343 ChessProgramState *cps;
12346 int len = strlen(name);
12347 if (strncmp((*p), name, len) == 0
12348 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12350 sscanf(*p, "%[^\"]", loc);
12351 while (**p && **p != '\"') (*p)++;
12352 if (**p == '\"') (*p)++;
12353 sprintf(buf, "accepted %s\n", name);
12354 SendToProgram(buf, cps);
12361 ParseOption(Option *opt, ChessProgramState *cps)
12362 // [HGM] options: process the string that defines an engine option, and determine
12363 // name, type, default value, and allowed value range
12365 char *p, *q, buf[MSG_SIZ];
12366 int n, min = (-1)<<31, max = 1<<31, def;
12368 if(p = strstr(opt->name, " -spin ")) {
12369 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12370 if(max < min) max = min; // enforce consistency
12371 if(def < min) def = min;
12372 if(def > max) def = max;
12377 } else if((p = strstr(opt->name, " -slider "))) {
12378 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12379 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12380 if(max < min) max = min; // enforce consistency
12381 if(def < min) def = min;
12382 if(def > max) def = max;
12386 opt->type = Spin; // Slider;
12387 } else if((p = strstr(opt->name, " -string "))) {
12388 opt->textValue = p+9;
12389 opt->type = TextBox;
12390 } else if((p = strstr(opt->name, " -file "))) {
12391 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12392 opt->textValue = p+7;
12393 opt->type = TextBox; // FileName;
12394 } else if((p = strstr(opt->name, " -path "))) {
12395 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12396 opt->textValue = p+7;
12397 opt->type = TextBox; // PathName;
12398 } else if(p = strstr(opt->name, " -check ")) {
12399 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12400 opt->value = (def != 0);
12401 opt->type = CheckBox;
12402 } else if(p = strstr(opt->name, " -combo ")) {
12403 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12404 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12405 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12406 opt->value = n = 0;
12407 while(q = StrStr(q, " /// ")) {
12408 n++; *q = 0; // count choices, and null-terminate each of them
12410 if(*q == '*') { // remember default, which is marked with * prefix
12414 cps->comboList[cps->comboCnt++] = q;
12416 cps->comboList[cps->comboCnt++] = NULL;
12418 opt->type = ComboBox;
12419 } else if(p = strstr(opt->name, " -button")) {
12420 opt->type = Button;
12421 } else if(p = strstr(opt->name, " -save")) {
12422 opt->type = SaveButton;
12423 } else return FALSE;
12424 *p = 0; // terminate option name
12425 // now look if the command-line options define a setting for this engine option.
12426 if(cps->optionSettings && cps->optionSettings[0])
12427 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12428 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12429 sprintf(buf, "option %s", p);
12430 if(p = strstr(buf, ",")) *p = 0;
12432 SendToProgram(buf, cps);
12438 FeatureDone(cps, val)
12439 ChessProgramState* cps;
12442 DelayedEventCallback cb = GetDelayedEvent();
12443 if ((cb == InitBackEnd3 && cps == &first) ||
12444 (cb == TwoMachinesEventIfReady && cps == &second)) {
12445 CancelDelayedEvent();
12446 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12448 cps->initDone = val;
12451 /* Parse feature command from engine */
12453 ParseFeatures(args, cps)
12455 ChessProgramState *cps;
12463 while (*p == ' ') p++;
12464 if (*p == NULLCHAR) return;
12466 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12467 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12468 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12469 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12470 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12471 if (BoolFeature(&p, "reuse", &val, cps)) {
12472 /* Engine can disable reuse, but can't enable it if user said no */
12473 if (!val) cps->reuse = FALSE;
12476 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12477 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12478 if (gameMode == TwoMachinesPlay) {
12479 DisplayTwoMachinesTitle();
12485 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12486 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12487 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12488 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12489 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12490 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12491 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12492 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12493 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12494 if (IntFeature(&p, "done", &val, cps)) {
12495 FeatureDone(cps, val);
12498 /* Added by Tord: */
12499 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12500 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12501 /* End of additions by Tord */
12503 /* [HGM] added features: */
12504 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12505 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12506 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12507 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12508 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12509 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12510 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12511 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12512 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12513 SendToProgram(buf, cps);
12516 if(cps->nrOptions >= MAX_OPTIONS) {
12518 sprintf(buf, "%s engine has too many options\n", cps->which);
12519 DisplayError(buf, 0);
12523 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12524 /* End of additions by HGM */
12526 /* unknown feature: complain and skip */
12528 while (*q && *q != '=') q++;
12529 sprintf(buf, "rejected %.*s\n", q-p, p);
12530 SendToProgram(buf, cps);
12536 while (*p && *p != '\"') p++;
12537 if (*p == '\"') p++;
12539 while (*p && *p != ' ') p++;
12547 PeriodicUpdatesEvent(newState)
12550 if (newState == appData.periodicUpdates)
12553 appData.periodicUpdates=newState;
12555 /* Display type changes, so update it now */
12558 /* Get the ball rolling again... */
12560 AnalysisPeriodicEvent(1);
12561 StartAnalysisClock();
12566 PonderNextMoveEvent(newState)
12569 if (newState == appData.ponderNextMove) return;
12570 if (gameMode == EditPosition) EditPositionDone();
12572 SendToProgram("hard\n", &first);
12573 if (gameMode == TwoMachinesPlay) {
12574 SendToProgram("hard\n", &second);
12577 SendToProgram("easy\n", &first);
12578 thinkOutput[0] = NULLCHAR;
12579 if (gameMode == TwoMachinesPlay) {
12580 SendToProgram("easy\n", &second);
12583 appData.ponderNextMove = newState;
12587 NewSettingEvent(option, command, value)
12593 if (gameMode == EditPosition) EditPositionDone();
12594 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12595 SendToProgram(buf, &first);
12596 if (gameMode == TwoMachinesPlay) {
12597 SendToProgram(buf, &second);
12602 ShowThinkingEvent()
12603 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12605 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12606 int newState = appData.showThinking
12607 // [HGM] thinking: other features now need thinking output as well
12608 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12610 if (oldState == newState) return;
12611 oldState = newState;
12612 if (gameMode == EditPosition) EditPositionDone();
12614 SendToProgram("post\n", &first);
12615 if (gameMode == TwoMachinesPlay) {
12616 SendToProgram("post\n", &second);
12619 SendToProgram("nopost\n", &first);
12620 thinkOutput[0] = NULLCHAR;
12621 if (gameMode == TwoMachinesPlay) {
12622 SendToProgram("nopost\n", &second);
12625 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12629 AskQuestionEvent(title, question, replyPrefix, which)
12630 char *title; char *question; char *replyPrefix; char *which;
12632 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12633 if (pr == NoProc) return;
12634 AskQuestion(title, question, replyPrefix, pr);
12638 DisplayMove(moveNumber)
12641 char message[MSG_SIZ];
12643 char cpThinkOutput[MSG_SIZ];
12645 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12647 if (moveNumber == forwardMostMove - 1 ||
12648 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12650 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12652 if (strchr(cpThinkOutput, '\n')) {
12653 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12656 *cpThinkOutput = NULLCHAR;
12659 /* [AS] Hide thinking from human user */
12660 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12661 *cpThinkOutput = NULLCHAR;
12662 if( thinkOutput[0] != NULLCHAR ) {
12665 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12666 cpThinkOutput[i] = '.';
12668 cpThinkOutput[i] = NULLCHAR;
12669 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12673 if (moveNumber == forwardMostMove - 1 &&
12674 gameInfo.resultDetails != NULL) {
12675 if (gameInfo.resultDetails[0] == NULLCHAR) {
12676 sprintf(res, " %s", PGNResult(gameInfo.result));
12678 sprintf(res, " {%s} %s",
12679 gameInfo.resultDetails, PGNResult(gameInfo.result));
12685 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12686 DisplayMessage(res, cpThinkOutput);
12688 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12689 WhiteOnMove(moveNumber) ? " " : ".. ",
12690 parseList[moveNumber], res);
12691 DisplayMessage(message, cpThinkOutput);
12696 DisplayAnalysisText(text)
12699 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12700 || appData.icsEngineAnalyze)
12702 EngineOutputPopUp();
12710 while (*str && isspace(*str)) ++str;
12711 while (*str && !isspace(*str)) ++str;
12712 if (!*str) return 1;
12713 while (*str && isspace(*str)) ++str;
12714 if (!*str) return 1;
12722 char lst[MSG_SIZ / 2];
12724 static char *xtra[] = { "", " (--)", " (++)" };
12727 if (programStats.time == 0) {
12728 programStats.time = 1;
12731 if (programStats.got_only_move) {
12732 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12734 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12736 nps = (u64ToDouble(programStats.nodes) /
12737 ((double)programStats.time /100.0));
12739 cs = programStats.time % 100;
12740 s = programStats.time / 100;
12746 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12747 if (programStats.move_name[0] != NULLCHAR) {
12748 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12749 programStats.depth,
12750 programStats.nr_moves-programStats.moves_left,
12751 programStats.nr_moves, programStats.move_name,
12752 ((float)programStats.score)/100.0, lst,
12753 only_one_move(lst)?
12754 xtra[programStats.got_fail] : "",
12755 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12757 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12758 programStats.depth,
12759 programStats.nr_moves-programStats.moves_left,
12760 programStats.nr_moves, ((float)programStats.score)/100.0,
12762 only_one_move(lst)?
12763 xtra[programStats.got_fail] : "",
12764 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12767 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12768 programStats.depth,
12769 ((float)programStats.score)/100.0,
12771 only_one_move(lst)?
12772 xtra[programStats.got_fail] : "",
12773 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12776 DisplayAnalysisText(buf);
12780 DisplayComment(moveNumber, text)
12784 char title[MSG_SIZ];
12785 char buf[8000]; // comment can be long!
12788 if( appData.autoDisplayComment ) {
12789 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12790 strcpy(title, "Comment");
12792 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12793 WhiteOnMove(moveNumber) ? " " : ".. ",
12794 parseList[moveNumber]);
12796 // [HGM] PV info: display PV info together with (or as) comment
12797 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12798 if(text == NULL) text = "";
12799 score = pvInfoList[moveNumber].score;
12800 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12801 depth, (pvInfoList[moveNumber].time+50)/100, text);
12804 } else title[0] = 0;
12807 CommentPopUp(title, text);
12810 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12811 * might be busy thinking or pondering. It can be omitted if your
12812 * gnuchess is configured to stop thinking immediately on any user
12813 * input. However, that gnuchess feature depends on the FIONREAD
12814 * ioctl, which does not work properly on some flavors of Unix.
12818 ChessProgramState *cps;
12821 if (!cps->useSigint) return;
12822 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12823 switch (gameMode) {
12824 case MachinePlaysWhite:
12825 case MachinePlaysBlack:
12826 case TwoMachinesPlay:
12827 case IcsPlayingWhite:
12828 case IcsPlayingBlack:
12831 /* Skip if we know it isn't thinking */
12832 if (!cps->maybeThinking) return;
12833 if (appData.debugMode)
12834 fprintf(debugFP, "Interrupting %s\n", cps->which);
12835 InterruptChildProcess(cps->pr);
12836 cps->maybeThinking = FALSE;
12841 #endif /*ATTENTION*/
12847 if (whiteTimeRemaining <= 0) {
12850 if (appData.icsActive) {
12851 if (appData.autoCallFlag &&
12852 gameMode == IcsPlayingBlack && !blackFlag) {
12853 SendToICS(ics_prefix);
12854 SendToICS("flag\n");
12858 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12860 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12861 if (appData.autoCallFlag) {
12862 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12869 if (blackTimeRemaining <= 0) {
12872 if (appData.icsActive) {
12873 if (appData.autoCallFlag &&
12874 gameMode == IcsPlayingWhite && !whiteFlag) {
12875 SendToICS(ics_prefix);
12876 SendToICS("flag\n");
12880 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12882 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12883 if (appData.autoCallFlag) {
12884 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12897 if (!appData.clockMode || appData.icsActive ||
12898 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12901 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12903 if ( !WhiteOnMove(forwardMostMove) )
12904 /* White made time control */
12905 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12906 /* [HGM] time odds: correct new time quota for time odds! */
12907 / WhitePlayer()->timeOdds;
12909 /* Black made time control */
12910 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12911 / WhitePlayer()->other->timeOdds;
12915 DisplayBothClocks()
12917 int wom = gameMode == EditPosition ?
12918 !blackPlaysFirst : WhiteOnMove(currentMove);
12919 DisplayWhiteClock(whiteTimeRemaining, wom);
12920 DisplayBlackClock(blackTimeRemaining, !wom);
12924 /* Timekeeping seems to be a portability nightmare. I think everyone
12925 has ftime(), but I'm really not sure, so I'm including some ifdefs
12926 to use other calls if you don't. Clocks will be less accurate if
12927 you have neither ftime nor gettimeofday.
12930 /* VS 2008 requires the #include outside of the function */
12931 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12932 #include <sys/timeb.h>
12935 /* Get the current time as a TimeMark */
12940 #if HAVE_GETTIMEOFDAY
12942 struct timeval timeVal;
12943 struct timezone timeZone;
12945 gettimeofday(&timeVal, &timeZone);
12946 tm->sec = (long) timeVal.tv_sec;
12947 tm->ms = (int) (timeVal.tv_usec / 1000L);
12949 #else /*!HAVE_GETTIMEOFDAY*/
12952 // include <sys/timeb.h> / moved to just above start of function
12953 struct timeb timeB;
12956 tm->sec = (long) timeB.time;
12957 tm->ms = (int) timeB.millitm;
12959 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12960 tm->sec = (long) time(NULL);
12966 /* Return the difference in milliseconds between two
12967 time marks. We assume the difference will fit in a long!
12970 SubtractTimeMarks(tm2, tm1)
12971 TimeMark *tm2, *tm1;
12973 return 1000L*(tm2->sec - tm1->sec) +
12974 (long) (tm2->ms - tm1->ms);
12979 * Code to manage the game clocks.
12981 * In tournament play, black starts the clock and then white makes a move.
12982 * We give the human user a slight advantage if he is playing white---the
12983 * clocks don't run until he makes his first move, so it takes zero time.
12984 * Also, we don't account for network lag, so we could get out of sync
12985 * with GNU Chess's clock -- but then, referees are always right.
12988 static TimeMark tickStartTM;
12989 static long intendedTickLength;
12992 NextTickLength(timeRemaining)
12993 long timeRemaining;
12995 long nominalTickLength, nextTickLength;
12997 if (timeRemaining > 0L && timeRemaining <= 10000L)
12998 nominalTickLength = 100L;
13000 nominalTickLength = 1000L;
13001 nextTickLength = timeRemaining % nominalTickLength;
13002 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13004 return nextTickLength;
13007 /* Adjust clock one minute up or down */
13009 AdjustClock(Boolean which, int dir)
13011 if(which) blackTimeRemaining += 60000*dir;
13012 else whiteTimeRemaining += 60000*dir;
13013 DisplayBothClocks();
13016 /* Stop clocks and reset to a fresh time control */
13020 (void) StopClockTimer();
13021 if (appData.icsActive) {
13022 whiteTimeRemaining = blackTimeRemaining = 0;
13023 } else { /* [HGM] correct new time quote for time odds */
13024 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13025 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13027 if (whiteFlag || blackFlag) {
13029 whiteFlag = blackFlag = FALSE;
13031 DisplayBothClocks();
13034 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13036 /* Decrement running clock by amount of time that has passed */
13040 long timeRemaining;
13041 long lastTickLength, fudge;
13044 if (!appData.clockMode) return;
13045 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13049 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13051 /* Fudge if we woke up a little too soon */
13052 fudge = intendedTickLength - lastTickLength;
13053 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13055 if (WhiteOnMove(forwardMostMove)) {
13056 if(whiteNPS >= 0) lastTickLength = 0;
13057 timeRemaining = whiteTimeRemaining -= lastTickLength;
13058 DisplayWhiteClock(whiteTimeRemaining - fudge,
13059 WhiteOnMove(currentMove));
13061 if(blackNPS >= 0) lastTickLength = 0;
13062 timeRemaining = blackTimeRemaining -= lastTickLength;
13063 DisplayBlackClock(blackTimeRemaining - fudge,
13064 !WhiteOnMove(currentMove));
13067 if (CheckFlags()) return;
13070 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13071 StartClockTimer(intendedTickLength);
13073 /* if the time remaining has fallen below the alarm threshold, sound the
13074 * alarm. if the alarm has sounded and (due to a takeback or time control
13075 * with increment) the time remaining has increased to a level above the
13076 * threshold, reset the alarm so it can sound again.
13079 if (appData.icsActive && appData.icsAlarm) {
13081 /* make sure we are dealing with the user's clock */
13082 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13083 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13086 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13087 alarmSounded = FALSE;
13088 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13090 alarmSounded = TRUE;
13096 /* A player has just moved, so stop the previously running
13097 clock and (if in clock mode) start the other one.
13098 We redisplay both clocks in case we're in ICS mode, because
13099 ICS gives us an update to both clocks after every move.
13100 Note that this routine is called *after* forwardMostMove
13101 is updated, so the last fractional tick must be subtracted
13102 from the color that is *not* on move now.
13107 long lastTickLength;
13109 int flagged = FALSE;
13113 if (StopClockTimer() && appData.clockMode) {
13114 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13115 if (WhiteOnMove(forwardMostMove)) {
13116 if(blackNPS >= 0) lastTickLength = 0;
13117 blackTimeRemaining -= lastTickLength;
13118 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13119 // if(pvInfoList[forwardMostMove-1].time == -1)
13120 pvInfoList[forwardMostMove-1].time = // use GUI time
13121 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13123 if(whiteNPS >= 0) lastTickLength = 0;
13124 whiteTimeRemaining -= lastTickLength;
13125 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13126 // if(pvInfoList[forwardMostMove-1].time == -1)
13127 pvInfoList[forwardMostMove-1].time =
13128 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13130 flagged = CheckFlags();
13132 CheckTimeControl();
13134 if (flagged || !appData.clockMode) return;
13136 switch (gameMode) {
13137 case MachinePlaysBlack:
13138 case MachinePlaysWhite:
13139 case BeginningOfGame:
13140 if (pausing) return;
13144 case PlayFromGameFile:
13153 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13154 whiteTimeRemaining : blackTimeRemaining);
13155 StartClockTimer(intendedTickLength);
13159 /* Stop both clocks */
13163 long lastTickLength;
13166 if (!StopClockTimer()) return;
13167 if (!appData.clockMode) return;
13171 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13172 if (WhiteOnMove(forwardMostMove)) {
13173 if(whiteNPS >= 0) lastTickLength = 0;
13174 whiteTimeRemaining -= lastTickLength;
13175 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13177 if(blackNPS >= 0) lastTickLength = 0;
13178 blackTimeRemaining -= lastTickLength;
13179 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13184 /* Start clock of player on move. Time may have been reset, so
13185 if clock is already running, stop and restart it. */
13189 (void) StopClockTimer(); /* in case it was running already */
13190 DisplayBothClocks();
13191 if (CheckFlags()) return;
13193 if (!appData.clockMode) return;
13194 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13196 GetTimeMark(&tickStartTM);
13197 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13198 whiteTimeRemaining : blackTimeRemaining);
13200 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13201 whiteNPS = blackNPS = -1;
13202 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13203 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13204 whiteNPS = first.nps;
13205 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13206 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13207 blackNPS = first.nps;
13208 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13209 whiteNPS = second.nps;
13210 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13211 blackNPS = second.nps;
13212 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13214 StartClockTimer(intendedTickLength);
13221 long second, minute, hour, day;
13223 static char buf[32];
13225 if (ms > 0 && ms <= 9900) {
13226 /* convert milliseconds to tenths, rounding up */
13227 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13229 sprintf(buf, " %03.1f ", tenths/10.0);
13233 /* convert milliseconds to seconds, rounding up */
13234 /* use floating point to avoid strangeness of integer division
13235 with negative dividends on many machines */
13236 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13243 day = second / (60 * 60 * 24);
13244 second = second % (60 * 60 * 24);
13245 hour = second / (60 * 60);
13246 second = second % (60 * 60);
13247 minute = second / 60;
13248 second = second % 60;
13251 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13252 sign, day, hour, minute, second);
13254 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13256 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13263 * This is necessary because some C libraries aren't ANSI C compliant yet.
13266 StrStr(string, match)
13267 char *string, *match;
13271 length = strlen(match);
13273 for (i = strlen(string) - length; i >= 0; i--, string++)
13274 if (!strncmp(match, string, length))
13281 StrCaseStr(string, match)
13282 char *string, *match;
13286 length = strlen(match);
13288 for (i = strlen(string) - length; i >= 0; i--, string++) {
13289 for (j = 0; j < length; j++) {
13290 if (ToLower(match[j]) != ToLower(string[j]))
13293 if (j == length) return string;
13307 c1 = ToLower(*s1++);
13308 c2 = ToLower(*s2++);
13309 if (c1 > c2) return 1;
13310 if (c1 < c2) return -1;
13311 if (c1 == NULLCHAR) return 0;
13320 return isupper(c) ? tolower(c) : c;
13328 return islower(c) ? toupper(c) : c;
13330 #endif /* !_amigados */
13338 if ((ret = (char *) malloc(strlen(s) + 1))) {
13345 StrSavePtr(s, savePtr)
13346 char *s, **savePtr;
13351 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13352 strcpy(*savePtr, s);
13364 clock = time((time_t *)NULL);
13365 tm = localtime(&clock);
13366 sprintf(buf, "%04d.%02d.%02d",
13367 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13368 return StrSave(buf);
13373 PositionToFEN(move, overrideCastling)
13375 char *overrideCastling;
13377 int i, j, fromX, fromY, toX, toY;
13384 whiteToPlay = (gameMode == EditPosition) ?
13385 !blackPlaysFirst : (move % 2 == 0);
13388 /* Piece placement data */
13389 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13391 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13392 if (boards[move][i][j] == EmptySquare) {
13394 } else { ChessSquare piece = boards[move][i][j];
13395 if (emptycount > 0) {
13396 if(emptycount<10) /* [HGM] can be >= 10 */
13397 *p++ = '0' + emptycount;
13398 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13401 if(PieceToChar(piece) == '+') {
13402 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13404 piece = (ChessSquare)(DEMOTED piece);
13406 *p++ = PieceToChar(piece);
13408 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13409 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13414 if (emptycount > 0) {
13415 if(emptycount<10) /* [HGM] can be >= 10 */
13416 *p++ = '0' + emptycount;
13417 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13424 /* [HGM] print Crazyhouse or Shogi holdings */
13425 if( gameInfo.holdingsWidth ) {
13426 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13428 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13429 piece = boards[move][i][BOARD_WIDTH-1];
13430 if( piece != EmptySquare )
13431 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13432 *p++ = PieceToChar(piece);
13434 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13435 piece = boards[move][BOARD_HEIGHT-i-1][0];
13436 if( piece != EmptySquare )
13437 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13438 *p++ = PieceToChar(piece);
13441 if( q == p ) *p++ = '-';
13447 *p++ = whiteToPlay ? 'w' : 'b';
13450 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13451 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13453 if(nrCastlingRights) {
13455 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13456 /* [HGM] write directly from rights */
13457 if(castlingRights[move][2] >= 0 &&
13458 castlingRights[move][0] >= 0 )
13459 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13460 if(castlingRights[move][2] >= 0 &&
13461 castlingRights[move][1] >= 0 )
13462 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13463 if(castlingRights[move][5] >= 0 &&
13464 castlingRights[move][3] >= 0 )
13465 *p++ = castlingRights[move][3] + AAA;
13466 if(castlingRights[move][5] >= 0 &&
13467 castlingRights[move][4] >= 0 )
13468 *p++ = castlingRights[move][4] + AAA;
13471 /* [HGM] write true castling rights */
13472 if( nrCastlingRights == 6 ) {
13473 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13474 castlingRights[move][2] >= 0 ) *p++ = 'K';
13475 if(castlingRights[move][1] == BOARD_LEFT &&
13476 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13477 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13478 castlingRights[move][5] >= 0 ) *p++ = 'k';
13479 if(castlingRights[move][4] == BOARD_LEFT &&
13480 castlingRights[move][5] >= 0 ) *p++ = 'q';
13483 if (q == p) *p++ = '-'; /* No castling rights */
13487 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13488 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13489 /* En passant target square */
13490 if (move > backwardMostMove) {
13491 fromX = moveList[move - 1][0] - AAA;
13492 fromY = moveList[move - 1][1] - ONE;
13493 toX = moveList[move - 1][2] - AAA;
13494 toY = moveList[move - 1][3] - ONE;
13495 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13496 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13497 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13499 /* 2-square pawn move just happened */
13501 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13505 } else if(move == backwardMostMove) {
13506 // [HGM] perhaps we should always do it like this, and forget the above?
13507 if(epStatus[move] >= 0) {
13508 *p++ = epStatus[move] + AAA;
13509 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13520 /* [HGM] find reversible plies */
13521 { int i = 0, j=move;
13523 if (appData.debugMode) { int k;
13524 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13525 for(k=backwardMostMove; k<=forwardMostMove; k++)
13526 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13530 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13531 if( j == backwardMostMove ) i += initialRulePlies;
13532 sprintf(p, "%d ", i);
13533 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13535 /* Fullmove number */
13536 sprintf(p, "%d", (move / 2) + 1);
13538 return StrSave(buf);
13542 ParseFEN(board, blackPlaysFirst, fen)
13544 int *blackPlaysFirst;
13554 /* [HGM] by default clear Crazyhouse holdings, if present */
13555 if(gameInfo.holdingsWidth) {
13556 for(i=0; i<BOARD_HEIGHT; i++) {
13557 board[i][0] = EmptySquare; /* black holdings */
13558 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13559 board[i][1] = (ChessSquare) 0; /* black counts */
13560 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13564 /* Piece placement data */
13565 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13568 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13569 if (*p == '/') p++;
13570 emptycount = gameInfo.boardWidth - j;
13571 while (emptycount--)
13572 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13574 #if(BOARD_SIZE >= 10)
13575 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13576 p++; emptycount=10;
13577 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13578 while (emptycount--)
13579 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13581 } else if (isdigit(*p)) {
13582 emptycount = *p++ - '0';
13583 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13584 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13585 while (emptycount--)
13586 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13587 } else if (*p == '+' || isalpha(*p)) {
13588 if (j >= gameInfo.boardWidth) return FALSE;
13590 piece = CharToPiece(*++p);
13591 if(piece == EmptySquare) return FALSE; /* unknown piece */
13592 piece = (ChessSquare) (PROMOTED piece ); p++;
13593 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13594 } else piece = CharToPiece(*p++);
13596 if(piece==EmptySquare) return FALSE; /* unknown piece */
13597 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13598 piece = (ChessSquare) (PROMOTED piece);
13599 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13602 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13608 while (*p == '/' || *p == ' ') p++;
13610 /* [HGM] look for Crazyhouse holdings here */
13611 while(*p==' ') p++;
13612 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13614 if(*p == '-' ) *p++; /* empty holdings */ else {
13615 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13616 /* if we would allow FEN reading to set board size, we would */
13617 /* have to add holdings and shift the board read so far here */
13618 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13620 if((int) piece >= (int) BlackPawn ) {
13621 i = (int)piece - (int)BlackPawn;
13622 i = PieceToNumber((ChessSquare)i);
13623 if( i >= gameInfo.holdingsSize ) return FALSE;
13624 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13625 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13627 i = (int)piece - (int)WhitePawn;
13628 i = PieceToNumber((ChessSquare)i);
13629 if( i >= gameInfo.holdingsSize ) return FALSE;
13630 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13631 board[i][BOARD_WIDTH-2]++; /* black holdings */
13635 if(*p == ']') *p++;
13638 while(*p == ' ') p++;
13643 *blackPlaysFirst = FALSE;
13646 *blackPlaysFirst = TRUE;
13652 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13653 /* return the extra info in global variiables */
13655 /* set defaults in case FEN is incomplete */
13656 FENepStatus = EP_UNKNOWN;
13657 for(i=0; i<nrCastlingRights; i++ ) {
13658 FENcastlingRights[i] =
13659 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13660 } /* assume possible unless obviously impossible */
13661 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13662 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13663 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13664 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13665 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13666 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13669 while(*p==' ') p++;
13670 if(nrCastlingRights) {
13671 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13672 /* castling indicator present, so default becomes no castlings */
13673 for(i=0; i<nrCastlingRights; i++ ) {
13674 FENcastlingRights[i] = -1;
13677 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13678 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13679 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13680 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13681 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13683 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13684 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13685 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13689 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13690 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13691 FENcastlingRights[2] = whiteKingFile;
13694 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13695 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13696 FENcastlingRights[2] = whiteKingFile;
13699 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13700 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13701 FENcastlingRights[5] = blackKingFile;
13704 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13705 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13706 FENcastlingRights[5] = blackKingFile;
13709 default: /* FRC castlings */
13710 if(c >= 'a') { /* black rights */
13711 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13712 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13713 if(i == BOARD_RGHT) break;
13714 FENcastlingRights[5] = i;
13716 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13717 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13719 FENcastlingRights[3] = c;
13721 FENcastlingRights[4] = c;
13722 } else { /* white rights */
13723 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13724 if(board[0][i] == WhiteKing) break;
13725 if(i == BOARD_RGHT) break;
13726 FENcastlingRights[2] = i;
13727 c -= AAA - 'a' + 'A';
13728 if(board[0][c] >= WhiteKing) break;
13730 FENcastlingRights[0] = c;
13732 FENcastlingRights[1] = c;
13736 if (appData.debugMode) {
13737 fprintf(debugFP, "FEN castling rights:");
13738 for(i=0; i<nrCastlingRights; i++)
13739 fprintf(debugFP, " %d", FENcastlingRights[i]);
13740 fprintf(debugFP, "\n");
13743 while(*p==' ') p++;
13746 /* read e.p. field in games that know e.p. capture */
13747 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13748 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13750 p++; FENepStatus = EP_NONE;
13752 char c = *p++ - AAA;
13754 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13755 if(*p >= '0' && *p <='9') *p++;
13761 if(sscanf(p, "%d", &i) == 1) {
13762 FENrulePlies = i; /* 50-move ply counter */
13763 /* (The move number is still ignored) */
13770 EditPositionPasteFEN(char *fen)
13773 Board initial_position;
13775 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13776 DisplayError(_("Bad FEN position in clipboard"), 0);
13779 int savedBlackPlaysFirst = blackPlaysFirst;
13780 EditPositionEvent();
13781 blackPlaysFirst = savedBlackPlaysFirst;
13782 CopyBoard(boards[0], initial_position);
13783 /* [HGM] copy FEN attributes as well */
13785 initialRulePlies = FENrulePlies;
13786 epStatus[0] = FENepStatus;
13787 for( i=0; i<nrCastlingRights; i++ )
13788 castlingRights[0][i] = FENcastlingRights[i];
13790 EditPositionDone();
13791 DisplayBothClocks();
13792 DrawPosition(FALSE, boards[currentMove]);