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;
1943 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1944 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1946 startedFromPositionFile = FALSE;
1947 if(gameInfo.variant == newVariant) return;
1949 /* [HGM] This routine is called each time an assignment is made to
1950 * gameInfo.variant during a game, to make sure the board sizes
1951 * are set to match the new variant. If that means adding or deleting
1952 * holdings, we shift the playing board accordingly
1953 * This kludge is needed because in ICS observe mode, we get boards
1954 * of an ongoing game without knowing the variant, and learn about the
1955 * latter only later. This can be because of the move list we requested,
1956 * in which case the game history is refilled from the beginning anyway,
1957 * but also when receiving holdings of a crazyhouse game. In the latter
1958 * case we want to add those holdings to the already received position.
1962 if (appData.debugMode) {
1963 fprintf(debugFP, "Switch board from %s to %s\n",
1964 VariantName(gameInfo.variant), VariantName(newVariant));
1965 setbuf(debugFP, NULL);
1967 shuffleOpenings = 0; /* [HGM] shuffle */
1968 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969 switch(newVariant) {
1971 newWidth = 9; newHeight = 9;
1972 gameInfo.holdingsSize = 7;
1973 case VariantBughouse:
1974 case VariantCrazyhouse:
1975 newHoldingsWidth = 2; break;
1977 newHoldingsWidth = gameInfo.holdingsSize = 0;
1980 if(newWidth != gameInfo.boardWidth ||
1981 newHeight != gameInfo.boardHeight ||
1982 newHoldingsWidth != gameInfo.holdingsWidth ) {
1984 /* shift position to new playing area, if needed */
1985 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1986 for(i=0; i<BOARD_HEIGHT; i++)
1987 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1988 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1990 for(i=0; i<newHeight; i++) {
1991 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1992 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1994 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1995 for(i=0; i<BOARD_HEIGHT; i++)
1996 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1997 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2001 gameInfo.boardWidth = newWidth;
2002 gameInfo.boardHeight = newHeight;
2003 gameInfo.holdingsWidth = newHoldingsWidth;
2004 gameInfo.variant = newVariant;
2005 InitDrawingSizes(-2, 0);
2007 /* [HGM] The following should definitely be solved in a better way */
2008 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2009 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2011 forwardMostMove = oldForwardMostMove;
2012 backwardMostMove = oldBackwardMostMove;
2013 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2016 static int loggedOn = FALSE;
2018 /*-- Game start info cache: --*/
2020 char gs_kind[MSG_SIZ];
2021 static char player1Name[128] = "";
2022 static char player2Name[128] = "";
2023 static int player1Rating = -1;
2024 static int player2Rating = -1;
2025 /*----------------------------*/
2027 ColorClass curColor = ColorNormal;
2028 int suppressKibitz = 0;
2031 read_from_ics(isr, closure, data, count, error)
2038 #define BUF_SIZE 8192
2039 #define STARTED_NONE 0
2040 #define STARTED_MOVES 1
2041 #define STARTED_BOARD 2
2042 #define STARTED_OBSERVE 3
2043 #define STARTED_HOLDINGS 4
2044 #define STARTED_CHATTER 5
2045 #define STARTED_COMMENT 6
2046 #define STARTED_MOVES_NOHIDE 7
2048 static int started = STARTED_NONE;
2049 static char parse[20000];
2050 static int parse_pos = 0;
2051 static char buf[BUF_SIZE + 1];
2052 static int firstTime = TRUE, intfSet = FALSE;
2053 static ColorClass prevColor = ColorNormal;
2054 static int savingComment = FALSE;
2060 int backup; /* [DM] For zippy color lines */
2062 char talker[MSG_SIZ]; // [HGM] chat
2065 if (appData.debugMode) {
2067 fprintf(debugFP, "<ICS: ");
2068 show_bytes(debugFP, data, count);
2069 fprintf(debugFP, "\n");
2073 if (appData.debugMode) { int f = forwardMostMove;
2074 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2075 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2078 /* If last read ended with a partial line that we couldn't parse,
2079 prepend it to the new read and try again. */
2080 if (leftover_len > 0) {
2081 for (i=0; i<leftover_len; i++)
2082 buf[i] = buf[leftover_start + i];
2085 /* Copy in new characters, removing nulls and \r's */
2086 buf_len = leftover_len;
2087 for (i = 0; i < count; i++) {
2088 if (data[i] != NULLCHAR && data[i] != '\r')
2089 buf[buf_len++] = data[i];
2090 if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2091 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2092 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2093 if(buf_len == 0 || buf[buf_len-1] != ' ')
2094 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2098 buf[buf_len] = NULLCHAR;
2099 next_out = leftover_len;
2103 while (i < buf_len) {
2104 /* Deal with part of the TELNET option negotiation
2105 protocol. We refuse to do anything beyond the
2106 defaults, except that we allow the WILL ECHO option,
2107 which ICS uses to turn off password echoing when we are
2108 directly connected to it. We reject this option
2109 if localLineEditing mode is on (always on in xboard)
2110 and we are talking to port 23, which might be a real
2111 telnet server that will try to keep WILL ECHO on permanently.
2113 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2114 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2115 unsigned char option;
2117 switch ((unsigned char) buf[++i]) {
2119 if (appData.debugMode)
2120 fprintf(debugFP, "\n<WILL ");
2121 switch (option = (unsigned char) buf[++i]) {
2123 if (appData.debugMode)
2124 fprintf(debugFP, "ECHO ");
2125 /* Reply only if this is a change, according
2126 to the protocol rules. */
2127 if (remoteEchoOption) break;
2128 if (appData.localLineEditing &&
2129 atoi(appData.icsPort) == TN_PORT) {
2130 TelnetRequest(TN_DONT, TN_ECHO);
2133 TelnetRequest(TN_DO, TN_ECHO);
2134 remoteEchoOption = TRUE;
2138 if (appData.debugMode)
2139 fprintf(debugFP, "%d ", option);
2140 /* Whatever this is, we don't want it. */
2141 TelnetRequest(TN_DONT, option);
2146 if (appData.debugMode)
2147 fprintf(debugFP, "\n<WONT ");
2148 switch (option = (unsigned char) buf[++i]) {
2150 if (appData.debugMode)
2151 fprintf(debugFP, "ECHO ");
2152 /* Reply only if this is a change, according
2153 to the protocol rules. */
2154 if (!remoteEchoOption) break;
2156 TelnetRequest(TN_DONT, TN_ECHO);
2157 remoteEchoOption = FALSE;
2160 if (appData.debugMode)
2161 fprintf(debugFP, "%d ", (unsigned char) option);
2162 /* Whatever this is, it must already be turned
2163 off, because we never agree to turn on
2164 anything non-default, so according to the
2165 protocol rules, we don't reply. */
2170 if (appData.debugMode)
2171 fprintf(debugFP, "\n<DO ");
2172 switch (option = (unsigned char) buf[++i]) {
2174 /* Whatever this is, we refuse to do it. */
2175 if (appData.debugMode)
2176 fprintf(debugFP, "%d ", option);
2177 TelnetRequest(TN_WONT, option);
2182 if (appData.debugMode)
2183 fprintf(debugFP, "\n<DONT ");
2184 switch (option = (unsigned char) buf[++i]) {
2186 if (appData.debugMode)
2187 fprintf(debugFP, "%d ", option);
2188 /* Whatever this is, we are already not doing
2189 it, because we never agree to do anything
2190 non-default, so according to the protocol
2191 rules, we don't reply. */
2196 if (appData.debugMode)
2197 fprintf(debugFP, "\n<IAC ");
2198 /* Doubled IAC; pass it through */
2202 if (appData.debugMode)
2203 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2204 /* Drop all other telnet commands on the floor */
2207 if (oldi > next_out)
2208 SendToPlayer(&buf[next_out], oldi - next_out);
2214 /* OK, this at least will *usually* work */
2215 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2219 if (loggedOn && !intfSet) {
2220 if (ics_type == ICS_ICC) {
2222 "/set-quietly interface %s\n/set-quietly style 12\n",
2224 if (!appData.noJoin)
2225 strcat(str, "/set-quietly wrap 0\n");
2226 } else if (ics_type == ICS_CHESSNET) {
2227 sprintf(str, "/style 12\n");
2229 strcpy(str, "alias $ @\n$set interface ");
2230 strcat(str, programVersion);
2231 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2233 strcat(str, "$iset nohighlight 1\n");
2235 if (!appData.noJoin)
2236 strcat(str, "$iset nowrap 1\n");
2237 strcat(str, "$iset lock 1\n$style 12\n");
2240 NotifyFrontendLogin();
2244 if (started == STARTED_COMMENT) {
2245 /* Accumulate characters in comment */
2246 parse[parse_pos++] = buf[i];
2247 if (buf[i] == '\n') {
2248 parse[parse_pos] = NULLCHAR;
2249 if(chattingPartner>=0) {
2251 sprintf(mess, "%s%s", talker, parse);
2252 OutputChatMessage(chattingPartner, mess);
2253 chattingPartner = -1;
2255 if(!suppressKibitz) // [HGM] kibitz
2256 AppendComment(forwardMostMove, StripHighlight(parse));
2257 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2258 int nrDigit = 0, nrAlph = 0, i;
2259 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2260 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2261 parse[parse_pos] = NULLCHAR;
2262 // try to be smart: if it does not look like search info, it should go to
2263 // ICS interaction window after all, not to engine-output window.
2264 for(i=0; i<parse_pos; i++) { // count letters and digits
2265 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2266 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2267 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2269 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2270 int depth=0; float score;
2271 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2272 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2273 pvInfoList[forwardMostMove-1].depth = depth;
2274 pvInfoList[forwardMostMove-1].score = 100*score;
2276 OutputKibitz(suppressKibitz, parse);
2279 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2280 SendToPlayer(tmp, strlen(tmp));
2283 started = STARTED_NONE;
2285 /* Don't match patterns against characters in chatter */
2290 if (started == STARTED_CHATTER) {
2291 if (buf[i] != '\n') {
2292 /* Don't match patterns against characters in chatter */
2296 started = STARTED_NONE;
2299 /* Kludge to deal with rcmd protocol */
2300 if (firstTime && looking_at(buf, &i, "\001*")) {
2301 DisplayFatalError(&buf[1], 0, 1);
2307 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2310 if (appData.debugMode)
2311 fprintf(debugFP, "ics_type %d\n", ics_type);
2314 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2315 ics_type = ICS_FICS;
2317 if (appData.debugMode)
2318 fprintf(debugFP, "ics_type %d\n", ics_type);
2321 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2322 ics_type = ICS_CHESSNET;
2324 if (appData.debugMode)
2325 fprintf(debugFP, "ics_type %d\n", ics_type);
2330 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2331 looking_at(buf, &i, "Logging you in as \"*\"") ||
2332 looking_at(buf, &i, "will be \"*\""))) {
2333 strcpy(ics_handle, star_match[0]);
2337 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2339 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2340 DisplayIcsInteractionTitle(buf);
2341 have_set_title = TRUE;
2344 /* skip finger notes */
2345 if (started == STARTED_NONE &&
2346 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2347 (buf[i] == '1' && buf[i+1] == '0')) &&
2348 buf[i+2] == ':' && buf[i+3] == ' ') {
2349 started = STARTED_CHATTER;
2354 /* skip formula vars */
2355 if (started == STARTED_NONE &&
2356 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2357 started = STARTED_CHATTER;
2363 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2364 if (appData.autoKibitz && started == STARTED_NONE &&
2365 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2366 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2367 if(looking_at(buf, &i, "* kibitzes: ") &&
2368 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2369 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2370 suppressKibitz = TRUE;
2371 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2372 && (gameMode == IcsPlayingWhite)) ||
2373 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2374 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2375 started = STARTED_CHATTER; // own kibitz we simply discard
2377 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2378 parse_pos = 0; parse[0] = NULLCHAR;
2379 savingComment = TRUE;
2380 suppressKibitz = gameMode != IcsObserving ? 2 :
2381 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2385 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2386 started = STARTED_CHATTER;
2387 suppressKibitz = TRUE;
2389 } // [HGM] kibitz: end of patch
2391 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2393 // [HGM] chat: intercept tells by users for which we have an open chat window
2395 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2396 looking_at(buf, &i, "* whispers:") ||
2397 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2398 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2400 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2401 chattingPartner = -1;
2403 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2404 for(p=0; p<MAX_CHAT; p++) {
2405 if(channel == atoi(chatPartner[p])) {
2406 talker[0] = '['; strcat(talker, "]");
2407 chattingPartner = p; break;
2410 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2411 for(p=0; p<MAX_CHAT; p++) {
2412 if(!strcmp("WHISPER", chatPartner[p])) {
2413 talker[0] = '['; strcat(talker, "]");
2414 chattingPartner = p; break;
2417 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2418 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2420 chattingPartner = p; break;
2422 if(chattingPartner<0) i = oldi; else {
2423 started = STARTED_COMMENT;
2424 parse_pos = 0; parse[0] = NULLCHAR;
2425 savingComment = TRUE;
2426 suppressKibitz = TRUE;
2428 } // [HGM] chat: end of patch
2430 if (appData.zippyTalk || appData.zippyPlay) {
2431 /* [DM] Backup address for color zippy lines */
2435 if (loggedOn == TRUE)
2436 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2437 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2439 if (ZippyControl(buf, &i) ||
2440 ZippyConverse(buf, &i) ||
2441 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2443 if (!appData.colorize) continue;
2447 } // [DM] 'else { ' deleted
2449 /* Regular tells and says */
2450 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2451 looking_at(buf, &i, "* (your partner) tells you: ") ||
2452 looking_at(buf, &i, "* says: ") ||
2453 /* Don't color "message" or "messages" output */
2454 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2455 looking_at(buf, &i, "*. * at *:*: ") ||
2456 looking_at(buf, &i, "--* (*:*): ") ||
2457 /* Message notifications (same color as tells) */
2458 looking_at(buf, &i, "* has left a message ") ||
2459 looking_at(buf, &i, "* just sent you a message:\n") ||
2460 /* Whispers and kibitzes */
2461 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2462 looking_at(buf, &i, "* kibitzes: ") ||
2464 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2466 if (tkind == 1 && strchr(star_match[0], ':')) {
2467 /* Avoid "tells you:" spoofs in channels */
2470 if (star_match[0][0] == NULLCHAR ||
2471 strchr(star_match[0], ' ') ||
2472 (tkind == 3 && strchr(star_match[1], ' '))) {
2473 /* Reject bogus matches */
2476 if (appData.colorize) {
2477 if (oldi > next_out) {
2478 SendToPlayer(&buf[next_out], oldi - next_out);
2483 Colorize(ColorTell, FALSE);
2484 curColor = ColorTell;
2487 Colorize(ColorKibitz, FALSE);
2488 curColor = ColorKibitz;
2491 p = strrchr(star_match[1], '(');
2498 Colorize(ColorChannel1, FALSE);
2499 curColor = ColorChannel1;
2501 Colorize(ColorChannel, FALSE);
2502 curColor = ColorChannel;
2506 curColor = ColorNormal;
2510 if (started == STARTED_NONE && appData.autoComment &&
2511 (gameMode == IcsObserving ||
2512 gameMode == IcsPlayingWhite ||
2513 gameMode == IcsPlayingBlack)) {
2514 parse_pos = i - oldi;
2515 memcpy(parse, &buf[oldi], parse_pos);
2516 parse[parse_pos] = NULLCHAR;
2517 started = STARTED_COMMENT;
2518 savingComment = TRUE;
2520 started = STARTED_CHATTER;
2521 savingComment = FALSE;
2528 if (looking_at(buf, &i, "* s-shouts: ") ||
2529 looking_at(buf, &i, "* c-shouts: ")) {
2530 if (appData.colorize) {
2531 if (oldi > next_out) {
2532 SendToPlayer(&buf[next_out], oldi - next_out);
2535 Colorize(ColorSShout, FALSE);
2536 curColor = ColorSShout;
2539 started = STARTED_CHATTER;
2543 if (looking_at(buf, &i, "--->")) {
2548 if (looking_at(buf, &i, "* shouts: ") ||
2549 looking_at(buf, &i, "--> ")) {
2550 if (appData.colorize) {
2551 if (oldi > next_out) {
2552 SendToPlayer(&buf[next_out], oldi - next_out);
2555 Colorize(ColorShout, FALSE);
2556 curColor = ColorShout;
2559 started = STARTED_CHATTER;
2563 if (looking_at( buf, &i, "Challenge:")) {
2564 if (appData.colorize) {
2565 if (oldi > next_out) {
2566 SendToPlayer(&buf[next_out], oldi - next_out);
2569 Colorize(ColorChallenge, FALSE);
2570 curColor = ColorChallenge;
2576 if (looking_at(buf, &i, "* offers you") ||
2577 looking_at(buf, &i, "* offers to be") ||
2578 looking_at(buf, &i, "* would like to") ||
2579 looking_at(buf, &i, "* requests to") ||
2580 looking_at(buf, &i, "Your opponent offers") ||
2581 looking_at(buf, &i, "Your opponent requests")) {
2583 if (appData.colorize) {
2584 if (oldi > next_out) {
2585 SendToPlayer(&buf[next_out], oldi - next_out);
2588 Colorize(ColorRequest, FALSE);
2589 curColor = ColorRequest;
2594 if (looking_at(buf, &i, "* (*) seeking")) {
2595 if (appData.colorize) {
2596 if (oldi > next_out) {
2597 SendToPlayer(&buf[next_out], oldi - next_out);
2600 Colorize(ColorSeek, FALSE);
2601 curColor = ColorSeek;
2606 if (looking_at(buf, &i, "\\ ")) {
2607 if (prevColor != ColorNormal) {
2608 if (oldi > next_out) {
2609 SendToPlayer(&buf[next_out], oldi - next_out);
2612 Colorize(prevColor, TRUE);
2613 curColor = prevColor;
2615 if (savingComment) {
2616 parse_pos = i - oldi;
2617 memcpy(parse, &buf[oldi], parse_pos);
2618 parse[parse_pos] = NULLCHAR;
2619 started = STARTED_COMMENT;
2621 started = STARTED_CHATTER;
2626 if (looking_at(buf, &i, "Black Strength :") ||
2627 looking_at(buf, &i, "<<< style 10 board >>>") ||
2628 looking_at(buf, &i, "<10>") ||
2629 looking_at(buf, &i, "#@#")) {
2630 /* Wrong board style */
2632 SendToICS(ics_prefix);
2633 SendToICS("set style 12\n");
2634 SendToICS(ics_prefix);
2635 SendToICS("refresh\n");
2639 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2641 have_sent_ICS_logon = 1;
2645 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2646 (looking_at(buf, &i, "\n<12> ") ||
2647 looking_at(buf, &i, "<12> "))) {
2649 if (oldi > next_out) {
2650 SendToPlayer(&buf[next_out], oldi - next_out);
2653 started = STARTED_BOARD;
2658 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2659 looking_at(buf, &i, "<b1> ")) {
2660 if (oldi > next_out) {
2661 SendToPlayer(&buf[next_out], oldi - next_out);
2664 started = STARTED_HOLDINGS;
2669 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2671 /* Header for a move list -- first line */
2673 switch (ics_getting_history) {
2677 case BeginningOfGame:
2678 /* User typed "moves" or "oldmoves" while we
2679 were idle. Pretend we asked for these
2680 moves and soak them up so user can step
2681 through them and/or save them.
2684 gameMode = IcsObserving;
2687 ics_getting_history = H_GOT_UNREQ_HEADER;
2689 case EditGame: /*?*/
2690 case EditPosition: /*?*/
2691 /* Should above feature work in these modes too? */
2692 /* For now it doesn't */
2693 ics_getting_history = H_GOT_UNWANTED_HEADER;
2696 ics_getting_history = H_GOT_UNWANTED_HEADER;
2701 /* Is this the right one? */
2702 if (gameInfo.white && gameInfo.black &&
2703 strcmp(gameInfo.white, star_match[0]) == 0 &&
2704 strcmp(gameInfo.black, star_match[2]) == 0) {
2706 ics_getting_history = H_GOT_REQ_HEADER;
2709 case H_GOT_REQ_HEADER:
2710 case H_GOT_UNREQ_HEADER:
2711 case H_GOT_UNWANTED_HEADER:
2712 case H_GETTING_MOVES:
2713 /* Should not happen */
2714 DisplayError(_("Error gathering move list: two headers"), 0);
2715 ics_getting_history = H_FALSE;
2719 /* Save player ratings into gameInfo if needed */
2720 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2721 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2722 (gameInfo.whiteRating == -1 ||
2723 gameInfo.blackRating == -1)) {
2725 gameInfo.whiteRating = string_to_rating(star_match[1]);
2726 gameInfo.blackRating = string_to_rating(star_match[3]);
2727 if (appData.debugMode)
2728 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2729 gameInfo.whiteRating, gameInfo.blackRating);
2734 if (looking_at(buf, &i,
2735 "* * match, initial time: * minute*, increment: * second")) {
2736 /* Header for a move list -- second line */
2737 /* Initial board will follow if this is a wild game */
2738 if (gameInfo.event != NULL) free(gameInfo.event);
2739 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2740 gameInfo.event = StrSave(str);
2741 /* [HGM] we switched variant. Translate boards if needed. */
2742 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2746 if (looking_at(buf, &i, "Move ")) {
2747 /* Beginning of a move list */
2748 switch (ics_getting_history) {
2750 /* Normally should not happen */
2751 /* Maybe user hit reset while we were parsing */
2754 /* Happens if we are ignoring a move list that is not
2755 * the one we just requested. Common if the user
2756 * tries to observe two games without turning off
2759 case H_GETTING_MOVES:
2760 /* Should not happen */
2761 DisplayError(_("Error gathering move list: nested"), 0);
2762 ics_getting_history = H_FALSE;
2764 case H_GOT_REQ_HEADER:
2765 ics_getting_history = H_GETTING_MOVES;
2766 started = STARTED_MOVES;
2768 if (oldi > next_out) {
2769 SendToPlayer(&buf[next_out], oldi - next_out);
2772 case H_GOT_UNREQ_HEADER:
2773 ics_getting_history = H_GETTING_MOVES;
2774 started = STARTED_MOVES_NOHIDE;
2777 case H_GOT_UNWANTED_HEADER:
2778 ics_getting_history = H_FALSE;
2784 if (looking_at(buf, &i, "% ") ||
2785 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2786 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2787 savingComment = FALSE;
2790 case STARTED_MOVES_NOHIDE:
2791 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2792 parse[parse_pos + i - oldi] = NULLCHAR;
2793 ParseGameHistory(parse);
2795 if (appData.zippyPlay && first.initDone) {
2796 FeedMovesToProgram(&first, forwardMostMove);
2797 if (gameMode == IcsPlayingWhite) {
2798 if (WhiteOnMove(forwardMostMove)) {
2799 if (first.sendTime) {
2800 if (first.useColors) {
2801 SendToProgram("black\n", &first);
2803 SendTimeRemaining(&first, TRUE);
2805 if (first.useColors) {
2806 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2808 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2809 first.maybeThinking = TRUE;
2811 if (first.usePlayother) {
2812 if (first.sendTime) {
2813 SendTimeRemaining(&first, TRUE);
2815 SendToProgram("playother\n", &first);
2821 } else if (gameMode == IcsPlayingBlack) {
2822 if (!WhiteOnMove(forwardMostMove)) {
2823 if (first.sendTime) {
2824 if (first.useColors) {
2825 SendToProgram("white\n", &first);
2827 SendTimeRemaining(&first, FALSE);
2829 if (first.useColors) {
2830 SendToProgram("black\n", &first);
2832 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2833 first.maybeThinking = TRUE;
2835 if (first.usePlayother) {
2836 if (first.sendTime) {
2837 SendTimeRemaining(&first, FALSE);
2839 SendToProgram("playother\n", &first);
2848 if (gameMode == IcsObserving && ics_gamenum == -1) {
2849 /* Moves came from oldmoves or moves command
2850 while we weren't doing anything else.
2852 currentMove = forwardMostMove;
2853 ClearHighlights();/*!!could figure this out*/
2854 flipView = appData.flipView;
2855 DrawPosition(FALSE, boards[currentMove]);
2856 DisplayBothClocks();
2857 sprintf(str, "%s vs. %s",
2858 gameInfo.white, gameInfo.black);
2862 /* Moves were history of an active game */
2863 if (gameInfo.resultDetails != NULL) {
2864 free(gameInfo.resultDetails);
2865 gameInfo.resultDetails = NULL;
2868 HistorySet(parseList, backwardMostMove,
2869 forwardMostMove, currentMove-1);
2870 DisplayMove(currentMove - 1);
2871 if (started == STARTED_MOVES) next_out = i;
2872 started = STARTED_NONE;
2873 ics_getting_history = H_FALSE;
2876 case STARTED_OBSERVE:
2877 started = STARTED_NONE;
2878 SendToICS(ics_prefix);
2879 SendToICS("refresh\n");
2885 if(bookHit) { // [HGM] book: simulate book reply
2886 static char bookMove[MSG_SIZ]; // a bit generous?
2888 programStats.nodes = programStats.depth = programStats.time =
2889 programStats.score = programStats.got_only_move = 0;
2890 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2892 strcpy(bookMove, "move ");
2893 strcat(bookMove, bookHit);
2894 HandleMachineMove(bookMove, &first);
2899 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2900 started == STARTED_HOLDINGS ||
2901 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2902 /* Accumulate characters in move list or board */
2903 parse[parse_pos++] = buf[i];
2906 /* Start of game messages. Mostly we detect start of game
2907 when the first board image arrives. On some versions
2908 of the ICS, though, we need to do a "refresh" after starting
2909 to observe in order to get the current board right away. */
2910 if (looking_at(buf, &i, "Adding game * to observation list")) {
2911 started = STARTED_OBSERVE;
2915 /* Handle auto-observe */
2916 if (appData.autoObserve &&
2917 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2918 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2920 /* Choose the player that was highlighted, if any. */
2921 if (star_match[0][0] == '\033' ||
2922 star_match[1][0] != '\033') {
2923 player = star_match[0];
2925 player = star_match[2];
2927 sprintf(str, "%sobserve %s\n",
2928 ics_prefix, StripHighlightAndTitle(player));
2931 /* Save ratings from notify string */
2932 strcpy(player1Name, star_match[0]);
2933 player1Rating = string_to_rating(star_match[1]);
2934 strcpy(player2Name, star_match[2]);
2935 player2Rating = string_to_rating(star_match[3]);
2937 if (appData.debugMode)
2939 "Ratings from 'Game notification:' %s %d, %s %d\n",
2940 player1Name, player1Rating,
2941 player2Name, player2Rating);
2946 /* Deal with automatic examine mode after a game,
2947 and with IcsObserving -> IcsExamining transition */
2948 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2949 looking_at(buf, &i, "has made you an examiner of game *")) {
2951 int gamenum = atoi(star_match[0]);
2952 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2953 gamenum == ics_gamenum) {
2954 /* We were already playing or observing this game;
2955 no need to refetch history */
2956 gameMode = IcsExamining;
2958 pauseExamForwardMostMove = forwardMostMove;
2959 } else if (currentMove < forwardMostMove) {
2960 ForwardInner(forwardMostMove);
2963 /* I don't think this case really can happen */
2964 SendToICS(ics_prefix);
2965 SendToICS("refresh\n");
2970 /* Error messages */
2971 // if (ics_user_moved) {
2972 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2973 if (looking_at(buf, &i, "Illegal move") ||
2974 looking_at(buf, &i, "Not a legal move") ||
2975 looking_at(buf, &i, "Your king is in check") ||
2976 looking_at(buf, &i, "It isn't your turn") ||
2977 looking_at(buf, &i, "It is not your move")) {
2979 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2980 currentMove = --forwardMostMove;
2981 DisplayMove(currentMove - 1); /* before DMError */
2982 DrawPosition(FALSE, boards[currentMove]);
2984 DisplayBothClocks();
2986 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2992 if (looking_at(buf, &i, "still have time") ||
2993 looking_at(buf, &i, "not out of time") ||
2994 looking_at(buf, &i, "either player is out of time") ||
2995 looking_at(buf, &i, "has timeseal; checking")) {
2996 /* We must have called his flag a little too soon */
2997 whiteFlag = blackFlag = FALSE;
3001 if (looking_at(buf, &i, "added * seconds to") ||
3002 looking_at(buf, &i, "seconds were added to")) {
3003 /* Update the clocks */
3004 SendToICS(ics_prefix);
3005 SendToICS("refresh\n");
3009 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3010 ics_clock_paused = TRUE;
3015 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3016 ics_clock_paused = FALSE;
3021 /* Grab player ratings from the Creating: message.
3022 Note we have to check for the special case when
3023 the ICS inserts things like [white] or [black]. */
3024 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3025 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3027 0 player 1 name (not necessarily white)
3029 2 empty, white, or black (IGNORED)
3030 3 player 2 name (not necessarily black)
3033 The names/ratings are sorted out when the game
3034 actually starts (below).
3036 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3037 player1Rating = string_to_rating(star_match[1]);
3038 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3039 player2Rating = string_to_rating(star_match[4]);
3041 if (appData.debugMode)
3043 "Ratings from 'Creating:' %s %d, %s %d\n",
3044 player1Name, player1Rating,
3045 player2Name, player2Rating);
3050 /* Improved generic start/end-of-game messages */
3051 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3052 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3053 /* If tkind == 0: */
3054 /* star_match[0] is the game number */
3055 /* [1] is the white player's name */
3056 /* [2] is the black player's name */
3057 /* For end-of-game: */
3058 /* [3] is the reason for the game end */
3059 /* [4] is a PGN end game-token, preceded by " " */
3060 /* For start-of-game: */
3061 /* [3] begins with "Creating" or "Continuing" */
3062 /* [4] is " *" or empty (don't care). */
3063 int gamenum = atoi(star_match[0]);
3064 char *whitename, *blackname, *why, *endtoken;
3065 ChessMove endtype = (ChessMove) 0;
3068 whitename = star_match[1];
3069 blackname = star_match[2];
3070 why = star_match[3];
3071 endtoken = star_match[4];
3073 whitename = star_match[1];
3074 blackname = star_match[3];
3075 why = star_match[5];
3076 endtoken = star_match[6];
3079 /* Game start messages */
3080 if (strncmp(why, "Creating ", 9) == 0 ||
3081 strncmp(why, "Continuing ", 11) == 0) {
3082 gs_gamenum = gamenum;
3083 strcpy(gs_kind, strchr(why, ' ') + 1);
3085 if (appData.zippyPlay) {
3086 ZippyGameStart(whitename, blackname);
3092 /* Game end messages */
3093 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3094 ics_gamenum != gamenum) {
3097 while (endtoken[0] == ' ') endtoken++;
3098 switch (endtoken[0]) {
3101 endtype = GameUnfinished;
3104 endtype = BlackWins;
3107 if (endtoken[1] == '/')
3108 endtype = GameIsDrawn;
3110 endtype = WhiteWins;
3113 GameEnds(endtype, why, GE_ICS);
3115 if (appData.zippyPlay && first.initDone) {
3116 ZippyGameEnd(endtype, why);
3117 if (first.pr == NULL) {
3118 /* Start the next process early so that we'll
3119 be ready for the next challenge */
3120 StartChessProgram(&first);
3122 /* Send "new" early, in case this command takes
3123 a long time to finish, so that we'll be ready
3124 for the next challenge. */
3125 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3132 if (looking_at(buf, &i, "Removing game * from observation") ||
3133 looking_at(buf, &i, "no longer observing game *") ||
3134 looking_at(buf, &i, "Game * (*) has no examiners")) {
3135 if (gameMode == IcsObserving &&
3136 atoi(star_match[0]) == ics_gamenum)
3138 /* icsEngineAnalyze */
3139 if (appData.icsEngineAnalyze) {
3146 ics_user_moved = FALSE;
3151 if (looking_at(buf, &i, "no longer examining game *")) {
3152 if (gameMode == IcsExamining &&
3153 atoi(star_match[0]) == ics_gamenum)
3157 ics_user_moved = FALSE;
3162 /* Advance leftover_start past any newlines we find,
3163 so only partial lines can get reparsed */
3164 if (looking_at(buf, &i, "\n")) {
3165 prevColor = curColor;
3166 if (curColor != ColorNormal) {
3167 if (oldi > next_out) {
3168 SendToPlayer(&buf[next_out], oldi - next_out);
3171 Colorize(ColorNormal, FALSE);
3172 curColor = ColorNormal;
3174 if (started == STARTED_BOARD) {
3175 started = STARTED_NONE;
3176 parse[parse_pos] = NULLCHAR;
3177 ParseBoard12(parse);
3180 /* Send premove here */
3181 if (appData.premove) {
3183 if (currentMove == 0 &&
3184 gameMode == IcsPlayingWhite &&
3185 appData.premoveWhite) {
3186 sprintf(str, "%s%s\n", ics_prefix,
3187 appData.premoveWhiteText);
3188 if (appData.debugMode)
3189 fprintf(debugFP, "Sending premove:\n");
3191 } else if (currentMove == 1 &&
3192 gameMode == IcsPlayingBlack &&
3193 appData.premoveBlack) {
3194 sprintf(str, "%s%s\n", ics_prefix,
3195 appData.premoveBlackText);
3196 if (appData.debugMode)
3197 fprintf(debugFP, "Sending premove:\n");
3199 } else if (gotPremove) {
3201 ClearPremoveHighlights();
3202 if (appData.debugMode)
3203 fprintf(debugFP, "Sending premove:\n");
3204 UserMoveEvent(premoveFromX, premoveFromY,
3205 premoveToX, premoveToY,
3210 /* Usually suppress following prompt */
3211 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3212 if (looking_at(buf, &i, "*% ")) {
3213 savingComment = FALSE;
3217 } else if (started == STARTED_HOLDINGS) {
3219 char new_piece[MSG_SIZ];
3220 started = STARTED_NONE;
3221 parse[parse_pos] = NULLCHAR;
3222 if (appData.debugMode)
3223 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3224 parse, currentMove);
3225 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3226 gamenum == ics_gamenum) {
3227 if (gameInfo.variant == VariantNormal) {
3228 /* [HGM] We seem to switch variant during a game!
3229 * Presumably no holdings were displayed, so we have
3230 * to move the position two files to the right to
3231 * create room for them!
3233 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3234 /* Get a move list just to see the header, which
3235 will tell us whether this is really bug or zh */
3236 if (ics_getting_history == H_FALSE) {
3237 ics_getting_history = H_REQUESTED;
3238 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3242 new_piece[0] = NULLCHAR;
3243 sscanf(parse, "game %d white [%s black [%s <- %s",
3244 &gamenum, white_holding, black_holding,
3246 white_holding[strlen(white_holding)-1] = NULLCHAR;
3247 black_holding[strlen(black_holding)-1] = NULLCHAR;
3248 /* [HGM] copy holdings to board holdings area */
3249 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3250 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3252 if (appData.zippyPlay && first.initDone) {
3253 ZippyHoldings(white_holding, black_holding,
3257 if (tinyLayout || smallLayout) {
3258 char wh[16], bh[16];
3259 PackHolding(wh, white_holding);
3260 PackHolding(bh, black_holding);
3261 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3262 gameInfo.white, gameInfo.black);
3264 sprintf(str, "%s [%s] vs. %s [%s]",
3265 gameInfo.white, white_holding,
3266 gameInfo.black, black_holding);
3269 DrawPosition(FALSE, boards[currentMove]);
3272 /* Suppress following prompt */
3273 if (looking_at(buf, &i, "*% ")) {
3274 savingComment = FALSE;
3281 i++; /* skip unparsed character and loop back */
3284 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3285 started != STARTED_HOLDINGS && i > next_out) {
3286 SendToPlayer(&buf[next_out], i - next_out);
3289 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3291 leftover_len = buf_len - leftover_start;
3292 /* if buffer ends with something we couldn't parse,
3293 reparse it after appending the next read */
3295 } else if (count == 0) {
3296 RemoveInputSource(isr);
3297 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3299 DisplayFatalError(_("Error reading from ICS"), error, 1);
3304 /* Board style 12 looks like this:
3306 <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
3308 * The "<12> " is stripped before it gets to this routine. The two
3309 * trailing 0's (flip state and clock ticking) are later addition, and
3310 * some chess servers may not have them, or may have only the first.
3311 * Additional trailing fields may be added in the future.
3314 #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"
3316 #define RELATION_OBSERVING_PLAYED 0
3317 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3318 #define RELATION_PLAYING_MYMOVE 1
3319 #define RELATION_PLAYING_NOTMYMOVE -1
3320 #define RELATION_EXAMINING 2
3321 #define RELATION_ISOLATED_BOARD -3
3322 #define RELATION_STARTING_POSITION -4 /* FICS only */
3325 ParseBoard12(string)
3328 GameMode newGameMode;
3329 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3330 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3331 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3332 char to_play, board_chars[200];
3333 char move_str[500], str[500], elapsed_time[500];
3334 char black[32], white[32];
3336 int prevMove = currentMove;
3339 int fromX, fromY, toX, toY;
3341 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3342 char *bookHit = NULL; // [HGM] book
3344 fromX = fromY = toX = toY = -1;
3348 if (appData.debugMode)
3349 fprintf(debugFP, _("Parsing board: %s\n"), string);
3351 move_str[0] = NULLCHAR;
3352 elapsed_time[0] = NULLCHAR;
3353 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3355 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3356 if(string[i] == ' ') { ranks++; files = 0; }
3360 for(j = 0; j <i; j++) board_chars[j] = string[j];
3361 board_chars[i] = '\0';
3364 n = sscanf(string, PATTERN, &to_play, &double_push,
3365 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3366 &gamenum, white, black, &relation, &basetime, &increment,
3367 &white_stren, &black_stren, &white_time, &black_time,
3368 &moveNum, str, elapsed_time, move_str, &ics_flip,
3372 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3373 DisplayError(str, 0);
3377 /* Convert the move number to internal form */
3378 moveNum = (moveNum - 1) * 2;
3379 if (to_play == 'B') moveNum++;
3380 if (moveNum >= MAX_MOVES) {
3381 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3387 case RELATION_OBSERVING_PLAYED:
3388 case RELATION_OBSERVING_STATIC:
3389 if (gamenum == -1) {
3390 /* Old ICC buglet */
3391 relation = RELATION_OBSERVING_STATIC;
3393 newGameMode = IcsObserving;
3395 case RELATION_PLAYING_MYMOVE:
3396 case RELATION_PLAYING_NOTMYMOVE:
3398 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3399 IcsPlayingWhite : IcsPlayingBlack;
3401 case RELATION_EXAMINING:
3402 newGameMode = IcsExamining;
3404 case RELATION_ISOLATED_BOARD:
3406 /* Just display this board. If user was doing something else,
3407 we will forget about it until the next board comes. */
3408 newGameMode = IcsIdle;
3410 case RELATION_STARTING_POSITION:
3411 newGameMode = gameMode;
3415 /* Modify behavior for initial board display on move listing
3418 switch (ics_getting_history) {
3422 case H_GOT_REQ_HEADER:
3423 case H_GOT_UNREQ_HEADER:
3424 /* This is the initial position of the current game */
3425 gamenum = ics_gamenum;
3426 moveNum = 0; /* old ICS bug workaround */
3427 if (to_play == 'B') {
3428 startedFromSetupPosition = TRUE;
3429 blackPlaysFirst = TRUE;
3431 if (forwardMostMove == 0) forwardMostMove = 1;
3432 if (backwardMostMove == 0) backwardMostMove = 1;
3433 if (currentMove == 0) currentMove = 1;
3435 newGameMode = gameMode;
3436 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3438 case H_GOT_UNWANTED_HEADER:
3439 /* This is an initial board that we don't want */
3441 case H_GETTING_MOVES:
3442 /* Should not happen */
3443 DisplayError(_("Error gathering move list: extra board"), 0);
3444 ics_getting_history = H_FALSE;
3448 /* Take action if this is the first board of a new game, or of a
3449 different game than is currently being displayed. */
3450 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3451 relation == RELATION_ISOLATED_BOARD) {
3453 /* Forget the old game and get the history (if any) of the new one */
3454 if (gameMode != BeginningOfGame) {
3458 if (appData.autoRaiseBoard) BoardToTop();
3460 if (gamenum == -1) {
3461 newGameMode = IcsIdle;
3462 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3463 appData.getMoveList) {
3464 /* Need to get game history */
3465 ics_getting_history = H_REQUESTED;
3466 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3470 /* Initially flip the board to have black on the bottom if playing
3471 black or if the ICS flip flag is set, but let the user change
3472 it with the Flip View button. */
3473 flipView = appData.autoFlipView ?
3474 (newGameMode == IcsPlayingBlack) || ics_flip :
3477 /* Done with values from previous mode; copy in new ones */
3478 gameMode = newGameMode;
3480 ics_gamenum = gamenum;
3481 if (gamenum == gs_gamenum) {
3482 int klen = strlen(gs_kind);
3483 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3484 sprintf(str, "ICS %s", gs_kind);
3485 gameInfo.event = StrSave(str);
3487 gameInfo.event = StrSave("ICS game");
3489 gameInfo.site = StrSave(appData.icsHost);
3490 gameInfo.date = PGNDate();
3491 gameInfo.round = StrSave("-");
3492 gameInfo.white = StrSave(white);
3493 gameInfo.black = StrSave(black);
3494 timeControl = basetime * 60 * 1000;
3496 timeIncrement = increment * 1000;
3497 movesPerSession = 0;
3498 gameInfo.timeControl = TimeControlTagValue();
3499 VariantSwitch(board, StringToVariant(gameInfo.event) );
3500 if (appData.debugMode) {
3501 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3502 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3503 setbuf(debugFP, NULL);
3506 gameInfo.outOfBook = NULL;
3508 /* Do we have the ratings? */
3509 if (strcmp(player1Name, white) == 0 &&
3510 strcmp(player2Name, black) == 0) {
3511 if (appData.debugMode)
3512 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3513 player1Rating, player2Rating);
3514 gameInfo.whiteRating = player1Rating;
3515 gameInfo.blackRating = player2Rating;
3516 } else if (strcmp(player2Name, white) == 0 &&
3517 strcmp(player1Name, black) == 0) {
3518 if (appData.debugMode)
3519 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3520 player2Rating, player1Rating);
3521 gameInfo.whiteRating = player2Rating;
3522 gameInfo.blackRating = player1Rating;
3524 player1Name[0] = player2Name[0] = NULLCHAR;
3526 /* Silence shouts if requested */
3527 if (appData.quietPlay &&
3528 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3529 SendToICS(ics_prefix);
3530 SendToICS("set shout 0\n");
3534 /* Deal with midgame name changes */
3536 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3537 if (gameInfo.white) free(gameInfo.white);
3538 gameInfo.white = StrSave(white);
3540 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3541 if (gameInfo.black) free(gameInfo.black);
3542 gameInfo.black = StrSave(black);
3546 /* Throw away game result if anything actually changes in examine mode */
3547 if (gameMode == IcsExamining && !newGame) {
3548 gameInfo.result = GameUnfinished;
3549 if (gameInfo.resultDetails != NULL) {
3550 free(gameInfo.resultDetails);
3551 gameInfo.resultDetails = NULL;
3555 /* In pausing && IcsExamining mode, we ignore boards coming
3556 in if they are in a different variation than we are. */
3557 if (pauseExamInvalid) return;
3558 if (pausing && gameMode == IcsExamining) {
3559 if (moveNum <= pauseExamForwardMostMove) {
3560 pauseExamInvalid = TRUE;
3561 forwardMostMove = pauseExamForwardMostMove;
3566 if (appData.debugMode) {
3567 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3569 /* Parse the board */
3570 for (k = 0; k < ranks; k++) {
3571 for (j = 0; j < files; j++)
3572 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3573 if(gameInfo.holdingsWidth > 1) {
3574 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3575 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3578 CopyBoard(boards[moveNum], board);
3580 startedFromSetupPosition =
3581 !CompareBoards(board, initialPosition);
3582 if(startedFromSetupPosition)
3583 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3586 /* [HGM] Set castling rights. Take the outermost Rooks,
3587 to make it also work for FRC opening positions. Note that board12
3588 is really defective for later FRC positions, as it has no way to
3589 indicate which Rook can castle if they are on the same side of King.
3590 For the initial position we grant rights to the outermost Rooks,
3591 and remember thos rights, and we then copy them on positions
3592 later in an FRC game. This means WB might not recognize castlings with
3593 Rooks that have moved back to their original position as illegal,
3594 but in ICS mode that is not its job anyway.
3596 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3597 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3599 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3600 if(board[0][i] == WhiteRook) j = i;
3601 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3602 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3603 if(board[0][i] == WhiteRook) j = i;
3604 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3606 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3607 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3608 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3609 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3610 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3612 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3613 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3614 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3615 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3616 if(board[BOARD_HEIGHT-1][k] == bKing)
3617 initialRights[5] = castlingRights[moveNum][5] = k;
3619 r = castlingRights[moveNum][0] = initialRights[0];
3620 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3621 r = castlingRights[moveNum][1] = initialRights[1];
3622 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3623 r = castlingRights[moveNum][3] = initialRights[3];
3624 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3625 r = castlingRights[moveNum][4] = initialRights[4];
3626 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3627 /* wildcastle kludge: always assume King has rights */
3628 r = castlingRights[moveNum][2] = initialRights[2];
3629 r = castlingRights[moveNum][5] = initialRights[5];
3631 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3632 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3635 if (ics_getting_history == H_GOT_REQ_HEADER ||
3636 ics_getting_history == H_GOT_UNREQ_HEADER) {
3637 /* This was an initial position from a move list, not
3638 the current position */
3642 /* Update currentMove and known move number limits */
3643 newMove = newGame || moveNum > forwardMostMove;
3645 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3646 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3647 takeback = forwardMostMove - moveNum;
3648 for (i = 0; i < takeback; i++) {
3649 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3650 SendToProgram("undo\n", &first);
3655 forwardMostMove = backwardMostMove = currentMove = moveNum;
3656 if (gameMode == IcsExamining && moveNum == 0) {
3657 /* Workaround for ICS limitation: we are not told the wild
3658 type when starting to examine a game. But if we ask for
3659 the move list, the move list header will tell us */
3660 ics_getting_history = H_REQUESTED;
3661 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3664 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3665 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3666 forwardMostMove = moveNum;
3667 if (!pausing || currentMove > forwardMostMove)
3668 currentMove = forwardMostMove;
3670 /* New part of history that is not contiguous with old part */
3671 if (pausing && gameMode == IcsExamining) {
3672 pauseExamInvalid = TRUE;
3673 forwardMostMove = pauseExamForwardMostMove;
3676 forwardMostMove = backwardMostMove = currentMove = moveNum;
3677 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3678 ics_getting_history = H_REQUESTED;
3679 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3684 /* Update the clocks */
3685 if (strchr(elapsed_time, '.')) {
3687 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3688 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3690 /* Time is in seconds */
3691 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3692 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3697 if (appData.zippyPlay && newGame &&
3698 gameMode != IcsObserving && gameMode != IcsIdle &&
3699 gameMode != IcsExamining)
3700 ZippyFirstBoard(moveNum, basetime, increment);
3703 /* Put the move on the move list, first converting
3704 to canonical algebraic form. */
3706 if (appData.debugMode) {
3707 if (appData.debugMode) { int f = forwardMostMove;
3708 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3709 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3711 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3712 fprintf(debugFP, "moveNum = %d\n", moveNum);
3713 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3714 setbuf(debugFP, NULL);
3716 if (moveNum <= backwardMostMove) {
3717 /* We don't know what the board looked like before
3719 strcpy(parseList[moveNum - 1], move_str);
3720 strcat(parseList[moveNum - 1], " ");
3721 strcat(parseList[moveNum - 1], elapsed_time);
3722 moveList[moveNum - 1][0] = NULLCHAR;
3723 } else if (strcmp(move_str, "none") == 0) {
3724 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3725 /* Again, we don't know what the board looked like;
3726 this is really the start of the game. */
3727 parseList[moveNum - 1][0] = NULLCHAR;
3728 moveList[moveNum - 1][0] = NULLCHAR;
3729 backwardMostMove = moveNum;
3730 startedFromSetupPosition = TRUE;
3731 fromX = fromY = toX = toY = -1;
3733 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3734 // So we parse the long-algebraic move string in stead of the SAN move
3735 int valid; char buf[MSG_SIZ], *prom;
3737 // str looks something like "Q/a1-a2"; kill the slash
3739 sprintf(buf, "%c%s", str[0], str+2);
3740 else strcpy(buf, str); // might be castling
3741 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3742 strcat(buf, prom); // long move lacks promo specification!
3743 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3744 if(appData.debugMode)
3745 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3746 strcpy(move_str, buf);
3748 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3749 &fromX, &fromY, &toX, &toY, &promoChar)
3750 || ParseOneMove(buf, moveNum - 1, &moveType,
3751 &fromX, &fromY, &toX, &toY, &promoChar);
3752 // end of long SAN patch
3754 (void) CoordsToAlgebraic(boards[moveNum - 1],
3755 PosFlags(moveNum - 1), EP_UNKNOWN,
3756 fromY, fromX, toY, toX, promoChar,
3757 parseList[moveNum-1]);
3758 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3759 castlingRights[moveNum]) ) {
3765 if(gameInfo.variant != VariantShogi)
3766 strcat(parseList[moveNum - 1], "+");
3769 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3770 strcat(parseList[moveNum - 1], "#");
3773 strcat(parseList[moveNum - 1], " ");
3774 strcat(parseList[moveNum - 1], elapsed_time);
3775 /* currentMoveString is set as a side-effect of ParseOneMove */
3776 strcpy(moveList[moveNum - 1], currentMoveString);
3777 strcat(moveList[moveNum - 1], "\n");
3779 /* Move from ICS was illegal!? Punt. */
3780 if (appData.debugMode) {
3781 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3782 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3784 strcpy(parseList[moveNum - 1], move_str);
3785 strcat(parseList[moveNum - 1], " ");
3786 strcat(parseList[moveNum - 1], elapsed_time);
3787 moveList[moveNum - 1][0] = NULLCHAR;
3788 fromX = fromY = toX = toY = -1;
3791 if (appData.debugMode) {
3792 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3793 setbuf(debugFP, NULL);
3797 /* Send move to chess program (BEFORE animating it). */
3798 if (appData.zippyPlay && !newGame && newMove &&
3799 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3801 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3802 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3803 if (moveList[moveNum - 1][0] == NULLCHAR) {
3804 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3806 DisplayError(str, 0);
3808 if (first.sendTime) {
3809 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3811 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3812 if (firstMove && !bookHit) {
3814 if (first.useColors) {
3815 SendToProgram(gameMode == IcsPlayingWhite ?
3817 "black\ngo\n", &first);
3819 SendToProgram("go\n", &first);
3821 first.maybeThinking = TRUE;
3824 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3825 if (moveList[moveNum - 1][0] == NULLCHAR) {
3826 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3827 DisplayError(str, 0);
3829 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3830 SendMoveToProgram(moveNum - 1, &first);
3837 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3838 /* If move comes from a remote source, animate it. If it
3839 isn't remote, it will have already been animated. */
3840 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3841 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3843 if (!pausing && appData.highlightLastMove) {
3844 SetHighlights(fromX, fromY, toX, toY);
3848 /* Start the clocks */
3849 whiteFlag = blackFlag = FALSE;
3850 appData.clockMode = !(basetime == 0 && increment == 0);
3852 ics_clock_paused = TRUE;
3854 } else if (ticking == 1) {
3855 ics_clock_paused = FALSE;
3857 if (gameMode == IcsIdle ||
3858 relation == RELATION_OBSERVING_STATIC ||
3859 relation == RELATION_EXAMINING ||
3861 DisplayBothClocks();
3865 /* Display opponents and material strengths */
3866 if (gameInfo.variant != VariantBughouse &&
3867 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3868 if (tinyLayout || smallLayout) {
3869 if(gameInfo.variant == VariantNormal)
3870 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3871 gameInfo.white, white_stren, gameInfo.black, black_stren,
3872 basetime, increment);
3874 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3875 gameInfo.white, white_stren, gameInfo.black, black_stren,
3876 basetime, increment, (int) gameInfo.variant);
3878 if(gameInfo.variant == VariantNormal)
3879 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3880 gameInfo.white, white_stren, gameInfo.black, black_stren,
3881 basetime, increment);
3883 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3884 gameInfo.white, white_stren, gameInfo.black, black_stren,
3885 basetime, increment, VariantName(gameInfo.variant));
3888 if (appData.debugMode) {
3889 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3894 /* Display the board */
3895 if (!pausing && !appData.noGUI) {
3897 if (appData.premove)
3899 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3900 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3901 ClearPremoveHighlights();
3903 DrawPosition(FALSE, boards[currentMove]);
3904 DisplayMove(moveNum - 1);
3905 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3906 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3907 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3908 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3912 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3914 if(bookHit) { // [HGM] book: simulate book reply
3915 static char bookMove[MSG_SIZ]; // a bit generous?
3917 programStats.nodes = programStats.depth = programStats.time =
3918 programStats.score = programStats.got_only_move = 0;
3919 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3921 strcpy(bookMove, "move ");
3922 strcat(bookMove, bookHit);
3923 HandleMachineMove(bookMove, &first);
3932 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3933 ics_getting_history = H_REQUESTED;
3934 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3940 AnalysisPeriodicEvent(force)
3943 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3944 && !force) || !appData.periodicUpdates)
3947 /* Send . command to Crafty to collect stats */
3948 SendToProgram(".\n", &first);
3950 /* Don't send another until we get a response (this makes
3951 us stop sending to old Crafty's which don't understand
3952 the "." command (sending illegal cmds resets node count & time,
3953 which looks bad)) */
3954 programStats.ok_to_send = 0;
3957 void ics_update_width(new_width)
3960 ics_printf("set width %d\n", new_width);
3964 SendMoveToProgram(moveNum, cps)
3966 ChessProgramState *cps;
3970 if (cps->useUsermove) {
3971 SendToProgram("usermove ", cps);
3975 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3976 int len = space - parseList[moveNum];
3977 memcpy(buf, parseList[moveNum], len);
3979 buf[len] = NULLCHAR;
3981 sprintf(buf, "%s\n", parseList[moveNum]);
3983 SendToProgram(buf, cps);
3985 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3986 AlphaRank(moveList[moveNum], 4);
3987 SendToProgram(moveList[moveNum], cps);
3988 AlphaRank(moveList[moveNum], 4); // and back
3990 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3991 * the engine. It would be nice to have a better way to identify castle
3993 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3994 && cps->useOOCastle) {
3995 int fromX = moveList[moveNum][0] - AAA;
3996 int fromY = moveList[moveNum][1] - ONE;
3997 int toX = moveList[moveNum][2] - AAA;
3998 int toY = moveList[moveNum][3] - ONE;
3999 if((boards[moveNum][fromY][fromX] == WhiteKing
4000 && boards[moveNum][toY][toX] == WhiteRook)
4001 || (boards[moveNum][fromY][fromX] == BlackKing
4002 && boards[moveNum][toY][toX] == BlackRook)) {
4003 if(toX > fromX) SendToProgram("O-O\n", cps);
4004 else SendToProgram("O-O-O\n", cps);
4006 else SendToProgram(moveList[moveNum], cps);
4008 else SendToProgram(moveList[moveNum], cps);
4009 /* End of additions by Tord */
4012 /* [HGM] setting up the opening has brought engine in force mode! */
4013 /* Send 'go' if we are in a mode where machine should play. */
4014 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4015 (gameMode == TwoMachinesPlay ||
4017 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4019 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4020 SendToProgram("go\n", cps);
4021 if (appData.debugMode) {
4022 fprintf(debugFP, "(extra)\n");
4025 setboardSpoiledMachineBlack = 0;
4029 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4031 int fromX, fromY, toX, toY;
4033 char user_move[MSG_SIZ];
4037 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4038 (int)moveType, fromX, fromY, toX, toY);
4039 DisplayError(user_move + strlen("say "), 0);
4041 case WhiteKingSideCastle:
4042 case BlackKingSideCastle:
4043 case WhiteQueenSideCastleWild:
4044 case BlackQueenSideCastleWild:
4046 case WhiteHSideCastleFR:
4047 case BlackHSideCastleFR:
4049 sprintf(user_move, "o-o\n");
4051 case WhiteQueenSideCastle:
4052 case BlackQueenSideCastle:
4053 case WhiteKingSideCastleWild:
4054 case BlackKingSideCastleWild:
4056 case WhiteASideCastleFR:
4057 case BlackASideCastleFR:
4059 sprintf(user_move, "o-o-o\n");
4061 case WhitePromotionQueen:
4062 case BlackPromotionQueen:
4063 case WhitePromotionRook:
4064 case BlackPromotionRook:
4065 case WhitePromotionBishop:
4066 case BlackPromotionBishop:
4067 case WhitePromotionKnight:
4068 case BlackPromotionKnight:
4069 case WhitePromotionKing:
4070 case BlackPromotionKing:
4071 case WhitePromotionChancellor:
4072 case BlackPromotionChancellor:
4073 case WhitePromotionArchbishop:
4074 case BlackPromotionArchbishop:
4075 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4076 sprintf(user_move, "%c%c%c%c=%c\n",
4077 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4078 PieceToChar(WhiteFerz));
4079 else if(gameInfo.variant == VariantGreat)
4080 sprintf(user_move, "%c%c%c%c=%c\n",
4081 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4082 PieceToChar(WhiteMan));
4084 sprintf(user_move, "%c%c%c%c=%c\n",
4085 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4086 PieceToChar(PromoPiece(moveType)));
4090 sprintf(user_move, "%c@%c%c\n",
4091 ToUpper(PieceToChar((ChessSquare) fromX)),
4092 AAA + toX, ONE + toY);
4095 case WhiteCapturesEnPassant:
4096 case BlackCapturesEnPassant:
4097 case IllegalMove: /* could be a variant we don't quite understand */
4098 sprintf(user_move, "%c%c%c%c\n",
4099 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4102 SendToICS(user_move);
4103 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4104 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4108 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4113 if (rf == DROP_RANK) {
4114 sprintf(move, "%c@%c%c\n",
4115 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4117 if (promoChar == 'x' || promoChar == NULLCHAR) {
4118 sprintf(move, "%c%c%c%c\n",
4119 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4121 sprintf(move, "%c%c%c%c%c\n",
4122 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4128 ProcessICSInitScript(f)
4133 while (fgets(buf, MSG_SIZ, f)) {
4134 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4141 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4143 AlphaRank(char *move, int n)
4145 // char *p = move, c; int x, y;
4147 if (appData.debugMode) {
4148 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4152 move[2]>='0' && move[2]<='9' &&
4153 move[3]>='a' && move[3]<='x' ) {
4155 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4156 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4158 if(move[0]>='0' && move[0]<='9' &&
4159 move[1]>='a' && move[1]<='x' &&
4160 move[2]>='0' && move[2]<='9' &&
4161 move[3]>='a' && move[3]<='x' ) {
4162 /* input move, Shogi -> normal */
4163 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4164 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4165 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4166 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4169 move[3]>='0' && move[3]<='9' &&
4170 move[2]>='a' && move[2]<='x' ) {
4172 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4173 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4176 move[0]>='a' && move[0]<='x' &&
4177 move[3]>='0' && move[3]<='9' &&
4178 move[2]>='a' && move[2]<='x' ) {
4179 /* output move, normal -> Shogi */
4180 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4181 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4182 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4183 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4184 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4186 if (appData.debugMode) {
4187 fprintf(debugFP, " out = '%s'\n", move);
4191 /* Parser for moves from gnuchess, ICS, or user typein box */
4193 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4196 ChessMove *moveType;
4197 int *fromX, *fromY, *toX, *toY;
4200 if (appData.debugMode) {
4201 fprintf(debugFP, "move to parse: %s\n", move);
4203 *moveType = yylexstr(moveNum, move);
4205 switch (*moveType) {
4206 case WhitePromotionChancellor:
4207 case BlackPromotionChancellor:
4208 case WhitePromotionArchbishop:
4209 case BlackPromotionArchbishop:
4210 case WhitePromotionQueen:
4211 case BlackPromotionQueen:
4212 case WhitePromotionRook:
4213 case BlackPromotionRook:
4214 case WhitePromotionBishop:
4215 case BlackPromotionBishop:
4216 case WhitePromotionKnight:
4217 case BlackPromotionKnight:
4218 case WhitePromotionKing:
4219 case BlackPromotionKing:
4221 case WhiteCapturesEnPassant:
4222 case BlackCapturesEnPassant:
4223 case WhiteKingSideCastle:
4224 case WhiteQueenSideCastle:
4225 case BlackKingSideCastle:
4226 case BlackQueenSideCastle:
4227 case WhiteKingSideCastleWild:
4228 case WhiteQueenSideCastleWild:
4229 case BlackKingSideCastleWild:
4230 case BlackQueenSideCastleWild:
4231 /* Code added by Tord: */
4232 case WhiteHSideCastleFR:
4233 case WhiteASideCastleFR:
4234 case BlackHSideCastleFR:
4235 case BlackASideCastleFR:
4236 /* End of code added by Tord */
4237 case IllegalMove: /* bug or odd chess variant */
4238 *fromX = currentMoveString[0] - AAA;
4239 *fromY = currentMoveString[1] - ONE;
4240 *toX = currentMoveString[2] - AAA;
4241 *toY = currentMoveString[3] - ONE;
4242 *promoChar = currentMoveString[4];
4243 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4244 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4245 if (appData.debugMode) {
4246 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4248 *fromX = *fromY = *toX = *toY = 0;
4251 if (appData.testLegality) {
4252 return (*moveType != IllegalMove);
4254 return !(fromX == fromY && toX == toY);
4259 *fromX = *moveType == WhiteDrop ?
4260 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4261 (int) CharToPiece(ToLower(currentMoveString[0]));
4263 *toX = currentMoveString[2] - AAA;
4264 *toY = currentMoveString[3] - ONE;
4265 *promoChar = NULLCHAR;
4269 case ImpossibleMove:
4270 case (ChessMove) 0: /* end of file */
4279 if (appData.debugMode) {
4280 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4283 *fromX = *fromY = *toX = *toY = 0;
4284 *promoChar = NULLCHAR;
4289 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4290 // All positions will have equal probability, but the current method will not provide a unique
4291 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4297 int piecesLeft[(int)BlackPawn];
4298 int seed, nrOfShuffles;
4300 void GetPositionNumber()
4301 { // sets global variable seed
4304 seed = appData.defaultFrcPosition;
4305 if(seed < 0) { // randomize based on time for negative FRC position numbers
4306 for(i=0; i<50; i++) seed += random();
4307 seed = random() ^ random() >> 8 ^ random() << 8;
4308 if(seed<0) seed = -seed;
4312 int put(Board board, int pieceType, int rank, int n, int shade)
4313 // put the piece on the (n-1)-th empty squares of the given shade
4317 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4318 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4319 board[rank][i] = (ChessSquare) pieceType;
4320 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4322 piecesLeft[pieceType]--;
4330 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4331 // calculate where the next piece goes, (any empty square), and put it there
4335 i = seed % squaresLeft[shade];
4336 nrOfShuffles *= squaresLeft[shade];
4337 seed /= squaresLeft[shade];
4338 put(board, pieceType, rank, i, shade);
4341 void AddTwoPieces(Board board, int pieceType, int rank)
4342 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4344 int i, n=squaresLeft[ANY], j=n-1, k;
4346 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4347 i = seed % k; // pick one
4350 while(i >= j) i -= j--;
4351 j = n - 1 - j; i += j;
4352 put(board, pieceType, rank, j, ANY);
4353 put(board, pieceType, rank, i, ANY);
4356 void SetUpShuffle(Board board, int number)
4360 GetPositionNumber(); nrOfShuffles = 1;
4362 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4363 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4364 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4366 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4368 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4369 p = (int) board[0][i];
4370 if(p < (int) BlackPawn) piecesLeft[p] ++;
4371 board[0][i] = EmptySquare;
4374 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4375 // shuffles restricted to allow normal castling put KRR first
4376 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4377 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4378 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4379 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4380 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4381 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4382 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4383 put(board, WhiteRook, 0, 0, ANY);
4384 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4387 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4388 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4389 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4390 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4391 while(piecesLeft[p] >= 2) {
4392 AddOnePiece(board, p, 0, LITE);
4393 AddOnePiece(board, p, 0, DARK);
4395 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4398 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4399 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4400 // but we leave King and Rooks for last, to possibly obey FRC restriction
4401 if(p == (int)WhiteRook) continue;
4402 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4403 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4406 // now everything is placed, except perhaps King (Unicorn) and Rooks
4408 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4409 // Last King gets castling rights
4410 while(piecesLeft[(int)WhiteUnicorn]) {
4411 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4412 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4415 while(piecesLeft[(int)WhiteKing]) {
4416 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4417 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4422 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4423 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4426 // Only Rooks can be left; simply place them all
4427 while(piecesLeft[(int)WhiteRook]) {
4428 i = put(board, WhiteRook, 0, 0, ANY);
4429 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4432 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4434 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4437 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4438 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4441 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4444 int SetCharTable( char *table, const char * map )
4445 /* [HGM] moved here from winboard.c because of its general usefulness */
4446 /* Basically a safe strcpy that uses the last character as King */
4448 int result = FALSE; int NrPieces;
4450 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4451 && NrPieces >= 12 && !(NrPieces&1)) {
4452 int i; /* [HGM] Accept even length from 12 to 34 */
4454 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4455 for( i=0; i<NrPieces/2-1; i++ ) {
4457 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4459 table[(int) WhiteKing] = map[NrPieces/2-1];
4460 table[(int) BlackKing] = map[NrPieces-1];
4468 void Prelude(Board board)
4469 { // [HGM] superchess: random selection of exo-pieces
4470 int i, j, k; ChessSquare p;
4471 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4473 GetPositionNumber(); // use FRC position number
4475 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4476 SetCharTable(pieceToChar, appData.pieceToCharTable);
4477 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4478 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4481 j = seed%4; seed /= 4;
4482 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4483 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4484 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4485 j = seed%3 + (seed%3 >= j); seed /= 3;
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;
4490 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
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%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4498 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4499 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4500 put(board, exoPieces[0], 0, 0, ANY);
4501 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4505 InitPosition(redraw)
4508 ChessSquare (* pieces)[BOARD_SIZE];
4509 int i, j, pawnRow, overrule,
4510 oldx = gameInfo.boardWidth,
4511 oldy = gameInfo.boardHeight,
4512 oldh = gameInfo.holdingsWidth,
4513 oldv = gameInfo.variant;
4515 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4517 /* [AS] Initialize pv info list [HGM] and game status */
4519 for( i=0; i<MAX_MOVES; i++ ) {
4520 pvInfoList[i].depth = 0;
4521 epStatus[i]=EP_NONE;
4522 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4525 initialRulePlies = 0; /* 50-move counter start */
4527 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4528 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4532 /* [HGM] logic here is completely changed. In stead of full positions */
4533 /* the initialized data only consist of the two backranks. The switch */
4534 /* selects which one we will use, which is than copied to the Board */
4535 /* initialPosition, which for the rest is initialized by Pawns and */
4536 /* empty squares. This initial position is then copied to boards[0], */
4537 /* possibly after shuffling, so that it remains available. */
4539 gameInfo.holdingsWidth = 0; /* default board sizes */
4540 gameInfo.boardWidth = 8;
4541 gameInfo.boardHeight = 8;
4542 gameInfo.holdingsSize = 0;
4543 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4544 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4545 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4547 switch (gameInfo.variant) {
4548 case VariantFischeRandom:
4549 shuffleOpenings = TRUE;
4553 case VariantShatranj:
4554 pieces = ShatranjArray;
4555 nrCastlingRights = 0;
4556 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4558 case VariantTwoKings:
4559 pieces = twoKingsArray;
4561 case VariantCapaRandom:
4562 shuffleOpenings = TRUE;
4563 case VariantCapablanca:
4564 pieces = CapablancaArray;
4565 gameInfo.boardWidth = 10;
4566 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4569 pieces = GothicArray;
4570 gameInfo.boardWidth = 10;
4571 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4574 pieces = JanusArray;
4575 gameInfo.boardWidth = 10;
4576 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4577 nrCastlingRights = 6;
4578 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4579 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4580 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4581 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4582 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4583 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4586 pieces = FalconArray;
4587 gameInfo.boardWidth = 10;
4588 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4590 case VariantXiangqi:
4591 pieces = XiangqiArray;
4592 gameInfo.boardWidth = 9;
4593 gameInfo.boardHeight = 10;
4594 nrCastlingRights = 0;
4595 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4598 pieces = ShogiArray;
4599 gameInfo.boardWidth = 9;
4600 gameInfo.boardHeight = 9;
4601 gameInfo.holdingsSize = 7;
4602 nrCastlingRights = 0;
4603 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4605 case VariantCourier:
4606 pieces = CourierArray;
4607 gameInfo.boardWidth = 12;
4608 nrCastlingRights = 0;
4609 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4610 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4612 case VariantKnightmate:
4613 pieces = KnightmateArray;
4614 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4617 pieces = fairyArray;
4618 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4621 pieces = GreatArray;
4622 gameInfo.boardWidth = 10;
4623 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4624 gameInfo.holdingsSize = 8;
4628 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4629 gameInfo.holdingsSize = 8;
4630 startedFromSetupPosition = TRUE;
4632 case VariantCrazyhouse:
4633 case VariantBughouse:
4635 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4636 gameInfo.holdingsSize = 5;
4638 case VariantWildCastle:
4640 /* !!?shuffle with kings guaranteed to be on d or e file */
4641 shuffleOpenings = 1;
4643 case VariantNoCastle:
4645 nrCastlingRights = 0;
4646 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4647 /* !!?unconstrained back-rank shuffle */
4648 shuffleOpenings = 1;
4653 if(appData.NrFiles >= 0) {
4654 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4655 gameInfo.boardWidth = appData.NrFiles;
4657 if(appData.NrRanks >= 0) {
4658 gameInfo.boardHeight = appData.NrRanks;
4660 if(appData.holdingsSize >= 0) {
4661 i = appData.holdingsSize;
4662 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4663 gameInfo.holdingsSize = i;
4665 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4666 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4667 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4669 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4670 if(pawnRow < 1) pawnRow = 1;
4672 /* User pieceToChar list overrules defaults */
4673 if(appData.pieceToCharTable != NULL)
4674 SetCharTable(pieceToChar, appData.pieceToCharTable);
4676 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4678 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4679 s = (ChessSquare) 0; /* account holding counts in guard band */
4680 for( i=0; i<BOARD_HEIGHT; i++ )
4681 initialPosition[i][j] = s;
4683 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4684 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4685 initialPosition[pawnRow][j] = WhitePawn;
4686 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4687 if(gameInfo.variant == VariantXiangqi) {
4689 initialPosition[pawnRow][j] =
4690 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4691 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4692 initialPosition[2][j] = WhiteCannon;
4693 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4697 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4699 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4702 initialPosition[1][j] = WhiteBishop;
4703 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4705 initialPosition[1][j] = WhiteRook;
4706 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4709 if( nrCastlingRights == -1) {
4710 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4711 /* This sets default castling rights from none to normal corners */
4712 /* Variants with other castling rights must set them themselves above */
4713 nrCastlingRights = 6;
4715 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4716 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4717 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4718 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4719 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4720 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4723 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4724 if(gameInfo.variant == VariantGreat) { // promotion commoners
4725 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4726 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4727 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4728 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4730 if (appData.debugMode) {
4731 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4733 if(shuffleOpenings) {
4734 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4735 startedFromSetupPosition = TRUE;
4737 if(startedFromPositionFile) {
4738 /* [HGM] loadPos: use PositionFile for every new game */
4739 CopyBoard(initialPosition, filePosition);
4740 for(i=0; i<nrCastlingRights; i++)
4741 castlingRights[0][i] = initialRights[i] = fileRights[i];
4742 startedFromSetupPosition = TRUE;
4745 CopyBoard(boards[0], initialPosition);
4747 if(oldx != gameInfo.boardWidth ||
4748 oldy != gameInfo.boardHeight ||
4749 oldh != gameInfo.holdingsWidth
4751 || oldv == VariantGothic || // For licensing popups
4752 gameInfo.variant == VariantGothic
4755 || oldv == VariantFalcon ||
4756 gameInfo.variant == VariantFalcon
4759 InitDrawingSizes(-2 ,0);
4762 DrawPosition(TRUE, boards[currentMove]);
4766 SendBoard(cps, moveNum)
4767 ChessProgramState *cps;
4770 char message[MSG_SIZ];
4772 if (cps->useSetboard) {
4773 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4774 sprintf(message, "setboard %s\n", fen);
4775 SendToProgram(message, cps);
4781 /* Kludge to set black to move, avoiding the troublesome and now
4782 * deprecated "black" command.
4784 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4786 SendToProgram("edit\n", cps);
4787 SendToProgram("#\n", cps);
4788 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4789 bp = &boards[moveNum][i][BOARD_LEFT];
4790 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4791 if ((int) *bp < (int) BlackPawn) {
4792 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4794 if(message[0] == '+' || message[0] == '~') {
4795 sprintf(message, "%c%c%c+\n",
4796 PieceToChar((ChessSquare)(DEMOTED *bp)),
4799 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4800 message[1] = BOARD_RGHT - 1 - j + '1';
4801 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4803 SendToProgram(message, cps);
4808 SendToProgram("c\n", cps);
4809 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4810 bp = &boards[moveNum][i][BOARD_LEFT];
4811 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4812 if (((int) *bp != (int) EmptySquare)
4813 && ((int) *bp >= (int) BlackPawn)) {
4814 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4816 if(message[0] == '+' || message[0] == '~') {
4817 sprintf(message, "%c%c%c+\n",
4818 PieceToChar((ChessSquare)(DEMOTED *bp)),
4821 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4822 message[1] = BOARD_RGHT - 1 - j + '1';
4823 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4825 SendToProgram(message, cps);
4830 SendToProgram(".\n", cps);
4832 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4836 IsPromotion(fromX, fromY, toX, toY)
4837 int fromX, fromY, toX, toY;
4839 /* [HGM] add Shogi promotions */
4840 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4843 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4844 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4845 /* [HGM] Note to self: line above also weeds out drops */
4846 piece = boards[currentMove][fromY][fromX];
4847 if(gameInfo.variant == VariantShogi) {
4848 promotionZoneSize = 3;
4849 highestPromotingPiece = (int)WhiteKing;
4850 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4851 and if in normal chess we then allow promotion to King, why not
4852 allow promotion of other piece in Shogi? */
4854 if((int)piece >= BlackPawn) {
4855 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4857 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4859 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4860 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4862 return ( (int)piece <= highestPromotingPiece );
4866 InPalace(row, column)
4868 { /* [HGM] for Xiangqi */
4869 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4870 column < (BOARD_WIDTH + 4)/2 &&
4871 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4876 PieceForSquare (x, y)
4880 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4883 return boards[currentMove][y][x];
4887 OKToStartUserMove(x, y)
4890 ChessSquare from_piece;
4893 if (matchMode) return FALSE;
4894 if (gameMode == EditPosition) return TRUE;
4896 if (x >= 0 && y >= 0)
4897 from_piece = boards[currentMove][y][x];
4899 from_piece = EmptySquare;
4901 if (from_piece == EmptySquare) return FALSE;
4903 white_piece = (int)from_piece >= (int)WhitePawn &&
4904 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4907 case PlayFromGameFile:
4909 case TwoMachinesPlay:
4917 case MachinePlaysWhite:
4918 case IcsPlayingBlack:
4919 if (appData.zippyPlay) return FALSE;
4921 DisplayMoveError(_("You are playing Black"));
4926 case MachinePlaysBlack:
4927 case IcsPlayingWhite:
4928 if (appData.zippyPlay) return FALSE;
4930 DisplayMoveError(_("You are playing White"));
4936 if (!white_piece && WhiteOnMove(currentMove)) {
4937 DisplayMoveError(_("It is White's turn"));
4940 if (white_piece && !WhiteOnMove(currentMove)) {
4941 DisplayMoveError(_("It is Black's turn"));
4944 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4945 /* Editing correspondence game history */
4946 /* Could disallow this or prompt for confirmation */
4949 if (currentMove < forwardMostMove) {
4950 /* Discarding moves */
4951 /* Could prompt for confirmation here,
4952 but I don't think that's such a good idea */
4953 forwardMostMove = currentMove;
4957 case BeginningOfGame:
4958 if (appData.icsActive) return FALSE;
4959 if (!appData.noChessProgram) {
4961 DisplayMoveError(_("You are playing White"));
4968 if (!white_piece && WhiteOnMove(currentMove)) {
4969 DisplayMoveError(_("It is White's turn"));
4972 if (white_piece && !WhiteOnMove(currentMove)) {
4973 DisplayMoveError(_("It is Black's turn"));
4982 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4983 && gameMode != AnalyzeFile && gameMode != Training) {
4984 DisplayMoveError(_("Displayed position is not current"));
4990 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4991 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4992 int lastLoadGameUseList = FALSE;
4993 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4994 ChessMove lastLoadGameStart = (ChessMove) 0;
4997 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4998 int fromX, fromY, toX, toY;
5003 ChessSquare pdown, pup;
5005 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5007 /* [HGM] suppress all moves into holdings area and guard band */
5008 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5009 return ImpossibleMove;
5011 /* [HGM] <sameColor> moved to here from winboard.c */
5012 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5013 pdown = boards[currentMove][fromY][fromX];
5014 pup = boards[currentMove][toY][toX];
5015 if ( gameMode != EditPosition && !captureOwn &&
5016 (WhitePawn <= pdown && pdown < BlackPawn &&
5017 WhitePawn <= pup && pup < BlackPawn ||
5018 BlackPawn <= pdown && pdown < EmptySquare &&
5019 BlackPawn <= pup && pup < EmptySquare
5020 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5021 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5022 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5023 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5024 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5028 /* Check if the user is playing in turn. This is complicated because we
5029 let the user "pick up" a piece before it is his turn. So the piece he
5030 tried to pick up may have been captured by the time he puts it down!
5031 Therefore we use the color the user is supposed to be playing in this
5032 test, not the color of the piece that is currently on the starting
5033 square---except in EditGame mode, where the user is playing both
5034 sides; fortunately there the capture race can't happen. (It can
5035 now happen in IcsExamining mode, but that's just too bad. The user
5036 will get a somewhat confusing message in that case.)
5040 case PlayFromGameFile:
5042 case TwoMachinesPlay:
5046 /* We switched into a game mode where moves are not accepted,
5047 perhaps while the mouse button was down. */
5048 return ImpossibleMove;
5050 case MachinePlaysWhite:
5051 /* User is moving for Black */
5052 if (WhiteOnMove(currentMove)) {
5053 DisplayMoveError(_("It is White's turn"));
5054 return ImpossibleMove;
5058 case MachinePlaysBlack:
5059 /* User is moving for White */
5060 if (!WhiteOnMove(currentMove)) {
5061 DisplayMoveError(_("It is Black's turn"));
5062 return ImpossibleMove;
5068 case BeginningOfGame:
5071 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5072 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5073 /* User is moving for Black */
5074 if (WhiteOnMove(currentMove)) {
5075 DisplayMoveError(_("It is White's turn"));
5076 return ImpossibleMove;
5079 /* User is moving for White */
5080 if (!WhiteOnMove(currentMove)) {
5081 DisplayMoveError(_("It is Black's turn"));
5082 return ImpossibleMove;
5087 case IcsPlayingBlack:
5088 /* User is moving for Black */
5089 if (WhiteOnMove(currentMove)) {
5090 if (!appData.premove) {
5091 DisplayMoveError(_("It is White's turn"));
5092 } else if (toX >= 0 && toY >= 0) {
5095 premoveFromX = fromX;
5096 premoveFromY = fromY;
5097 premovePromoChar = promoChar;
5099 if (appData.debugMode)
5100 fprintf(debugFP, "Got premove: fromX %d,"
5101 "fromY %d, toX %d, toY %d\n",
5102 fromX, fromY, toX, toY);
5104 return ImpossibleMove;
5108 case IcsPlayingWhite:
5109 /* User is moving for White */
5110 if (!WhiteOnMove(currentMove)) {
5111 if (!appData.premove) {
5112 DisplayMoveError(_("It is Black's turn"));
5113 } else if (toX >= 0 && toY >= 0) {
5116 premoveFromX = fromX;
5117 premoveFromY = fromY;
5118 premovePromoChar = promoChar;
5120 if (appData.debugMode)
5121 fprintf(debugFP, "Got premove: fromX %d,"
5122 "fromY %d, toX %d, toY %d\n",
5123 fromX, fromY, toX, toY);
5125 return ImpossibleMove;
5133 /* EditPosition, empty square, or different color piece;
5134 click-click move is possible */
5135 if (toX == -2 || toY == -2) {
5136 boards[0][fromY][fromX] = EmptySquare;
5137 return AmbiguousMove;
5138 } else if (toX >= 0 && toY >= 0) {
5139 boards[0][toY][toX] = boards[0][fromY][fromX];
5140 boards[0][fromY][fromX] = EmptySquare;
5141 return AmbiguousMove;
5143 return ImpossibleMove;
5146 /* [HGM] If move started in holdings, it means a drop */
5147 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5148 if( pup != EmptySquare ) return ImpossibleMove;
5149 if(appData.testLegality) {
5150 /* it would be more logical if LegalityTest() also figured out
5151 * which drops are legal. For now we forbid pawns on back rank.
5152 * Shogi is on its own here...
5154 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5155 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5156 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5158 return WhiteDrop; /* Not needed to specify white or black yet */
5161 userOfferedDraw = FALSE;
5163 /* [HGM] always test for legality, to get promotion info */
5164 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5165 epStatus[currentMove], castlingRights[currentMove],
5166 fromY, fromX, toY, toX, promoChar);
5167 /* [HGM] but possibly ignore an IllegalMove result */
5168 if (appData.testLegality) {
5169 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5170 DisplayMoveError(_("Illegal move"));
5171 return ImpossibleMove;
5174 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5176 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5177 function is made into one that returns an OK move type if FinishMove
5178 should be called. This to give the calling driver routine the
5179 opportunity to finish the userMove input with a promotion popup,
5180 without bothering the user with this for invalid or illegal moves */
5182 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5185 /* Common tail of UserMoveEvent and DropMenuEvent */
5187 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5189 int fromX, fromY, toX, toY;
5190 /*char*/int promoChar;
5193 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5194 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5195 // [HGM] superchess: suppress promotions to non-available piece
5196 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5197 if(WhiteOnMove(currentMove)) {
5198 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5200 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5204 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5205 move type in caller when we know the move is a legal promotion */
5206 if(moveType == NormalMove && promoChar)
5207 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5208 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5209 /* [HGM] convert drag-and-drop piece drops to standard form */
5210 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5211 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5212 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5213 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5214 // fromX = boards[currentMove][fromY][fromX];
5215 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5216 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5217 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5218 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5222 /* [HGM] <popupFix> The following if has been moved here from
5223 UserMoveEvent(). Because it seemed to belon here (why not allow
5224 piece drops in training games?), and because it can only be
5225 performed after it is known to what we promote. */
5226 if (gameMode == Training) {
5227 /* compare the move played on the board to the next move in the
5228 * game. If they match, display the move and the opponent's response.
5229 * If they don't match, display an error message.
5232 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5233 CopyBoard(testBoard, boards[currentMove]);
5234 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5236 if (CompareBoards(testBoard, boards[currentMove+1])) {
5237 ForwardInner(currentMove+1);
5239 /* Autoplay the opponent's response.
5240 * if appData.animate was TRUE when Training mode was entered,
5241 * the response will be animated.
5243 saveAnimate = appData.animate;
5244 appData.animate = animateTraining;
5245 ForwardInner(currentMove+1);
5246 appData.animate = saveAnimate;
5248 /* check for the end of the game */
5249 if (currentMove >= forwardMostMove) {
5250 gameMode = PlayFromGameFile;
5252 SetTrainingModeOff();
5253 DisplayInformation(_("End of game"));
5256 DisplayError(_("Incorrect move"), 0);
5261 /* Ok, now we know that the move is good, so we can kill
5262 the previous line in Analysis Mode */
5263 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5264 forwardMostMove = currentMove;
5267 /* If we need the chess program but it's dead, restart it */
5268 ResurrectChessProgram();
5270 /* A user move restarts a paused game*/
5274 thinkOutput[0] = NULLCHAR;
5276 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5278 if (gameMode == BeginningOfGame) {
5279 if (appData.noChessProgram) {
5280 gameMode = EditGame;
5284 gameMode = MachinePlaysBlack;
5287 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5289 if (first.sendName) {
5290 sprintf(buf, "name %s\n", gameInfo.white);
5291 SendToProgram(buf, &first);
5297 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5298 /* Relay move to ICS or chess engine */
5299 if (appData.icsActive) {
5300 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5301 gameMode == IcsExamining) {
5302 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5306 if (first.sendTime && (gameMode == BeginningOfGame ||
5307 gameMode == MachinePlaysWhite ||
5308 gameMode == MachinePlaysBlack)) {
5309 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5311 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5312 // [HGM] book: if program might be playing, let it use book
5313 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5314 first.maybeThinking = TRUE;
5315 } else SendMoveToProgram(forwardMostMove-1, &first);
5316 if (currentMove == cmailOldMove + 1) {
5317 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5321 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5325 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5326 EP_UNKNOWN, castlingRights[currentMove]) ) {
5332 if (WhiteOnMove(currentMove)) {
5333 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5335 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5339 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5344 case MachinePlaysBlack:
5345 case MachinePlaysWhite:
5346 /* disable certain menu options while machine is thinking */
5347 SetMachineThinkingEnables();
5354 if(bookHit) { // [HGM] book: simulate book reply
5355 static char bookMove[MSG_SIZ]; // a bit generous?
5357 programStats.nodes = programStats.depth = programStats.time =
5358 programStats.score = programStats.got_only_move = 0;
5359 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5361 strcpy(bookMove, "move ");
5362 strcat(bookMove, bookHit);
5363 HandleMachineMove(bookMove, &first);
5369 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5370 int fromX, fromY, toX, toY;
5373 /* [HGM] This routine was added to allow calling of its two logical
5374 parts from other modules in the old way. Before, UserMoveEvent()
5375 automatically called FinishMove() if the move was OK, and returned
5376 otherwise. I separated the two, in order to make it possible to
5377 slip a promotion popup in between. But that it always needs two
5378 calls, to the first part, (now called UserMoveTest() ), and to
5379 FinishMove if the first part succeeded. Calls that do not need
5380 to do anything in between, can call this routine the old way.
5382 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5383 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5384 if(moveType == AmbiguousMove)
5385 DrawPosition(FALSE, boards[currentMove]);
5386 else if(moveType != ImpossibleMove && moveType != Comment)
5387 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5390 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5392 // char * hint = lastHint;
5393 FrontEndProgramStats stats;
5395 stats.which = cps == &first ? 0 : 1;
5396 stats.depth = cpstats->depth;
5397 stats.nodes = cpstats->nodes;
5398 stats.score = cpstats->score;
5399 stats.time = cpstats->time;
5400 stats.pv = cpstats->movelist;
5401 stats.hint = lastHint;
5402 stats.an_move_index = 0;
5403 stats.an_move_count = 0;
5405 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5406 stats.hint = cpstats->move_name;
5407 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5408 stats.an_move_count = cpstats->nr_moves;
5411 SetProgramStats( &stats );
5414 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5415 { // [HGM] book: this routine intercepts moves to simulate book replies
5416 char *bookHit = NULL;
5418 //first determine if the incoming move brings opponent into his book
5419 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5420 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5421 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5422 if(bookHit != NULL && !cps->bookSuspend) {
5423 // make sure opponent is not going to reply after receiving move to book position
5424 SendToProgram("force\n", cps);
5425 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5427 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5428 // now arrange restart after book miss
5430 // after a book hit we never send 'go', and the code after the call to this routine
5431 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5433 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5434 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5435 SendToProgram(buf, cps);
5436 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5437 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5438 SendToProgram("go\n", cps);
5439 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5440 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5441 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5442 SendToProgram("go\n", cps);
5443 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5445 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5449 ChessProgramState *savedState;
5450 void DeferredBookMove(void)
5452 if(savedState->lastPing != savedState->lastPong)
5453 ScheduleDelayedEvent(DeferredBookMove, 10);
5455 HandleMachineMove(savedMessage, savedState);
5459 HandleMachineMove(message, cps)
5461 ChessProgramState *cps;
5463 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5464 char realname[MSG_SIZ];
5465 int fromX, fromY, toX, toY;
5472 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5474 * Kludge to ignore BEL characters
5476 while (*message == '\007') message++;
5479 * [HGM] engine debug message: ignore lines starting with '#' character
5481 if(cps->debug && *message == '#') return;
5484 * Look for book output
5486 if (cps == &first && bookRequested) {
5487 if (message[0] == '\t' || message[0] == ' ') {
5488 /* Part of the book output is here; append it */
5489 strcat(bookOutput, message);
5490 strcat(bookOutput, " \n");
5492 } else if (bookOutput[0] != NULLCHAR) {
5493 /* All of book output has arrived; display it */
5494 char *p = bookOutput;
5495 while (*p != NULLCHAR) {
5496 if (*p == '\t') *p = ' ';
5499 DisplayInformation(bookOutput);
5500 bookRequested = FALSE;
5501 /* Fall through to parse the current output */
5506 * Look for machine move.
5508 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5509 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5511 /* This method is only useful on engines that support ping */
5512 if (cps->lastPing != cps->lastPong) {
5513 if (gameMode == BeginningOfGame) {
5514 /* Extra move from before last new; ignore */
5515 if (appData.debugMode) {
5516 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5519 if (appData.debugMode) {
5520 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5521 cps->which, gameMode);
5524 SendToProgram("undo\n", cps);
5530 case BeginningOfGame:
5531 /* Extra move from before last reset; ignore */
5532 if (appData.debugMode) {
5533 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5540 /* Extra move after we tried to stop. The mode test is
5541 not a reliable way of detecting this problem, but it's
5542 the best we can do on engines that don't support ping.
5544 if (appData.debugMode) {
5545 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5546 cps->which, gameMode);
5548 SendToProgram("undo\n", cps);
5551 case MachinePlaysWhite:
5552 case IcsPlayingWhite:
5553 machineWhite = TRUE;
5556 case MachinePlaysBlack:
5557 case IcsPlayingBlack:
5558 machineWhite = FALSE;
5561 case TwoMachinesPlay:
5562 machineWhite = (cps->twoMachinesColor[0] == 'w');
5565 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5566 if (appData.debugMode) {
5568 "Ignoring move out of turn by %s, gameMode %d"
5569 ", forwardMost %d\n",
5570 cps->which, gameMode, forwardMostMove);
5575 if (appData.debugMode) { int f = forwardMostMove;
5576 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5577 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5579 if(cps->alphaRank) AlphaRank(machineMove, 4);
5580 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5581 &fromX, &fromY, &toX, &toY, &promoChar)) {
5582 /* Machine move could not be parsed; ignore it. */
5583 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5584 machineMove, cps->which);
5585 DisplayError(buf1, 0);
5586 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5587 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5588 if (gameMode == TwoMachinesPlay) {
5589 GameEnds(machineWhite ? BlackWins : WhiteWins,
5595 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5596 /* So we have to redo legality test with true e.p. status here, */
5597 /* to make sure an illegal e.p. capture does not slip through, */
5598 /* to cause a forfeit on a justified illegal-move complaint */
5599 /* of the opponent. */
5600 if( gameMode==TwoMachinesPlay && appData.testLegality
5601 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5604 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5605 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5606 fromY, fromX, toY, toX, promoChar);
5607 if (appData.debugMode) {
5609 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5610 castlingRights[forwardMostMove][i], castlingRank[i]);
5611 fprintf(debugFP, "castling rights\n");
5613 if(moveType == IllegalMove) {
5614 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5615 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5616 GameEnds(machineWhite ? BlackWins : WhiteWins,
5619 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5620 /* [HGM] Kludge to handle engines that send FRC-style castling
5621 when they shouldn't (like TSCP-Gothic) */
5623 case WhiteASideCastleFR:
5624 case BlackASideCastleFR:
5626 currentMoveString[2]++;
5628 case WhiteHSideCastleFR:
5629 case BlackHSideCastleFR:
5631 currentMoveString[2]--;
5633 default: ; // nothing to do, but suppresses warning of pedantic compilers
5636 hintRequested = FALSE;
5637 lastHint[0] = NULLCHAR;
5638 bookRequested = FALSE;
5639 /* Program may be pondering now */
5640 cps->maybeThinking = TRUE;
5641 if (cps->sendTime == 2) cps->sendTime = 1;
5642 if (cps->offeredDraw) cps->offeredDraw--;
5645 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5647 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5649 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5650 char buf[3*MSG_SIZ];
5652 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5653 programStats.score / 100.,
5655 programStats.time / 100.,
5656 (unsigned int)programStats.nodes,
5657 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5658 programStats.movelist);
5660 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5664 /* currentMoveString is set as a side-effect of ParseOneMove */
5665 strcpy(machineMove, currentMoveString);
5666 strcat(machineMove, "\n");
5667 strcpy(moveList[forwardMostMove], machineMove);
5669 /* [AS] Save move info and clear stats for next move */
5670 pvInfoList[ forwardMostMove ].score = programStats.score;
5671 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5672 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5673 ClearProgramStats();
5674 thinkOutput[0] = NULLCHAR;
5675 hiddenThinkOutputState = 0;
5677 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5679 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5680 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5683 while( count < adjudicateLossPlies ) {
5684 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5687 score = -score; /* Flip score for winning side */
5690 if( score > adjudicateLossThreshold ) {
5697 if( count >= adjudicateLossPlies ) {
5698 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5700 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5701 "Xboard adjudication",
5708 if( gameMode == TwoMachinesPlay ) {
5709 // [HGM] some adjudications useful with buggy engines
5710 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5711 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5714 if( appData.testLegality )
5715 { /* [HGM] Some more adjudications for obstinate engines */
5716 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5717 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5718 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5719 static int moveCount = 6;
5721 char *reason = NULL;
5723 /* Count what is on board. */
5724 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5725 { ChessSquare p = boards[forwardMostMove][i][j];
5729 { /* count B,N,R and other of each side */
5732 NrK++; break; // [HGM] atomic: count Kings
5736 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5737 bishopsColor |= 1 << ((i^j)&1);
5742 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5743 bishopsColor |= 1 << ((i^j)&1);
5758 PawnAdvance += m; NrPawns++;
5760 NrPieces += (p != EmptySquare);
5761 NrW += ((int)p < (int)BlackPawn);
5762 if(gameInfo.variant == VariantXiangqi &&
5763 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5764 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5765 NrW -= ((int)p < (int)BlackPawn);
5769 /* Some material-based adjudications that have to be made before stalemate test */
5770 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5771 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5772 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5773 if(appData.checkMates) {
5774 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5775 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5776 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5777 "Xboard adjudication: King destroyed", GE_XBOARD );
5782 /* Bare King in Shatranj (loses) or Losers (wins) */
5783 if( NrW == 1 || NrPieces - NrW == 1) {
5784 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5785 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5786 if(appData.checkMates) {
5787 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5788 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5789 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5790 "Xboard adjudication: Bare king", GE_XBOARD );
5794 if( gameInfo.variant == VariantShatranj && --bare < 0)
5796 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5797 if(appData.checkMates) {
5798 /* but only adjudicate if adjudication enabled */
5799 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5800 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5801 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5802 "Xboard adjudication: Bare king", GE_XBOARD );
5809 // don't wait for engine to announce game end if we can judge ourselves
5810 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5811 castlingRights[forwardMostMove]) ) {
5813 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5814 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5815 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5816 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5819 reason = "Xboard adjudication: 3rd check";
5820 epStatus[forwardMostMove] = EP_CHECKMATE;
5830 reason = "Xboard adjudication: Stalemate";
5831 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5832 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5833 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5834 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5835 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5836 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5837 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5838 EP_CHECKMATE : EP_WINS);
5839 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5840 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5844 reason = "Xboard adjudication: Checkmate";
5845 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5849 switch(i = epStatus[forwardMostMove]) {
5851 result = GameIsDrawn; break;
5853 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5855 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5857 result = (ChessMove) 0;
5859 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5860 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5861 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5862 GameEnds( result, reason, GE_XBOARD );
5866 /* Next absolutely insufficient mating material. */
5867 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5868 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5869 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5870 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5871 { /* KBK, KNK, KK of KBKB with like Bishops */
5873 /* always flag draws, for judging claims */
5874 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5876 if(appData.materialDraws) {
5877 /* but only adjudicate them if adjudication enabled */
5878 SendToProgram("force\n", cps->other); // suppress reply
5879 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5880 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5881 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5886 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5888 ( NrWR == 1 && NrBR == 1 /* KRKR */
5889 || NrWQ==1 && NrBQ==1 /* KQKQ */
5890 || NrWN==2 || NrBN==2 /* KNNK */
5891 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5893 if(--moveCount < 0 && appData.trivialDraws)
5894 { /* if the first 3 moves do not show a tactical win, declare draw */
5895 SendToProgram("force\n", cps->other); // suppress reply
5896 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5897 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5898 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5901 } else moveCount = 6;
5905 if (appData.debugMode) { int i;
5906 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5907 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5908 appData.drawRepeats);
5909 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5910 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5914 /* Check for rep-draws */
5916 for(k = forwardMostMove-2;
5917 k>=backwardMostMove && k>=forwardMostMove-100 &&
5918 epStatus[k] < EP_UNKNOWN &&
5919 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5922 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5923 /* compare castling rights */
5924 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5925 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5926 rights++; /* King lost rights, while rook still had them */
5927 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5928 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5929 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5930 rights++; /* but at least one rook lost them */
5932 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5933 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5935 if( castlingRights[forwardMostMove][5] >= 0 ) {
5936 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5937 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5940 if( rights == 0 && ++count > appData.drawRepeats-2
5941 && appData.drawRepeats > 1) {
5942 /* adjudicate after user-specified nr of repeats */
5943 SendToProgram("force\n", cps->other); // suppress reply
5944 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5945 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5946 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5947 // [HGM] xiangqi: check for forbidden perpetuals
5948 int m, ourPerpetual = 1, hisPerpetual = 1;
5949 for(m=forwardMostMove; m>k; m-=2) {
5950 if(MateTest(boards[m], PosFlags(m),
5951 EP_NONE, castlingRights[m]) != MT_CHECK)
5952 ourPerpetual = 0; // the current mover did not always check
5953 if(MateTest(boards[m-1], PosFlags(m-1),
5954 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5955 hisPerpetual = 0; // the opponent did not always check
5957 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5958 ourPerpetual, hisPerpetual);
5959 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5960 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5961 "Xboard adjudication: perpetual checking", GE_XBOARD );
5964 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5965 break; // (or we would have caught him before). Abort repetition-checking loop.
5966 // Now check for perpetual chases
5967 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5968 hisPerpetual = PerpetualChase(k, forwardMostMove);
5969 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5970 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5971 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5972 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5975 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5976 break; // Abort repetition-checking loop.
5978 // if neither of us is checking or chasing all the time, or both are, it is draw
5980 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5983 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5984 epStatus[forwardMostMove] = EP_REP_DRAW;
5988 /* Now we test for 50-move draws. Determine ply count */
5989 count = forwardMostMove;
5990 /* look for last irreversble move */
5991 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5993 /* if we hit starting position, add initial plies */
5994 if( count == backwardMostMove )
5995 count -= initialRulePlies;
5996 count = forwardMostMove - count;
5998 epStatus[forwardMostMove] = EP_RULE_DRAW;
5999 /* this is used to judge if draw claims are legal */
6000 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6001 SendToProgram("force\n", cps->other); // suppress reply
6002 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6003 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6004 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6008 /* if draw offer is pending, treat it as a draw claim
6009 * when draw condition present, to allow engines a way to
6010 * claim draws before making their move to avoid a race
6011 * condition occurring after their move
6013 if( cps->other->offeredDraw || cps->offeredDraw ) {
6015 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6016 p = "Draw claim: 50-move rule";
6017 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6018 p = "Draw claim: 3-fold repetition";
6019 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6020 p = "Draw claim: insufficient mating material";
6022 SendToProgram("force\n", cps->other); // suppress reply
6023 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6024 GameEnds( GameIsDrawn, p, GE_XBOARD );
6025 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6031 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6032 SendToProgram("force\n", cps->other); // suppress reply
6033 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6034 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6036 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6043 if (gameMode == TwoMachinesPlay) {
6044 /* [HGM] relaying draw offers moved to after reception of move */
6045 /* and interpreting offer as claim if it brings draw condition */
6046 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6047 SendToProgram("draw\n", cps->other);
6049 if (cps->other->sendTime) {
6050 SendTimeRemaining(cps->other,
6051 cps->other->twoMachinesColor[0] == 'w');
6053 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6054 if (firstMove && !bookHit) {
6056 if (cps->other->useColors) {
6057 SendToProgram(cps->other->twoMachinesColor, cps->other);
6059 SendToProgram("go\n", cps->other);
6061 cps->other->maybeThinking = TRUE;
6064 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6066 if (!pausing && appData.ringBellAfterMoves) {
6071 * Reenable menu items that were disabled while
6072 * machine was thinking
6074 if (gameMode != TwoMachinesPlay)
6075 SetUserThinkingEnables();
6077 // [HGM] book: after book hit opponent has received move and is now in force mode
6078 // force the book reply into it, and then fake that it outputted this move by jumping
6079 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6081 static char bookMove[MSG_SIZ]; // a bit generous?
6083 strcpy(bookMove, "move ");
6084 strcat(bookMove, bookHit);
6087 programStats.nodes = programStats.depth = programStats.time =
6088 programStats.score = programStats.got_only_move = 0;
6089 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6091 if(cps->lastPing != cps->lastPong) {
6092 savedMessage = message; // args for deferred call
6094 ScheduleDelayedEvent(DeferredBookMove, 10);
6103 /* Set special modes for chess engines. Later something general
6104 * could be added here; for now there is just one kludge feature,
6105 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6106 * when "xboard" is given as an interactive command.
6108 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6109 cps->useSigint = FALSE;
6110 cps->useSigterm = FALSE;
6112 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6113 ParseFeatures(message+8, cps);
6114 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6117 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6118 * want this, I was asked to put it in, and obliged.
6120 if (!strncmp(message, "setboard ", 9)) {
6121 Board initial_position; int i;
6123 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6125 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6126 DisplayError(_("Bad FEN received from engine"), 0);
6129 Reset(FALSE, FALSE);
6130 CopyBoard(boards[0], initial_position);
6131 initialRulePlies = FENrulePlies;
6132 epStatus[0] = FENepStatus;
6133 for( i=0; i<nrCastlingRights; i++ )
6134 castlingRights[0][i] = FENcastlingRights[i];
6135 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6136 else gameMode = MachinePlaysBlack;
6137 DrawPosition(FALSE, boards[currentMove]);
6143 * Look for communication commands
6145 if (!strncmp(message, "telluser ", 9)) {
6146 DisplayNote(message + 9);
6149 if (!strncmp(message, "tellusererror ", 14)) {
6150 DisplayError(message + 14, 0);
6153 if (!strncmp(message, "tellopponent ", 13)) {
6154 if (appData.icsActive) {
6156 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6160 DisplayNote(message + 13);
6164 if (!strncmp(message, "tellothers ", 11)) {
6165 if (appData.icsActive) {
6167 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6173 if (!strncmp(message, "tellall ", 8)) {
6174 if (appData.icsActive) {
6176 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6180 DisplayNote(message + 8);
6184 if (strncmp(message, "warning", 7) == 0) {
6185 /* Undocumented feature, use tellusererror in new code */
6186 DisplayError(message, 0);
6189 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6190 strcpy(realname, cps->tidy);
6191 strcat(realname, " query");
6192 AskQuestion(realname, buf2, buf1, cps->pr);
6195 /* Commands from the engine directly to ICS. We don't allow these to be
6196 * sent until we are logged on. Crafty kibitzes have been known to
6197 * interfere with the login process.
6200 if (!strncmp(message, "tellics ", 8)) {
6201 SendToICS(message + 8);
6205 if (!strncmp(message, "tellicsnoalias ", 15)) {
6206 SendToICS(ics_prefix);
6207 SendToICS(message + 15);
6211 /* The following are for backward compatibility only */
6212 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6213 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6214 SendToICS(ics_prefix);
6220 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6224 * If the move is illegal, cancel it and redraw the board.
6225 * Also deal with other error cases. Matching is rather loose
6226 * here to accommodate engines written before the spec.
6228 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6229 strncmp(message, "Error", 5) == 0) {
6230 if (StrStr(message, "name") ||
6231 StrStr(message, "rating") || StrStr(message, "?") ||
6232 StrStr(message, "result") || StrStr(message, "board") ||
6233 StrStr(message, "bk") || StrStr(message, "computer") ||
6234 StrStr(message, "variant") || StrStr(message, "hint") ||
6235 StrStr(message, "random") || StrStr(message, "depth") ||
6236 StrStr(message, "accepted")) {
6239 if (StrStr(message, "protover")) {
6240 /* Program is responding to input, so it's apparently done
6241 initializing, and this error message indicates it is
6242 protocol version 1. So we don't need to wait any longer
6243 for it to initialize and send feature commands. */
6244 FeatureDone(cps, 1);
6245 cps->protocolVersion = 1;
6248 cps->maybeThinking = FALSE;
6250 if (StrStr(message, "draw")) {
6251 /* Program doesn't have "draw" command */
6252 cps->sendDrawOffers = 0;
6255 if (cps->sendTime != 1 &&
6256 (StrStr(message, "time") || StrStr(message, "otim"))) {
6257 /* Program apparently doesn't have "time" or "otim" command */
6261 if (StrStr(message, "analyze")) {
6262 cps->analysisSupport = FALSE;
6263 cps->analyzing = FALSE;
6265 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6266 DisplayError(buf2, 0);
6269 if (StrStr(message, "(no matching move)st")) {
6270 /* Special kludge for GNU Chess 4 only */
6271 cps->stKludge = TRUE;
6272 SendTimeControl(cps, movesPerSession, timeControl,
6273 timeIncrement, appData.searchDepth,
6277 if (StrStr(message, "(no matching move)sd")) {
6278 /* Special kludge for GNU Chess 4 only */
6279 cps->sdKludge = TRUE;
6280 SendTimeControl(cps, movesPerSession, timeControl,
6281 timeIncrement, appData.searchDepth,
6285 if (!StrStr(message, "llegal")) {
6288 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6289 gameMode == IcsIdle) return;
6290 if (forwardMostMove <= backwardMostMove) return;
6291 if (pausing) PauseEvent();
6292 if(appData.forceIllegal) {
6293 // [HGM] illegal: machine refused move; force position after move into it
6294 SendToProgram("force\n", cps);
6295 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6296 // we have a real problem now, as SendBoard will use the a2a3 kludge
6297 // when black is to move, while there might be nothing on a2 or black
6298 // might already have the move. So send the board as if white has the move.
6299 // But first we must change the stm of the engine, as it refused the last move
6300 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6301 if(WhiteOnMove(forwardMostMove)) {
6302 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6303 SendBoard(cps, forwardMostMove); // kludgeless board
6305 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6306 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6307 SendBoard(cps, forwardMostMove+1); // kludgeless board
6309 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6310 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6311 gameMode == TwoMachinesPlay)
6312 SendToProgram("go\n", cps);
6315 if (gameMode == PlayFromGameFile) {
6316 /* Stop reading this game file */
6317 gameMode = EditGame;
6320 currentMove = --forwardMostMove;
6321 DisplayMove(currentMove-1); /* before DisplayMoveError */
6323 DisplayBothClocks();
6324 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6325 parseList[currentMove], cps->which);
6326 DisplayMoveError(buf1);
6327 DrawPosition(FALSE, boards[currentMove]);
6329 /* [HGM] illegal-move claim should forfeit game when Xboard */
6330 /* only passes fully legal moves */
6331 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6332 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6333 "False illegal-move claim", GE_XBOARD );
6337 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6338 /* Program has a broken "time" command that
6339 outputs a string not ending in newline.
6345 * If chess program startup fails, exit with an error message.
6346 * Attempts to recover here are futile.
6348 if ((StrStr(message, "unknown host") != NULL)
6349 || (StrStr(message, "No remote directory") != NULL)
6350 || (StrStr(message, "not found") != NULL)
6351 || (StrStr(message, "No such file") != NULL)
6352 || (StrStr(message, "can't alloc") != NULL)
6353 || (StrStr(message, "Permission denied") != NULL)) {
6355 cps->maybeThinking = FALSE;
6356 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6357 cps->which, cps->program, cps->host, message);
6358 RemoveInputSource(cps->isr);
6359 DisplayFatalError(buf1, 0, 1);
6364 * Look for hint output
6366 if (sscanf(message, "Hint: %s", buf1) == 1) {
6367 if (cps == &first && hintRequested) {
6368 hintRequested = FALSE;
6369 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6370 &fromX, &fromY, &toX, &toY, &promoChar)) {
6371 (void) CoordsToAlgebraic(boards[forwardMostMove],
6372 PosFlags(forwardMostMove), EP_UNKNOWN,
6373 fromY, fromX, toY, toX, promoChar, buf1);
6374 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6375 DisplayInformation(buf2);
6377 /* Hint move could not be parsed!? */
6378 snprintf(buf2, sizeof(buf2),
6379 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6381 DisplayError(buf2, 0);
6384 strcpy(lastHint, buf1);
6390 * Ignore other messages if game is not in progress
6392 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6393 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6396 * look for win, lose, draw, or draw offer
6398 if (strncmp(message, "1-0", 3) == 0) {
6399 char *p, *q, *r = "";
6400 p = strchr(message, '{');
6408 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6410 } else if (strncmp(message, "0-1", 3) == 0) {
6411 char *p, *q, *r = "";
6412 p = strchr(message, '{');
6420 /* Kludge for Arasan 4.1 bug */
6421 if (strcmp(r, "Black resigns") == 0) {
6422 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6425 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6427 } else if (strncmp(message, "1/2", 3) == 0) {
6428 char *p, *q, *r = "";
6429 p = strchr(message, '{');
6438 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6441 } else if (strncmp(message, "White resign", 12) == 0) {
6442 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6444 } else if (strncmp(message, "Black resign", 12) == 0) {
6445 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6447 } else if (strncmp(message, "White matches", 13) == 0 ||
6448 strncmp(message, "Black matches", 13) == 0 ) {
6449 /* [HGM] ignore GNUShogi noises */
6451 } else if (strncmp(message, "White", 5) == 0 &&
6452 message[5] != '(' &&
6453 StrStr(message, "Black") == NULL) {
6454 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6456 } else if (strncmp(message, "Black", 5) == 0 &&
6457 message[5] != '(') {
6458 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6460 } else if (strcmp(message, "resign") == 0 ||
6461 strcmp(message, "computer resigns") == 0) {
6463 case MachinePlaysBlack:
6464 case IcsPlayingBlack:
6465 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6467 case MachinePlaysWhite:
6468 case IcsPlayingWhite:
6469 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6471 case TwoMachinesPlay:
6472 if (cps->twoMachinesColor[0] == 'w')
6473 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6475 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6482 } else if (strncmp(message, "opponent mates", 14) == 0) {
6484 case MachinePlaysBlack:
6485 case IcsPlayingBlack:
6486 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6488 case MachinePlaysWhite:
6489 case IcsPlayingWhite:
6490 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6492 case TwoMachinesPlay:
6493 if (cps->twoMachinesColor[0] == 'w')
6494 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6496 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6503 } else if (strncmp(message, "computer mates", 14) == 0) {
6505 case MachinePlaysBlack:
6506 case IcsPlayingBlack:
6507 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6509 case MachinePlaysWhite:
6510 case IcsPlayingWhite:
6511 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6513 case TwoMachinesPlay:
6514 if (cps->twoMachinesColor[0] == 'w')
6515 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6517 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6524 } else if (strncmp(message, "checkmate", 9) == 0) {
6525 if (WhiteOnMove(forwardMostMove)) {
6526 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6528 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6531 } else if (strstr(message, "Draw") != NULL ||
6532 strstr(message, "game is a draw") != NULL) {
6533 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6535 } else if (strstr(message, "offer") != NULL &&
6536 strstr(message, "draw") != NULL) {
6538 if (appData.zippyPlay && first.initDone) {
6539 /* Relay offer to ICS */
6540 SendToICS(ics_prefix);
6541 SendToICS("draw\n");
6544 cps->offeredDraw = 2; /* valid until this engine moves twice */
6545 if (gameMode == TwoMachinesPlay) {
6546 if (cps->other->offeredDraw) {
6547 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6548 /* [HGM] in two-machine mode we delay relaying draw offer */
6549 /* until after we also have move, to see if it is really claim */
6551 } else if (gameMode == MachinePlaysWhite ||
6552 gameMode == MachinePlaysBlack) {
6553 if (userOfferedDraw) {
6554 DisplayInformation(_("Machine accepts your draw offer"));
6555 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6557 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6564 * Look for thinking output
6566 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6567 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6569 int plylev, mvleft, mvtot, curscore, time;
6570 char mvname[MOVE_LEN];
6574 int prefixHint = FALSE;
6575 mvname[0] = NULLCHAR;
6578 case MachinePlaysBlack:
6579 case IcsPlayingBlack:
6580 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6582 case MachinePlaysWhite:
6583 case IcsPlayingWhite:
6584 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6589 case IcsObserving: /* [DM] icsEngineAnalyze */
6590 if (!appData.icsEngineAnalyze) ignore = TRUE;
6592 case TwoMachinesPlay:
6593 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6604 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6605 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6607 if (plyext != ' ' && plyext != '\t') {
6611 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6612 if( cps->scoreIsAbsolute &&
6613 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6615 curscore = -curscore;
6619 programStats.depth = plylev;
6620 programStats.nodes = nodes;
6621 programStats.time = time;
6622 programStats.score = curscore;
6623 programStats.got_only_move = 0;
6625 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6628 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6629 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6630 if(WhiteOnMove(forwardMostMove))
6631 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6632 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6635 /* Buffer overflow protection */
6636 if (buf1[0] != NULLCHAR) {
6637 if (strlen(buf1) >= sizeof(programStats.movelist)
6638 && appData.debugMode) {
6640 "PV is too long; using the first %d bytes.\n",
6641 sizeof(programStats.movelist) - 1);
6644 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6646 sprintf(programStats.movelist, " no PV\n");
6649 if (programStats.seen_stat) {
6650 programStats.ok_to_send = 1;
6653 if (strchr(programStats.movelist, '(') != NULL) {
6654 programStats.line_is_book = 1;
6655 programStats.nr_moves = 0;
6656 programStats.moves_left = 0;
6658 programStats.line_is_book = 0;
6661 SendProgramStatsToFrontend( cps, &programStats );
6664 [AS] Protect the thinkOutput buffer from overflow... this
6665 is only useful if buf1 hasn't overflowed first!
6667 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6669 (gameMode == TwoMachinesPlay ?
6670 ToUpper(cps->twoMachinesColor[0]) : ' '),
6671 ((double) curscore) / 100.0,
6672 prefixHint ? lastHint : "",
6673 prefixHint ? " " : "" );
6675 if( buf1[0] != NULLCHAR ) {
6676 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6678 if( strlen(buf1) > max_len ) {
6679 if( appData.debugMode) {
6680 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6682 buf1[max_len+1] = '\0';
6685 strcat( thinkOutput, buf1 );
6688 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6689 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6690 DisplayMove(currentMove - 1);
6695 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6696 /* crafty (9.25+) says "(only move) <move>"
6697 * if there is only 1 legal move
6699 sscanf(p, "(only move) %s", buf1);
6700 sprintf(thinkOutput, "%s (only move)", buf1);
6701 sprintf(programStats.movelist, "%s (only move)", buf1);
6702 programStats.depth = 1;
6703 programStats.nr_moves = 1;
6704 programStats.moves_left = 1;
6705 programStats.nodes = 1;
6706 programStats.time = 1;
6707 programStats.got_only_move = 1;
6709 /* Not really, but we also use this member to
6710 mean "line isn't going to change" (Crafty
6711 isn't searching, so stats won't change) */
6712 programStats.line_is_book = 1;
6714 SendProgramStatsToFrontend( cps, &programStats );
6716 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6717 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6718 DisplayMove(currentMove - 1);
6722 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6723 &time, &nodes, &plylev, &mvleft,
6724 &mvtot, mvname) >= 5) {
6725 /* The stat01: line is from Crafty (9.29+) in response
6726 to the "." command */
6727 programStats.seen_stat = 1;
6728 cps->maybeThinking = TRUE;
6730 if (programStats.got_only_move || !appData.periodicUpdates)
6733 programStats.depth = plylev;
6734 programStats.time = time;
6735 programStats.nodes = nodes;
6736 programStats.moves_left = mvleft;
6737 programStats.nr_moves = mvtot;
6738 strcpy(programStats.move_name, mvname);
6739 programStats.ok_to_send = 1;
6740 programStats.movelist[0] = '\0';
6742 SendProgramStatsToFrontend( cps, &programStats );
6747 } else if (strncmp(message,"++",2) == 0) {
6748 /* Crafty 9.29+ outputs this */
6749 programStats.got_fail = 2;
6752 } else if (strncmp(message,"--",2) == 0) {
6753 /* Crafty 9.29+ outputs this */
6754 programStats.got_fail = 1;
6757 } else if (thinkOutput[0] != NULLCHAR &&
6758 strncmp(message, " ", 4) == 0) {
6759 unsigned message_len;
6762 while (*p && *p == ' ') p++;
6764 message_len = strlen( p );
6766 /* [AS] Avoid buffer overflow */
6767 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6768 strcat(thinkOutput, " ");
6769 strcat(thinkOutput, p);
6772 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6773 strcat(programStats.movelist, " ");
6774 strcat(programStats.movelist, p);
6777 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6778 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6779 DisplayMove(currentMove - 1);
6788 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6789 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6791 ChessProgramStats cpstats;
6793 if (plyext != ' ' && plyext != '\t') {
6797 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6798 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6799 curscore = -curscore;
6802 cpstats.depth = plylev;
6803 cpstats.nodes = nodes;
6804 cpstats.time = time;
6805 cpstats.score = curscore;
6806 cpstats.got_only_move = 0;
6807 cpstats.movelist[0] = '\0';
6809 if (buf1[0] != NULLCHAR) {
6810 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6813 cpstats.ok_to_send = 0;
6814 cpstats.line_is_book = 0;
6815 cpstats.nr_moves = 0;
6816 cpstats.moves_left = 0;
6818 SendProgramStatsToFrontend( cps, &cpstats );
6825 /* Parse a game score from the character string "game", and
6826 record it as the history of the current game. The game
6827 score is NOT assumed to start from the standard position.
6828 The display is not updated in any way.
6831 ParseGameHistory(game)
6835 int fromX, fromY, toX, toY, boardIndex;
6840 if (appData.debugMode)
6841 fprintf(debugFP, "Parsing game history: %s\n", game);
6843 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6844 gameInfo.site = StrSave(appData.icsHost);
6845 gameInfo.date = PGNDate();
6846 gameInfo.round = StrSave("-");
6848 /* Parse out names of players */
6849 while (*game == ' ') game++;
6851 while (*game != ' ') *p++ = *game++;
6853 gameInfo.white = StrSave(buf);
6854 while (*game == ' ') game++;
6856 while (*game != ' ' && *game != '\n') *p++ = *game++;
6858 gameInfo.black = StrSave(buf);
6861 boardIndex = blackPlaysFirst ? 1 : 0;
6864 yyboardindex = boardIndex;
6865 moveType = (ChessMove) yylex();
6867 case IllegalMove: /* maybe suicide chess, etc. */
6868 if (appData.debugMode) {
6869 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6870 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6871 setbuf(debugFP, NULL);
6873 case WhitePromotionChancellor:
6874 case BlackPromotionChancellor:
6875 case WhitePromotionArchbishop:
6876 case BlackPromotionArchbishop:
6877 case WhitePromotionQueen:
6878 case BlackPromotionQueen:
6879 case WhitePromotionRook:
6880 case BlackPromotionRook:
6881 case WhitePromotionBishop:
6882 case BlackPromotionBishop:
6883 case WhitePromotionKnight:
6884 case BlackPromotionKnight:
6885 case WhitePromotionKing:
6886 case BlackPromotionKing:
6888 case WhiteCapturesEnPassant:
6889 case BlackCapturesEnPassant:
6890 case WhiteKingSideCastle:
6891 case WhiteQueenSideCastle:
6892 case BlackKingSideCastle:
6893 case BlackQueenSideCastle:
6894 case WhiteKingSideCastleWild:
6895 case WhiteQueenSideCastleWild:
6896 case BlackKingSideCastleWild:
6897 case BlackQueenSideCastleWild:
6899 case WhiteHSideCastleFR:
6900 case WhiteASideCastleFR:
6901 case BlackHSideCastleFR:
6902 case BlackASideCastleFR:
6904 fromX = currentMoveString[0] - AAA;
6905 fromY = currentMoveString[1] - ONE;
6906 toX = currentMoveString[2] - AAA;
6907 toY = currentMoveString[3] - ONE;
6908 promoChar = currentMoveString[4];
6912 fromX = moveType == WhiteDrop ?
6913 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6914 (int) CharToPiece(ToLower(currentMoveString[0]));
6916 toX = currentMoveString[2] - AAA;
6917 toY = currentMoveString[3] - ONE;
6918 promoChar = NULLCHAR;
6922 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6923 if (appData.debugMode) {
6924 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6925 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6926 setbuf(debugFP, NULL);
6928 DisplayError(buf, 0);
6930 case ImpossibleMove:
6932 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6933 if (appData.debugMode) {
6934 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6935 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6936 setbuf(debugFP, NULL);
6938 DisplayError(buf, 0);
6940 case (ChessMove) 0: /* end of file */
6941 if (boardIndex < backwardMostMove) {
6942 /* Oops, gap. How did that happen? */
6943 DisplayError(_("Gap in move list"), 0);
6946 backwardMostMove = blackPlaysFirst ? 1 : 0;
6947 if (boardIndex > forwardMostMove) {
6948 forwardMostMove = boardIndex;
6952 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6953 strcat(parseList[boardIndex-1], " ");
6954 strcat(parseList[boardIndex-1], yy_text);
6966 case GameUnfinished:
6967 if (gameMode == IcsExamining) {
6968 if (boardIndex < backwardMostMove) {
6969 /* Oops, gap. How did that happen? */
6972 backwardMostMove = blackPlaysFirst ? 1 : 0;
6975 gameInfo.result = moveType;
6976 p = strchr(yy_text, '{');
6977 if (p == NULL) p = strchr(yy_text, '(');
6980 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6982 q = strchr(p, *p == '{' ? '}' : ')');
6983 if (q != NULL) *q = NULLCHAR;
6986 gameInfo.resultDetails = StrSave(p);
6989 if (boardIndex >= forwardMostMove &&
6990 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6991 backwardMostMove = blackPlaysFirst ? 1 : 0;
6994 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6995 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6996 parseList[boardIndex]);
6997 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6998 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6999 /* currentMoveString is set as a side-effect of yylex */
7000 strcpy(moveList[boardIndex], currentMoveString);
7001 strcat(moveList[boardIndex], "\n");
7003 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7004 castlingRights[boardIndex], &epStatus[boardIndex]);
7005 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7006 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7012 if(gameInfo.variant != VariantShogi)
7013 strcat(parseList[boardIndex - 1], "+");
7017 strcat(parseList[boardIndex - 1], "#");
7024 /* Apply a move to the given board */
7026 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7027 int fromX, fromY, toX, toY;
7033 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7035 /* [HGM] compute & store e.p. status and castling rights for new position */
7036 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7039 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7043 if( board[toY][toX] != EmptySquare )
7046 if( board[fromY][fromX] == WhitePawn ) {
7047 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7050 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7051 gameInfo.variant != VariantBerolina || toX < fromX)
7052 *ep = toX | berolina;
7053 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7054 gameInfo.variant != VariantBerolina || toX > fromX)
7058 if( board[fromY][fromX] == BlackPawn ) {
7059 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7061 if( toY-fromY== -2) {
7062 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7063 gameInfo.variant != VariantBerolina || toX < fromX)
7064 *ep = toX | berolina;
7065 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7066 gameInfo.variant != VariantBerolina || toX > fromX)
7071 for(i=0; i<nrCastlingRights; i++) {
7072 if(castling[i] == fromX && castlingRank[i] == fromY ||
7073 castling[i] == toX && castlingRank[i] == toY
7074 ) castling[i] = -1; // revoke for moved or captured piece
7079 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7080 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7081 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7083 if (fromX == toX && fromY == toY) return;
7085 if (fromY == DROP_RANK) {
7087 piece = board[toY][toX] = (ChessSquare) fromX;
7089 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7090 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7091 if(gameInfo.variant == VariantKnightmate)
7092 king += (int) WhiteUnicorn - (int) WhiteKing;
7094 /* Code added by Tord: */
7095 /* FRC castling assumed when king captures friendly rook. */
7096 if (board[fromY][fromX] == WhiteKing &&
7097 board[toY][toX] == WhiteRook) {
7098 board[fromY][fromX] = EmptySquare;
7099 board[toY][toX] = EmptySquare;
7101 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7103 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7105 } else if (board[fromY][fromX] == BlackKing &&
7106 board[toY][toX] == BlackRook) {
7107 board[fromY][fromX] = EmptySquare;
7108 board[toY][toX] = EmptySquare;
7110 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7112 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7114 /* End of code added by Tord */
7116 } else if (board[fromY][fromX] == king
7117 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7118 && toY == fromY && toX > fromX+1) {
7119 board[fromY][fromX] = EmptySquare;
7120 board[toY][toX] = king;
7121 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7122 board[fromY][BOARD_RGHT-1] = EmptySquare;
7123 } else if (board[fromY][fromX] == king
7124 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7125 && toY == fromY && toX < fromX-1) {
7126 board[fromY][fromX] = EmptySquare;
7127 board[toY][toX] = king;
7128 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7129 board[fromY][BOARD_LEFT] = EmptySquare;
7130 } else if (board[fromY][fromX] == WhitePawn
7131 && toY == BOARD_HEIGHT-1
7132 && gameInfo.variant != VariantXiangqi
7134 /* white pawn promotion */
7135 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7136 if (board[toY][toX] == EmptySquare) {
7137 board[toY][toX] = WhiteQueen;
7139 if(gameInfo.variant==VariantBughouse ||
7140 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7141 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7142 board[fromY][fromX] = EmptySquare;
7143 } else if ((fromY == BOARD_HEIGHT-4)
7145 && gameInfo.variant != VariantXiangqi
7146 && gameInfo.variant != VariantBerolina
7147 && (board[fromY][fromX] == WhitePawn)
7148 && (board[toY][toX] == EmptySquare)) {
7149 board[fromY][fromX] = EmptySquare;
7150 board[toY][toX] = WhitePawn;
7151 captured = board[toY - 1][toX];
7152 board[toY - 1][toX] = EmptySquare;
7153 } else if ((fromY == BOARD_HEIGHT-4)
7155 && gameInfo.variant == VariantBerolina
7156 && (board[fromY][fromX] == WhitePawn)
7157 && (board[toY][toX] == EmptySquare)) {
7158 board[fromY][fromX] = EmptySquare;
7159 board[toY][toX] = WhitePawn;
7160 if(oldEP & EP_BEROLIN_A) {
7161 captured = board[fromY][fromX-1];
7162 board[fromY][fromX-1] = EmptySquare;
7163 }else{ captured = board[fromY][fromX+1];
7164 board[fromY][fromX+1] = EmptySquare;
7166 } else if (board[fromY][fromX] == king
7167 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7168 && toY == fromY && toX > fromX+1) {
7169 board[fromY][fromX] = EmptySquare;
7170 board[toY][toX] = king;
7171 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7172 board[fromY][BOARD_RGHT-1] = EmptySquare;
7173 } else if (board[fromY][fromX] == king
7174 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7175 && toY == fromY && toX < fromX-1) {
7176 board[fromY][fromX] = EmptySquare;
7177 board[toY][toX] = king;
7178 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7179 board[fromY][BOARD_LEFT] = EmptySquare;
7180 } else if (fromY == 7 && fromX == 3
7181 && board[fromY][fromX] == BlackKing
7182 && toY == 7 && toX == 5) {
7183 board[fromY][fromX] = EmptySquare;
7184 board[toY][toX] = BlackKing;
7185 board[fromY][7] = EmptySquare;
7186 board[toY][4] = BlackRook;
7187 } else if (fromY == 7 && fromX == 3
7188 && board[fromY][fromX] == BlackKing
7189 && toY == 7 && toX == 1) {
7190 board[fromY][fromX] = EmptySquare;
7191 board[toY][toX] = BlackKing;
7192 board[fromY][0] = EmptySquare;
7193 board[toY][2] = BlackRook;
7194 } else if (board[fromY][fromX] == BlackPawn
7196 && gameInfo.variant != VariantXiangqi
7198 /* black pawn promotion */
7199 board[0][toX] = CharToPiece(ToLower(promoChar));
7200 if (board[0][toX] == EmptySquare) {
7201 board[0][toX] = BlackQueen;
7203 if(gameInfo.variant==VariantBughouse ||
7204 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7205 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7206 board[fromY][fromX] = EmptySquare;
7207 } else if ((fromY == 3)
7209 && gameInfo.variant != VariantXiangqi
7210 && gameInfo.variant != VariantBerolina
7211 && (board[fromY][fromX] == BlackPawn)
7212 && (board[toY][toX] == EmptySquare)) {
7213 board[fromY][fromX] = EmptySquare;
7214 board[toY][toX] = BlackPawn;
7215 captured = board[toY + 1][toX];
7216 board[toY + 1][toX] = EmptySquare;
7217 } else if ((fromY == 3)
7219 && gameInfo.variant == VariantBerolina
7220 && (board[fromY][fromX] == BlackPawn)
7221 && (board[toY][toX] == EmptySquare)) {
7222 board[fromY][fromX] = EmptySquare;
7223 board[toY][toX] = BlackPawn;
7224 if(oldEP & EP_BEROLIN_A) {
7225 captured = board[fromY][fromX-1];
7226 board[fromY][fromX-1] = EmptySquare;
7227 }else{ captured = board[fromY][fromX+1];
7228 board[fromY][fromX+1] = EmptySquare;
7231 board[toY][toX] = board[fromY][fromX];
7232 board[fromY][fromX] = EmptySquare;
7235 /* [HGM] now we promote for Shogi, if needed */
7236 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7237 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7240 if (gameInfo.holdingsWidth != 0) {
7242 /* !!A lot more code needs to be written to support holdings */
7243 /* [HGM] OK, so I have written it. Holdings are stored in the */
7244 /* penultimate board files, so they are automaticlly stored */
7245 /* in the game history. */
7246 if (fromY == DROP_RANK) {
7247 /* Delete from holdings, by decreasing count */
7248 /* and erasing image if necessary */
7250 if(p < (int) BlackPawn) { /* white drop */
7251 p -= (int)WhitePawn;
7252 if(p >= gameInfo.holdingsSize) p = 0;
7253 if(--board[p][BOARD_WIDTH-2] == 0)
7254 board[p][BOARD_WIDTH-1] = EmptySquare;
7255 } else { /* black drop */
7256 p -= (int)BlackPawn;
7257 if(p >= gameInfo.holdingsSize) p = 0;
7258 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7259 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7262 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7263 && gameInfo.variant != VariantBughouse ) {
7264 /* [HGM] holdings: Add to holdings, if holdings exist */
7265 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7266 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7267 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7270 if (p >= (int) BlackPawn) {
7271 p -= (int)BlackPawn;
7272 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7273 /* in Shogi restore piece to its original first */
7274 captured = (ChessSquare) (DEMOTED captured);
7277 p = PieceToNumber((ChessSquare)p);
7278 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7279 board[p][BOARD_WIDTH-2]++;
7280 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7282 p -= (int)WhitePawn;
7283 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7284 captured = (ChessSquare) (DEMOTED captured);
7287 p = PieceToNumber((ChessSquare)p);
7288 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7289 board[BOARD_HEIGHT-1-p][1]++;
7290 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7294 } else if (gameInfo.variant == VariantAtomic) {
7295 if (captured != EmptySquare) {
7297 for (y = toY-1; y <= toY+1; y++) {
7298 for (x = toX-1; x <= toX+1; x++) {
7299 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7300 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7301 board[y][x] = EmptySquare;
7305 board[toY][toX] = EmptySquare;
7308 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7309 /* [HGM] Shogi promotions */
7310 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7313 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7314 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7315 // [HGM] superchess: take promotion piece out of holdings
7316 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7317 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7318 if(!--board[k][BOARD_WIDTH-2])
7319 board[k][BOARD_WIDTH-1] = EmptySquare;
7321 if(!--board[BOARD_HEIGHT-1-k][1])
7322 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7328 /* Updates forwardMostMove */
7330 MakeMove(fromX, fromY, toX, toY, promoChar)
7331 int fromX, fromY, toX, toY;
7334 // forwardMostMove++; // [HGM] bare: moved downstream
7336 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7337 int timeLeft; static int lastLoadFlag=0; int king, piece;
7338 piece = boards[forwardMostMove][fromY][fromX];
7339 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7340 if(gameInfo.variant == VariantKnightmate)
7341 king += (int) WhiteUnicorn - (int) WhiteKing;
7342 if(forwardMostMove == 0) {
7344 fprintf(serverMoves, "%s;", second.tidy);
7345 fprintf(serverMoves, "%s;", first.tidy);
7346 if(!blackPlaysFirst)
7347 fprintf(serverMoves, "%s;", second.tidy);
7348 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7349 lastLoadFlag = loadFlag;
7351 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7352 // print castling suffix
7353 if( toY == fromY && piece == king ) {
7355 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7357 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7360 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7361 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7362 boards[forwardMostMove][toY][toX] == EmptySquare
7364 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7366 if(promoChar != NULLCHAR)
7367 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7369 fprintf(serverMoves, "/%d/%d",
7370 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7371 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7372 else timeLeft = blackTimeRemaining/1000;
7373 fprintf(serverMoves, "/%d", timeLeft);
7375 fflush(serverMoves);
7378 if (forwardMostMove+1 >= MAX_MOVES) {
7379 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7383 if (commentList[forwardMostMove+1] != NULL) {
7384 free(commentList[forwardMostMove+1]);
7385 commentList[forwardMostMove+1] = NULL;
7387 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7388 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7389 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7390 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7391 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7392 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7393 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7394 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7395 gameInfo.result = GameUnfinished;
7396 if (gameInfo.resultDetails != NULL) {
7397 free(gameInfo.resultDetails);
7398 gameInfo.resultDetails = NULL;
7400 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7401 moveList[forwardMostMove - 1]);
7402 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7403 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7404 fromY, fromX, toY, toX, promoChar,
7405 parseList[forwardMostMove - 1]);
7406 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7407 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7408 castlingRights[forwardMostMove]) ) {
7414 if(gameInfo.variant != VariantShogi)
7415 strcat(parseList[forwardMostMove - 1], "+");
7419 strcat(parseList[forwardMostMove - 1], "#");
7422 if (appData.debugMode) {
7423 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7428 /* Updates currentMove if not pausing */
7430 ShowMove(fromX, fromY, toX, toY)
7432 int instant = (gameMode == PlayFromGameFile) ?
7433 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7434 if(appData.noGUI) return;
7435 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7437 if (forwardMostMove == currentMove + 1) {
7438 AnimateMove(boards[forwardMostMove - 1],
7439 fromX, fromY, toX, toY);
7441 if (appData.highlightLastMove) {
7442 SetHighlights(fromX, fromY, toX, toY);
7445 currentMove = forwardMostMove;
7448 if (instant) return;
7450 DisplayMove(currentMove - 1);
7451 DrawPosition(FALSE, boards[currentMove]);
7452 DisplayBothClocks();
7453 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7456 void SendEgtPath(ChessProgramState *cps)
7457 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7458 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7460 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7463 char c, *q = name+1, *r, *s;
7465 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7466 while(*p && *p != ',') *q++ = *p++;
7468 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7469 strcmp(name, ",nalimov:") == 0 ) {
7470 // take nalimov path from the menu-changeable option first, if it is defined
7471 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7472 SendToProgram(buf,cps); // send egtbpath command for nalimov
7474 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7475 (s = StrStr(appData.egtFormats, name)) != NULL) {
7476 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7477 s = r = StrStr(s, ":") + 1; // beginning of path info
7478 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7479 c = *r; *r = 0; // temporarily null-terminate path info
7480 *--q = 0; // strip of trailig ':' from name
7481 sprintf(buf, "egtpath %s %s\n", name+1, s);
7483 SendToProgram(buf,cps); // send egtbpath command for this format
7485 if(*p == ',') p++; // read away comma to position for next format name
7490 InitChessProgram(cps, setup)
7491 ChessProgramState *cps;
7492 int setup; /* [HGM] needed to setup FRC opening position */
7494 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7495 if (appData.noChessProgram) return;
7496 hintRequested = FALSE;
7497 bookRequested = FALSE;
7499 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7500 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7501 if(cps->memSize) { /* [HGM] memory */
7502 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7503 SendToProgram(buf, cps);
7505 SendEgtPath(cps); /* [HGM] EGT */
7506 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7507 sprintf(buf, "cores %d\n", appData.smpCores);
7508 SendToProgram(buf, cps);
7511 SendToProgram(cps->initString, cps);
7512 if (gameInfo.variant != VariantNormal &&
7513 gameInfo.variant != VariantLoadable
7514 /* [HGM] also send variant if board size non-standard */
7515 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7517 char *v = VariantName(gameInfo.variant);
7518 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7519 /* [HGM] in protocol 1 we have to assume all variants valid */
7520 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7521 DisplayFatalError(buf, 0, 1);
7525 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7526 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7527 if( gameInfo.variant == VariantXiangqi )
7528 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7529 if( gameInfo.variant == VariantShogi )
7530 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7531 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7532 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7533 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7534 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7535 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7536 if( gameInfo.variant == VariantCourier )
7537 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7538 if( gameInfo.variant == VariantSuper )
7539 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7540 if( gameInfo.variant == VariantGreat )
7541 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7544 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7545 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7546 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7547 if(StrStr(cps->variants, b) == NULL) {
7548 // specific sized variant not known, check if general sizing allowed
7549 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7550 if(StrStr(cps->variants, "boardsize") == NULL) {
7551 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7552 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7553 DisplayFatalError(buf, 0, 1);
7556 /* [HGM] here we really should compare with the maximum supported board size */
7559 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7560 sprintf(buf, "variant %s\n", b);
7561 SendToProgram(buf, cps);
7563 currentlyInitializedVariant = gameInfo.variant;
7565 /* [HGM] send opening position in FRC to first engine */
7567 SendToProgram("force\n", cps);
7569 /* engine is now in force mode! Set flag to wake it up after first move. */
7570 setboardSpoiledMachineBlack = 1;
7574 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7575 SendToProgram(buf, cps);
7577 cps->maybeThinking = FALSE;
7578 cps->offeredDraw = 0;
7579 if (!appData.icsActive) {
7580 SendTimeControl(cps, movesPerSession, timeControl,
7581 timeIncrement, appData.searchDepth,
7584 if (appData.showThinking
7585 // [HGM] thinking: four options require thinking output to be sent
7586 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7588 SendToProgram("post\n", cps);
7590 SendToProgram("hard\n", cps);
7591 if (!appData.ponderNextMove) {
7592 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7593 it without being sure what state we are in first. "hard"
7594 is not a toggle, so that one is OK.
7596 SendToProgram("easy\n", cps);
7599 sprintf(buf, "ping %d\n", ++cps->lastPing);
7600 SendToProgram(buf, cps);
7602 cps->initDone = TRUE;
7607 StartChessProgram(cps)
7608 ChessProgramState *cps;
7613 if (appData.noChessProgram) return;
7614 cps->initDone = FALSE;
7616 if (strcmp(cps->host, "localhost") == 0) {
7617 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7618 } else if (*appData.remoteShell == NULLCHAR) {
7619 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7621 if (*appData.remoteUser == NULLCHAR) {
7622 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7625 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7626 cps->host, appData.remoteUser, cps->program);
7628 err = StartChildProcess(buf, "", &cps->pr);
7632 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7633 DisplayFatalError(buf, err, 1);
7639 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7640 if (cps->protocolVersion > 1) {
7641 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7642 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7643 cps->comboCnt = 0; // and values of combo boxes
7644 SendToProgram(buf, cps);
7646 SendToProgram("xboard\n", cps);
7652 TwoMachinesEventIfReady P((void))
7654 if (first.lastPing != first.lastPong) {
7655 DisplayMessage("", _("Waiting for first chess program"));
7656 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7659 if (second.lastPing != second.lastPong) {
7660 DisplayMessage("", _("Waiting for second chess program"));
7661 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7669 NextMatchGame P((void))
7671 int index; /* [HGM] autoinc: step lod index during match */
7673 if (*appData.loadGameFile != NULLCHAR) {
7674 index = appData.loadGameIndex;
7675 if(index < 0) { // [HGM] autoinc
7676 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7677 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7679 LoadGameFromFile(appData.loadGameFile,
7681 appData.loadGameFile, FALSE);
7682 } else if (*appData.loadPositionFile != NULLCHAR) {
7683 index = appData.loadPositionIndex;
7684 if(index < 0) { // [HGM] autoinc
7685 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7686 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7688 LoadPositionFromFile(appData.loadPositionFile,
7690 appData.loadPositionFile);
7692 TwoMachinesEventIfReady();
7695 void UserAdjudicationEvent( int result )
7697 ChessMove gameResult = GameIsDrawn;
7700 gameResult = WhiteWins;
7702 else if( result < 0 ) {
7703 gameResult = BlackWins;
7706 if( gameMode == TwoMachinesPlay ) {
7707 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7712 // [HGM] save: calculate checksum of game to make games easily identifiable
7713 int StringCheckSum(char *s)
7716 if(s==NULL) return 0;
7717 while(*s) i = i*259 + *s++;
7724 for(i=backwardMostMove; i<forwardMostMove; i++) {
7725 sum += pvInfoList[i].depth;
7726 sum += StringCheckSum(parseList[i]);
7727 sum += StringCheckSum(commentList[i]);
7730 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7731 return sum + StringCheckSum(commentList[i]);
7732 } // end of save patch
7735 GameEnds(result, resultDetails, whosays)
7737 char *resultDetails;
7740 GameMode nextGameMode;
7744 if(endingGame) return; /* [HGM] crash: forbid recursion */
7747 if (appData.debugMode) {
7748 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7749 result, resultDetails ? resultDetails : "(null)", whosays);
7752 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7753 /* If we are playing on ICS, the server decides when the
7754 game is over, but the engine can offer to draw, claim
7758 if (appData.zippyPlay && first.initDone) {
7759 if (result == GameIsDrawn) {
7760 /* In case draw still needs to be claimed */
7761 SendToICS(ics_prefix);
7762 SendToICS("draw\n");
7763 } else if (StrCaseStr(resultDetails, "resign")) {
7764 SendToICS(ics_prefix);
7765 SendToICS("resign\n");
7769 endingGame = 0; /* [HGM] crash */
7773 /* If we're loading the game from a file, stop */
7774 if (whosays == GE_FILE) {
7775 (void) StopLoadGameTimer();
7779 /* Cancel draw offers */
7780 first.offeredDraw = second.offeredDraw = 0;
7782 /* If this is an ICS game, only ICS can really say it's done;
7783 if not, anyone can. */
7784 isIcsGame = (gameMode == IcsPlayingWhite ||
7785 gameMode == IcsPlayingBlack ||
7786 gameMode == IcsObserving ||
7787 gameMode == IcsExamining);
7789 if (!isIcsGame || whosays == GE_ICS) {
7790 /* OK -- not an ICS game, or ICS said it was done */
7792 if (!isIcsGame && !appData.noChessProgram)
7793 SetUserThinkingEnables();
7795 /* [HGM] if a machine claims the game end we verify this claim */
7796 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7797 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7799 ChessMove trueResult = (ChessMove) -1;
7801 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7802 first.twoMachinesColor[0] :
7803 second.twoMachinesColor[0] ;
7805 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7806 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7807 /* [HGM] verify: engine mate claims accepted if they were flagged */
7808 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7810 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7811 /* [HGM] verify: engine mate claims accepted if they were flagged */
7812 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7814 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7815 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7818 // now verify win claims, but not in drop games, as we don't understand those yet
7819 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7820 || gameInfo.variant == VariantGreat) &&
7821 (result == WhiteWins && claimer == 'w' ||
7822 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7823 if (appData.debugMode) {
7824 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7825 result, epStatus[forwardMostMove], forwardMostMove);
7827 if(result != trueResult) {
7828 sprintf(buf, "False win claim: '%s'", resultDetails);
7829 result = claimer == 'w' ? BlackWins : WhiteWins;
7830 resultDetails = buf;
7833 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7834 && (forwardMostMove <= backwardMostMove ||
7835 epStatus[forwardMostMove-1] > EP_DRAWS ||
7836 (claimer=='b')==(forwardMostMove&1))
7838 /* [HGM] verify: draws that were not flagged are false claims */
7839 sprintf(buf, "False draw claim: '%s'", resultDetails);
7840 result = claimer == 'w' ? BlackWins : WhiteWins;
7841 resultDetails = buf;
7843 /* (Claiming a loss is accepted no questions asked!) */
7845 /* [HGM] bare: don't allow bare King to win */
7846 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7847 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7848 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7849 && result != GameIsDrawn)
7850 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7851 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7852 int p = (int)boards[forwardMostMove][i][j] - color;
7853 if(p >= 0 && p <= (int)WhiteKing) k++;
7855 if (appData.debugMode) {
7856 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7857 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7860 result = GameIsDrawn;
7861 sprintf(buf, "%s but bare king", resultDetails);
7862 resultDetails = buf;
7868 if(serverMoves != NULL && !loadFlag) { char c = '=';
7869 if(result==WhiteWins) c = '+';
7870 if(result==BlackWins) c = '-';
7871 if(resultDetails != NULL)
7872 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7874 if (resultDetails != NULL) {
7875 gameInfo.result = result;
7876 gameInfo.resultDetails = StrSave(resultDetails);
7878 /* display last move only if game was not loaded from file */
7879 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7880 DisplayMove(currentMove - 1);
7882 if (forwardMostMove != 0) {
7883 if (gameMode != PlayFromGameFile && gameMode != EditGame
7884 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7886 if (*appData.saveGameFile != NULLCHAR) {
7887 SaveGameToFile(appData.saveGameFile, TRUE);
7888 } else if (appData.autoSaveGames) {
7891 if (*appData.savePositionFile != NULLCHAR) {
7892 SavePositionToFile(appData.savePositionFile);
7897 /* Tell program how game ended in case it is learning */
7898 /* [HGM] Moved this to after saving the PGN, just in case */
7899 /* engine died and we got here through time loss. In that */
7900 /* case we will get a fatal error writing the pipe, which */
7901 /* would otherwise lose us the PGN. */
7902 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7903 /* output during GameEnds should never be fatal anymore */
7904 if (gameMode == MachinePlaysWhite ||
7905 gameMode == MachinePlaysBlack ||
7906 gameMode == TwoMachinesPlay ||
7907 gameMode == IcsPlayingWhite ||
7908 gameMode == IcsPlayingBlack ||
7909 gameMode == BeginningOfGame) {
7911 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7913 if (first.pr != NoProc) {
7914 SendToProgram(buf, &first);
7916 if (second.pr != NoProc &&
7917 gameMode == TwoMachinesPlay) {
7918 SendToProgram(buf, &second);
7923 if (appData.icsActive) {
7924 if (appData.quietPlay &&
7925 (gameMode == IcsPlayingWhite ||
7926 gameMode == IcsPlayingBlack)) {
7927 SendToICS(ics_prefix);
7928 SendToICS("set shout 1\n");
7930 nextGameMode = IcsIdle;
7931 ics_user_moved = FALSE;
7932 /* clean up premove. It's ugly when the game has ended and the
7933 * premove highlights are still on the board.
7937 ClearPremoveHighlights();
7938 DrawPosition(FALSE, boards[currentMove]);
7940 if (whosays == GE_ICS) {
7943 if (gameMode == IcsPlayingWhite)
7945 else if(gameMode == IcsPlayingBlack)
7949 if (gameMode == IcsPlayingBlack)
7951 else if(gameMode == IcsPlayingWhite)
7958 PlayIcsUnfinishedSound();
7961 } else if (gameMode == EditGame ||
7962 gameMode == PlayFromGameFile ||
7963 gameMode == AnalyzeMode ||
7964 gameMode == AnalyzeFile) {
7965 nextGameMode = gameMode;
7967 nextGameMode = EndOfGame;
7972 nextGameMode = gameMode;
7975 if (appData.noChessProgram) {
7976 gameMode = nextGameMode;
7978 endingGame = 0; /* [HGM] crash */
7983 /* Put first chess program into idle state */
7984 if (first.pr != NoProc &&
7985 (gameMode == MachinePlaysWhite ||
7986 gameMode == MachinePlaysBlack ||
7987 gameMode == TwoMachinesPlay ||
7988 gameMode == IcsPlayingWhite ||
7989 gameMode == IcsPlayingBlack ||
7990 gameMode == BeginningOfGame)) {
7991 SendToProgram("force\n", &first);
7992 if (first.usePing) {
7994 sprintf(buf, "ping %d\n", ++first.lastPing);
7995 SendToProgram(buf, &first);
7998 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7999 /* Kill off first chess program */
8000 if (first.isr != NULL)
8001 RemoveInputSource(first.isr);
8004 if (first.pr != NoProc) {
8006 DoSleep( appData.delayBeforeQuit );
8007 SendToProgram("quit\n", &first);
8008 DoSleep( appData.delayAfterQuit );
8009 DestroyChildProcess(first.pr, first.useSigterm);
8014 /* Put second chess program into idle state */
8015 if (second.pr != NoProc &&
8016 gameMode == TwoMachinesPlay) {
8017 SendToProgram("force\n", &second);
8018 if (second.usePing) {
8020 sprintf(buf, "ping %d\n", ++second.lastPing);
8021 SendToProgram(buf, &second);
8024 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8025 /* Kill off second chess program */
8026 if (second.isr != NULL)
8027 RemoveInputSource(second.isr);
8030 if (second.pr != NoProc) {
8031 DoSleep( appData.delayBeforeQuit );
8032 SendToProgram("quit\n", &second);
8033 DoSleep( appData.delayAfterQuit );
8034 DestroyChildProcess(second.pr, second.useSigterm);
8039 if (matchMode && gameMode == TwoMachinesPlay) {
8042 if (first.twoMachinesColor[0] == 'w') {
8049 if (first.twoMachinesColor[0] == 'b') {
8058 if (matchGame < appData.matchGames) {
8060 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8061 tmp = first.twoMachinesColor;
8062 first.twoMachinesColor = second.twoMachinesColor;
8063 second.twoMachinesColor = tmp;
8065 gameMode = nextGameMode;
8067 if(appData.matchPause>10000 || appData.matchPause<10)
8068 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8069 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8070 endingGame = 0; /* [HGM] crash */
8074 gameMode = nextGameMode;
8075 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8076 first.tidy, second.tidy,
8077 first.matchWins, second.matchWins,
8078 appData.matchGames - (first.matchWins + second.matchWins));
8079 DisplayFatalError(buf, 0, 0);
8082 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8083 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8085 gameMode = nextGameMode;
8087 endingGame = 0; /* [HGM] crash */
8090 /* Assumes program was just initialized (initString sent).
8091 Leaves program in force mode. */
8093 FeedMovesToProgram(cps, upto)
8094 ChessProgramState *cps;
8099 if (appData.debugMode)
8100 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8101 startedFromSetupPosition ? "position and " : "",
8102 backwardMostMove, upto, cps->which);
8103 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8104 // [HGM] variantswitch: make engine aware of new variant
8105 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8106 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8107 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8108 SendToProgram(buf, cps);
8109 currentlyInitializedVariant = gameInfo.variant;
8111 SendToProgram("force\n", cps);
8112 if (startedFromSetupPosition) {
8113 SendBoard(cps, backwardMostMove);
8114 if (appData.debugMode) {
8115 fprintf(debugFP, "feedMoves\n");
8118 for (i = backwardMostMove; i < upto; i++) {
8119 SendMoveToProgram(i, cps);
8125 ResurrectChessProgram()
8127 /* The chess program may have exited.
8128 If so, restart it and feed it all the moves made so far. */
8130 if (appData.noChessProgram || first.pr != NoProc) return;
8132 StartChessProgram(&first);
8133 InitChessProgram(&first, FALSE);
8134 FeedMovesToProgram(&first, currentMove);
8136 if (!first.sendTime) {
8137 /* can't tell gnuchess what its clock should read,
8138 so we bow to its notion. */
8140 timeRemaining[0][currentMove] = whiteTimeRemaining;
8141 timeRemaining[1][currentMove] = blackTimeRemaining;
8144 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8145 appData.icsEngineAnalyze) && first.analysisSupport) {
8146 SendToProgram("analyze\n", &first);
8147 first.analyzing = TRUE;
8160 if (appData.debugMode) {
8161 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8162 redraw, init, gameMode);
8164 pausing = pauseExamInvalid = FALSE;
8165 startedFromSetupPosition = blackPlaysFirst = FALSE;
8167 whiteFlag = blackFlag = FALSE;
8168 userOfferedDraw = FALSE;
8169 hintRequested = bookRequested = FALSE;
8170 first.maybeThinking = FALSE;
8171 second.maybeThinking = FALSE;
8172 first.bookSuspend = FALSE; // [HGM] book
8173 second.bookSuspend = FALSE;
8174 thinkOutput[0] = NULLCHAR;
8175 lastHint[0] = NULLCHAR;
8176 ClearGameInfo(&gameInfo);
8177 gameInfo.variant = StringToVariant(appData.variant);
8178 ics_user_moved = ics_clock_paused = FALSE;
8179 ics_getting_history = H_FALSE;
8181 white_holding[0] = black_holding[0] = NULLCHAR;
8182 ClearProgramStats();
8183 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8187 flipView = appData.flipView;
8188 ClearPremoveHighlights();
8190 alarmSounded = FALSE;
8192 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8193 if(appData.serverMovesName != NULL) {
8194 /* [HGM] prepare to make moves file for broadcasting */
8195 clock_t t = clock();
8196 if(serverMoves != NULL) fclose(serverMoves);
8197 serverMoves = fopen(appData.serverMovesName, "r");
8198 if(serverMoves != NULL) {
8199 fclose(serverMoves);
8200 /* delay 15 sec before overwriting, so all clients can see end */
8201 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8203 serverMoves = fopen(appData.serverMovesName, "w");
8207 gameMode = BeginningOfGame;
8209 if(appData.icsActive) gameInfo.variant = VariantNormal;
8210 currentMove = forwardMostMove = backwardMostMove = 0;
8211 InitPosition(redraw);
8212 for (i = 0; i < MAX_MOVES; i++) {
8213 if (commentList[i] != NULL) {
8214 free(commentList[i]);
8215 commentList[i] = NULL;
8219 timeRemaining[0][0] = whiteTimeRemaining;
8220 timeRemaining[1][0] = blackTimeRemaining;
8221 if (first.pr == NULL) {
8222 StartChessProgram(&first);
8225 InitChessProgram(&first, startedFromSetupPosition);
8228 DisplayMessage("", "");
8229 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8230 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8237 if (!AutoPlayOneMove())
8239 if (matchMode || appData.timeDelay == 0)
8241 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8243 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8252 int fromX, fromY, toX, toY;
8254 if (appData.debugMode) {
8255 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8258 if (gameMode != PlayFromGameFile)
8261 if (currentMove >= forwardMostMove) {
8262 gameMode = EditGame;
8265 /* [AS] Clear current move marker at the end of a game */
8266 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8271 toX = moveList[currentMove][2] - AAA;
8272 toY = moveList[currentMove][3] - ONE;
8274 if (moveList[currentMove][1] == '@') {
8275 if (appData.highlightLastMove) {
8276 SetHighlights(-1, -1, toX, toY);
8279 fromX = moveList[currentMove][0] - AAA;
8280 fromY = moveList[currentMove][1] - ONE;
8282 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8284 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8286 if (appData.highlightLastMove) {
8287 SetHighlights(fromX, fromY, toX, toY);
8290 DisplayMove(currentMove);
8291 SendMoveToProgram(currentMove++, &first);
8292 DisplayBothClocks();
8293 DrawPosition(FALSE, boards[currentMove]);
8294 // [HGM] PV info: always display, routine tests if empty
8295 DisplayComment(currentMove - 1, commentList[currentMove]);
8301 LoadGameOneMove(readAhead)
8302 ChessMove readAhead;
8304 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8305 char promoChar = NULLCHAR;
8310 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8311 gameMode != AnalyzeMode && gameMode != Training) {
8316 yyboardindex = forwardMostMove;
8317 if (readAhead != (ChessMove)0) {
8318 moveType = readAhead;
8320 if (gameFileFP == NULL)
8322 moveType = (ChessMove) yylex();
8328 if (appData.debugMode)
8329 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8331 if (*p == '{' || *p == '[' || *p == '(') {
8332 p[strlen(p) - 1] = NULLCHAR;
8336 /* append the comment but don't display it */
8337 while (*p == '\n') p++;
8338 AppendComment(currentMove, p);
8341 case WhiteCapturesEnPassant:
8342 case BlackCapturesEnPassant:
8343 case WhitePromotionChancellor:
8344 case BlackPromotionChancellor:
8345 case WhitePromotionArchbishop:
8346 case BlackPromotionArchbishop:
8347 case WhitePromotionCentaur:
8348 case BlackPromotionCentaur:
8349 case WhitePromotionQueen:
8350 case BlackPromotionQueen:
8351 case WhitePromotionRook:
8352 case BlackPromotionRook:
8353 case WhitePromotionBishop:
8354 case BlackPromotionBishop:
8355 case WhitePromotionKnight:
8356 case BlackPromotionKnight:
8357 case WhitePromotionKing:
8358 case BlackPromotionKing:
8360 case WhiteKingSideCastle:
8361 case WhiteQueenSideCastle:
8362 case BlackKingSideCastle:
8363 case BlackQueenSideCastle:
8364 case WhiteKingSideCastleWild:
8365 case WhiteQueenSideCastleWild:
8366 case BlackKingSideCastleWild:
8367 case BlackQueenSideCastleWild:
8369 case WhiteHSideCastleFR:
8370 case WhiteASideCastleFR:
8371 case BlackHSideCastleFR:
8372 case BlackASideCastleFR:
8374 if (appData.debugMode)
8375 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8376 fromX = currentMoveString[0] - AAA;
8377 fromY = currentMoveString[1] - ONE;
8378 toX = currentMoveString[2] - AAA;
8379 toY = currentMoveString[3] - ONE;
8380 promoChar = currentMoveString[4];
8385 if (appData.debugMode)
8386 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8387 fromX = moveType == WhiteDrop ?
8388 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8389 (int) CharToPiece(ToLower(currentMoveString[0]));
8391 toX = currentMoveString[2] - AAA;
8392 toY = currentMoveString[3] - ONE;
8398 case GameUnfinished:
8399 if (appData.debugMode)
8400 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8401 p = strchr(yy_text, '{');
8402 if (p == NULL) p = strchr(yy_text, '(');
8405 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8407 q = strchr(p, *p == '{' ? '}' : ')');
8408 if (q != NULL) *q = NULLCHAR;
8411 GameEnds(moveType, p, GE_FILE);
8413 if (cmailMsgLoaded) {
8415 flipView = WhiteOnMove(currentMove);
8416 if (moveType == GameUnfinished) flipView = !flipView;
8417 if (appData.debugMode)
8418 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8422 case (ChessMove) 0: /* end of file */
8423 if (appData.debugMode)
8424 fprintf(debugFP, "Parser hit end of file\n");
8425 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8426 EP_UNKNOWN, castlingRights[currentMove]) ) {
8432 if (WhiteOnMove(currentMove)) {
8433 GameEnds(BlackWins, "Black mates", GE_FILE);
8435 GameEnds(WhiteWins, "White mates", GE_FILE);
8439 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8446 if (lastLoadGameStart == GNUChessGame) {
8447 /* GNUChessGames have numbers, but they aren't move numbers */
8448 if (appData.debugMode)
8449 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8450 yy_text, (int) moveType);
8451 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8453 /* else fall thru */
8458 /* Reached start of next game in file */
8459 if (appData.debugMode)
8460 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8461 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8462 EP_UNKNOWN, castlingRights[currentMove]) ) {
8468 if (WhiteOnMove(currentMove)) {
8469 GameEnds(BlackWins, "Black mates", GE_FILE);
8471 GameEnds(WhiteWins, "White mates", GE_FILE);
8475 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8481 case PositionDiagram: /* should not happen; ignore */
8482 case ElapsedTime: /* ignore */
8483 case NAG: /* ignore */
8484 if (appData.debugMode)
8485 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8486 yy_text, (int) moveType);
8487 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8490 if (appData.testLegality) {
8491 if (appData.debugMode)
8492 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8493 sprintf(move, _("Illegal move: %d.%s%s"),
8494 (forwardMostMove / 2) + 1,
8495 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8496 DisplayError(move, 0);
8499 if (appData.debugMode)
8500 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8501 yy_text, currentMoveString);
8502 fromX = currentMoveString[0] - AAA;
8503 fromY = currentMoveString[1] - ONE;
8504 toX = currentMoveString[2] - AAA;
8505 toY = currentMoveString[3] - ONE;
8506 promoChar = currentMoveString[4];
8511 if (appData.debugMode)
8512 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8513 sprintf(move, _("Ambiguous move: %d.%s%s"),
8514 (forwardMostMove / 2) + 1,
8515 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8516 DisplayError(move, 0);
8521 case ImpossibleMove:
8522 if (appData.debugMode)
8523 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8524 sprintf(move, _("Illegal move: %d.%s%s"),
8525 (forwardMostMove / 2) + 1,
8526 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8527 DisplayError(move, 0);
8533 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8534 DrawPosition(FALSE, boards[currentMove]);
8535 DisplayBothClocks();
8536 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8537 DisplayComment(currentMove - 1, commentList[currentMove]);
8539 (void) StopLoadGameTimer();
8541 cmailOldMove = forwardMostMove;
8544 /* currentMoveString is set as a side-effect of yylex */
8545 strcat(currentMoveString, "\n");
8546 strcpy(moveList[forwardMostMove], currentMoveString);
8548 thinkOutput[0] = NULLCHAR;
8549 MakeMove(fromX, fromY, toX, toY, promoChar);
8550 currentMove = forwardMostMove;
8555 /* Load the nth game from the given file */
8557 LoadGameFromFile(filename, n, title, useList)
8561 /*Boolean*/ int useList;
8566 if (strcmp(filename, "-") == 0) {
8570 f = fopen(filename, "rb");
8572 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8573 DisplayError(buf, errno);
8577 if (fseek(f, 0, 0) == -1) {
8578 /* f is not seekable; probably a pipe */
8581 if (useList && n == 0) {
8582 int error = GameListBuild(f);
8584 DisplayError(_("Cannot build game list"), error);
8585 } else if (!ListEmpty(&gameList) &&
8586 ((ListGame *) gameList.tailPred)->number > 1) {
8587 GameListPopUp(f, title);
8594 return LoadGame(f, n, title, FALSE);
8599 MakeRegisteredMove()
8601 int fromX, fromY, toX, toY;
8603 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8604 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8607 if (appData.debugMode)
8608 fprintf(debugFP, "Restoring %s for game %d\n",
8609 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8611 thinkOutput[0] = NULLCHAR;
8612 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8613 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8614 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8615 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8616 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8617 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8618 MakeMove(fromX, fromY, toX, toY, promoChar);
8619 ShowMove(fromX, fromY, toX, toY);
8621 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8622 EP_UNKNOWN, castlingRights[currentMove]) ) {
8629 if (WhiteOnMove(currentMove)) {
8630 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8632 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8637 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8644 if (WhiteOnMove(currentMove)) {
8645 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8647 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8652 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8663 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8665 CmailLoadGame(f, gameNumber, title, useList)
8673 if (gameNumber > nCmailGames) {
8674 DisplayError(_("No more games in this message"), 0);
8677 if (f == lastLoadGameFP) {
8678 int offset = gameNumber - lastLoadGameNumber;
8680 cmailMsg[0] = NULLCHAR;
8681 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8682 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8683 nCmailMovesRegistered--;
8685 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8686 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8687 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8690 if (! RegisterMove()) return FALSE;
8694 retVal = LoadGame(f, gameNumber, title, useList);
8696 /* Make move registered during previous look at this game, if any */
8697 MakeRegisteredMove();
8699 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8700 commentList[currentMove]
8701 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8702 DisplayComment(currentMove - 1, commentList[currentMove]);
8708 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8713 int gameNumber = lastLoadGameNumber + offset;
8714 if (lastLoadGameFP == NULL) {
8715 DisplayError(_("No game has been loaded yet"), 0);
8718 if (gameNumber <= 0) {
8719 DisplayError(_("Can't back up any further"), 0);
8722 if (cmailMsgLoaded) {
8723 return CmailLoadGame(lastLoadGameFP, gameNumber,
8724 lastLoadGameTitle, lastLoadGameUseList);
8726 return LoadGame(lastLoadGameFP, gameNumber,
8727 lastLoadGameTitle, lastLoadGameUseList);
8733 /* Load the nth game from open file f */
8735 LoadGame(f, gameNumber, title, useList)
8743 int gn = gameNumber;
8744 ListGame *lg = NULL;
8747 GameMode oldGameMode;
8748 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8750 if (appData.debugMode)
8751 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8753 if (gameMode == Training )
8754 SetTrainingModeOff();
8756 oldGameMode = gameMode;
8757 if (gameMode != BeginningOfGame) {
8762 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8763 fclose(lastLoadGameFP);
8767 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8770 fseek(f, lg->offset, 0);
8771 GameListHighlight(gameNumber);
8775 DisplayError(_("Game number out of range"), 0);
8780 if (fseek(f, 0, 0) == -1) {
8781 if (f == lastLoadGameFP ?
8782 gameNumber == lastLoadGameNumber + 1 :
8786 DisplayError(_("Can't seek on game file"), 0);
8792 lastLoadGameNumber = gameNumber;
8793 strcpy(lastLoadGameTitle, title);
8794 lastLoadGameUseList = useList;
8798 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8799 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8800 lg->gameInfo.black);
8802 } else if (*title != NULLCHAR) {
8803 if (gameNumber > 1) {
8804 sprintf(buf, "%s %d", title, gameNumber);
8807 DisplayTitle(title);
8811 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8812 gameMode = PlayFromGameFile;
8816 currentMove = forwardMostMove = backwardMostMove = 0;
8817 CopyBoard(boards[0], initialPosition);
8821 * Skip the first gn-1 games in the file.
8822 * Also skip over anything that precedes an identifiable
8823 * start of game marker, to avoid being confused by
8824 * garbage at the start of the file. Currently
8825 * recognized start of game markers are the move number "1",
8826 * the pattern "gnuchess .* game", the pattern
8827 * "^[#;%] [^ ]* game file", and a PGN tag block.
8828 * A game that starts with one of the latter two patterns
8829 * will also have a move number 1, possibly
8830 * following a position diagram.
8831 * 5-4-02: Let's try being more lenient and allowing a game to
8832 * start with an unnumbered move. Does that break anything?
8834 cm = lastLoadGameStart = (ChessMove) 0;
8836 yyboardindex = forwardMostMove;
8837 cm = (ChessMove) yylex();
8840 if (cmailMsgLoaded) {
8841 nCmailGames = CMAIL_MAX_GAMES - gn;
8844 DisplayError(_("Game not found in file"), 0);
8851 lastLoadGameStart = cm;
8855 switch (lastLoadGameStart) {
8862 gn--; /* count this game */
8863 lastLoadGameStart = cm;
8872 switch (lastLoadGameStart) {
8877 gn--; /* count this game */
8878 lastLoadGameStart = cm;
8881 lastLoadGameStart = cm; /* game counted already */
8889 yyboardindex = forwardMostMove;
8890 cm = (ChessMove) yylex();
8891 } while (cm == PGNTag || cm == Comment);
8898 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8899 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8900 != CMAIL_OLD_RESULT) {
8902 cmailResult[ CMAIL_MAX_GAMES
8903 - gn - 1] = CMAIL_OLD_RESULT;
8909 /* Only a NormalMove can be at the start of a game
8910 * without a position diagram. */
8911 if (lastLoadGameStart == (ChessMove) 0) {
8913 lastLoadGameStart = MoveNumberOne;
8922 if (appData.debugMode)
8923 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8925 if (cm == XBoardGame) {
8926 /* Skip any header junk before position diagram and/or move 1 */
8928 yyboardindex = forwardMostMove;
8929 cm = (ChessMove) yylex();
8931 if (cm == (ChessMove) 0 ||
8932 cm == GNUChessGame || cm == XBoardGame) {
8933 /* Empty game; pretend end-of-file and handle later */
8938 if (cm == MoveNumberOne || cm == PositionDiagram ||
8939 cm == PGNTag || cm == Comment)
8942 } else if (cm == GNUChessGame) {
8943 if (gameInfo.event != NULL) {
8944 free(gameInfo.event);
8946 gameInfo.event = StrSave(yy_text);
8949 startedFromSetupPosition = FALSE;
8950 while (cm == PGNTag) {
8951 if (appData.debugMode)
8952 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8953 err = ParsePGNTag(yy_text, &gameInfo);
8954 if (!err) numPGNTags++;
8956 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8957 if(gameInfo.variant != oldVariant) {
8958 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8960 oldVariant = gameInfo.variant;
8961 if (appData.debugMode)
8962 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8966 if (gameInfo.fen != NULL) {
8967 Board initial_position;
8968 startedFromSetupPosition = TRUE;
8969 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8971 DisplayError(_("Bad FEN position in file"), 0);
8974 CopyBoard(boards[0], initial_position);
8975 if (blackPlaysFirst) {
8976 currentMove = forwardMostMove = backwardMostMove = 1;
8977 CopyBoard(boards[1], initial_position);
8978 strcpy(moveList[0], "");
8979 strcpy(parseList[0], "");
8980 timeRemaining[0][1] = whiteTimeRemaining;
8981 timeRemaining[1][1] = blackTimeRemaining;
8982 if (commentList[0] != NULL) {
8983 commentList[1] = commentList[0];
8984 commentList[0] = NULL;
8987 currentMove = forwardMostMove = backwardMostMove = 0;
8989 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8991 initialRulePlies = FENrulePlies;
8992 epStatus[forwardMostMove] = FENepStatus;
8993 for( i=0; i< nrCastlingRights; i++ )
8994 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8996 yyboardindex = forwardMostMove;
8998 gameInfo.fen = NULL;
9001 yyboardindex = forwardMostMove;
9002 cm = (ChessMove) yylex();
9004 /* Handle comments interspersed among the tags */
9005 while (cm == Comment) {
9007 if (appData.debugMode)
9008 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9010 if (*p == '{' || *p == '[' || *p == '(') {
9011 p[strlen(p) - 1] = NULLCHAR;
9014 while (*p == '\n') p++;
9015 AppendComment(currentMove, p);
9016 yyboardindex = forwardMostMove;
9017 cm = (ChessMove) yylex();
9021 /* don't rely on existence of Event tag since if game was
9022 * pasted from clipboard the Event tag may not exist
9024 if (numPGNTags > 0){
9026 if (gameInfo.variant == VariantNormal) {
9027 gameInfo.variant = StringToVariant(gameInfo.event);
9030 if( appData.autoDisplayTags ) {
9031 tags = PGNTags(&gameInfo);
9032 TagsPopUp(tags, CmailMsg());
9037 /* Make something up, but don't display it now */
9042 if (cm == PositionDiagram) {
9045 Board initial_position;
9047 if (appData.debugMode)
9048 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9050 if (!startedFromSetupPosition) {
9052 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9053 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9063 initial_position[i][j++] = CharToPiece(*p);
9066 while (*p == ' ' || *p == '\t' ||
9067 *p == '\n' || *p == '\r') p++;
9069 if (strncmp(p, "black", strlen("black"))==0)
9070 blackPlaysFirst = TRUE;
9072 blackPlaysFirst = FALSE;
9073 startedFromSetupPosition = TRUE;
9075 CopyBoard(boards[0], initial_position);
9076 if (blackPlaysFirst) {
9077 currentMove = forwardMostMove = backwardMostMove = 1;
9078 CopyBoard(boards[1], initial_position);
9079 strcpy(moveList[0], "");
9080 strcpy(parseList[0], "");
9081 timeRemaining[0][1] = whiteTimeRemaining;
9082 timeRemaining[1][1] = blackTimeRemaining;
9083 if (commentList[0] != NULL) {
9084 commentList[1] = commentList[0];
9085 commentList[0] = NULL;
9088 currentMove = forwardMostMove = backwardMostMove = 0;
9091 yyboardindex = forwardMostMove;
9092 cm = (ChessMove) yylex();
9095 if (first.pr == NoProc) {
9096 StartChessProgram(&first);
9098 InitChessProgram(&first, FALSE);
9099 SendToProgram("force\n", &first);
9100 if (startedFromSetupPosition) {
9101 SendBoard(&first, forwardMostMove);
9102 if (appData.debugMode) {
9103 fprintf(debugFP, "Load Game\n");
9105 DisplayBothClocks();
9108 /* [HGM] server: flag to write setup moves in broadcast file as one */
9109 loadFlag = appData.suppressLoadMoves;
9111 while (cm == Comment) {
9113 if (appData.debugMode)
9114 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9116 if (*p == '{' || *p == '[' || *p == '(') {
9117 p[strlen(p) - 1] = NULLCHAR;
9120 while (*p == '\n') p++;
9121 AppendComment(currentMove, p);
9122 yyboardindex = forwardMostMove;
9123 cm = (ChessMove) yylex();
9126 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9127 cm == WhiteWins || cm == BlackWins ||
9128 cm == GameIsDrawn || cm == GameUnfinished) {
9129 DisplayMessage("", _("No moves in game"));
9130 if (cmailMsgLoaded) {
9131 if (appData.debugMode)
9132 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9136 DrawPosition(FALSE, boards[currentMove]);
9137 DisplayBothClocks();
9138 gameMode = EditGame;
9145 // [HGM] PV info: routine tests if comment empty
9146 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9147 DisplayComment(currentMove - 1, commentList[currentMove]);
9149 if (!matchMode && appData.timeDelay != 0)
9150 DrawPosition(FALSE, boards[currentMove]);
9152 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9153 programStats.ok_to_send = 1;
9156 /* if the first token after the PGN tags is a move
9157 * and not move number 1, retrieve it from the parser
9159 if (cm != MoveNumberOne)
9160 LoadGameOneMove(cm);
9162 /* load the remaining moves from the file */
9163 while (LoadGameOneMove((ChessMove)0)) {
9164 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9165 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9168 /* rewind to the start of the game */
9169 currentMove = backwardMostMove;
9171 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9173 if (oldGameMode == AnalyzeFile ||
9174 oldGameMode == AnalyzeMode) {
9178 if (matchMode || appData.timeDelay == 0) {
9180 gameMode = EditGame;
9182 } else if (appData.timeDelay > 0) {
9186 if (appData.debugMode)
9187 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9189 loadFlag = 0; /* [HGM] true game starts */
9193 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9195 ReloadPosition(offset)
9198 int positionNumber = lastLoadPositionNumber + offset;
9199 if (lastLoadPositionFP == NULL) {
9200 DisplayError(_("No position has been loaded yet"), 0);
9203 if (positionNumber <= 0) {
9204 DisplayError(_("Can't back up any further"), 0);
9207 return LoadPosition(lastLoadPositionFP, positionNumber,
9208 lastLoadPositionTitle);
9211 /* Load the nth position from the given file */
9213 LoadPositionFromFile(filename, n, title)
9221 if (strcmp(filename, "-") == 0) {
9222 return LoadPosition(stdin, n, "stdin");
9224 f = fopen(filename, "rb");
9226 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9227 DisplayError(buf, errno);
9230 return LoadPosition(f, n, title);
9235 /* Load the nth position from the given open file, and close it */
9237 LoadPosition(f, positionNumber, title)
9242 char *p, line[MSG_SIZ];
9243 Board initial_position;
9244 int i, j, fenMode, pn;
9246 if (gameMode == Training )
9247 SetTrainingModeOff();
9249 if (gameMode != BeginningOfGame) {
9252 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9253 fclose(lastLoadPositionFP);
9255 if (positionNumber == 0) positionNumber = 1;
9256 lastLoadPositionFP = f;
9257 lastLoadPositionNumber = positionNumber;
9258 strcpy(lastLoadPositionTitle, title);
9259 if (first.pr == NoProc) {
9260 StartChessProgram(&first);
9261 InitChessProgram(&first, FALSE);
9263 pn = positionNumber;
9264 if (positionNumber < 0) {
9265 /* Negative position number means to seek to that byte offset */
9266 if (fseek(f, -positionNumber, 0) == -1) {
9267 DisplayError(_("Can't seek on position file"), 0);
9272 if (fseek(f, 0, 0) == -1) {
9273 if (f == lastLoadPositionFP ?
9274 positionNumber == lastLoadPositionNumber + 1 :
9275 positionNumber == 1) {
9278 DisplayError(_("Can't seek on position file"), 0);
9283 /* See if this file is FEN or old-style xboard */
9284 if (fgets(line, MSG_SIZ, f) == NULL) {
9285 DisplayError(_("Position not found in file"), 0);
9288 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9289 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9292 if (fenMode || line[0] == '#') pn--;
9294 /* skip positions before number pn */
9295 if (fgets(line, MSG_SIZ, f) == NULL) {
9297 DisplayError(_("Position not found in file"), 0);
9300 if (fenMode || line[0] == '#') pn--;
9305 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9306 DisplayError(_("Bad FEN position in file"), 0);
9310 (void) fgets(line, MSG_SIZ, f);
9311 (void) fgets(line, MSG_SIZ, f);
9313 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9314 (void) fgets(line, MSG_SIZ, f);
9315 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9318 initial_position[i][j++] = CharToPiece(*p);
9322 blackPlaysFirst = FALSE;
9324 (void) fgets(line, MSG_SIZ, f);
9325 if (strncmp(line, "black", strlen("black"))==0)
9326 blackPlaysFirst = TRUE;
9329 startedFromSetupPosition = TRUE;
9331 SendToProgram("force\n", &first);
9332 CopyBoard(boards[0], initial_position);
9333 if (blackPlaysFirst) {
9334 currentMove = forwardMostMove = backwardMostMove = 1;
9335 strcpy(moveList[0], "");
9336 strcpy(parseList[0], "");
9337 CopyBoard(boards[1], initial_position);
9338 DisplayMessage("", _("Black to play"));
9340 currentMove = forwardMostMove = backwardMostMove = 0;
9341 DisplayMessage("", _("White to play"));
9343 /* [HGM] copy FEN attributes as well */
9345 initialRulePlies = FENrulePlies;
9346 epStatus[forwardMostMove] = FENepStatus;
9347 for( i=0; i< nrCastlingRights; i++ )
9348 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9350 SendBoard(&first, forwardMostMove);
9351 if (appData.debugMode) {
9353 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9354 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9355 fprintf(debugFP, "Load Position\n");
9358 if (positionNumber > 1) {
9359 sprintf(line, "%s %d", title, positionNumber);
9362 DisplayTitle(title);
9364 gameMode = EditGame;
9367 timeRemaining[0][1] = whiteTimeRemaining;
9368 timeRemaining[1][1] = blackTimeRemaining;
9369 DrawPosition(FALSE, boards[currentMove]);
9376 CopyPlayerNameIntoFileName(dest, src)
9379 while (*src != NULLCHAR && *src != ',') {
9384 *(*dest)++ = *src++;
9389 char *DefaultFileName(ext)
9392 static char def[MSG_SIZ];
9395 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9397 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9399 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9408 /* Save the current game to the given file */
9410 SaveGameToFile(filename, append)
9417 if (strcmp(filename, "-") == 0) {
9418 return SaveGame(stdout, 0, NULL);
9420 f = fopen(filename, append ? "a" : "w");
9422 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9423 DisplayError(buf, errno);
9426 return SaveGame(f, 0, NULL);
9435 static char buf[MSG_SIZ];
9438 p = strchr(str, ' ');
9439 if (p == NULL) return str;
9440 strncpy(buf, str, p - str);
9441 buf[p - str] = NULLCHAR;
9445 #define PGN_MAX_LINE 75
9447 #define PGN_SIDE_WHITE 0
9448 #define PGN_SIDE_BLACK 1
9451 static int FindFirstMoveOutOfBook( int side )
9455 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9456 int index = backwardMostMove;
9457 int has_book_hit = 0;
9459 if( (index % 2) != side ) {
9463 while( index < forwardMostMove ) {
9464 /* Check to see if engine is in book */
9465 int depth = pvInfoList[index].depth;
9466 int score = pvInfoList[index].score;
9472 else if( score == 0 && depth == 63 ) {
9473 in_book = 1; /* Zappa */
9475 else if( score == 2 && depth == 99 ) {
9476 in_book = 1; /* Abrok */
9479 has_book_hit += in_book;
9495 void GetOutOfBookInfo( char * buf )
9499 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9501 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9502 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9506 if( oob[0] >= 0 || oob[1] >= 0 ) {
9507 for( i=0; i<2; i++ ) {
9511 if( i > 0 && oob[0] >= 0 ) {
9515 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9516 sprintf( buf+strlen(buf), "%s%.2f",
9517 pvInfoList[idx].score >= 0 ? "+" : "",
9518 pvInfoList[idx].score / 100.0 );
9524 /* Save game in PGN style and close the file */
9529 int i, offset, linelen, newblock;
9533 int movelen, numlen, blank;
9534 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9536 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9538 tm = time((time_t *) NULL);
9540 PrintPGNTags(f, &gameInfo);
9542 if (backwardMostMove > 0 || startedFromSetupPosition) {
9543 char *fen = PositionToFEN(backwardMostMove, NULL);
9544 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9545 fprintf(f, "\n{--------------\n");
9546 PrintPosition(f, backwardMostMove);
9547 fprintf(f, "--------------}\n");
9551 /* [AS] Out of book annotation */
9552 if( appData.saveOutOfBookInfo ) {
9555 GetOutOfBookInfo( buf );
9557 if( buf[0] != '\0' ) {
9558 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9565 i = backwardMostMove;
9569 while (i < forwardMostMove) {
9570 /* Print comments preceding this move */
9571 if (commentList[i] != NULL) {
9572 if (linelen > 0) fprintf(f, "\n");
9573 fprintf(f, "{\n%s}\n", commentList[i]);
9578 /* Format move number */
9580 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9583 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9585 numtext[0] = NULLCHAR;
9588 numlen = strlen(numtext);
9591 /* Print move number */
9592 blank = linelen > 0 && numlen > 0;
9593 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9602 fprintf(f, "%s", numtext);
9606 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9607 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9610 blank = linelen > 0 && movelen > 0;
9611 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9620 fprintf(f, "%s", move_buffer);
9623 /* [AS] Add PV info if present */
9624 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9625 /* [HGM] add time */
9626 char buf[MSG_SIZ]; int seconds = 0;
9628 if(i >= backwardMostMove) {
9630 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9631 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9633 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9634 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9636 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9638 if( seconds <= 0) buf[0] = 0; else
9639 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9640 seconds = (seconds + 4)/10; // round to full seconds
9641 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9642 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9645 sprintf( move_buffer, "{%s%.2f/%d%s}",
9646 pvInfoList[i].score >= 0 ? "+" : "",
9647 pvInfoList[i].score / 100.0,
9648 pvInfoList[i].depth,
9651 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9653 /* Print score/depth */
9654 blank = linelen > 0 && movelen > 0;
9655 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9664 fprintf(f, "%s", move_buffer);
9671 /* Start a new line */
9672 if (linelen > 0) fprintf(f, "\n");
9674 /* Print comments after last move */
9675 if (commentList[i] != NULL) {
9676 fprintf(f, "{\n%s}\n", commentList[i]);
9680 if (gameInfo.resultDetails != NULL &&
9681 gameInfo.resultDetails[0] != NULLCHAR) {
9682 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9683 PGNResult(gameInfo.result));
9685 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9689 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9693 /* Save game in old style and close the file */
9701 tm = time((time_t *) NULL);
9703 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9706 if (backwardMostMove > 0 || startedFromSetupPosition) {
9707 fprintf(f, "\n[--------------\n");
9708 PrintPosition(f, backwardMostMove);
9709 fprintf(f, "--------------]\n");
9714 i = backwardMostMove;
9715 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9717 while (i < forwardMostMove) {
9718 if (commentList[i] != NULL) {
9719 fprintf(f, "[%s]\n", commentList[i]);
9723 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9726 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9728 if (commentList[i] != NULL) {
9732 if (i >= forwardMostMove) {
9736 fprintf(f, "%s\n", parseList[i]);
9741 if (commentList[i] != NULL) {
9742 fprintf(f, "[%s]\n", commentList[i]);
9745 /* This isn't really the old style, but it's close enough */
9746 if (gameInfo.resultDetails != NULL &&
9747 gameInfo.resultDetails[0] != NULLCHAR) {
9748 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9749 gameInfo.resultDetails);
9751 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9758 /* Save the current game to open file f and close the file */
9760 SaveGame(f, dummy, dummy2)
9765 if (gameMode == EditPosition) EditPositionDone();
9766 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9767 if (appData.oldSaveStyle)
9768 return SaveGameOldStyle(f);
9770 return SaveGamePGN(f);
9773 /* Save the current position to the given file */
9775 SavePositionToFile(filename)
9781 if (strcmp(filename, "-") == 0) {
9782 return SavePosition(stdout, 0, NULL);
9784 f = fopen(filename, "a");
9786 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9787 DisplayError(buf, errno);
9790 SavePosition(f, 0, NULL);
9796 /* Save the current position to the given open file and close the file */
9798 SavePosition(f, dummy, dummy2)
9806 if (appData.oldSaveStyle) {
9807 tm = time((time_t *) NULL);
9809 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9811 fprintf(f, "[--------------\n");
9812 PrintPosition(f, currentMove);
9813 fprintf(f, "--------------]\n");
9815 fen = PositionToFEN(currentMove, NULL);
9816 fprintf(f, "%s\n", fen);
9824 ReloadCmailMsgEvent(unregister)
9828 static char *inFilename = NULL;
9829 static char *outFilename;
9831 struct stat inbuf, outbuf;
9834 /* Any registered moves are unregistered if unregister is set, */
9835 /* i.e. invoked by the signal handler */
9837 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9838 cmailMoveRegistered[i] = FALSE;
9839 if (cmailCommentList[i] != NULL) {
9840 free(cmailCommentList[i]);
9841 cmailCommentList[i] = NULL;
9844 nCmailMovesRegistered = 0;
9847 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9848 cmailResult[i] = CMAIL_NOT_RESULT;
9852 if (inFilename == NULL) {
9853 /* Because the filenames are static they only get malloced once */
9854 /* and they never get freed */
9855 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9856 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9858 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9859 sprintf(outFilename, "%s.out", appData.cmailGameName);
9862 status = stat(outFilename, &outbuf);
9864 cmailMailedMove = FALSE;
9866 status = stat(inFilename, &inbuf);
9867 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9870 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9871 counts the games, notes how each one terminated, etc.
9873 It would be nice to remove this kludge and instead gather all
9874 the information while building the game list. (And to keep it
9875 in the game list nodes instead of having a bunch of fixed-size
9876 parallel arrays.) Note this will require getting each game's
9877 termination from the PGN tags, as the game list builder does
9878 not process the game moves. --mann
9880 cmailMsgLoaded = TRUE;
9881 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9883 /* Load first game in the file or popup game menu */
9884 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9894 char string[MSG_SIZ];
9896 if ( cmailMailedMove
9897 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9898 return TRUE; /* Allow free viewing */
9901 /* Unregister move to ensure that we don't leave RegisterMove */
9902 /* with the move registered when the conditions for registering no */
9904 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9905 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9906 nCmailMovesRegistered --;
9908 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9910 free(cmailCommentList[lastLoadGameNumber - 1]);
9911 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9915 if (cmailOldMove == -1) {
9916 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9920 if (currentMove > cmailOldMove + 1) {
9921 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9925 if (currentMove < cmailOldMove) {
9926 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9930 if (forwardMostMove > currentMove) {
9931 /* Silently truncate extra moves */
9935 if ( (currentMove == cmailOldMove + 1)
9936 || ( (currentMove == cmailOldMove)
9937 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9938 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9939 if (gameInfo.result != GameUnfinished) {
9940 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9943 if (commentList[currentMove] != NULL) {
9944 cmailCommentList[lastLoadGameNumber - 1]
9945 = StrSave(commentList[currentMove]);
9947 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9949 if (appData.debugMode)
9950 fprintf(debugFP, "Saving %s for game %d\n",
9951 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9954 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9956 f = fopen(string, "w");
9957 if (appData.oldSaveStyle) {
9958 SaveGameOldStyle(f); /* also closes the file */
9960 sprintf(string, "%s.pos.out", appData.cmailGameName);
9961 f = fopen(string, "w");
9962 SavePosition(f, 0, NULL); /* also closes the file */
9964 fprintf(f, "{--------------\n");
9965 PrintPosition(f, currentMove);
9966 fprintf(f, "--------------}\n\n");
9968 SaveGame(f, 0, NULL); /* also closes the file*/
9971 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9972 nCmailMovesRegistered ++;
9973 } else if (nCmailGames == 1) {
9974 DisplayError(_("You have not made a move yet"), 0);
9985 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9986 FILE *commandOutput;
9987 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9988 int nBytes = 0; /* Suppress warnings on uninitialized variables */
9994 if (! cmailMsgLoaded) {
9995 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9999 if (nCmailGames == nCmailResults) {
10000 DisplayError(_("No unfinished games"), 0);
10004 #if CMAIL_PROHIBIT_REMAIL
10005 if (cmailMailedMove) {
10006 sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
10007 DisplayError(msg, 0);
10012 if (! (cmailMailedMove || RegisterMove())) return;
10014 if ( cmailMailedMove
10015 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10016 sprintf(string, partCommandString,
10017 appData.debugMode ? " -v" : "", appData.cmailGameName);
10018 commandOutput = popen(string, "r");
10020 if (commandOutput == NULL) {
10021 DisplayError(_("Failed to invoke cmail"), 0);
10023 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10024 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10026 if (nBuffers > 1) {
10027 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10028 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10029 nBytes = MSG_SIZ - 1;
10031 (void) memcpy(msg, buffer, nBytes);
10033 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10035 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10036 cmailMailedMove = TRUE; /* Prevent >1 moves */
10039 for (i = 0; i < nCmailGames; i ++) {
10040 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10045 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10047 sprintf(buffer, "%s/%s.%s.archive",
10049 appData.cmailGameName,
10051 LoadGameFromFile(buffer, 1, buffer, FALSE);
10052 cmailMsgLoaded = FALSE;
10056 DisplayInformation(msg);
10057 pclose(commandOutput);
10060 if ((*cmailMsg) != '\0') {
10061 DisplayInformation(cmailMsg);
10066 #endif /* !WIN32 */
10075 int prependComma = 0;
10077 char string[MSG_SIZ]; /* Space for game-list */
10080 if (!cmailMsgLoaded) return "";
10082 if (cmailMailedMove) {
10083 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10085 /* Create a list of games left */
10086 sprintf(string, "[");
10087 for (i = 0; i < nCmailGames; i ++) {
10088 if (! ( cmailMoveRegistered[i]
10089 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10090 if (prependComma) {
10091 sprintf(number, ",%d", i + 1);
10093 sprintf(number, "%d", i + 1);
10097 strcat(string, number);
10100 strcat(string, "]");
10102 if (nCmailMovesRegistered + nCmailResults == 0) {
10103 switch (nCmailGames) {
10106 _("Still need to make move for game\n"));
10111 _("Still need to make moves for both games\n"));
10116 _("Still need to make moves for all %d games\n"),
10121 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10124 _("Still need to make a move for game %s\n"),
10129 if (nCmailResults == nCmailGames) {
10130 sprintf(cmailMsg, _("No unfinished games\n"));
10132 sprintf(cmailMsg, _("Ready to send mail\n"));
10138 _("Still need to make moves for games %s\n"),
10150 if (gameMode == Training)
10151 SetTrainingModeOff();
10154 cmailMsgLoaded = FALSE;
10155 if (appData.icsActive) {
10156 SendToICS(ics_prefix);
10157 SendToICS("refresh\n");
10167 /* Give up on clean exit */
10171 /* Keep trying for clean exit */
10175 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10177 if (telnetISR != NULL) {
10178 RemoveInputSource(telnetISR);
10180 if (icsPR != NoProc) {
10181 DestroyChildProcess(icsPR, TRUE);
10184 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10185 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10187 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10188 /* make sure this other one finishes before killing it! */
10189 if(endingGame) { int count = 0;
10190 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10191 while(endingGame && count++ < 10) DoSleep(1);
10192 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10195 /* Kill off chess programs */
10196 if (first.pr != NoProc) {
10199 DoSleep( appData.delayBeforeQuit );
10200 SendToProgram("quit\n", &first);
10201 DoSleep( appData.delayAfterQuit );
10202 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10204 if (second.pr != NoProc) {
10205 DoSleep( appData.delayBeforeQuit );
10206 SendToProgram("quit\n", &second);
10207 DoSleep( appData.delayAfterQuit );
10208 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10210 if (first.isr != NULL) {
10211 RemoveInputSource(first.isr);
10213 if (second.isr != NULL) {
10214 RemoveInputSource(second.isr);
10217 ShutDownFrontEnd();
10224 if (appData.debugMode)
10225 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10229 if (gameMode == MachinePlaysWhite ||
10230 gameMode == MachinePlaysBlack) {
10233 DisplayBothClocks();
10235 if (gameMode == PlayFromGameFile) {
10236 if (appData.timeDelay >= 0)
10237 AutoPlayGameLoop();
10238 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10239 Reset(FALSE, TRUE);
10240 SendToICS(ics_prefix);
10241 SendToICS("refresh\n");
10242 } else if (currentMove < forwardMostMove) {
10243 ForwardInner(forwardMostMove);
10245 pauseExamInvalid = FALSE;
10247 switch (gameMode) {
10251 pauseExamForwardMostMove = forwardMostMove;
10252 pauseExamInvalid = FALSE;
10255 case IcsPlayingWhite:
10256 case IcsPlayingBlack:
10260 case PlayFromGameFile:
10261 (void) StopLoadGameTimer();
10265 case BeginningOfGame:
10266 if (appData.icsActive) return;
10267 /* else fall through */
10268 case MachinePlaysWhite:
10269 case MachinePlaysBlack:
10270 case TwoMachinesPlay:
10271 if (forwardMostMove == 0)
10272 return; /* don't pause if no one has moved */
10273 if ((gameMode == MachinePlaysWhite &&
10274 !WhiteOnMove(forwardMostMove)) ||
10275 (gameMode == MachinePlaysBlack &&
10276 WhiteOnMove(forwardMostMove))) {
10289 char title[MSG_SIZ];
10291 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10292 strcpy(title, _("Edit comment"));
10294 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10295 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10296 parseList[currentMove - 1]);
10299 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10306 char *tags = PGNTags(&gameInfo);
10307 EditTagsPopUp(tags);
10314 if (appData.noChessProgram || gameMode == AnalyzeMode)
10317 if (gameMode != AnalyzeFile) {
10318 if (!appData.icsEngineAnalyze) {
10320 if (gameMode != EditGame) return;
10322 ResurrectChessProgram();
10323 SendToProgram("analyze\n", &first);
10324 first.analyzing = TRUE;
10325 /*first.maybeThinking = TRUE;*/
10326 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10327 AnalysisPopUp(_("Analysis"),
10328 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10330 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10335 StartAnalysisClock();
10336 GetTimeMark(&lastNodeCountTime);
10343 if (appData.noChessProgram || gameMode == AnalyzeFile)
10346 if (gameMode != AnalyzeMode) {
10348 if (gameMode != EditGame) return;
10349 ResurrectChessProgram();
10350 SendToProgram("analyze\n", &first);
10351 first.analyzing = TRUE;
10352 /*first.maybeThinking = TRUE;*/
10353 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10354 AnalysisPopUp(_("Analysis"),
10355 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10357 gameMode = AnalyzeFile;
10362 StartAnalysisClock();
10363 GetTimeMark(&lastNodeCountTime);
10368 MachineWhiteEvent()
10371 char *bookHit = NULL;
10373 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10377 if (gameMode == PlayFromGameFile ||
10378 gameMode == TwoMachinesPlay ||
10379 gameMode == Training ||
10380 gameMode == AnalyzeMode ||
10381 gameMode == EndOfGame)
10384 if (gameMode == EditPosition)
10385 EditPositionDone();
10387 if (!WhiteOnMove(currentMove)) {
10388 DisplayError(_("It is not White's turn"), 0);
10392 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10395 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10396 gameMode == AnalyzeFile)
10399 ResurrectChessProgram(); /* in case it isn't running */
10400 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10401 gameMode = MachinePlaysWhite;
10404 gameMode = MachinePlaysWhite;
10408 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10410 if (first.sendName) {
10411 sprintf(buf, "name %s\n", gameInfo.black);
10412 SendToProgram(buf, &first);
10414 if (first.sendTime) {
10415 if (first.useColors) {
10416 SendToProgram("black\n", &first); /*gnu kludge*/
10418 SendTimeRemaining(&first, TRUE);
10420 if (first.useColors) {
10421 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10423 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10424 SetMachineThinkingEnables();
10425 first.maybeThinking = TRUE;
10429 if (appData.autoFlipView && !flipView) {
10430 flipView = !flipView;
10431 DrawPosition(FALSE, NULL);
10432 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10435 if(bookHit) { // [HGM] book: simulate book reply
10436 static char bookMove[MSG_SIZ]; // a bit generous?
10438 programStats.nodes = programStats.depth = programStats.time =
10439 programStats.score = programStats.got_only_move = 0;
10440 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10442 strcpy(bookMove, "move ");
10443 strcat(bookMove, bookHit);
10444 HandleMachineMove(bookMove, &first);
10449 MachineBlackEvent()
10452 char *bookHit = NULL;
10454 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10458 if (gameMode == PlayFromGameFile ||
10459 gameMode == TwoMachinesPlay ||
10460 gameMode == Training ||
10461 gameMode == AnalyzeMode ||
10462 gameMode == EndOfGame)
10465 if (gameMode == EditPosition)
10466 EditPositionDone();
10468 if (WhiteOnMove(currentMove)) {
10469 DisplayError(_("It is not Black's turn"), 0);
10473 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10476 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10477 gameMode == AnalyzeFile)
10480 ResurrectChessProgram(); /* in case it isn't running */
10481 gameMode = MachinePlaysBlack;
10485 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10487 if (first.sendName) {
10488 sprintf(buf, "name %s\n", gameInfo.white);
10489 SendToProgram(buf, &first);
10491 if (first.sendTime) {
10492 if (first.useColors) {
10493 SendToProgram("white\n", &first); /*gnu kludge*/
10495 SendTimeRemaining(&first, FALSE);
10497 if (first.useColors) {
10498 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10500 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10501 SetMachineThinkingEnables();
10502 first.maybeThinking = TRUE;
10505 if (appData.autoFlipView && flipView) {
10506 flipView = !flipView;
10507 DrawPosition(FALSE, NULL);
10508 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10510 if(bookHit) { // [HGM] book: simulate book reply
10511 static char bookMove[MSG_SIZ]; // a bit generous?
10513 programStats.nodes = programStats.depth = programStats.time =
10514 programStats.score = programStats.got_only_move = 0;
10515 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10517 strcpy(bookMove, "move ");
10518 strcat(bookMove, bookHit);
10519 HandleMachineMove(bookMove, &first);
10525 DisplayTwoMachinesTitle()
10528 if (appData.matchGames > 0) {
10529 if (first.twoMachinesColor[0] == 'w') {
10530 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10531 gameInfo.white, gameInfo.black,
10532 first.matchWins, second.matchWins,
10533 matchGame - 1 - (first.matchWins + second.matchWins));
10535 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10536 gameInfo.white, gameInfo.black,
10537 second.matchWins, first.matchWins,
10538 matchGame - 1 - (first.matchWins + second.matchWins));
10541 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10547 TwoMachinesEvent P((void))
10551 ChessProgramState *onmove;
10552 char *bookHit = NULL;
10554 if (appData.noChessProgram) return;
10556 switch (gameMode) {
10557 case TwoMachinesPlay:
10559 case MachinePlaysWhite:
10560 case MachinePlaysBlack:
10561 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10562 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10566 case BeginningOfGame:
10567 case PlayFromGameFile:
10570 if (gameMode != EditGame) return;
10573 EditPositionDone();
10584 forwardMostMove = currentMove;
10585 ResurrectChessProgram(); /* in case first program isn't running */
10587 if (second.pr == NULL) {
10588 StartChessProgram(&second);
10589 if (second.protocolVersion == 1) {
10590 TwoMachinesEventIfReady();
10592 /* kludge: allow timeout for initial "feature" command */
10594 DisplayMessage("", _("Starting second chess program"));
10595 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10599 DisplayMessage("", "");
10600 InitChessProgram(&second, FALSE);
10601 SendToProgram("force\n", &second);
10602 if (startedFromSetupPosition) {
10603 SendBoard(&second, backwardMostMove);
10604 if (appData.debugMode) {
10605 fprintf(debugFP, "Two Machines\n");
10608 for (i = backwardMostMove; i < forwardMostMove; i++) {
10609 SendMoveToProgram(i, &second);
10612 gameMode = TwoMachinesPlay;
10616 DisplayTwoMachinesTitle();
10618 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10624 SendToProgram(first.computerString, &first);
10625 if (first.sendName) {
10626 sprintf(buf, "name %s\n", second.tidy);
10627 SendToProgram(buf, &first);
10629 SendToProgram(second.computerString, &second);
10630 if (second.sendName) {
10631 sprintf(buf, "name %s\n", first.tidy);
10632 SendToProgram(buf, &second);
10636 if (!first.sendTime || !second.sendTime) {
10637 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10638 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10640 if (onmove->sendTime) {
10641 if (onmove->useColors) {
10642 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10644 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10646 if (onmove->useColors) {
10647 SendToProgram(onmove->twoMachinesColor, onmove);
10649 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10650 // SendToProgram("go\n", onmove);
10651 onmove->maybeThinking = TRUE;
10652 SetMachineThinkingEnables();
10656 if(bookHit) { // [HGM] book: simulate book reply
10657 static char bookMove[MSG_SIZ]; // a bit generous?
10659 programStats.nodes = programStats.depth = programStats.time =
10660 programStats.score = programStats.got_only_move = 0;
10661 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10663 strcpy(bookMove, "move ");
10664 strcat(bookMove, bookHit);
10665 HandleMachineMove(bookMove, &first);
10672 if (gameMode == Training) {
10673 SetTrainingModeOff();
10674 gameMode = PlayFromGameFile;
10675 DisplayMessage("", _("Training mode off"));
10677 gameMode = Training;
10678 animateTraining = appData.animate;
10680 /* make sure we are not already at the end of the game */
10681 if (currentMove < forwardMostMove) {
10682 SetTrainingModeOn();
10683 DisplayMessage("", _("Training mode on"));
10685 gameMode = PlayFromGameFile;
10686 DisplayError(_("Already at end of game"), 0);
10695 if (!appData.icsActive) return;
10696 switch (gameMode) {
10697 case IcsPlayingWhite:
10698 case IcsPlayingBlack:
10701 case BeginningOfGame:
10709 EditPositionDone();
10722 gameMode = IcsIdle;
10733 switch (gameMode) {
10735 SetTrainingModeOff();
10737 case MachinePlaysWhite:
10738 case MachinePlaysBlack:
10739 case BeginningOfGame:
10740 SendToProgram("force\n", &first);
10741 SetUserThinkingEnables();
10743 case PlayFromGameFile:
10744 (void) StopLoadGameTimer();
10745 if (gameFileFP != NULL) {
10750 EditPositionDone();
10755 SendToProgram("force\n", &first);
10757 case TwoMachinesPlay:
10758 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10759 ResurrectChessProgram();
10760 SetUserThinkingEnables();
10763 ResurrectChessProgram();
10765 case IcsPlayingBlack:
10766 case IcsPlayingWhite:
10767 DisplayError(_("Warning: You are still playing a game"), 0);
10770 DisplayError(_("Warning: You are still observing a game"), 0);
10773 DisplayError(_("Warning: You are still examining a game"), 0);
10784 first.offeredDraw = second.offeredDraw = 0;
10786 if (gameMode == PlayFromGameFile) {
10787 whiteTimeRemaining = timeRemaining[0][currentMove];
10788 blackTimeRemaining = timeRemaining[1][currentMove];
10792 if (gameMode == MachinePlaysWhite ||
10793 gameMode == MachinePlaysBlack ||
10794 gameMode == TwoMachinesPlay ||
10795 gameMode == EndOfGame) {
10796 i = forwardMostMove;
10797 while (i > currentMove) {
10798 SendToProgram("undo\n", &first);
10801 whiteTimeRemaining = timeRemaining[0][currentMove];
10802 blackTimeRemaining = timeRemaining[1][currentMove];
10803 DisplayBothClocks();
10804 if (whiteFlag || blackFlag) {
10805 whiteFlag = blackFlag = 0;
10810 gameMode = EditGame;
10817 EditPositionEvent()
10819 if (gameMode == EditPosition) {
10825 if (gameMode != EditGame) return;
10827 gameMode = EditPosition;
10830 if (currentMove > 0)
10831 CopyBoard(boards[0], boards[currentMove]);
10833 blackPlaysFirst = !WhiteOnMove(currentMove);
10835 currentMove = forwardMostMove = backwardMostMove = 0;
10836 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10843 /* [DM] icsEngineAnalyze - possible call from other functions */
10844 if (appData.icsEngineAnalyze) {
10845 appData.icsEngineAnalyze = FALSE;
10847 DisplayMessage("",_("Close ICS engine analyze..."));
10849 if (first.analysisSupport && first.analyzing) {
10850 SendToProgram("exit\n", &first);
10851 first.analyzing = FALSE;
10854 thinkOutput[0] = NULLCHAR;
10860 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10862 startedFromSetupPosition = TRUE;
10863 InitChessProgram(&first, FALSE);
10864 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10865 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10866 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10867 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10868 } else castlingRights[0][2] = -1;
10869 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10870 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10871 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10872 } else castlingRights[0][5] = -1;
10873 SendToProgram("force\n", &first);
10874 if (blackPlaysFirst) {
10875 strcpy(moveList[0], "");
10876 strcpy(parseList[0], "");
10877 currentMove = forwardMostMove = backwardMostMove = 1;
10878 CopyBoard(boards[1], boards[0]);
10879 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10881 epStatus[1] = epStatus[0];
10882 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10885 currentMove = forwardMostMove = backwardMostMove = 0;
10887 SendBoard(&first, forwardMostMove);
10888 if (appData.debugMode) {
10889 fprintf(debugFP, "EditPosDone\n");
10892 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10893 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10894 gameMode = EditGame;
10896 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10897 ClearHighlights(); /* [AS] */
10900 /* Pause for `ms' milliseconds */
10901 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10911 } while (SubtractTimeMarks(&m2, &m1) < ms);
10914 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10916 SendMultiLineToICS(buf)
10919 char temp[MSG_SIZ+1], *p;
10926 strncpy(temp, buf, len);
10931 if (*p == '\n' || *p == '\r')
10936 strcat(temp, "\n");
10938 SendToPlayer(temp, strlen(temp));
10942 SetWhiteToPlayEvent()
10944 if (gameMode == EditPosition) {
10945 blackPlaysFirst = FALSE;
10946 DisplayBothClocks(); /* works because currentMove is 0 */
10947 } else if (gameMode == IcsExamining) {
10948 SendToICS(ics_prefix);
10949 SendToICS("tomove white\n");
10954 SetBlackToPlayEvent()
10956 if (gameMode == EditPosition) {
10957 blackPlaysFirst = TRUE;
10958 currentMove = 1; /* kludge */
10959 DisplayBothClocks();
10961 } else if (gameMode == IcsExamining) {
10962 SendToICS(ics_prefix);
10963 SendToICS("tomove black\n");
10968 EditPositionMenuEvent(selection, x, y)
10969 ChessSquare selection;
10973 ChessSquare piece = boards[0][y][x];
10975 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10977 switch (selection) {
10979 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10980 SendToICS(ics_prefix);
10981 SendToICS("bsetup clear\n");
10982 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10983 SendToICS(ics_prefix);
10984 SendToICS("clearboard\n");
10986 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10987 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10988 for (y = 0; y < BOARD_HEIGHT; y++) {
10989 if (gameMode == IcsExamining) {
10990 if (boards[currentMove][y][x] != EmptySquare) {
10991 sprintf(buf, "%sx@%c%c\n", ics_prefix,
10996 boards[0][y][x] = p;
11001 if (gameMode == EditPosition) {
11002 DrawPosition(FALSE, boards[0]);
11007 SetWhiteToPlayEvent();
11011 SetBlackToPlayEvent();
11015 if (gameMode == IcsExamining) {
11016 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11019 boards[0][y][x] = EmptySquare;
11020 DrawPosition(FALSE, boards[0]);
11025 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11026 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11027 selection = (ChessSquare) (PROMOTED piece);
11028 } else if(piece == EmptySquare) selection = WhiteSilver;
11029 else selection = (ChessSquare)((int)piece - 1);
11033 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11034 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11035 selection = (ChessSquare) (DEMOTED piece);
11036 } else if(piece == EmptySquare) selection = BlackSilver;
11037 else selection = (ChessSquare)((int)piece + 1);
11042 if(gameInfo.variant == VariantShatranj ||
11043 gameInfo.variant == VariantXiangqi ||
11044 gameInfo.variant == VariantCourier )
11045 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11050 if(gameInfo.variant == VariantXiangqi)
11051 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11052 if(gameInfo.variant == VariantKnightmate)
11053 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11056 if (gameMode == IcsExamining) {
11057 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11058 PieceToChar(selection), AAA + x, ONE + y);
11061 boards[0][y][x] = selection;
11062 DrawPosition(FALSE, boards[0]);
11070 DropMenuEvent(selection, x, y)
11071 ChessSquare selection;
11074 ChessMove moveType;
11076 switch (gameMode) {
11077 case IcsPlayingWhite:
11078 case MachinePlaysBlack:
11079 if (!WhiteOnMove(currentMove)) {
11080 DisplayMoveError(_("It is Black's turn"));
11083 moveType = WhiteDrop;
11085 case IcsPlayingBlack:
11086 case MachinePlaysWhite:
11087 if (WhiteOnMove(currentMove)) {
11088 DisplayMoveError(_("It is White's turn"));
11091 moveType = BlackDrop;
11094 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11100 if (moveType == BlackDrop && selection < BlackPawn) {
11101 selection = (ChessSquare) ((int) selection
11102 + (int) BlackPawn - (int) WhitePawn);
11104 if (boards[currentMove][y][x] != EmptySquare) {
11105 DisplayMoveError(_("That square is occupied"));
11109 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11115 /* Accept a pending offer of any kind from opponent */
11117 if (appData.icsActive) {
11118 SendToICS(ics_prefix);
11119 SendToICS("accept\n");
11120 } else if (cmailMsgLoaded) {
11121 if (currentMove == cmailOldMove &&
11122 commentList[cmailOldMove] != NULL &&
11123 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11124 "Black offers a draw" : "White offers a draw")) {
11126 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11127 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11129 DisplayError(_("There is no pending offer on this move"), 0);
11130 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11133 /* Not used for offers from chess program */
11140 /* Decline a pending offer of any kind from opponent */
11142 if (appData.icsActive) {
11143 SendToICS(ics_prefix);
11144 SendToICS("decline\n");
11145 } else if (cmailMsgLoaded) {
11146 if (currentMove == cmailOldMove &&
11147 commentList[cmailOldMove] != NULL &&
11148 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11149 "Black offers a draw" : "White offers a draw")) {
11151 AppendComment(cmailOldMove, "Draw declined");
11152 DisplayComment(cmailOldMove - 1, "Draw declined");
11155 DisplayError(_("There is no pending offer on this move"), 0);
11158 /* Not used for offers from chess program */
11165 /* Issue ICS rematch command */
11166 if (appData.icsActive) {
11167 SendToICS(ics_prefix);
11168 SendToICS("rematch\n");
11175 /* Call your opponent's flag (claim a win on time) */
11176 if (appData.icsActive) {
11177 SendToICS(ics_prefix);
11178 SendToICS("flag\n");
11180 switch (gameMode) {
11183 case MachinePlaysWhite:
11186 GameEnds(GameIsDrawn, "Both players ran out of time",
11189 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11191 DisplayError(_("Your opponent is not out of time"), 0);
11194 case MachinePlaysBlack:
11197 GameEnds(GameIsDrawn, "Both players ran out of time",
11200 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11202 DisplayError(_("Your opponent is not out of time"), 0);
11212 /* Offer draw or accept pending draw offer from opponent */
11214 if (appData.icsActive) {
11215 /* Note: tournament rules require draw offers to be
11216 made after you make your move but before you punch
11217 your clock. Currently ICS doesn't let you do that;
11218 instead, you immediately punch your clock after making
11219 a move, but you can offer a draw at any time. */
11221 SendToICS(ics_prefix);
11222 SendToICS("draw\n");
11223 } else if (cmailMsgLoaded) {
11224 if (currentMove == cmailOldMove &&
11225 commentList[cmailOldMove] != NULL &&
11226 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11227 "Black offers a draw" : "White offers a draw")) {
11228 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11229 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11230 } else if (currentMove == cmailOldMove + 1) {
11231 char *offer = WhiteOnMove(cmailOldMove) ?
11232 "White offers a draw" : "Black offers a draw";
11233 AppendComment(currentMove, offer);
11234 DisplayComment(currentMove - 1, offer);
11235 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11237 DisplayError(_("You must make your move before offering a draw"), 0);
11238 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11240 } else if (first.offeredDraw) {
11241 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11243 if (first.sendDrawOffers) {
11244 SendToProgram("draw\n", &first);
11245 userOfferedDraw = TRUE;
11253 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11255 if (appData.icsActive) {
11256 SendToICS(ics_prefix);
11257 SendToICS("adjourn\n");
11259 /* Currently GNU Chess doesn't offer or accept Adjourns */
11267 /* Offer Abort or accept pending Abort offer from opponent */
11269 if (appData.icsActive) {
11270 SendToICS(ics_prefix);
11271 SendToICS("abort\n");
11273 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11280 /* Resign. You can do this even if it's not your turn. */
11282 if (appData.icsActive) {
11283 SendToICS(ics_prefix);
11284 SendToICS("resign\n");
11286 switch (gameMode) {
11287 case MachinePlaysWhite:
11288 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11290 case MachinePlaysBlack:
11291 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11294 if (cmailMsgLoaded) {
11296 if (WhiteOnMove(cmailOldMove)) {
11297 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11299 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11301 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11312 StopObservingEvent()
11314 /* Stop observing current games */
11315 SendToICS(ics_prefix);
11316 SendToICS("unobserve\n");
11320 StopExaminingEvent()
11322 /* Stop observing current game */
11323 SendToICS(ics_prefix);
11324 SendToICS("unexamine\n");
11328 ForwardInner(target)
11333 if (appData.debugMode)
11334 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11335 target, currentMove, forwardMostMove);
11337 if (gameMode == EditPosition)
11340 if (gameMode == PlayFromGameFile && !pausing)
11343 if (gameMode == IcsExamining && pausing)
11344 limit = pauseExamForwardMostMove;
11346 limit = forwardMostMove;
11348 if (target > limit) target = limit;
11350 if (target > 0 && moveList[target - 1][0]) {
11351 int fromX, fromY, toX, toY;
11352 toX = moveList[target - 1][2] - AAA;
11353 toY = moveList[target - 1][3] - ONE;
11354 if (moveList[target - 1][1] == '@') {
11355 if (appData.highlightLastMove) {
11356 SetHighlights(-1, -1, toX, toY);
11359 fromX = moveList[target - 1][0] - AAA;
11360 fromY = moveList[target - 1][1] - ONE;
11361 if (target == currentMove + 1) {
11362 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11364 if (appData.highlightLastMove) {
11365 SetHighlights(fromX, fromY, toX, toY);
11369 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11370 gameMode == Training || gameMode == PlayFromGameFile ||
11371 gameMode == AnalyzeFile) {
11372 while (currentMove < target) {
11373 SendMoveToProgram(currentMove++, &first);
11376 currentMove = target;
11379 if (gameMode == EditGame || gameMode == EndOfGame) {
11380 whiteTimeRemaining = timeRemaining[0][currentMove];
11381 blackTimeRemaining = timeRemaining[1][currentMove];
11383 DisplayBothClocks();
11384 DisplayMove(currentMove - 1);
11385 DrawPosition(FALSE, boards[currentMove]);
11386 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11387 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11388 DisplayComment(currentMove - 1, commentList[currentMove]);
11396 if (gameMode == IcsExamining && !pausing) {
11397 SendToICS(ics_prefix);
11398 SendToICS("forward\n");
11400 ForwardInner(currentMove + 1);
11407 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11408 /* to optimze, we temporarily turn off analysis mode while we feed
11409 * the remaining moves to the engine. Otherwise we get analysis output
11412 if (first.analysisSupport) {
11413 SendToProgram("exit\nforce\n", &first);
11414 first.analyzing = FALSE;
11418 if (gameMode == IcsExamining && !pausing) {
11419 SendToICS(ics_prefix);
11420 SendToICS("forward 999999\n");
11422 ForwardInner(forwardMostMove);
11425 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11426 /* we have fed all the moves, so reactivate analysis mode */
11427 SendToProgram("analyze\n", &first);
11428 first.analyzing = TRUE;
11429 /*first.maybeThinking = TRUE;*/
11430 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11435 BackwardInner(target)
11438 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11440 if (appData.debugMode)
11441 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11442 target, currentMove, forwardMostMove);
11444 if (gameMode == EditPosition) return;
11445 if (currentMove <= backwardMostMove) {
11447 DrawPosition(full_redraw, boards[currentMove]);
11450 if (gameMode == PlayFromGameFile && !pausing)
11453 if (moveList[target][0]) {
11454 int fromX, fromY, toX, toY;
11455 toX = moveList[target][2] - AAA;
11456 toY = moveList[target][3] - ONE;
11457 if (moveList[target][1] == '@') {
11458 if (appData.highlightLastMove) {
11459 SetHighlights(-1, -1, toX, toY);
11462 fromX = moveList[target][0] - AAA;
11463 fromY = moveList[target][1] - ONE;
11464 if (target == currentMove - 1) {
11465 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11467 if (appData.highlightLastMove) {
11468 SetHighlights(fromX, fromY, toX, toY);
11472 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11473 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11474 while (currentMove > target) {
11475 SendToProgram("undo\n", &first);
11479 currentMove = target;
11482 if (gameMode == EditGame || gameMode == EndOfGame) {
11483 whiteTimeRemaining = timeRemaining[0][currentMove];
11484 blackTimeRemaining = timeRemaining[1][currentMove];
11486 DisplayBothClocks();
11487 DisplayMove(currentMove - 1);
11488 DrawPosition(full_redraw, boards[currentMove]);
11489 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11490 // [HGM] PV info: routine tests if comment empty
11491 DisplayComment(currentMove - 1, commentList[currentMove]);
11497 if (gameMode == IcsExamining && !pausing) {
11498 SendToICS(ics_prefix);
11499 SendToICS("backward\n");
11501 BackwardInner(currentMove - 1);
11508 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11509 /* to optimze, we temporarily turn off analysis mode while we undo
11510 * all the moves. Otherwise we get analysis output after each undo.
11512 if (first.analysisSupport) {
11513 SendToProgram("exit\nforce\n", &first);
11514 first.analyzing = FALSE;
11518 if (gameMode == IcsExamining && !pausing) {
11519 SendToICS(ics_prefix);
11520 SendToICS("backward 999999\n");
11522 BackwardInner(backwardMostMove);
11525 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11526 /* we have fed all the moves, so reactivate analysis mode */
11527 SendToProgram("analyze\n", &first);
11528 first.analyzing = TRUE;
11529 /*first.maybeThinking = TRUE;*/
11530 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11537 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11538 if (to >= forwardMostMove) to = forwardMostMove;
11539 if (to <= backwardMostMove) to = backwardMostMove;
11540 if (to < currentMove) {
11550 if (gameMode != IcsExamining) {
11551 DisplayError(_("You are not examining a game"), 0);
11555 DisplayError(_("You can't revert while pausing"), 0);
11558 SendToICS(ics_prefix);
11559 SendToICS("revert\n");
11565 switch (gameMode) {
11566 case MachinePlaysWhite:
11567 case MachinePlaysBlack:
11568 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11569 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11572 if (forwardMostMove < 2) return;
11573 currentMove = forwardMostMove = forwardMostMove - 2;
11574 whiteTimeRemaining = timeRemaining[0][currentMove];
11575 blackTimeRemaining = timeRemaining[1][currentMove];
11576 DisplayBothClocks();
11577 DisplayMove(currentMove - 1);
11578 ClearHighlights();/*!! could figure this out*/
11579 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11580 SendToProgram("remove\n", &first);
11581 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11584 case BeginningOfGame:
11588 case IcsPlayingWhite:
11589 case IcsPlayingBlack:
11590 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11591 SendToICS(ics_prefix);
11592 SendToICS("takeback 2\n");
11594 SendToICS(ics_prefix);
11595 SendToICS("takeback 1\n");
11604 ChessProgramState *cps;
11606 switch (gameMode) {
11607 case MachinePlaysWhite:
11608 if (!WhiteOnMove(forwardMostMove)) {
11609 DisplayError(_("It is your turn"), 0);
11614 case MachinePlaysBlack:
11615 if (WhiteOnMove(forwardMostMove)) {
11616 DisplayError(_("It is your turn"), 0);
11621 case TwoMachinesPlay:
11622 if (WhiteOnMove(forwardMostMove) ==
11623 (first.twoMachinesColor[0] == 'w')) {
11629 case BeginningOfGame:
11633 SendToProgram("?\n", cps);
11637 TruncateGameEvent()
11640 if (gameMode != EditGame) return;
11647 if (forwardMostMove > currentMove) {
11648 if (gameInfo.resultDetails != NULL) {
11649 free(gameInfo.resultDetails);
11650 gameInfo.resultDetails = NULL;
11651 gameInfo.result = GameUnfinished;
11653 forwardMostMove = currentMove;
11654 HistorySet(parseList, backwardMostMove, forwardMostMove,
11662 if (appData.noChessProgram) return;
11663 switch (gameMode) {
11664 case MachinePlaysWhite:
11665 if (WhiteOnMove(forwardMostMove)) {
11666 DisplayError(_("Wait until your turn"), 0);
11670 case BeginningOfGame:
11671 case MachinePlaysBlack:
11672 if (!WhiteOnMove(forwardMostMove)) {
11673 DisplayError(_("Wait until your turn"), 0);
11678 DisplayError(_("No hint available"), 0);
11681 SendToProgram("hint\n", &first);
11682 hintRequested = TRUE;
11688 if (appData.noChessProgram) return;
11689 switch (gameMode) {
11690 case MachinePlaysWhite:
11691 if (WhiteOnMove(forwardMostMove)) {
11692 DisplayError(_("Wait until your turn"), 0);
11696 case BeginningOfGame:
11697 case MachinePlaysBlack:
11698 if (!WhiteOnMove(forwardMostMove)) {
11699 DisplayError(_("Wait until your turn"), 0);
11704 EditPositionDone();
11706 case TwoMachinesPlay:
11711 SendToProgram("bk\n", &first);
11712 bookOutput[0] = NULLCHAR;
11713 bookRequested = TRUE;
11719 char *tags = PGNTags(&gameInfo);
11720 TagsPopUp(tags, CmailMsg());
11724 /* end button procedures */
11727 PrintPosition(fp, move)
11733 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11734 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11735 char c = PieceToChar(boards[move][i][j]);
11736 fputc(c == 'x' ? '.' : c, fp);
11737 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11740 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11741 fprintf(fp, "white to play\n");
11743 fprintf(fp, "black to play\n");
11750 if (gameInfo.white != NULL) {
11751 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11757 /* Find last component of program's own name, using some heuristics */
11759 TidyProgramName(prog, host, buf)
11760 char *prog, *host, buf[MSG_SIZ];
11763 int local = (strcmp(host, "localhost") == 0);
11764 while (!local && (p = strchr(prog, ';')) != NULL) {
11766 while (*p == ' ') p++;
11769 if (*prog == '"' || *prog == '\'') {
11770 q = strchr(prog + 1, *prog);
11772 q = strchr(prog, ' ');
11774 if (q == NULL) q = prog + strlen(prog);
11776 while (p >= prog && *p != '/' && *p != '\\') p--;
11778 if(p == prog && *p == '"') p++;
11779 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11780 memcpy(buf, p, q - p);
11781 buf[q - p] = NULLCHAR;
11789 TimeControlTagValue()
11792 if (!appData.clockMode) {
11794 } else if (movesPerSession > 0) {
11795 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11796 } else if (timeIncrement == 0) {
11797 sprintf(buf, "%ld", timeControl/1000);
11799 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11801 return StrSave(buf);
11807 /* This routine is used only for certain modes */
11808 VariantClass v = gameInfo.variant;
11809 ClearGameInfo(&gameInfo);
11810 gameInfo.variant = v;
11812 switch (gameMode) {
11813 case MachinePlaysWhite:
11814 gameInfo.event = StrSave( appData.pgnEventHeader );
11815 gameInfo.site = StrSave(HostName());
11816 gameInfo.date = PGNDate();
11817 gameInfo.round = StrSave("-");
11818 gameInfo.white = StrSave(first.tidy);
11819 gameInfo.black = StrSave(UserName());
11820 gameInfo.timeControl = TimeControlTagValue();
11823 case MachinePlaysBlack:
11824 gameInfo.event = StrSave( appData.pgnEventHeader );
11825 gameInfo.site = StrSave(HostName());
11826 gameInfo.date = PGNDate();
11827 gameInfo.round = StrSave("-");
11828 gameInfo.white = StrSave(UserName());
11829 gameInfo.black = StrSave(first.tidy);
11830 gameInfo.timeControl = TimeControlTagValue();
11833 case TwoMachinesPlay:
11834 gameInfo.event = StrSave( appData.pgnEventHeader );
11835 gameInfo.site = StrSave(HostName());
11836 gameInfo.date = PGNDate();
11837 if (matchGame > 0) {
11839 sprintf(buf, "%d", matchGame);
11840 gameInfo.round = StrSave(buf);
11842 gameInfo.round = StrSave("-");
11844 if (first.twoMachinesColor[0] == 'w') {
11845 gameInfo.white = StrSave(first.tidy);
11846 gameInfo.black = StrSave(second.tidy);
11848 gameInfo.white = StrSave(second.tidy);
11849 gameInfo.black = StrSave(first.tidy);
11851 gameInfo.timeControl = TimeControlTagValue();
11855 gameInfo.event = StrSave("Edited game");
11856 gameInfo.site = StrSave(HostName());
11857 gameInfo.date = PGNDate();
11858 gameInfo.round = StrSave("-");
11859 gameInfo.white = StrSave("-");
11860 gameInfo.black = StrSave("-");
11864 gameInfo.event = StrSave("Edited position");
11865 gameInfo.site = StrSave(HostName());
11866 gameInfo.date = PGNDate();
11867 gameInfo.round = StrSave("-");
11868 gameInfo.white = StrSave("-");
11869 gameInfo.black = StrSave("-");
11872 case IcsPlayingWhite:
11873 case IcsPlayingBlack:
11878 case PlayFromGameFile:
11879 gameInfo.event = StrSave("Game from non-PGN file");
11880 gameInfo.site = StrSave(HostName());
11881 gameInfo.date = PGNDate();
11882 gameInfo.round = StrSave("-");
11883 gameInfo.white = StrSave("?");
11884 gameInfo.black = StrSave("?");
11893 ReplaceComment(index, text)
11899 while (*text == '\n') text++;
11900 len = strlen(text);
11901 while (len > 0 && text[len - 1] == '\n') len--;
11903 if (commentList[index] != NULL)
11904 free(commentList[index]);
11907 commentList[index] = NULL;
11910 commentList[index] = (char *) malloc(len + 2);
11911 strncpy(commentList[index], text, len);
11912 commentList[index][len] = '\n';
11913 commentList[index][len + 1] = NULLCHAR;
11926 if (ch == '\r') continue;
11928 } while (ch != '\0');
11932 AppendComment(index, text)
11939 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11942 while (*text == '\n') text++;
11943 len = strlen(text);
11944 while (len > 0 && text[len - 1] == '\n') len--;
11946 if (len == 0) return;
11948 if (commentList[index] != NULL) {
11949 old = commentList[index];
11950 oldlen = strlen(old);
11951 commentList[index] = (char *) malloc(oldlen + len + 2);
11952 strcpy(commentList[index], old);
11954 strncpy(&commentList[index][oldlen], text, len);
11955 commentList[index][oldlen + len] = '\n';
11956 commentList[index][oldlen + len + 1] = NULLCHAR;
11958 commentList[index] = (char *) malloc(len + 2);
11959 strncpy(commentList[index], text, len);
11960 commentList[index][len] = '\n';
11961 commentList[index][len + 1] = NULLCHAR;
11965 static char * FindStr( char * text, char * sub_text )
11967 char * result = strstr( text, sub_text );
11969 if( result != NULL ) {
11970 result += strlen( sub_text );
11976 /* [AS] Try to extract PV info from PGN comment */
11977 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11978 char *GetInfoFromComment( int index, char * text )
11982 if( text != NULL && index > 0 ) {
11985 int time = -1, sec = 0, deci;
11986 char * s_eval = FindStr( text, "[%eval " );
11987 char * s_emt = FindStr( text, "[%emt " );
11989 if( s_eval != NULL || s_emt != NULL ) {
11993 if( s_eval != NULL ) {
11994 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11998 if( delim != ']' ) {
12003 if( s_emt != NULL ) {
12007 /* We expect something like: [+|-]nnn.nn/dd */
12010 sep = strchr( text, '/' );
12011 if( sep == NULL || sep < (text+4) ) {
12015 time = -1; sec = -1; deci = -1;
12016 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12017 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12018 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12019 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12023 if( score_lo < 0 || score_lo >= 100 ) {
12027 if(sec >= 0) time = 600*time + 10*sec; else
12028 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12030 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12032 /* [HGM] PV time: now locate end of PV info */
12033 while( *++sep >= '0' && *sep <= '9'); // strip depth
12035 while( *++sep >= '0' && *sep <= '9'); // strip time
12037 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12039 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12040 while(*sep == ' ') sep++;
12051 pvInfoList[index-1].depth = depth;
12052 pvInfoList[index-1].score = score;
12053 pvInfoList[index-1].time = 10*time; // centi-sec
12059 SendToProgram(message, cps)
12061 ChessProgramState *cps;
12063 int count, outCount, error;
12066 if (cps->pr == NULL) return;
12069 if (appData.debugMode) {
12072 fprintf(debugFP, "%ld >%-6s: %s",
12073 SubtractTimeMarks(&now, &programStartTime),
12074 cps->which, message);
12077 count = strlen(message);
12078 outCount = OutputToProcess(cps->pr, message, count, &error);
12079 if (outCount < count && !exiting
12080 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12081 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12082 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12083 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12084 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12085 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12087 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12089 gameInfo.resultDetails = buf;
12091 DisplayFatalError(buf, error, 1);
12096 ReceiveFromProgram(isr, closure, message, count, error)
12097 InputSourceRef isr;
12105 ChessProgramState *cps = (ChessProgramState *)closure;
12107 if (isr != cps->isr) return; /* Killed intentionally */
12111 _("Error: %s chess program (%s) exited unexpectedly"),
12112 cps->which, cps->program);
12113 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12114 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12115 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12116 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12118 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12120 gameInfo.resultDetails = buf;
12122 RemoveInputSource(cps->isr);
12123 DisplayFatalError(buf, 0, 1);
12126 _("Error reading from %s chess program (%s)"),
12127 cps->which, cps->program);
12128 RemoveInputSource(cps->isr);
12130 /* [AS] Program is misbehaving badly... kill it */
12131 if( count == -2 ) {
12132 DestroyChildProcess( cps->pr, 9 );
12136 DisplayFatalError(buf, error, 1);
12141 if ((end_str = strchr(message, '\r')) != NULL)
12142 *end_str = NULLCHAR;
12143 if ((end_str = strchr(message, '\n')) != NULL)
12144 *end_str = NULLCHAR;
12146 if (appData.debugMode) {
12147 TimeMark now; int print = 1;
12148 char *quote = ""; char c; int i;
12150 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12151 char start = message[0];
12152 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12153 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12154 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12155 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12156 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12157 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12158 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12159 sscanf(message, "pong %c", &c)!=1 && start != '#')
12160 { quote = "# "; print = (appData.engineComments == 2); }
12161 message[0] = start; // restore original message
12165 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12166 SubtractTimeMarks(&now, &programStartTime), cps->which,
12172 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12173 if (appData.icsEngineAnalyze) {
12174 if (strstr(message, "whisper") != NULL ||
12175 strstr(message, "kibitz") != NULL ||
12176 strstr(message, "tellics") != NULL) return;
12179 HandleMachineMove(message, cps);
12184 SendTimeControl(cps, mps, tc, inc, sd, st)
12185 ChessProgramState *cps;
12186 int mps, inc, sd, st;
12192 if( timeControl_2 > 0 ) {
12193 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12194 tc = timeControl_2;
12197 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12198 inc /= cps->timeOdds;
12199 st /= cps->timeOdds;
12201 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12204 /* Set exact time per move, normally using st command */
12205 if (cps->stKludge) {
12206 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12208 if (seconds == 0) {
12209 sprintf(buf, "level 1 %d\n", st/60);
12211 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12214 sprintf(buf, "st %d\n", st);
12217 /* Set conventional or incremental time control, using level command */
12218 if (seconds == 0) {
12219 /* Note old gnuchess bug -- minutes:seconds used to not work.
12220 Fixed in later versions, but still avoid :seconds
12221 when seconds is 0. */
12222 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12224 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12225 seconds, inc/1000);
12228 SendToProgram(buf, cps);
12230 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12231 /* Orthogonally, limit search to given depth */
12233 if (cps->sdKludge) {
12234 sprintf(buf, "depth\n%d\n", sd);
12236 sprintf(buf, "sd %d\n", sd);
12238 SendToProgram(buf, cps);
12241 if(cps->nps > 0) { /* [HGM] nps */
12242 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12244 sprintf(buf, "nps %d\n", cps->nps);
12245 SendToProgram(buf, cps);
12250 ChessProgramState *WhitePlayer()
12251 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12253 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12254 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12260 SendTimeRemaining(cps, machineWhite)
12261 ChessProgramState *cps;
12262 int /*boolean*/ machineWhite;
12264 char message[MSG_SIZ];
12267 /* Note: this routine must be called when the clocks are stopped
12268 or when they have *just* been set or switched; otherwise
12269 it will be off by the time since the current tick started.
12271 if (machineWhite) {
12272 time = whiteTimeRemaining / 10;
12273 otime = blackTimeRemaining / 10;
12275 time = blackTimeRemaining / 10;
12276 otime = whiteTimeRemaining / 10;
12278 /* [HGM] translate opponent's time by time-odds factor */
12279 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12280 if (appData.debugMode) {
12281 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12284 if (time <= 0) time = 1;
12285 if (otime <= 0) otime = 1;
12287 sprintf(message, "time %ld\n", time);
12288 SendToProgram(message, cps);
12290 sprintf(message, "otim %ld\n", otime);
12291 SendToProgram(message, cps);
12295 BoolFeature(p, name, loc, cps)
12299 ChessProgramState *cps;
12302 int len = strlen(name);
12304 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12306 sscanf(*p, "%d", &val);
12308 while (**p && **p != ' ') (*p)++;
12309 sprintf(buf, "accepted %s\n", name);
12310 SendToProgram(buf, cps);
12317 IntFeature(p, name, loc, cps)
12321 ChessProgramState *cps;
12324 int len = strlen(name);
12325 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12327 sscanf(*p, "%d", loc);
12328 while (**p && **p != ' ') (*p)++;
12329 sprintf(buf, "accepted %s\n", name);
12330 SendToProgram(buf, cps);
12337 StringFeature(p, name, loc, cps)
12341 ChessProgramState *cps;
12344 int len = strlen(name);
12345 if (strncmp((*p), name, len) == 0
12346 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12348 sscanf(*p, "%[^\"]", loc);
12349 while (**p && **p != '\"') (*p)++;
12350 if (**p == '\"') (*p)++;
12351 sprintf(buf, "accepted %s\n", name);
12352 SendToProgram(buf, cps);
12359 ParseOption(Option *opt, ChessProgramState *cps)
12360 // [HGM] options: process the string that defines an engine option, and determine
12361 // name, type, default value, and allowed value range
12363 char *p, *q, buf[MSG_SIZ];
12364 int n, min = (-1)<<31, max = 1<<31, def;
12366 if(p = strstr(opt->name, " -spin ")) {
12367 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12368 if(max < min) max = min; // enforce consistency
12369 if(def < min) def = min;
12370 if(def > max) def = max;
12375 } else if((p = strstr(opt->name, " -slider "))) {
12376 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12377 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12378 if(max < min) max = min; // enforce consistency
12379 if(def < min) def = min;
12380 if(def > max) def = max;
12384 opt->type = Spin; // Slider;
12385 } else if((p = strstr(opt->name, " -string "))) {
12386 opt->textValue = p+9;
12387 opt->type = TextBox;
12388 } else if((p = strstr(opt->name, " -file "))) {
12389 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12390 opt->textValue = p+7;
12391 opt->type = TextBox; // FileName;
12392 } else if((p = strstr(opt->name, " -path "))) {
12393 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12394 opt->textValue = p+7;
12395 opt->type = TextBox; // PathName;
12396 } else if(p = strstr(opt->name, " -check ")) {
12397 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12398 opt->value = (def != 0);
12399 opt->type = CheckBox;
12400 } else if(p = strstr(opt->name, " -combo ")) {
12401 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12402 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12403 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12404 opt->value = n = 0;
12405 while(q = StrStr(q, " /// ")) {
12406 n++; *q = 0; // count choices, and null-terminate each of them
12408 if(*q == '*') { // remember default, which is marked with * prefix
12412 cps->comboList[cps->comboCnt++] = q;
12414 cps->comboList[cps->comboCnt++] = NULL;
12416 opt->type = ComboBox;
12417 } else if(p = strstr(opt->name, " -button")) {
12418 opt->type = Button;
12419 } else if(p = strstr(opt->name, " -save")) {
12420 opt->type = SaveButton;
12421 } else return FALSE;
12422 *p = 0; // terminate option name
12423 // now look if the command-line options define a setting for this engine option.
12424 if(cps->optionSettings && cps->optionSettings[0])
12425 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12426 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12427 sprintf(buf, "option %s", p);
12428 if(p = strstr(buf, ",")) *p = 0;
12430 SendToProgram(buf, cps);
12436 FeatureDone(cps, val)
12437 ChessProgramState* cps;
12440 DelayedEventCallback cb = GetDelayedEvent();
12441 if ((cb == InitBackEnd3 && cps == &first) ||
12442 (cb == TwoMachinesEventIfReady && cps == &second)) {
12443 CancelDelayedEvent();
12444 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12446 cps->initDone = val;
12449 /* Parse feature command from engine */
12451 ParseFeatures(args, cps)
12453 ChessProgramState *cps;
12461 while (*p == ' ') p++;
12462 if (*p == NULLCHAR) return;
12464 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12465 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12466 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12467 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12468 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12469 if (BoolFeature(&p, "reuse", &val, cps)) {
12470 /* Engine can disable reuse, but can't enable it if user said no */
12471 if (!val) cps->reuse = FALSE;
12474 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12475 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12476 if (gameMode == TwoMachinesPlay) {
12477 DisplayTwoMachinesTitle();
12483 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12484 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12485 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12486 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12487 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12488 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12489 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12490 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12491 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12492 if (IntFeature(&p, "done", &val, cps)) {
12493 FeatureDone(cps, val);
12496 /* Added by Tord: */
12497 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12498 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12499 /* End of additions by Tord */
12501 /* [HGM] added features: */
12502 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12503 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12504 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12505 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12506 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12507 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12508 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12509 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12510 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12511 SendToProgram(buf, cps);
12514 if(cps->nrOptions >= MAX_OPTIONS) {
12516 sprintf(buf, "%s engine has too many options\n", cps->which);
12517 DisplayError(buf, 0);
12521 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12522 /* End of additions by HGM */
12524 /* unknown feature: complain and skip */
12526 while (*q && *q != '=') q++;
12527 sprintf(buf, "rejected %.*s\n", q-p, p);
12528 SendToProgram(buf, cps);
12534 while (*p && *p != '\"') p++;
12535 if (*p == '\"') p++;
12537 while (*p && *p != ' ') p++;
12545 PeriodicUpdatesEvent(newState)
12548 if (newState == appData.periodicUpdates)
12551 appData.periodicUpdates=newState;
12553 /* Display type changes, so update it now */
12556 /* Get the ball rolling again... */
12558 AnalysisPeriodicEvent(1);
12559 StartAnalysisClock();
12564 PonderNextMoveEvent(newState)
12567 if (newState == appData.ponderNextMove) return;
12568 if (gameMode == EditPosition) EditPositionDone();
12570 SendToProgram("hard\n", &first);
12571 if (gameMode == TwoMachinesPlay) {
12572 SendToProgram("hard\n", &second);
12575 SendToProgram("easy\n", &first);
12576 thinkOutput[0] = NULLCHAR;
12577 if (gameMode == TwoMachinesPlay) {
12578 SendToProgram("easy\n", &second);
12581 appData.ponderNextMove = newState;
12585 NewSettingEvent(option, command, value)
12591 if (gameMode == EditPosition) EditPositionDone();
12592 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12593 SendToProgram(buf, &first);
12594 if (gameMode == TwoMachinesPlay) {
12595 SendToProgram(buf, &second);
12600 ShowThinkingEvent()
12601 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12603 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12604 int newState = appData.showThinking
12605 // [HGM] thinking: other features now need thinking output as well
12606 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12608 if (oldState == newState) return;
12609 oldState = newState;
12610 if (gameMode == EditPosition) EditPositionDone();
12612 SendToProgram("post\n", &first);
12613 if (gameMode == TwoMachinesPlay) {
12614 SendToProgram("post\n", &second);
12617 SendToProgram("nopost\n", &first);
12618 thinkOutput[0] = NULLCHAR;
12619 if (gameMode == TwoMachinesPlay) {
12620 SendToProgram("nopost\n", &second);
12623 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12627 AskQuestionEvent(title, question, replyPrefix, which)
12628 char *title; char *question; char *replyPrefix; char *which;
12630 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12631 if (pr == NoProc) return;
12632 AskQuestion(title, question, replyPrefix, pr);
12636 DisplayMove(moveNumber)
12639 char message[MSG_SIZ];
12641 char cpThinkOutput[MSG_SIZ];
12643 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12645 if (moveNumber == forwardMostMove - 1 ||
12646 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12648 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12650 if (strchr(cpThinkOutput, '\n')) {
12651 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12654 *cpThinkOutput = NULLCHAR;
12657 /* [AS] Hide thinking from human user */
12658 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12659 *cpThinkOutput = NULLCHAR;
12660 if( thinkOutput[0] != NULLCHAR ) {
12663 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12664 cpThinkOutput[i] = '.';
12666 cpThinkOutput[i] = NULLCHAR;
12667 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12671 if (moveNumber == forwardMostMove - 1 &&
12672 gameInfo.resultDetails != NULL) {
12673 if (gameInfo.resultDetails[0] == NULLCHAR) {
12674 sprintf(res, " %s", PGNResult(gameInfo.result));
12676 sprintf(res, " {%s} %s",
12677 gameInfo.resultDetails, PGNResult(gameInfo.result));
12683 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12684 DisplayMessage(res, cpThinkOutput);
12686 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12687 WhiteOnMove(moveNumber) ? " " : ".. ",
12688 parseList[moveNumber], res);
12689 DisplayMessage(message, cpThinkOutput);
12694 DisplayAnalysisText(text)
12699 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12700 || appData.icsEngineAnalyze) {
12701 sprintf(buf, "Analysis (%s)", first.tidy);
12702 AnalysisPopUp(buf, text);
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]);