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));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char epStatus[MAX_MOVES];
445 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 ChessSquare FIDEArray[2][BOARD_SIZE] = {
457 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460 BlackKing, BlackBishop, BlackKnight, BlackRook }
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467 BlackKing, BlackKing, BlackKnight, BlackRook }
470 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473 { BlackRook, BlackMan, BlackBishop, BlackQueen,
474 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481 BlackKing, BlackBishop, BlackKnight, BlackRook }
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 #define GothicArray CapablancaArray
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 #define FalconArray CapablancaArray
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
569 Board initialPosition;
572 /* Convert str to a rating. Checks for special cases of "----",
574 "++++", etc. Also strips ()'s */
576 string_to_rating(str)
579 while(*str && !isdigit(*str)) ++str;
581 return 0; /* One of the special "no rating" cases */
589 /* Init programStats */
590 programStats.movelist[0] = 0;
591 programStats.depth = 0;
592 programStats.nr_moves = 0;
593 programStats.moves_left = 0;
594 programStats.nodes = 0;
595 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
596 programStats.score = 0;
597 programStats.got_only_move = 0;
598 programStats.got_fail = 0;
599 programStats.line_is_book = 0;
605 int matched, min, sec;
607 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609 GetTimeMark(&programStartTime);
610 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
613 programStats.ok_to_send = 1;
614 programStats.seen_stat = 0;
617 * Initialize game list
623 * Internet chess server status
625 if (appData.icsActive) {
626 appData.matchMode = FALSE;
627 appData.matchGames = 0;
629 appData.noChessProgram = !appData.zippyPlay;
631 appData.zippyPlay = FALSE;
632 appData.zippyTalk = FALSE;
633 appData.noChessProgram = TRUE;
635 if (*appData.icsHelper != NULLCHAR) {
636 appData.useTelnet = TRUE;
637 appData.telnetProgram = appData.icsHelper;
640 appData.zippyTalk = appData.zippyPlay = FALSE;
643 /* [AS] Initialize pv info list [HGM] and game state */
647 for( i=0; i<MAX_MOVES; i++ ) {
648 pvInfoList[i].depth = -1;
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
655 * Parse timeControl resource
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658 appData.movesPerSession)) {
660 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661 DisplayFatalError(buf, 0, 2);
665 * Parse searchTime resource
667 if (*appData.searchTime != NULLCHAR) {
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670 searchTime = min * 60;
671 } else if (matched == 2) {
672 searchTime = min * 60 + sec;
675 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676 DisplayFatalError(buf, 0, 2);
680 /* [AS] Adjudication threshold */
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
683 first.which = "first";
684 second.which = "second";
685 first.maybeThinking = second.maybeThinking = FALSE;
686 first.pr = second.pr = NoProc;
687 first.isr = second.isr = NULL;
688 first.sendTime = second.sendTime = 2;
689 first.sendDrawOffers = 1;
690 if (appData.firstPlaysBlack) {
691 first.twoMachinesColor = "black\n";
692 second.twoMachinesColor = "white\n";
694 first.twoMachinesColor = "white\n";
695 second.twoMachinesColor = "black\n";
697 first.program = appData.firstChessProgram;
698 second.program = appData.secondChessProgram;
699 first.host = appData.firstHost;
700 second.host = appData.secondHost;
701 first.dir = appData.firstDirectory;
702 second.dir = appData.secondDirectory;
703 first.other = &second;
704 second.other = &first;
705 first.initString = appData.initString;
706 second.initString = appData.secondInitString;
707 first.computerString = appData.firstComputerString;
708 second.computerString = appData.secondComputerString;
709 first.useSigint = second.useSigint = TRUE;
710 first.useSigterm = second.useSigterm = TRUE;
711 first.reuse = appData.reuseFirst;
712 second.reuse = appData.reuseSecond;
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
714 second.nps = appData.secondNPS;
715 first.useSetboard = second.useSetboard = FALSE;
716 first.useSAN = second.useSAN = FALSE;
717 first.usePing = second.usePing = FALSE;
718 first.lastPing = second.lastPing = 0;
719 first.lastPong = second.lastPong = 0;
720 first.usePlayother = second.usePlayother = FALSE;
721 first.useColors = second.useColors = TRUE;
722 first.useUsermove = second.useUsermove = FALSE;
723 first.sendICS = second.sendICS = FALSE;
724 first.sendName = second.sendName = appData.icsActive;
725 first.sdKludge = second.sdKludge = FALSE;
726 first.stKludge = second.stKludge = FALSE;
727 TidyProgramName(first.program, first.host, first.tidy);
728 TidyProgramName(second.program, second.host, second.tidy);
729 first.matchWins = second.matchWins = 0;
730 strcpy(first.variants, appData.variant);
731 strcpy(second.variants, appData.variant);
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
733 first.analyzing = second.analyzing = FALSE;
734 first.initDone = second.initDone = FALSE;
736 /* New features added by Tord: */
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739 /* End of new features added by Tord. */
740 first.fenOverride = appData.fenOverride1;
741 second.fenOverride = appData.fenOverride2;
743 /* [HGM] time odds: set factor for each machine */
744 first.timeOdds = appData.firstTimeOdds;
745 second.timeOdds = appData.secondTimeOdds;
747 if(appData.timeOddsMode) {
748 norm = first.timeOdds;
749 if(norm > second.timeOdds) norm = second.timeOdds;
751 first.timeOdds /= norm;
752 second.timeOdds /= norm;
755 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756 first.accumulateTC = appData.firstAccumulateTC;
757 second.accumulateTC = appData.secondAccumulateTC;
758 first.maxNrOfSessions = second.maxNrOfSessions = 1;
761 first.debug = second.debug = FALSE;
762 first.supportsNPS = second.supportsNPS = UNKNOWN;
765 first.optionSettings = appData.firstOptions;
766 second.optionSettings = appData.secondOptions;
768 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770 first.isUCI = appData.firstIsUCI; /* [AS] */
771 second.isUCI = appData.secondIsUCI; /* [AS] */
772 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775 if (appData.firstProtocolVersion > PROTOVER ||
776 appData.firstProtocolVersion < 1) {
778 sprintf(buf, _("protocol version %d not supported"),
779 appData.firstProtocolVersion);
780 DisplayFatalError(buf, 0, 2);
782 first.protocolVersion = appData.firstProtocolVersion;
785 if (appData.secondProtocolVersion > PROTOVER ||
786 appData.secondProtocolVersion < 1) {
788 sprintf(buf, _("protocol version %d not supported"),
789 appData.secondProtocolVersion);
790 DisplayFatalError(buf, 0, 2);
792 second.protocolVersion = appData.secondProtocolVersion;
795 if (appData.icsActive) {
796 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
797 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798 appData.clockMode = FALSE;
799 first.sendTime = second.sendTime = 0;
803 /* Override some settings from environment variables, for backward
804 compatibility. Unfortunately it's not feasible to have the env
805 vars just set defaults, at least in xboard. Ugh.
807 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
812 if (appData.noChessProgram) {
813 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814 sprintf(programVersion, "%s", PACKAGE_STRING);
816 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821 if (!appData.icsActive) {
823 /* Check for variants that are supported only in ICS mode,
824 or not at all. Some that are accepted here nevertheless
825 have bugs; see comments below.
827 VariantClass variant = StringToVariant(appData.variant);
829 case VariantBughouse: /* need four players and two boards */
830 case VariantKriegspiel: /* need to hide pieces and move details */
831 /* case VariantFischeRandom: (Fabien: moved below) */
832 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833 DisplayFatalError(buf, 0, 2);
837 case VariantLoadable:
847 sprintf(buf, _("Unknown variant name %s"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
851 case VariantXiangqi: /* [HGM] repetition rules not implemented */
852 case VariantFairy: /* [HGM] TestLegality definitely off! */
853 case VariantGothic: /* [HGM] should work */
854 case VariantCapablanca: /* [HGM] should work */
855 case VariantCourier: /* [HGM] initial forced moves not implemented */
856 case VariantShogi: /* [HGM] drops not tested for legality */
857 case VariantKnightmate: /* [HGM] should work */
858 case VariantCylinder: /* [HGM] untested */
859 case VariantFalcon: /* [HGM] untested */
860 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861 offboard interposition not understood */
862 case VariantNormal: /* definitely works! */
863 case VariantWildCastle: /* pieces not automatically shuffled */
864 case VariantNoCastle: /* pieces not automatically shuffled */
865 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866 case VariantLosers: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantSuicide: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantGiveaway: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantTwoKings: /* should work */
873 case VariantAtomic: /* should work except for win condition */
874 case Variant3Check: /* should work except for win condition */
875 case VariantShatranj: /* should work except for all win conditions */
876 case VariantBerolina: /* might work if TestLegality is off */
877 case VariantCapaRandom: /* should work */
878 case VariantJanus: /* should work */
879 case VariantSuper: /* experimental */
880 case VariantGreat: /* experimental, requires legality testing to be off */
885 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
886 InitEngineUCI( installDir, &second );
889 int NextIntegerFromString( char ** str, long * value )
894 while( *s == ' ' || *s == '\t' ) {
900 if( *s >= '0' && *s <= '9' ) {
901 while( *s >= '0' && *s <= '9' ) {
902 *value = *value * 10 + (*s - '0');
914 int NextTimeControlFromString( char ** str, long * value )
917 int result = NextIntegerFromString( str, &temp );
920 *value = temp * 60; /* Minutes */
923 result = NextIntegerFromString( str, &temp );
924 *value += temp; /* Seconds */
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 { /* [HGM] routine added to read '+moves/time' for secondary time control */
933 int result = -1; long temp, temp2;
935 if(**str != '+') return -1; // old params remain in force!
937 if( NextTimeControlFromString( str, &temp ) ) return -1;
940 /* time only: incremental or sudden-death time control */
941 if(**str == '+') { /* increment follows; read it */
943 if(result = NextIntegerFromString( str, &temp2)) return -1;
946 *moves = 0; *tc = temp * 1000;
948 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
950 (*str)++; /* classical time control */
951 result = NextTimeControlFromString( str, &temp2);
960 int GetTimeQuota(int movenr)
961 { /* [HGM] get time to add from the multi-session time-control string */
962 int moves=1; /* kludge to force reading of first session */
963 long time, increment;
964 char *s = fullTimeControlString;
966 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970 if(movenr == -1) return time; /* last move before new session */
971 if(!moves) return increment; /* current session is incremental */
972 if(movenr >= 0) movenr -= moves; /* we already finished this session */
973 } while(movenr >= -1); /* try again for next session */
975 return 0; // no new time quota on this move
979 ParseTimeControl(tc, ti, mps)
988 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992 else sprintf(buf, "+%s+%d", tc, ti);
995 sprintf(buf, "+%d/%s", mps, tc);
996 else sprintf(buf, "+%s", tc);
998 fullTimeControlString = StrSave(buf);
1000 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1005 /* Parse second time control */
1008 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1016 timeControl_2 = tc2 * 1000;
1026 timeControl = tc1 * 1000;
1029 timeIncrement = ti * 1000; /* convert to ms */
1030 movesPerSession = 0;
1033 movesPerSession = mps;
1041 if (appData.debugMode) {
1042 fprintf(debugFP, "%s\n", programVersion);
1045 if (appData.matchGames > 0) {
1046 appData.matchMode = TRUE;
1047 } else if (appData.matchMode) {
1048 appData.matchGames = 1;
1050 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051 appData.matchGames = appData.sameColorGames;
1052 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1057 if (appData.noChessProgram || first.protocolVersion == 1) {
1060 /* kludge: allow timeout for initial "feature" commands */
1062 DisplayMessage("", _("Starting chess program"));
1063 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1068 InitBackEnd3 P((void))
1070 GameMode initialMode;
1074 InitChessProgram(&first, startedFromSetupPosition);
1077 if (appData.icsActive) {
1079 /* [DM] Make a console window if needed [HGM] merged ifs */
1084 if (*appData.icsCommPort != NULLCHAR) {
1085 sprintf(buf, _("Could not open comm port %s"),
1086 appData.icsCommPort);
1088 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1089 appData.icsHost, appData.icsPort);
1091 DisplayFatalError(buf, err, 1);
1096 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099 } else if (appData.noChessProgram) {
1105 if (*appData.cmailGameName != NULLCHAR) {
1107 OpenLoopback(&cmailPR);
1109 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1113 DisplayMessage("", "");
1114 if (StrCaseCmp(appData.initialMode, "") == 0) {
1115 initialMode = BeginningOfGame;
1116 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117 initialMode = TwoMachinesPlay;
1118 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119 initialMode = AnalyzeFile;
1120 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121 initialMode = AnalyzeMode;
1122 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123 initialMode = MachinePlaysWhite;
1124 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125 initialMode = MachinePlaysBlack;
1126 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127 initialMode = EditGame;
1128 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129 initialMode = EditPosition;
1130 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131 initialMode = Training;
1133 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134 DisplayFatalError(buf, 0, 2);
1138 if (appData.matchMode) {
1139 /* Set up machine vs. machine match */
1140 if (appData.noChessProgram) {
1141 DisplayFatalError(_("Can't have a match with no chess programs"),
1147 if (*appData.loadGameFile != NULLCHAR) {
1148 int index = appData.loadGameIndex; // [HGM] autoinc
1149 if(index<0) lastIndex = index = 1;
1150 if (!LoadGameFromFile(appData.loadGameFile,
1152 appData.loadGameFile, FALSE)) {
1153 DisplayFatalError(_("Bad game file"), 0, 1);
1156 } else if (*appData.loadPositionFile != NULLCHAR) {
1157 int index = appData.loadPositionIndex; // [HGM] autoinc
1158 if(index<0) lastIndex = index = 1;
1159 if (!LoadPositionFromFile(appData.loadPositionFile,
1161 appData.loadPositionFile)) {
1162 DisplayFatalError(_("Bad position file"), 0, 1);
1167 } else if (*appData.cmailGameName != NULLCHAR) {
1168 /* Set up cmail mode */
1169 ReloadCmailMsgEvent(TRUE);
1171 /* Set up other modes */
1172 if (initialMode == AnalyzeFile) {
1173 if (*appData.loadGameFile == NULLCHAR) {
1174 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1178 if (*appData.loadGameFile != NULLCHAR) {
1179 (void) LoadGameFromFile(appData.loadGameFile,
1180 appData.loadGameIndex,
1181 appData.loadGameFile, TRUE);
1182 } else if (*appData.loadPositionFile != NULLCHAR) {
1183 (void) LoadPositionFromFile(appData.loadPositionFile,
1184 appData.loadPositionIndex,
1185 appData.loadPositionFile);
1186 /* [HGM] try to make self-starting even after FEN load */
1187 /* to allow automatic setup of fairy variants with wtm */
1188 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189 gameMode = BeginningOfGame;
1190 setboardSpoiledMachineBlack = 1;
1192 /* [HGM] loadPos: make that every new game uses the setup */
1193 /* from file as long as we do not switch variant */
1194 if(!blackPlaysFirst) { int i;
1195 startedFromPositionFile = TRUE;
1196 CopyBoard(filePosition, boards[0]);
1197 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1200 if (initialMode == AnalyzeMode) {
1201 if (appData.noChessProgram) {
1202 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1205 if (appData.icsActive) {
1206 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1210 } else if (initialMode == AnalyzeFile) {
1211 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212 ShowThinkingEvent();
1214 AnalysisPeriodicEvent(1);
1215 } else if (initialMode == MachinePlaysWhite) {
1216 if (appData.noChessProgram) {
1217 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1221 if (appData.icsActive) {
1222 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1226 MachineWhiteEvent();
1227 } else if (initialMode == MachinePlaysBlack) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1238 MachineBlackEvent();
1239 } else if (initialMode == TwoMachinesPlay) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1251 } else if (initialMode == EditGame) {
1253 } else if (initialMode == EditPosition) {
1254 EditPositionEvent();
1255 } else if (initialMode == Training) {
1256 if (*appData.loadGameFile == NULLCHAR) {
1257 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1266 * Establish will establish a contact to a remote host.port.
1267 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268 * used to talk to the host.
1269 * Returns 0 if okay, error code if not.
1276 if (*appData.icsCommPort != NULLCHAR) {
1277 /* Talk to the host through a serial comm port */
1278 return OpenCommPort(appData.icsCommPort, &icsPR);
1280 } else if (*appData.gateway != NULLCHAR) {
1281 if (*appData.remoteShell == NULLCHAR) {
1282 /* Use the rcmd protocol to run telnet program on a gateway host */
1283 snprintf(buf, sizeof(buf), "%s %s %s",
1284 appData.telnetProgram, appData.icsHost, appData.icsPort);
1285 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1288 /* Use the rsh program to run telnet program on a gateway host */
1289 if (*appData.remoteUser == NULLCHAR) {
1290 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291 appData.gateway, appData.telnetProgram,
1292 appData.icsHost, appData.icsPort);
1294 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295 appData.remoteShell, appData.gateway,
1296 appData.remoteUser, appData.telnetProgram,
1297 appData.icsHost, appData.icsPort);
1299 return StartChildProcess(buf, "", &icsPR);
1302 } else if (appData.useTelnet) {
1303 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1306 /* TCP socket interface differs somewhat between
1307 Unix and NT; handle details in the front end.
1309 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1314 show_bytes(fp, buf, count)
1320 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321 fprintf(fp, "\\%03o", *buf & 0xff);
1330 /* Returns an errno value */
1332 OutputMaybeTelnet(pr, message, count, outError)
1338 char buf[8192], *p, *q, *buflim;
1339 int left, newcount, outcount;
1341 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342 *appData.gateway != NULLCHAR) {
1343 if (appData.debugMode) {
1344 fprintf(debugFP, ">ICS: ");
1345 show_bytes(debugFP, message, count);
1346 fprintf(debugFP, "\n");
1348 return OutputToProcess(pr, message, count, outError);
1351 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1358 if (appData.debugMode) {
1359 fprintf(debugFP, ">ICS: ");
1360 show_bytes(debugFP, buf, newcount);
1361 fprintf(debugFP, "\n");
1363 outcount = OutputToProcess(pr, buf, newcount, outError);
1364 if (outcount < newcount) return -1; /* to be sure */
1371 } else if (((unsigned char) *p) == TN_IAC) {
1372 *q++ = (char) TN_IAC;
1379 if (appData.debugMode) {
1380 fprintf(debugFP, ">ICS: ");
1381 show_bytes(debugFP, buf, newcount);
1382 fprintf(debugFP, "\n");
1384 outcount = OutputToProcess(pr, buf, newcount, outError);
1385 if (outcount < newcount) return -1; /* to be sure */
1390 read_from_player(isr, closure, message, count, error)
1397 int outError, outCount;
1398 static int gotEof = 0;
1400 /* Pass data read from player on to ICS */
1403 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404 if (outCount < count) {
1405 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407 } else if (count < 0) {
1408 RemoveInputSource(isr);
1409 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410 } else if (gotEof++ > 0) {
1411 RemoveInputSource(isr);
1412 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1418 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419 SendToICS("date\n");
1420 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1426 char buffer[MSG_SIZ];
1429 va_start(args, format);
1430 vsnprintf(buffer, sizeof(buffer), format, args);
1431 buffer[sizeof(buffer)-1] = '\0';
1440 int count, outCount, outError;
1442 if (icsPR == NULL) return;
1445 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1446 if (outCount < count) {
1447 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1451 /* This is used for sending logon scripts to the ICS. Sending
1452 without a delay causes problems when using timestamp on ICC
1453 (at least on my machine). */
1455 SendToICSDelayed(s,msdelay)
1459 int count, outCount, outError;
1461 if (icsPR == NULL) return;
1464 if (appData.debugMode) {
1465 fprintf(debugFP, ">ICS: ");
1466 show_bytes(debugFP, s, count);
1467 fprintf(debugFP, "\n");
1469 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471 if (outCount < count) {
1472 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1477 /* Remove all highlighting escape sequences in s
1478 Also deletes any suffix starting with '('
1481 StripHighlightAndTitle(s)
1484 static char retbuf[MSG_SIZ];
1487 while (*s != NULLCHAR) {
1488 while (*s == '\033') {
1489 while (*s != NULLCHAR && !isalpha(*s)) s++;
1490 if (*s != NULLCHAR) s++;
1492 while (*s != NULLCHAR && *s != '\033') {
1493 if (*s == '(' || *s == '[') {
1504 /* Remove all highlighting escape sequences in s */
1509 static char retbuf[MSG_SIZ];
1512 while (*s != NULLCHAR) {
1513 while (*s == '\033') {
1514 while (*s != NULLCHAR && !isalpha(*s)) s++;
1515 if (*s != NULLCHAR) s++;
1517 while (*s != NULLCHAR && *s != '\033') {
1525 char *variantNames[] = VARIANT_NAMES;
1530 return variantNames[v];
1534 /* Identify a variant from the strings the chess servers use or the
1535 PGN Variant tag names we use. */
1542 VariantClass v = VariantNormal;
1543 int i, found = FALSE;
1548 /* [HGM] skip over optional board-size prefixes */
1549 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1550 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1551 while( *e++ != '_');
1554 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1558 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1559 if (StrCaseStr(e, variantNames[i])) {
1560 v = (VariantClass) i;
1567 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1568 || StrCaseStr(e, "wild/fr")
1569 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1570 v = VariantFischeRandom;
1571 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1572 (i = 1, p = StrCaseStr(e, "w"))) {
1574 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1581 case 0: /* FICS only, actually */
1583 /* Castling legal even if K starts on d-file */
1584 v = VariantWildCastle;
1589 /* Castling illegal even if K & R happen to start in
1590 normal positions. */
1591 v = VariantNoCastle;
1604 /* Castling legal iff K & R start in normal positions */
1610 /* Special wilds for position setup; unclear what to do here */
1611 v = VariantLoadable;
1614 /* Bizarre ICC game */
1615 v = VariantTwoKings;
1618 v = VariantKriegspiel;
1624 v = VariantFischeRandom;
1627 v = VariantCrazyhouse;
1630 v = VariantBughouse;
1636 /* Not quite the same as FICS suicide! */
1637 v = VariantGiveaway;
1643 v = VariantShatranj;
1646 /* Temporary names for future ICC types. The name *will* change in
1647 the next xboard/WinBoard release after ICC defines it. */
1685 v = VariantCapablanca;
1688 v = VariantKnightmate;
1694 v = VariantCylinder;
1700 v = VariantCapaRandom;
1703 v = VariantBerolina;
1715 /* Found "wild" or "w" in the string but no number;
1716 must assume it's normal chess. */
1720 sprintf(buf, _("Unknown wild type %d"), wnum);
1721 DisplayError(buf, 0);
1727 if (appData.debugMode) {
1728 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1729 e, wnum, VariantName(v));
1734 static int leftover_start = 0, leftover_len = 0;
1735 char star_match[STAR_MATCH_N][MSG_SIZ];
1737 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1738 advance *index beyond it, and set leftover_start to the new value of
1739 *index; else return FALSE. If pattern contains the character '*', it
1740 matches any sequence of characters not containing '\r', '\n', or the
1741 character following the '*' (if any), and the matched sequence(s) are
1742 copied into star_match.
1745 looking_at(buf, index, pattern)
1750 char *bufp = &buf[*index], *patternp = pattern;
1752 char *matchp = star_match[0];
1755 if (*patternp == NULLCHAR) {
1756 *index = leftover_start = bufp - buf;
1760 if (*bufp == NULLCHAR) return FALSE;
1761 if (*patternp == '*') {
1762 if (*bufp == *(patternp + 1)) {
1764 matchp = star_match[++star_count];
1768 } else if (*bufp == '\n' || *bufp == '\r') {
1770 if (*patternp == NULLCHAR)
1775 *matchp++ = *bufp++;
1779 if (*patternp != *bufp) return FALSE;
1786 SendToPlayer(data, length)
1790 int error, outCount;
1791 outCount = OutputToProcess(NoProc, data, length, &error);
1792 if (outCount < length) {
1793 DisplayFatalError(_("Error writing to display"), error, 1);
1798 PackHolding(packed, holding)
1810 switch (runlength) {
1821 sprintf(q, "%d", runlength);
1833 /* Telnet protocol requests from the front end */
1835 TelnetRequest(ddww, option)
1836 unsigned char ddww, option;
1838 unsigned char msg[3];
1839 int outCount, outError;
1841 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843 if (appData.debugMode) {
1844 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1860 sprintf(buf1, "%d", ddww);
1869 sprintf(buf2, "%d", option);
1872 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1877 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1886 if (!appData.icsActive) return;
1887 TelnetRequest(TN_DO, TN_ECHO);
1893 if (!appData.icsActive) return;
1894 TelnetRequest(TN_DONT, TN_ECHO);
1898 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 /* put the holdings sent to us by the server on the board holdings area */
1901 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1905 if(gameInfo.holdingsWidth < 2) return;
1907 if( (int)lowestPiece >= BlackPawn ) {
1910 holdingsStartRow = BOARD_HEIGHT-1;
1913 holdingsColumn = BOARD_WIDTH-1;
1914 countsColumn = BOARD_WIDTH-2;
1915 holdingsStartRow = 0;
1919 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1920 board[i][holdingsColumn] = EmptySquare;
1921 board[i][countsColumn] = (ChessSquare) 0;
1923 while( (p=*holdings++) != NULLCHAR ) {
1924 piece = CharToPiece( ToUpper(p) );
1925 if(piece == EmptySquare) continue;
1926 /*j = (int) piece - (int) WhitePawn;*/
1927 j = PieceToNumber(piece);
1928 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1929 if(j < 0) continue; /* should not happen */
1930 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1931 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1932 board[holdingsStartRow+j*direction][countsColumn]++;
1939 VariantSwitch(Board board, VariantClass newVariant)
1941 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943 startedFromPositionFile = FALSE;
1944 if(gameInfo.variant == newVariant) return;
1946 /* [HGM] This routine is called each time an assignment is made to
1947 * gameInfo.variant during a game, to make sure the board sizes
1948 * are set to match the new variant. If that means adding or deleting
1949 * holdings, we shift the playing board accordingly
1950 * This kludge is needed because in ICS observe mode, we get boards
1951 * of an ongoing game without knowing the variant, and learn about the
1952 * latter only later. This can be because of the move list we requested,
1953 * in which case the game history is refilled from the beginning anyway,
1954 * but also when receiving holdings of a crazyhouse game. In the latter
1955 * case we want to add those holdings to the already received position.
1959 if (appData.debugMode) {
1960 fprintf(debugFP, "Switch board from %s to %s\n",
1961 VariantName(gameInfo.variant), VariantName(newVariant));
1962 setbuf(debugFP, NULL);
1964 shuffleOpenings = 0; /* [HGM] shuffle */
1965 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969 newWidth = 9; newHeight = 9;
1970 gameInfo.holdingsSize = 7;
1971 case VariantBughouse:
1972 case VariantCrazyhouse:
1973 newHoldingsWidth = 2; break;
1977 newHoldingsWidth = 2;
1978 gameInfo.holdingsSize = 8;
1981 case VariantCapablanca:
1982 case VariantCapaRandom:
1985 newHoldingsWidth = gameInfo.holdingsSize = 0;
1988 if(newWidth != gameInfo.boardWidth ||
1989 newHeight != gameInfo.boardHeight ||
1990 newHoldingsWidth != gameInfo.holdingsWidth ) {
1992 /* shift position to new playing area, if needed */
1993 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994 for(i=0; i<BOARD_HEIGHT; i++)
1995 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998 for(i=0; i<newHeight; i++) {
1999 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003 for(i=0; i<BOARD_HEIGHT; i++)
2004 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2008 gameInfo.boardWidth = newWidth;
2009 gameInfo.boardHeight = newHeight;
2010 gameInfo.holdingsWidth = newHoldingsWidth;
2011 gameInfo.variant = newVariant;
2012 InitDrawingSizes(-2, 0);
2013 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2014 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2016 DrawPosition(TRUE, boards[currentMove]);
2019 static int loggedOn = FALSE;
2021 /*-- Game start info cache: --*/
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
\r
2026 static char cont_seq[] = "\n\\ ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2035 read_from_ics(isr, closure, data, count, error)
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2052 static int started = STARTED_NONE;
2053 static char parse[20000];
2054 static int parse_pos = 0;
2055 static char buf[BUF_SIZE + 1];
2056 static int firstTime = TRUE, intfSet = FALSE;
2057 static ColorClass prevColor = ColorNormal;
2058 static int savingComment = FALSE;
2059 static int cmatch = 0; // continuation sequence match
2066 int backup; /* [DM] For zippy color lines */
2068 char talker[MSG_SIZ]; // [HGM] chat
2071 if (appData.debugMode) {
2073 fprintf(debugFP, "<ICS: ");
2074 show_bytes(debugFP, data, count);
2075 fprintf(debugFP, "\n");
2079 if (appData.debugMode) { int f = forwardMostMove;
2080 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2084 /* If last read ended with a partial line that we couldn't parse,
2085 prepend it to the new read and try again. */
2086 if (leftover_len > 0) {
2087 for (i=0; i<leftover_len; i++)
2088 buf[i] = buf[leftover_start + i];
2091 /* copy new characters into the buffer */
2092 bp = buf + leftover_len;
2093 buf_len=leftover_len;
2094 for (i=0; i<count; i++)
2097 if (data[i] == '\r')
2100 // join lines split by ICS?
2101 if (!appData.noJoin)
2104 Joining just consists of finding matches against the
2105 continuation sequence, and discarding that sequence
2106 if found instead of copying it. So, until a match
2107 fails, there's nothing to do since it might be the
2108 complete sequence, and thus, something we don't want
2111 if (data[i] == cont_seq[cmatch])
2114 if (cmatch == strlen(cont_seq))
2116 cmatch = 0; // complete match. just reset the counter
2119 it's possible for the ICS to not include the space
2120 at the end of the last word, making our [correct]
2121 join operation fuse two separate words. the server
2122 does this when the space occurs at the width setting.
2124 if (!buf_len || buf[buf_len-1] != ' ')
2135 match failed, so we have to copy what matched before
2136 falling through and copying this character. In reality,
2137 this will only ever be just the newline character, but
2138 it doesn't hurt to be precise.
2140 strncpy(bp, cont_seq, cmatch);
2152 buf[buf_len] = NULLCHAR;
2153 next_out = leftover_len;
2157 while (i < buf_len) {
2158 /* Deal with part of the TELNET option negotiation
2159 protocol. We refuse to do anything beyond the
2160 defaults, except that we allow the WILL ECHO option,
2161 which ICS uses to turn off password echoing when we are
2162 directly connected to it. We reject this option
2163 if localLineEditing mode is on (always on in xboard)
2164 and we are talking to port 23, which might be a real
2165 telnet server that will try to keep WILL ECHO on permanently.
2167 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2168 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2169 unsigned char option;
2171 switch ((unsigned char) buf[++i]) {
2173 if (appData.debugMode)
2174 fprintf(debugFP, "\n<WILL ");
2175 switch (option = (unsigned char) buf[++i]) {
2177 if (appData.debugMode)
2178 fprintf(debugFP, "ECHO ");
2179 /* Reply only if this is a change, according
2180 to the protocol rules. */
2181 if (remoteEchoOption) break;
2182 if (appData.localLineEditing &&
2183 atoi(appData.icsPort) == TN_PORT) {
2184 TelnetRequest(TN_DONT, TN_ECHO);
2187 TelnetRequest(TN_DO, TN_ECHO);
2188 remoteEchoOption = TRUE;
2192 if (appData.debugMode)
2193 fprintf(debugFP, "%d ", option);
2194 /* Whatever this is, we don't want it. */
2195 TelnetRequest(TN_DONT, option);
2200 if (appData.debugMode)
2201 fprintf(debugFP, "\n<WONT ");
2202 switch (option = (unsigned char) buf[++i]) {
2204 if (appData.debugMode)
2205 fprintf(debugFP, "ECHO ");
2206 /* Reply only if this is a change, according
2207 to the protocol rules. */
2208 if (!remoteEchoOption) break;
2210 TelnetRequest(TN_DONT, TN_ECHO);
2211 remoteEchoOption = FALSE;
2214 if (appData.debugMode)
2215 fprintf(debugFP, "%d ", (unsigned char) option);
2216 /* Whatever this is, it must already be turned
2217 off, because we never agree to turn on
2218 anything non-default, so according to the
2219 protocol rules, we don't reply. */
2224 if (appData.debugMode)
2225 fprintf(debugFP, "\n<DO ");
2226 switch (option = (unsigned char) buf[++i]) {
2228 /* Whatever this is, we refuse to do it. */
2229 if (appData.debugMode)
2230 fprintf(debugFP, "%d ", option);
2231 TelnetRequest(TN_WONT, option);
2236 if (appData.debugMode)
2237 fprintf(debugFP, "\n<DONT ");
2238 switch (option = (unsigned char) buf[++i]) {
2240 if (appData.debugMode)
2241 fprintf(debugFP, "%d ", option);
2242 /* Whatever this is, we are already not doing
2243 it, because we never agree to do anything
2244 non-default, so according to the protocol
2245 rules, we don't reply. */
2250 if (appData.debugMode)
2251 fprintf(debugFP, "\n<IAC ");
2252 /* Doubled IAC; pass it through */
2256 if (appData.debugMode)
2257 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2258 /* Drop all other telnet commands on the floor */
2261 if (oldi > next_out)
2262 SendToPlayer(&buf[next_out], oldi - next_out);
2268 /* OK, this at least will *usually* work */
2269 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2273 if (loggedOn && !intfSet) {
2274 if (ics_type == ICS_ICC) {
2276 "/set-quietly interface %s\n/set-quietly style 12\n",
2278 } else if (ics_type == ICS_CHESSNET) {
2279 sprintf(str, "/style 12\n");
2281 strcpy(str, "alias $ @\n$set interface ");
2282 strcat(str, programVersion);
2283 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2285 strcat(str, "$iset nohighlight 1\n");
2287 strcat(str, "$iset lock 1\n$style 12\n");
2290 NotifyFrontendLogin();
2294 if (started == STARTED_COMMENT) {
2295 /* Accumulate characters in comment */
2296 parse[parse_pos++] = buf[i];
2297 if (buf[i] == '\n') {
2298 parse[parse_pos] = NULLCHAR;
2299 if(chattingPartner>=0) {
2301 sprintf(mess, "%s%s", talker, parse);
2302 OutputChatMessage(chattingPartner, mess);
2303 chattingPartner = -1;
2305 if(!suppressKibitz) // [HGM] kibitz
2306 AppendComment(forwardMostMove, StripHighlight(parse));
2307 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2308 int nrDigit = 0, nrAlph = 0, i;
2309 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2310 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2311 parse[parse_pos] = NULLCHAR;
2312 // try to be smart: if it does not look like search info, it should go to
2313 // ICS interaction window after all, not to engine-output window.
2314 for(i=0; i<parse_pos; i++) { // count letters and digits
2315 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2316 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2317 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2319 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2320 int depth=0; float score;
2321 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2322 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2323 pvInfoList[forwardMostMove-1].depth = depth;
2324 pvInfoList[forwardMostMove-1].score = 100*score;
2326 OutputKibitz(suppressKibitz, parse);
2329 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2330 SendToPlayer(tmp, strlen(tmp));
2333 started = STARTED_NONE;
2335 /* Don't match patterns against characters in chatter */
2340 if (started == STARTED_CHATTER) {
2341 if (buf[i] != '\n') {
2342 /* Don't match patterns against characters in chatter */
2346 started = STARTED_NONE;
2349 /* Kludge to deal with rcmd protocol */
2350 if (firstTime && looking_at(buf, &i, "\001*")) {
2351 DisplayFatalError(&buf[1], 0, 1);
2357 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2360 if (appData.debugMode)
2361 fprintf(debugFP, "ics_type %d\n", ics_type);
2364 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2365 ics_type = ICS_FICS;
2367 if (appData.debugMode)
2368 fprintf(debugFP, "ics_type %d\n", ics_type);
2371 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2372 ics_type = ICS_CHESSNET;
2374 if (appData.debugMode)
2375 fprintf(debugFP, "ics_type %d\n", ics_type);
2380 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2381 looking_at(buf, &i, "Logging you in as \"*\"") ||
2382 looking_at(buf, &i, "will be \"*\""))) {
2383 strcpy(ics_handle, star_match[0]);
2387 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2389 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2390 DisplayIcsInteractionTitle(buf);
2391 have_set_title = TRUE;
2394 /* skip finger notes */
2395 if (started == STARTED_NONE &&
2396 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2397 (buf[i] == '1' && buf[i+1] == '0')) &&
2398 buf[i+2] == ':' && buf[i+3] == ' ') {
2399 started = STARTED_CHATTER;
2404 /* skip formula vars */
2405 if (started == STARTED_NONE &&
2406 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2407 started = STARTED_CHATTER;
2413 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2414 if (appData.autoKibitz && started == STARTED_NONE &&
2415 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2416 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2417 if(looking_at(buf, &i, "* kibitzes: ") &&
2418 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2419 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2420 suppressKibitz = TRUE;
2421 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2422 && (gameMode == IcsPlayingWhite)) ||
2423 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2424 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2425 started = STARTED_CHATTER; // own kibitz we simply discard
2427 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2428 parse_pos = 0; parse[0] = NULLCHAR;
2429 savingComment = TRUE;
2430 suppressKibitz = gameMode != IcsObserving ? 2 :
2431 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2435 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2436 started = STARTED_CHATTER;
2437 suppressKibitz = TRUE;
2439 } // [HGM] kibitz: end of patch
2441 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2443 // [HGM] chat: intercept tells by users for which we have an open chat window
2445 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2446 looking_at(buf, &i, "* whispers:") ||
2447 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2448 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2450 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2451 chattingPartner = -1;
2453 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2454 for(p=0; p<MAX_CHAT; p++) {
2455 if(channel == atoi(chatPartner[p])) {
2456 talker[0] = '['; strcat(talker, "]");
2457 chattingPartner = p; break;
2460 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2461 for(p=0; p<MAX_CHAT; p++) {
2462 if(!strcmp("WHISPER", chatPartner[p])) {
2463 talker[0] = '['; strcat(talker, "]");
2464 chattingPartner = p; break;
2467 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2468 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2470 chattingPartner = p; break;
2472 if(chattingPartner<0) i = oldi; else {
2473 started = STARTED_COMMENT;
2474 parse_pos = 0; parse[0] = NULLCHAR;
2475 savingComment = TRUE;
2476 suppressKibitz = TRUE;
2478 } // [HGM] chat: end of patch
2480 if (appData.zippyTalk || appData.zippyPlay) {
2481 /* [DM] Backup address for color zippy lines */
2485 if (loggedOn == TRUE)
2486 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2487 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2489 if (ZippyControl(buf, &i) ||
2490 ZippyConverse(buf, &i) ||
2491 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2493 if (!appData.colorize) continue;
2497 } // [DM] 'else { ' deleted
2499 /* Regular tells and says */
2500 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2501 looking_at(buf, &i, "* (your partner) tells you: ") ||
2502 looking_at(buf, &i, "* says: ") ||
2503 /* Don't color "message" or "messages" output */
2504 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2505 looking_at(buf, &i, "*. * at *:*: ") ||
2506 looking_at(buf, &i, "--* (*:*): ") ||
2507 /* Message notifications (same color as tells) */
2508 looking_at(buf, &i, "* has left a message ") ||
2509 looking_at(buf, &i, "* just sent you a message:\n") ||
2510 /* Whispers and kibitzes */
2511 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2512 looking_at(buf, &i, "* kibitzes: ") ||
2514 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2516 if (tkind == 1 && strchr(star_match[0], ':')) {
2517 /* Avoid "tells you:" spoofs in channels */
2520 if (star_match[0][0] == NULLCHAR ||
2521 strchr(star_match[0], ' ') ||
2522 (tkind == 3 && strchr(star_match[1], ' '))) {
2523 /* Reject bogus matches */
2526 if (appData.colorize) {
2527 if (oldi > next_out) {
2528 SendToPlayer(&buf[next_out], oldi - next_out);
2533 Colorize(ColorTell, FALSE);
2534 curColor = ColorTell;
2537 Colorize(ColorKibitz, FALSE);
2538 curColor = ColorKibitz;
2541 p = strrchr(star_match[1], '(');
2548 Colorize(ColorChannel1, FALSE);
2549 curColor = ColorChannel1;
2551 Colorize(ColorChannel, FALSE);
2552 curColor = ColorChannel;
2556 curColor = ColorNormal;
2560 if (started == STARTED_NONE && appData.autoComment &&
2561 (gameMode == IcsObserving ||
2562 gameMode == IcsPlayingWhite ||
2563 gameMode == IcsPlayingBlack)) {
2564 parse_pos = i - oldi;
2565 memcpy(parse, &buf[oldi], parse_pos);
2566 parse[parse_pos] = NULLCHAR;
2567 started = STARTED_COMMENT;
2568 savingComment = TRUE;
2570 started = STARTED_CHATTER;
2571 savingComment = FALSE;
2578 if (looking_at(buf, &i, "* s-shouts: ") ||
2579 looking_at(buf, &i, "* c-shouts: ")) {
2580 if (appData.colorize) {
2581 if (oldi > next_out) {
2582 SendToPlayer(&buf[next_out], oldi - next_out);
2585 Colorize(ColorSShout, FALSE);
2586 curColor = ColorSShout;
2589 started = STARTED_CHATTER;
2593 if (looking_at(buf, &i, "--->")) {
2598 if (looking_at(buf, &i, "* shouts: ") ||
2599 looking_at(buf, &i, "--> ")) {
2600 if (appData.colorize) {
2601 if (oldi > next_out) {
2602 SendToPlayer(&buf[next_out], oldi - next_out);
2605 Colorize(ColorShout, FALSE);
2606 curColor = ColorShout;
2609 started = STARTED_CHATTER;
2613 if (looking_at( buf, &i, "Challenge:")) {
2614 if (appData.colorize) {
2615 if (oldi > next_out) {
2616 SendToPlayer(&buf[next_out], oldi - next_out);
2619 Colorize(ColorChallenge, FALSE);
2620 curColor = ColorChallenge;
2626 if (looking_at(buf, &i, "* offers you") ||
2627 looking_at(buf, &i, "* offers to be") ||
2628 looking_at(buf, &i, "* would like to") ||
2629 looking_at(buf, &i, "* requests to") ||
2630 looking_at(buf, &i, "Your opponent offers") ||
2631 looking_at(buf, &i, "Your opponent requests")) {
2633 if (appData.colorize) {
2634 if (oldi > next_out) {
2635 SendToPlayer(&buf[next_out], oldi - next_out);
2638 Colorize(ColorRequest, FALSE);
2639 curColor = ColorRequest;
2644 if (looking_at(buf, &i, "* (*) seeking")) {
2645 if (appData.colorize) {
2646 if (oldi > next_out) {
2647 SendToPlayer(&buf[next_out], oldi - next_out);
2650 Colorize(ColorSeek, FALSE);
2651 curColor = ColorSeek;
2656 if (looking_at(buf, &i, "\\ ")) {
2657 if (prevColor != ColorNormal) {
2658 if (oldi > next_out) {
2659 SendToPlayer(&buf[next_out], oldi - next_out);
2662 Colorize(prevColor, TRUE);
2663 curColor = prevColor;
2665 if (savingComment) {
2666 parse_pos = i - oldi;
2667 memcpy(parse, &buf[oldi], parse_pos);
2668 parse[parse_pos] = NULLCHAR;
2669 started = STARTED_COMMENT;
2671 started = STARTED_CHATTER;
2676 if (looking_at(buf, &i, "Black Strength :") ||
2677 looking_at(buf, &i, "<<< style 10 board >>>") ||
2678 looking_at(buf, &i, "<10>") ||
2679 looking_at(buf, &i, "#@#")) {
2680 /* Wrong board style */
2682 SendToICS(ics_prefix);
2683 SendToICS("set style 12\n");
2684 SendToICS(ics_prefix);
2685 SendToICS("refresh\n");
2689 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2691 have_sent_ICS_logon = 1;
2695 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2696 (looking_at(buf, &i, "\n<12> ") ||
2697 looking_at(buf, &i, "<12> "))) {
2699 if (oldi > next_out) {
2700 SendToPlayer(&buf[next_out], oldi - next_out);
2703 started = STARTED_BOARD;
2708 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2709 looking_at(buf, &i, "<b1> ")) {
2710 if (oldi > next_out) {
2711 SendToPlayer(&buf[next_out], oldi - next_out);
2714 started = STARTED_HOLDINGS;
2719 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2721 /* Header for a move list -- first line */
2723 switch (ics_getting_history) {
2727 case BeginningOfGame:
2728 /* User typed "moves" or "oldmoves" while we
2729 were idle. Pretend we asked for these
2730 moves and soak them up so user can step
2731 through them and/or save them.
2734 gameMode = IcsObserving;
2737 ics_getting_history = H_GOT_UNREQ_HEADER;
2739 case EditGame: /*?*/
2740 case EditPosition: /*?*/
2741 /* Should above feature work in these modes too? */
2742 /* For now it doesn't */
2743 ics_getting_history = H_GOT_UNWANTED_HEADER;
2746 ics_getting_history = H_GOT_UNWANTED_HEADER;
2751 /* Is this the right one? */
2752 if (gameInfo.white && gameInfo.black &&
2753 strcmp(gameInfo.white, star_match[0]) == 0 &&
2754 strcmp(gameInfo.black, star_match[2]) == 0) {
2756 ics_getting_history = H_GOT_REQ_HEADER;
2759 case H_GOT_REQ_HEADER:
2760 case H_GOT_UNREQ_HEADER:
2761 case H_GOT_UNWANTED_HEADER:
2762 case H_GETTING_MOVES:
2763 /* Should not happen */
2764 DisplayError(_("Error gathering move list: two headers"), 0);
2765 ics_getting_history = H_FALSE;
2769 /* Save player ratings into gameInfo if needed */
2770 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2771 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2772 (gameInfo.whiteRating == -1 ||
2773 gameInfo.blackRating == -1)) {
2775 gameInfo.whiteRating = string_to_rating(star_match[1]);
2776 gameInfo.blackRating = string_to_rating(star_match[3]);
2777 if (appData.debugMode)
2778 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2779 gameInfo.whiteRating, gameInfo.blackRating);
2784 if (looking_at(buf, &i,
2785 "* * match, initial time: * minute*, increment: * second")) {
2786 /* Header for a move list -- second line */
2787 /* Initial board will follow if this is a wild game */
2788 if (gameInfo.event != NULL) free(gameInfo.event);
2789 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2790 gameInfo.event = StrSave(str);
2791 /* [HGM] we switched variant. Translate boards if needed. */
2792 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2796 if (looking_at(buf, &i, "Move ")) {
2797 /* Beginning of a move list */
2798 switch (ics_getting_history) {
2800 /* Normally should not happen */
2801 /* Maybe user hit reset while we were parsing */
2804 /* Happens if we are ignoring a move list that is not
2805 * the one we just requested. Common if the user
2806 * tries to observe two games without turning off
2809 case H_GETTING_MOVES:
2810 /* Should not happen */
2811 DisplayError(_("Error gathering move list: nested"), 0);
2812 ics_getting_history = H_FALSE;
2814 case H_GOT_REQ_HEADER:
2815 ics_getting_history = H_GETTING_MOVES;
2816 started = STARTED_MOVES;
2818 if (oldi > next_out) {
2819 SendToPlayer(&buf[next_out], oldi - next_out);
2822 case H_GOT_UNREQ_HEADER:
2823 ics_getting_history = H_GETTING_MOVES;
2824 started = STARTED_MOVES_NOHIDE;
2827 case H_GOT_UNWANTED_HEADER:
2828 ics_getting_history = H_FALSE;
2834 if (looking_at(buf, &i, "% ") ||
2835 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2836 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2837 savingComment = FALSE;
2840 case STARTED_MOVES_NOHIDE:
2841 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2842 parse[parse_pos + i - oldi] = NULLCHAR;
2843 ParseGameHistory(parse);
2845 if (appData.zippyPlay && first.initDone) {
2846 FeedMovesToProgram(&first, forwardMostMove);
2847 if (gameMode == IcsPlayingWhite) {
2848 if (WhiteOnMove(forwardMostMove)) {
2849 if (first.sendTime) {
2850 if (first.useColors) {
2851 SendToProgram("black\n", &first);
2853 SendTimeRemaining(&first, TRUE);
2855 if (first.useColors) {
2856 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2858 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2859 first.maybeThinking = TRUE;
2861 if (first.usePlayother) {
2862 if (first.sendTime) {
2863 SendTimeRemaining(&first, TRUE);
2865 SendToProgram("playother\n", &first);
2871 } else if (gameMode == IcsPlayingBlack) {
2872 if (!WhiteOnMove(forwardMostMove)) {
2873 if (first.sendTime) {
2874 if (first.useColors) {
2875 SendToProgram("white\n", &first);
2877 SendTimeRemaining(&first, FALSE);
2879 if (first.useColors) {
2880 SendToProgram("black\n", &first);
2882 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2883 first.maybeThinking = TRUE;
2885 if (first.usePlayother) {
2886 if (first.sendTime) {
2887 SendTimeRemaining(&first, FALSE);
2889 SendToProgram("playother\n", &first);
2898 if (gameMode == IcsObserving && ics_gamenum == -1) {
2899 /* Moves came from oldmoves or moves command
2900 while we weren't doing anything else.
2902 currentMove = forwardMostMove;
2903 ClearHighlights();/*!!could figure this out*/
2904 flipView = appData.flipView;
2905 DrawPosition(FALSE, boards[currentMove]);
2906 DisplayBothClocks();
2907 sprintf(str, "%s vs. %s",
2908 gameInfo.white, gameInfo.black);
2912 /* Moves were history of an active game */
2913 if (gameInfo.resultDetails != NULL) {
2914 free(gameInfo.resultDetails);
2915 gameInfo.resultDetails = NULL;
2918 HistorySet(parseList, backwardMostMove,
2919 forwardMostMove, currentMove-1);
2920 DisplayMove(currentMove - 1);
2921 if (started == STARTED_MOVES) next_out = i;
2922 started = STARTED_NONE;
2923 ics_getting_history = H_FALSE;
2926 case STARTED_OBSERVE:
2927 started = STARTED_NONE;
2928 SendToICS(ics_prefix);
2929 SendToICS("refresh\n");
2935 if(bookHit) { // [HGM] book: simulate book reply
2936 static char bookMove[MSG_SIZ]; // a bit generous?
2938 programStats.nodes = programStats.depth = programStats.time =
2939 programStats.score = programStats.got_only_move = 0;
2940 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2942 strcpy(bookMove, "move ");
2943 strcat(bookMove, bookHit);
2944 HandleMachineMove(bookMove, &first);
2949 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2950 started == STARTED_HOLDINGS ||
2951 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2952 /* Accumulate characters in move list or board */
2953 parse[parse_pos++] = buf[i];
2956 /* Start of game messages. Mostly we detect start of game
2957 when the first board image arrives. On some versions
2958 of the ICS, though, we need to do a "refresh" after starting
2959 to observe in order to get the current board right away. */
2960 if (looking_at(buf, &i, "Adding game * to observation list")) {
2961 started = STARTED_OBSERVE;
2965 /* Handle auto-observe */
2966 if (appData.autoObserve &&
2967 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2968 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2970 /* Choose the player that was highlighted, if any. */
2971 if (star_match[0][0] == '\033' ||
2972 star_match[1][0] != '\033') {
2973 player = star_match[0];
2975 player = star_match[2];
2977 sprintf(str, "%sobserve %s\n",
2978 ics_prefix, StripHighlightAndTitle(player));
2981 /* Save ratings from notify string */
2982 strcpy(player1Name, star_match[0]);
2983 player1Rating = string_to_rating(star_match[1]);
2984 strcpy(player2Name, star_match[2]);
2985 player2Rating = string_to_rating(star_match[3]);
2987 if (appData.debugMode)
2989 "Ratings from 'Game notification:' %s %d, %s %d\n",
2990 player1Name, player1Rating,
2991 player2Name, player2Rating);
2996 /* Deal with automatic examine mode after a game,
2997 and with IcsObserving -> IcsExamining transition */
2998 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2999 looking_at(buf, &i, "has made you an examiner of game *")) {
3001 int gamenum = atoi(star_match[0]);
3002 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3003 gamenum == ics_gamenum) {
3004 /* We were already playing or observing this game;
3005 no need to refetch history */
3006 gameMode = IcsExamining;
3008 pauseExamForwardMostMove = forwardMostMove;
3009 } else if (currentMove < forwardMostMove) {
3010 ForwardInner(forwardMostMove);
3013 /* I don't think this case really can happen */
3014 SendToICS(ics_prefix);
3015 SendToICS("refresh\n");
3020 /* Error messages */
3021 // if (ics_user_moved) {
3022 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3023 if (looking_at(buf, &i, "Illegal move") ||
3024 looking_at(buf, &i, "Not a legal move") ||
3025 looking_at(buf, &i, "Your king is in check") ||
3026 looking_at(buf, &i, "It isn't your turn") ||
3027 looking_at(buf, &i, "It is not your move")) {
3029 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3030 currentMove = --forwardMostMove;
3031 DisplayMove(currentMove - 1); /* before DMError */
3032 DrawPosition(FALSE, boards[currentMove]);
3034 DisplayBothClocks();
3036 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3042 if (looking_at(buf, &i, "still have time") ||
3043 looking_at(buf, &i, "not out of time") ||
3044 looking_at(buf, &i, "either player is out of time") ||
3045 looking_at(buf, &i, "has timeseal; checking")) {
3046 /* We must have called his flag a little too soon */
3047 whiteFlag = blackFlag = FALSE;
3051 if (looking_at(buf, &i, "added * seconds to") ||
3052 looking_at(buf, &i, "seconds were added to")) {
3053 /* Update the clocks */
3054 SendToICS(ics_prefix);
3055 SendToICS("refresh\n");
3059 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3060 ics_clock_paused = TRUE;
3065 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3066 ics_clock_paused = FALSE;
3071 /* Grab player ratings from the Creating: message.
3072 Note we have to check for the special case when
3073 the ICS inserts things like [white] or [black]. */
3074 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3075 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3077 0 player 1 name (not necessarily white)
3079 2 empty, white, or black (IGNORED)
3080 3 player 2 name (not necessarily black)
3083 The names/ratings are sorted out when the game
3084 actually starts (below).
3086 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3087 player1Rating = string_to_rating(star_match[1]);
3088 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3089 player2Rating = string_to_rating(star_match[4]);
3091 if (appData.debugMode)
3093 "Ratings from 'Creating:' %s %d, %s %d\n",
3094 player1Name, player1Rating,
3095 player2Name, player2Rating);
3100 /* Improved generic start/end-of-game messages */
3101 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3102 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3103 /* If tkind == 0: */
3104 /* star_match[0] is the game number */
3105 /* [1] is the white player's name */
3106 /* [2] is the black player's name */
3107 /* For end-of-game: */
3108 /* [3] is the reason for the game end */
3109 /* [4] is a PGN end game-token, preceded by " " */
3110 /* For start-of-game: */
3111 /* [3] begins with "Creating" or "Continuing" */
3112 /* [4] is " *" or empty (don't care). */
3113 int gamenum = atoi(star_match[0]);
3114 char *whitename, *blackname, *why, *endtoken;
3115 ChessMove endtype = (ChessMove) 0;
3118 whitename = star_match[1];
3119 blackname = star_match[2];
3120 why = star_match[3];
3121 endtoken = star_match[4];
3123 whitename = star_match[1];
3124 blackname = star_match[3];
3125 why = star_match[5];
3126 endtoken = star_match[6];
3129 /* Game start messages */
3130 if (strncmp(why, "Creating ", 9) == 0 ||
3131 strncmp(why, "Continuing ", 11) == 0) {
3132 gs_gamenum = gamenum;
3133 strcpy(gs_kind, strchr(why, ' ') + 1);
3135 if (appData.zippyPlay) {
3136 ZippyGameStart(whitename, blackname);
3142 /* Game end messages */
3143 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3144 ics_gamenum != gamenum) {
3147 while (endtoken[0] == ' ') endtoken++;
3148 switch (endtoken[0]) {
3151 endtype = GameUnfinished;
3154 endtype = BlackWins;
3157 if (endtoken[1] == '/')
3158 endtype = GameIsDrawn;
3160 endtype = WhiteWins;
3163 GameEnds(endtype, why, GE_ICS);
3165 if (appData.zippyPlay && first.initDone) {
3166 ZippyGameEnd(endtype, why);
3167 if (first.pr == NULL) {
3168 /* Start the next process early so that we'll
3169 be ready for the next challenge */
3170 StartChessProgram(&first);
3172 /* Send "new" early, in case this command takes
3173 a long time to finish, so that we'll be ready
3174 for the next challenge. */
3175 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3182 if (looking_at(buf, &i, "Removing game * from observation") ||
3183 looking_at(buf, &i, "no longer observing game *") ||
3184 looking_at(buf, &i, "Game * (*) has no examiners")) {
3185 if (gameMode == IcsObserving &&
3186 atoi(star_match[0]) == ics_gamenum)
3188 /* icsEngineAnalyze */
3189 if (appData.icsEngineAnalyze) {
3196 ics_user_moved = FALSE;
3201 if (looking_at(buf, &i, "no longer examining game *")) {
3202 if (gameMode == IcsExamining &&
3203 atoi(star_match[0]) == ics_gamenum)
3207 ics_user_moved = FALSE;
3212 /* Advance leftover_start past any newlines we find,
3213 so only partial lines can get reparsed */
3214 if (looking_at(buf, &i, "\n")) {
3215 prevColor = curColor;
3216 if (curColor != ColorNormal) {
3217 if (oldi > next_out) {
3218 SendToPlayer(&buf[next_out], oldi - next_out);
3221 Colorize(ColorNormal, FALSE);
3222 curColor = ColorNormal;
3224 if (started == STARTED_BOARD) {
3225 started = STARTED_NONE;
3226 parse[parse_pos] = NULLCHAR;
3227 ParseBoard12(parse);
3230 /* Send premove here */
3231 if (appData.premove) {
3233 if (currentMove == 0 &&
3234 gameMode == IcsPlayingWhite &&
3235 appData.premoveWhite) {
3236 sprintf(str, "%s%s\n", ics_prefix,
3237 appData.premoveWhiteText);
3238 if (appData.debugMode)
3239 fprintf(debugFP, "Sending premove:\n");
3241 } else if (currentMove == 1 &&
3242 gameMode == IcsPlayingBlack &&
3243 appData.premoveBlack) {
3244 sprintf(str, "%s%s\n", ics_prefix,
3245 appData.premoveBlackText);
3246 if (appData.debugMode)
3247 fprintf(debugFP, "Sending premove:\n");
3249 } else if (gotPremove) {
3251 ClearPremoveHighlights();
3252 if (appData.debugMode)
3253 fprintf(debugFP, "Sending premove:\n");
3254 UserMoveEvent(premoveFromX, premoveFromY,
3255 premoveToX, premoveToY,
3260 /* Usually suppress following prompt */
3261 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3262 if (looking_at(buf, &i, "*% ")) {
3263 savingComment = FALSE;
3267 } else if (started == STARTED_HOLDINGS) {
3269 char new_piece[MSG_SIZ];
3270 started = STARTED_NONE;
3271 parse[parse_pos] = NULLCHAR;
3272 if (appData.debugMode)
3273 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3274 parse, currentMove);
3275 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3276 gamenum == ics_gamenum) {
3277 if (gameInfo.variant == VariantNormal) {
3278 /* [HGM] We seem to switch variant during a game!
3279 * Presumably no holdings were displayed, so we have
3280 * to move the position two files to the right to
3281 * create room for them!
3283 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3284 /* Get a move list just to see the header, which
3285 will tell us whether this is really bug or zh */
3286 if (ics_getting_history == H_FALSE) {
3287 ics_getting_history = H_REQUESTED;
3288 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3292 new_piece[0] = NULLCHAR;
3293 sscanf(parse, "game %d white [%s black [%s <- %s",
3294 &gamenum, white_holding, black_holding,
3296 white_holding[strlen(white_holding)-1] = NULLCHAR;
3297 black_holding[strlen(black_holding)-1] = NULLCHAR;
3298 /* [HGM] copy holdings to board holdings area */
3299 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3300 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3302 if (appData.zippyPlay && first.initDone) {
3303 ZippyHoldings(white_holding, black_holding,
3307 if (tinyLayout || smallLayout) {
3308 char wh[16], bh[16];
3309 PackHolding(wh, white_holding);
3310 PackHolding(bh, black_holding);
3311 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3312 gameInfo.white, gameInfo.black);
3314 sprintf(str, "%s [%s] vs. %s [%s]",
3315 gameInfo.white, white_holding,
3316 gameInfo.black, black_holding);
3319 DrawPosition(FALSE, boards[currentMove]);
3322 /* Suppress following prompt */
3323 if (looking_at(buf, &i, "*% ")) {
3324 savingComment = FALSE;
3331 i++; /* skip unparsed character and loop back */
3334 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3335 started != STARTED_HOLDINGS && i > next_out) {
3336 SendToPlayer(&buf[next_out], i - next_out);
3339 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3341 leftover_len = buf_len - leftover_start;
3342 /* if buffer ends with something we couldn't parse,
3343 reparse it after appending the next read */
3345 } else if (count == 0) {
3346 RemoveInputSource(isr);
3347 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3349 DisplayFatalError(_("Error reading from ICS"), error, 1);
3354 /* Board style 12 looks like this:
3356 <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
3358 * The "<12> " is stripped before it gets to this routine. The two
3359 * trailing 0's (flip state and clock ticking) are later addition, and
3360 * some chess servers may not have them, or may have only the first.
3361 * Additional trailing fields may be added in the future.
3364 #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"
3366 #define RELATION_OBSERVING_PLAYED 0
3367 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3368 #define RELATION_PLAYING_MYMOVE 1
3369 #define RELATION_PLAYING_NOTMYMOVE -1
3370 #define RELATION_EXAMINING 2
3371 #define RELATION_ISOLATED_BOARD -3
3372 #define RELATION_STARTING_POSITION -4 /* FICS only */
3375 ParseBoard12(string)
3378 GameMode newGameMode;
3379 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3380 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3381 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3382 char to_play, board_chars[200];
3383 char move_str[500], str[500], elapsed_time[500];
3384 char black[32], white[32];
3386 int prevMove = currentMove;
3389 int fromX, fromY, toX, toY;
3391 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3392 char *bookHit = NULL; // [HGM] book
3394 fromX = fromY = toX = toY = -1;
3398 if (appData.debugMode)
3399 fprintf(debugFP, _("Parsing board: %s\n"), string);
3401 move_str[0] = NULLCHAR;
3402 elapsed_time[0] = NULLCHAR;
3403 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3405 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3406 if(string[i] == ' ') { ranks++; files = 0; }
3410 for(j = 0; j <i; j++) board_chars[j] = string[j];
3411 board_chars[i] = '\0';
3414 n = sscanf(string, PATTERN, &to_play, &double_push,
3415 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3416 &gamenum, white, black, &relation, &basetime, &increment,
3417 &white_stren, &black_stren, &white_time, &black_time,
3418 &moveNum, str, elapsed_time, move_str, &ics_flip,
3422 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3423 DisplayError(str, 0);
3427 /* Convert the move number to internal form */
3428 moveNum = (moveNum - 1) * 2;
3429 if (to_play == 'B') moveNum++;
3430 if (moveNum >= MAX_MOVES) {
3431 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3437 case RELATION_OBSERVING_PLAYED:
3438 case RELATION_OBSERVING_STATIC:
3439 if (gamenum == -1) {
3440 /* Old ICC buglet */
3441 relation = RELATION_OBSERVING_STATIC;
3443 newGameMode = IcsObserving;
3445 case RELATION_PLAYING_MYMOVE:
3446 case RELATION_PLAYING_NOTMYMOVE:
3448 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3449 IcsPlayingWhite : IcsPlayingBlack;
3451 case RELATION_EXAMINING:
3452 newGameMode = IcsExamining;
3454 case RELATION_ISOLATED_BOARD:
3456 /* Just display this board. If user was doing something else,
3457 we will forget about it until the next board comes. */
3458 newGameMode = IcsIdle;
3460 case RELATION_STARTING_POSITION:
3461 newGameMode = gameMode;
3465 /* Modify behavior for initial board display on move listing
3468 switch (ics_getting_history) {
3472 case H_GOT_REQ_HEADER:
3473 case H_GOT_UNREQ_HEADER:
3474 /* This is the initial position of the current game */
3475 gamenum = ics_gamenum;
3476 moveNum = 0; /* old ICS bug workaround */
3477 if (to_play == 'B') {
3478 startedFromSetupPosition = TRUE;
3479 blackPlaysFirst = TRUE;
3481 if (forwardMostMove == 0) forwardMostMove = 1;
3482 if (backwardMostMove == 0) backwardMostMove = 1;
3483 if (currentMove == 0) currentMove = 1;
3485 newGameMode = gameMode;
3486 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3488 case H_GOT_UNWANTED_HEADER:
3489 /* This is an initial board that we don't want */
3491 case H_GETTING_MOVES:
3492 /* Should not happen */
3493 DisplayError(_("Error gathering move list: extra board"), 0);
3494 ics_getting_history = H_FALSE;
3498 /* Take action if this is the first board of a new game, or of a
3499 different game than is currently being displayed. */
3500 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3501 relation == RELATION_ISOLATED_BOARD) {
3503 /* Forget the old game and get the history (if any) of the new one */
3504 if (gameMode != BeginningOfGame) {
3508 if (appData.autoRaiseBoard) BoardToTop();
3510 if (gamenum == -1) {
3511 newGameMode = IcsIdle;
3512 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3513 appData.getMoveList) {
3514 /* Need to get game history */
3515 ics_getting_history = H_REQUESTED;
3516 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3520 /* Initially flip the board to have black on the bottom if playing
3521 black or if the ICS flip flag is set, but let the user change
3522 it with the Flip View button. */
3523 flipView = appData.autoFlipView ?
3524 (newGameMode == IcsPlayingBlack) || ics_flip :
3527 /* Done with values from previous mode; copy in new ones */
3528 gameMode = newGameMode;
3530 ics_gamenum = gamenum;
3531 if (gamenum == gs_gamenum) {
3532 int klen = strlen(gs_kind);
3533 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3534 sprintf(str, "ICS %s", gs_kind);
3535 gameInfo.event = StrSave(str);
3537 gameInfo.event = StrSave("ICS game");
3539 gameInfo.site = StrSave(appData.icsHost);
3540 gameInfo.date = PGNDate();
3541 gameInfo.round = StrSave("-");
3542 gameInfo.white = StrSave(white);
3543 gameInfo.black = StrSave(black);
3544 timeControl = basetime * 60 * 1000;
3546 timeIncrement = increment * 1000;
3547 movesPerSession = 0;
3548 gameInfo.timeControl = TimeControlTagValue();
3549 VariantSwitch(board, StringToVariant(gameInfo.event) );
3550 if (appData.debugMode) {
3551 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3552 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3553 setbuf(debugFP, NULL);
3556 gameInfo.outOfBook = NULL;
3558 /* Do we have the ratings? */
3559 if (strcmp(player1Name, white) == 0 &&
3560 strcmp(player2Name, black) == 0) {
3561 if (appData.debugMode)
3562 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3563 player1Rating, player2Rating);
3564 gameInfo.whiteRating = player1Rating;
3565 gameInfo.blackRating = player2Rating;
3566 } else if (strcmp(player2Name, white) == 0 &&
3567 strcmp(player1Name, black) == 0) {
3568 if (appData.debugMode)
3569 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3570 player2Rating, player1Rating);
3571 gameInfo.whiteRating = player2Rating;
3572 gameInfo.blackRating = player1Rating;
3574 player1Name[0] = player2Name[0] = NULLCHAR;
3576 /* Silence shouts if requested */
3577 if (appData.quietPlay &&
3578 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3579 SendToICS(ics_prefix);
3580 SendToICS("set shout 0\n");
3584 /* Deal with midgame name changes */
3586 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3587 if (gameInfo.white) free(gameInfo.white);
3588 gameInfo.white = StrSave(white);
3590 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3591 if (gameInfo.black) free(gameInfo.black);
3592 gameInfo.black = StrSave(black);
3596 /* Throw away game result if anything actually changes in examine mode */
3597 if (gameMode == IcsExamining && !newGame) {
3598 gameInfo.result = GameUnfinished;
3599 if (gameInfo.resultDetails != NULL) {
3600 free(gameInfo.resultDetails);
3601 gameInfo.resultDetails = NULL;
3605 /* In pausing && IcsExamining mode, we ignore boards coming
3606 in if they are in a different variation than we are. */
3607 if (pauseExamInvalid) return;
3608 if (pausing && gameMode == IcsExamining) {
3609 if (moveNum <= pauseExamForwardMostMove) {
3610 pauseExamInvalid = TRUE;
3611 forwardMostMove = pauseExamForwardMostMove;
3616 if (appData.debugMode) {
3617 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3619 /* Parse the board */
3620 for (k = 0; k < ranks; k++) {
3621 for (j = 0; j < files; j++)
3622 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3623 if(gameInfo.holdingsWidth > 1) {
3624 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3625 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3628 CopyBoard(boards[moveNum], board);
3630 startedFromSetupPosition =
3631 !CompareBoards(board, initialPosition);
3632 if(startedFromSetupPosition)
3633 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3636 /* [HGM] Set castling rights. Take the outermost Rooks,
3637 to make it also work for FRC opening positions. Note that board12
3638 is really defective for later FRC positions, as it has no way to
3639 indicate which Rook can castle if they are on the same side of King.
3640 For the initial position we grant rights to the outermost Rooks,
3641 and remember thos rights, and we then copy them on positions
3642 later in an FRC game. This means WB might not recognize castlings with
3643 Rooks that have moved back to their original position as illegal,
3644 but in ICS mode that is not its job anyway.
3646 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3647 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3649 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3650 if(board[0][i] == WhiteRook) j = i;
3651 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3652 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3653 if(board[0][i] == WhiteRook) j = i;
3654 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3655 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3656 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3657 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3658 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3659 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3660 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3662 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3663 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3664 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3665 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3666 if(board[BOARD_HEIGHT-1][k] == bKing)
3667 initialRights[5] = castlingRights[moveNum][5] = k;
3669 r = castlingRights[moveNum][0] = initialRights[0];
3670 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3671 r = castlingRights[moveNum][1] = initialRights[1];
3672 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3673 r = castlingRights[moveNum][3] = initialRights[3];
3674 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3675 r = castlingRights[moveNum][4] = initialRights[4];
3676 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3677 /* wildcastle kludge: always assume King has rights */
3678 r = castlingRights[moveNum][2] = initialRights[2];
3679 r = castlingRights[moveNum][5] = initialRights[5];
3681 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3682 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3685 if (ics_getting_history == H_GOT_REQ_HEADER ||
3686 ics_getting_history == H_GOT_UNREQ_HEADER) {
3687 /* This was an initial position from a move list, not
3688 the current position */
3692 /* Update currentMove and known move number limits */
3693 newMove = newGame || moveNum > forwardMostMove;
3695 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3696 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3697 takeback = forwardMostMove - moveNum;
3698 for (i = 0; i < takeback; i++) {
3699 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3700 SendToProgram("undo\n", &first);
3705 forwardMostMove = backwardMostMove = currentMove = moveNum;
3706 if (gameMode == IcsExamining && moveNum == 0) {
3707 /* Workaround for ICS limitation: we are not told the wild
3708 type when starting to examine a game. But if we ask for
3709 the move list, the move list header will tell us */
3710 ics_getting_history = H_REQUESTED;
3711 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3714 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3715 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3716 forwardMostMove = moveNum;
3717 if (!pausing || currentMove > forwardMostMove)
3718 currentMove = forwardMostMove;
3720 /* New part of history that is not contiguous with old part */
3721 if (pausing && gameMode == IcsExamining) {
3722 pauseExamInvalid = TRUE;
3723 forwardMostMove = pauseExamForwardMostMove;
3726 forwardMostMove = backwardMostMove = currentMove = moveNum;
3727 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3728 ics_getting_history = H_REQUESTED;
3729 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3734 /* Update the clocks */
3735 if (strchr(elapsed_time, '.')) {
3737 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3738 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3740 /* Time is in seconds */
3741 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3742 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3747 if (appData.zippyPlay && newGame &&
3748 gameMode != IcsObserving && gameMode != IcsIdle &&
3749 gameMode != IcsExamining)
3750 ZippyFirstBoard(moveNum, basetime, increment);
3753 /* Put the move on the move list, first converting
3754 to canonical algebraic form. */
3756 if (appData.debugMode) {
3757 if (appData.debugMode) { int f = forwardMostMove;
3758 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3759 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3761 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3762 fprintf(debugFP, "moveNum = %d\n", moveNum);
3763 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3764 setbuf(debugFP, NULL);
3766 if (moveNum <= backwardMostMove) {
3767 /* We don't know what the board looked like before
3769 strcpy(parseList[moveNum - 1], move_str);
3770 strcat(parseList[moveNum - 1], " ");
3771 strcat(parseList[moveNum - 1], elapsed_time);
3772 moveList[moveNum - 1][0] = NULLCHAR;
3773 } else if (strcmp(move_str, "none") == 0) {
3774 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3775 /* Again, we don't know what the board looked like;
3776 this is really the start of the game. */
3777 parseList[moveNum - 1][0] = NULLCHAR;
3778 moveList[moveNum - 1][0] = NULLCHAR;
3779 backwardMostMove = moveNum;
3780 startedFromSetupPosition = TRUE;
3781 fromX = fromY = toX = toY = -1;
3783 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3784 // So we parse the long-algebraic move string in stead of the SAN move
3785 int valid; char buf[MSG_SIZ], *prom;
3787 // str looks something like "Q/a1-a2"; kill the slash
3789 sprintf(buf, "%c%s", str[0], str+2);
3790 else strcpy(buf, str); // might be castling
3791 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3792 strcat(buf, prom); // long move lacks promo specification!
3793 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3794 if(appData.debugMode)
3795 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3796 strcpy(move_str, buf);
3798 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3799 &fromX, &fromY, &toX, &toY, &promoChar)
3800 || ParseOneMove(buf, moveNum - 1, &moveType,
3801 &fromX, &fromY, &toX, &toY, &promoChar);
3802 // end of long SAN patch
3804 (void) CoordsToAlgebraic(boards[moveNum - 1],
3805 PosFlags(moveNum - 1), EP_UNKNOWN,
3806 fromY, fromX, toY, toX, promoChar,
3807 parseList[moveNum-1]);
3808 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3809 castlingRights[moveNum]) ) {
3815 if(gameInfo.variant != VariantShogi)
3816 strcat(parseList[moveNum - 1], "+");
3819 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3820 strcat(parseList[moveNum - 1], "#");
3823 strcat(parseList[moveNum - 1], " ");
3824 strcat(parseList[moveNum - 1], elapsed_time);
3825 /* currentMoveString is set as a side-effect of ParseOneMove */
3826 strcpy(moveList[moveNum - 1], currentMoveString);
3827 strcat(moveList[moveNum - 1], "\n");
3829 /* Move from ICS was illegal!? Punt. */
3830 if (appData.debugMode) {
3831 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3832 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3834 strcpy(parseList[moveNum - 1], move_str);
3835 strcat(parseList[moveNum - 1], " ");
3836 strcat(parseList[moveNum - 1], elapsed_time);
3837 moveList[moveNum - 1][0] = NULLCHAR;
3838 fromX = fromY = toX = toY = -1;
3841 if (appData.debugMode) {
3842 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3843 setbuf(debugFP, NULL);
3847 /* Send move to chess program (BEFORE animating it). */
3848 if (appData.zippyPlay && !newGame && newMove &&
3849 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3851 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3852 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3853 if (moveList[moveNum - 1][0] == NULLCHAR) {
3854 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3856 DisplayError(str, 0);
3858 if (first.sendTime) {
3859 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3861 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3862 if (firstMove && !bookHit) {
3864 if (first.useColors) {
3865 SendToProgram(gameMode == IcsPlayingWhite ?
3867 "black\ngo\n", &first);
3869 SendToProgram("go\n", &first);
3871 first.maybeThinking = TRUE;
3874 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3875 if (moveList[moveNum - 1][0] == NULLCHAR) {
3876 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3877 DisplayError(str, 0);
3879 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3880 SendMoveToProgram(moveNum - 1, &first);
3887 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3888 /* If move comes from a remote source, animate it. If it
3889 isn't remote, it will have already been animated. */
3890 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3891 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3893 if (!pausing && appData.highlightLastMove) {
3894 SetHighlights(fromX, fromY, toX, toY);
3898 /* Start the clocks */
3899 whiteFlag = blackFlag = FALSE;
3900 appData.clockMode = !(basetime == 0 && increment == 0);
3902 ics_clock_paused = TRUE;
3904 } else if (ticking == 1) {
3905 ics_clock_paused = FALSE;
3907 if (gameMode == IcsIdle ||
3908 relation == RELATION_OBSERVING_STATIC ||
3909 relation == RELATION_EXAMINING ||
3911 DisplayBothClocks();
3915 /* Display opponents and material strengths */
3916 if (gameInfo.variant != VariantBughouse &&
3917 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3918 if (tinyLayout || smallLayout) {
3919 if(gameInfo.variant == VariantNormal)
3920 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3921 gameInfo.white, white_stren, gameInfo.black, black_stren,
3922 basetime, increment);
3924 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3925 gameInfo.white, white_stren, gameInfo.black, black_stren,
3926 basetime, increment, (int) gameInfo.variant);
3928 if(gameInfo.variant == VariantNormal)
3929 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3930 gameInfo.white, white_stren, gameInfo.black, black_stren,
3931 basetime, increment);
3933 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3934 gameInfo.white, white_stren, gameInfo.black, black_stren,
3935 basetime, increment, VariantName(gameInfo.variant));
3938 if (appData.debugMode) {
3939 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3944 /* Display the board */
3945 if (!pausing && !appData.noGUI) {
3947 if (appData.premove)
3949 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3950 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3951 ClearPremoveHighlights();
3953 DrawPosition(FALSE, boards[currentMove]);
3954 DisplayMove(moveNum - 1);
3955 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3956 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3957 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3958 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3962 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3964 if(bookHit) { // [HGM] book: simulate book reply
3965 static char bookMove[MSG_SIZ]; // a bit generous?
3967 programStats.nodes = programStats.depth = programStats.time =
3968 programStats.score = programStats.got_only_move = 0;
3969 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3971 strcpy(bookMove, "move ");
3972 strcat(bookMove, bookHit);
3973 HandleMachineMove(bookMove, &first);
3982 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3983 ics_getting_history = H_REQUESTED;
3984 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3990 AnalysisPeriodicEvent(force)
3993 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3994 && !force) || !appData.periodicUpdates)
3997 /* Send . command to Crafty to collect stats */
3998 SendToProgram(".\n", &first);
4000 /* Don't send another until we get a response (this makes
4001 us stop sending to old Crafty's which don't understand
4002 the "." command (sending illegal cmds resets node count & time,
4003 which looks bad)) */
4004 programStats.ok_to_send = 0;
4007 void ics_update_width(new_width)
4010 ics_printf("set width %d\n", new_width);
4014 SendMoveToProgram(moveNum, cps)
4016 ChessProgramState *cps;
4020 if (cps->useUsermove) {
4021 SendToProgram("usermove ", cps);
4025 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4026 int len = space - parseList[moveNum];
4027 memcpy(buf, parseList[moveNum], len);
4029 buf[len] = NULLCHAR;
4031 sprintf(buf, "%s\n", parseList[moveNum]);
4033 SendToProgram(buf, cps);
4035 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4036 AlphaRank(moveList[moveNum], 4);
4037 SendToProgram(moveList[moveNum], cps);
4038 AlphaRank(moveList[moveNum], 4); // and back
4040 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4041 * the engine. It would be nice to have a better way to identify castle
4043 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4044 && cps->useOOCastle) {
4045 int fromX = moveList[moveNum][0] - AAA;
4046 int fromY = moveList[moveNum][1] - ONE;
4047 int toX = moveList[moveNum][2] - AAA;
4048 int toY = moveList[moveNum][3] - ONE;
4049 if((boards[moveNum][fromY][fromX] == WhiteKing
4050 && boards[moveNum][toY][toX] == WhiteRook)
4051 || (boards[moveNum][fromY][fromX] == BlackKing
4052 && boards[moveNum][toY][toX] == BlackRook)) {
4053 if(toX > fromX) SendToProgram("O-O\n", cps);
4054 else SendToProgram("O-O-O\n", cps);
4056 else SendToProgram(moveList[moveNum], cps);
4058 else SendToProgram(moveList[moveNum], cps);
4059 /* End of additions by Tord */
4062 /* [HGM] setting up the opening has brought engine in force mode! */
4063 /* Send 'go' if we are in a mode where machine should play. */
4064 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4065 (gameMode == TwoMachinesPlay ||
4067 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4069 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4070 SendToProgram("go\n", cps);
4071 if (appData.debugMode) {
4072 fprintf(debugFP, "(extra)\n");
4075 setboardSpoiledMachineBlack = 0;
4079 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4081 int fromX, fromY, toX, toY;
4083 char user_move[MSG_SIZ];
4087 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4088 (int)moveType, fromX, fromY, toX, toY);
4089 DisplayError(user_move + strlen("say "), 0);
4091 case WhiteKingSideCastle:
4092 case BlackKingSideCastle:
4093 case WhiteQueenSideCastleWild:
4094 case BlackQueenSideCastleWild:
4096 case WhiteHSideCastleFR:
4097 case BlackHSideCastleFR:
4099 sprintf(user_move, "o-o\n");
4101 case WhiteQueenSideCastle:
4102 case BlackQueenSideCastle:
4103 case WhiteKingSideCastleWild:
4104 case BlackKingSideCastleWild:
4106 case WhiteASideCastleFR:
4107 case BlackASideCastleFR:
4109 sprintf(user_move, "o-o-o\n");
4111 case WhitePromotionQueen:
4112 case BlackPromotionQueen:
4113 case WhitePromotionRook:
4114 case BlackPromotionRook:
4115 case WhitePromotionBishop:
4116 case BlackPromotionBishop:
4117 case WhitePromotionKnight:
4118 case BlackPromotionKnight:
4119 case WhitePromotionKing:
4120 case BlackPromotionKing:
4121 case WhitePromotionChancellor:
4122 case BlackPromotionChancellor:
4123 case WhitePromotionArchbishop:
4124 case BlackPromotionArchbishop:
4125 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4126 sprintf(user_move, "%c%c%c%c=%c\n",
4127 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4128 PieceToChar(WhiteFerz));
4129 else if(gameInfo.variant == VariantGreat)
4130 sprintf(user_move, "%c%c%c%c=%c\n",
4131 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4132 PieceToChar(WhiteMan));
4134 sprintf(user_move, "%c%c%c%c=%c\n",
4135 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4136 PieceToChar(PromoPiece(moveType)));
4140 sprintf(user_move, "%c@%c%c\n",
4141 ToUpper(PieceToChar((ChessSquare) fromX)),
4142 AAA + toX, ONE + toY);
4145 case WhiteCapturesEnPassant:
4146 case BlackCapturesEnPassant:
4147 case IllegalMove: /* could be a variant we don't quite understand */
4148 sprintf(user_move, "%c%c%c%c\n",
4149 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4152 SendToICS(user_move);
4153 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4154 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4158 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4163 if (rf == DROP_RANK) {
4164 sprintf(move, "%c@%c%c\n",
4165 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4167 if (promoChar == 'x' || promoChar == NULLCHAR) {
4168 sprintf(move, "%c%c%c%c\n",
4169 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4171 sprintf(move, "%c%c%c%c%c\n",
4172 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4178 ProcessICSInitScript(f)
4183 while (fgets(buf, MSG_SIZ, f)) {
4184 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4191 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4193 AlphaRank(char *move, int n)
4195 // char *p = move, c; int x, y;
4197 if (appData.debugMode) {
4198 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4202 move[2]>='0' && move[2]<='9' &&
4203 move[3]>='a' && move[3]<='x' ) {
4205 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4206 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4208 if(move[0]>='0' && move[0]<='9' &&
4209 move[1]>='a' && move[1]<='x' &&
4210 move[2]>='0' && move[2]<='9' &&
4211 move[3]>='a' && move[3]<='x' ) {
4212 /* input move, Shogi -> normal */
4213 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4214 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4215 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4216 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4219 move[3]>='0' && move[3]<='9' &&
4220 move[2]>='a' && move[2]<='x' ) {
4222 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4223 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4226 move[0]>='a' && move[0]<='x' &&
4227 move[3]>='0' && move[3]<='9' &&
4228 move[2]>='a' && move[2]<='x' ) {
4229 /* output move, normal -> Shogi */
4230 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4231 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4232 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4233 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4234 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4236 if (appData.debugMode) {
4237 fprintf(debugFP, " out = '%s'\n", move);
4241 /* Parser for moves from gnuchess, ICS, or user typein box */
4243 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4246 ChessMove *moveType;
4247 int *fromX, *fromY, *toX, *toY;
4250 if (appData.debugMode) {
4251 fprintf(debugFP, "move to parse: %s\n", move);
4253 *moveType = yylexstr(moveNum, move);
4255 switch (*moveType) {
4256 case WhitePromotionChancellor:
4257 case BlackPromotionChancellor:
4258 case WhitePromotionArchbishop:
4259 case BlackPromotionArchbishop:
4260 case WhitePromotionQueen:
4261 case BlackPromotionQueen:
4262 case WhitePromotionRook:
4263 case BlackPromotionRook:
4264 case WhitePromotionBishop:
4265 case BlackPromotionBishop:
4266 case WhitePromotionKnight:
4267 case BlackPromotionKnight:
4268 case WhitePromotionKing:
4269 case BlackPromotionKing:
4271 case WhiteCapturesEnPassant:
4272 case BlackCapturesEnPassant:
4273 case WhiteKingSideCastle:
4274 case WhiteQueenSideCastle:
4275 case BlackKingSideCastle:
4276 case BlackQueenSideCastle:
4277 case WhiteKingSideCastleWild:
4278 case WhiteQueenSideCastleWild:
4279 case BlackKingSideCastleWild:
4280 case BlackQueenSideCastleWild:
4281 /* Code added by Tord: */
4282 case WhiteHSideCastleFR:
4283 case WhiteASideCastleFR:
4284 case BlackHSideCastleFR:
4285 case BlackASideCastleFR:
4286 /* End of code added by Tord */
4287 case IllegalMove: /* bug or odd chess variant */
4288 *fromX = currentMoveString[0] - AAA;
4289 *fromY = currentMoveString[1] - ONE;
4290 *toX = currentMoveString[2] - AAA;
4291 *toY = currentMoveString[3] - ONE;
4292 *promoChar = currentMoveString[4];
4293 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4294 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4295 if (appData.debugMode) {
4296 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4298 *fromX = *fromY = *toX = *toY = 0;
4301 if (appData.testLegality) {
4302 return (*moveType != IllegalMove);
4304 return !(fromX == fromY && toX == toY);
4309 *fromX = *moveType == WhiteDrop ?
4310 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4311 (int) CharToPiece(ToLower(currentMoveString[0]));
4313 *toX = currentMoveString[2] - AAA;
4314 *toY = currentMoveString[3] - ONE;
4315 *promoChar = NULLCHAR;
4319 case ImpossibleMove:
4320 case (ChessMove) 0: /* end of file */
4329 if (appData.debugMode) {
4330 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4333 *fromX = *fromY = *toX = *toY = 0;
4334 *promoChar = NULLCHAR;
4339 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4340 // All positions will have equal probability, but the current method will not provide a unique
4341 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4347 int piecesLeft[(int)BlackPawn];
4348 int seed, nrOfShuffles;
4350 void GetPositionNumber()
4351 { // sets global variable seed
4354 seed = appData.defaultFrcPosition;
4355 if(seed < 0) { // randomize based on time for negative FRC position numbers
4356 for(i=0; i<50; i++) seed += random();
4357 seed = random() ^ random() >> 8 ^ random() << 8;
4358 if(seed<0) seed = -seed;
4362 int put(Board board, int pieceType, int rank, int n, int shade)
4363 // put the piece on the (n-1)-th empty squares of the given shade
4367 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4368 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4369 board[rank][i] = (ChessSquare) pieceType;
4370 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4372 piecesLeft[pieceType]--;
4380 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4381 // calculate where the next piece goes, (any empty square), and put it there
4385 i = seed % squaresLeft[shade];
4386 nrOfShuffles *= squaresLeft[shade];
4387 seed /= squaresLeft[shade];
4388 put(board, pieceType, rank, i, shade);
4391 void AddTwoPieces(Board board, int pieceType, int rank)
4392 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4394 int i, n=squaresLeft[ANY], j=n-1, k;
4396 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4397 i = seed % k; // pick one
4400 while(i >= j) i -= j--;
4401 j = n - 1 - j; i += j;
4402 put(board, pieceType, rank, j, ANY);
4403 put(board, pieceType, rank, i, ANY);
4406 void SetUpShuffle(Board board, int number)
4410 GetPositionNumber(); nrOfShuffles = 1;
4412 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4413 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4414 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4416 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4418 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4419 p = (int) board[0][i];
4420 if(p < (int) BlackPawn) piecesLeft[p] ++;
4421 board[0][i] = EmptySquare;
4424 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4425 // shuffles restricted to allow normal castling put KRR first
4426 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4427 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4428 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4429 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4430 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4431 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4432 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4433 put(board, WhiteRook, 0, 0, ANY);
4434 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4437 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4438 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4439 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4440 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4441 while(piecesLeft[p] >= 2) {
4442 AddOnePiece(board, p, 0, LITE);
4443 AddOnePiece(board, p, 0, DARK);
4445 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4448 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4449 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4450 // but we leave King and Rooks for last, to possibly obey FRC restriction
4451 if(p == (int)WhiteRook) continue;
4452 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4453 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4456 // now everything is placed, except perhaps King (Unicorn) and Rooks
4458 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4459 // Last King gets castling rights
4460 while(piecesLeft[(int)WhiteUnicorn]) {
4461 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4465 while(piecesLeft[(int)WhiteKing]) {
4466 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4467 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4472 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4473 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4476 // Only Rooks can be left; simply place them all
4477 while(piecesLeft[(int)WhiteRook]) {
4478 i = put(board, WhiteRook, 0, 0, ANY);
4479 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4482 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4484 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4487 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4488 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4491 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4494 int SetCharTable( char *table, const char * map )
4495 /* [HGM] moved here from winboard.c because of its general usefulness */
4496 /* Basically a safe strcpy that uses the last character as King */
4498 int result = FALSE; int NrPieces;
4500 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4501 && NrPieces >= 12 && !(NrPieces&1)) {
4502 int i; /* [HGM] Accept even length from 12 to 34 */
4504 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4505 for( i=0; i<NrPieces/2-1; i++ ) {
4507 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4509 table[(int) WhiteKing] = map[NrPieces/2-1];
4510 table[(int) BlackKing] = map[NrPieces-1];
4518 void Prelude(Board board)
4519 { // [HGM] superchess: random selection of exo-pieces
4520 int i, j, k; ChessSquare p;
4521 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4523 GetPositionNumber(); // use FRC position number
4525 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4526 SetCharTable(pieceToChar, appData.pieceToCharTable);
4527 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4528 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4531 j = seed%4; seed /= 4;
4532 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4533 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4534 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4535 j = seed%3 + (seed%3 >= j); seed /= 3;
4536 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4537 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4538 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4539 j = seed%3; seed /= 3;
4540 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4541 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4542 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4543 j = seed%2 + (seed%2 >= j); seed /= 2;
4544 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4545 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4546 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4547 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4548 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4549 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4550 put(board, exoPieces[0], 0, 0, ANY);
4551 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4555 InitPosition(redraw)
4558 ChessSquare (* pieces)[BOARD_SIZE];
4559 int i, j, pawnRow, overrule,
4560 oldx = gameInfo.boardWidth,
4561 oldy = gameInfo.boardHeight,
4562 oldh = gameInfo.holdingsWidth,
4563 oldv = gameInfo.variant;
4565 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4567 /* [AS] Initialize pv info list [HGM] and game status */
4569 for( i=0; i<MAX_MOVES; i++ ) {
4570 pvInfoList[i].depth = 0;
4571 epStatus[i]=EP_NONE;
4572 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4575 initialRulePlies = 0; /* 50-move counter start */
4577 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4578 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4582 /* [HGM] logic here is completely changed. In stead of full positions */
4583 /* the initialized data only consist of the two backranks. The switch */
4584 /* selects which one we will use, which is than copied to the Board */
4585 /* initialPosition, which for the rest is initialized by Pawns and */
4586 /* empty squares. This initial position is then copied to boards[0], */
4587 /* possibly after shuffling, so that it remains available. */
4589 gameInfo.holdingsWidth = 0; /* default board sizes */
4590 gameInfo.boardWidth = 8;
4591 gameInfo.boardHeight = 8;
4592 gameInfo.holdingsSize = 0;
4593 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4594 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4595 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4597 switch (gameInfo.variant) {
4598 case VariantFischeRandom:
4599 shuffleOpenings = TRUE;
4603 case VariantShatranj:
4604 pieces = ShatranjArray;
4605 nrCastlingRights = 0;
4606 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4608 case VariantTwoKings:
4609 pieces = twoKingsArray;
4611 case VariantCapaRandom:
4612 shuffleOpenings = TRUE;
4613 case VariantCapablanca:
4614 pieces = CapablancaArray;
4615 gameInfo.boardWidth = 10;
4616 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4619 pieces = GothicArray;
4620 gameInfo.boardWidth = 10;
4621 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4624 pieces = JanusArray;
4625 gameInfo.boardWidth = 10;
4626 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4627 nrCastlingRights = 6;
4628 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4629 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4630 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4631 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4632 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4633 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4636 pieces = FalconArray;
4637 gameInfo.boardWidth = 10;
4638 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4640 case VariantXiangqi:
4641 pieces = XiangqiArray;
4642 gameInfo.boardWidth = 9;
4643 gameInfo.boardHeight = 10;
4644 nrCastlingRights = 0;
4645 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4648 pieces = ShogiArray;
4649 gameInfo.boardWidth = 9;
4650 gameInfo.boardHeight = 9;
4651 gameInfo.holdingsSize = 7;
4652 nrCastlingRights = 0;
4653 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4655 case VariantCourier:
4656 pieces = CourierArray;
4657 gameInfo.boardWidth = 12;
4658 nrCastlingRights = 0;
4659 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4660 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4662 case VariantKnightmate:
4663 pieces = KnightmateArray;
4664 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4667 pieces = fairyArray;
4668 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4671 pieces = GreatArray;
4672 gameInfo.boardWidth = 10;
4673 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4674 gameInfo.holdingsSize = 8;
4678 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4679 gameInfo.holdingsSize = 8;
4680 startedFromSetupPosition = TRUE;
4682 case VariantCrazyhouse:
4683 case VariantBughouse:
4685 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4686 gameInfo.holdingsSize = 5;
4688 case VariantWildCastle:
4690 /* !!?shuffle with kings guaranteed to be on d or e file */
4691 shuffleOpenings = 1;
4693 case VariantNoCastle:
4695 nrCastlingRights = 0;
4696 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4697 /* !!?unconstrained back-rank shuffle */
4698 shuffleOpenings = 1;
4703 if(appData.NrFiles >= 0) {
4704 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4705 gameInfo.boardWidth = appData.NrFiles;
4707 if(appData.NrRanks >= 0) {
4708 gameInfo.boardHeight = appData.NrRanks;
4710 if(appData.holdingsSize >= 0) {
4711 i = appData.holdingsSize;
4712 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4713 gameInfo.holdingsSize = i;
4715 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4716 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4717 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4719 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4720 if(pawnRow < 1) pawnRow = 1;
4722 /* User pieceToChar list overrules defaults */
4723 if(appData.pieceToCharTable != NULL)
4724 SetCharTable(pieceToChar, appData.pieceToCharTable);
4726 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4728 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4729 s = (ChessSquare) 0; /* account holding counts in guard band */
4730 for( i=0; i<BOARD_HEIGHT; i++ )
4731 initialPosition[i][j] = s;
4733 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4734 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4735 initialPosition[pawnRow][j] = WhitePawn;
4736 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4737 if(gameInfo.variant == VariantXiangqi) {
4739 initialPosition[pawnRow][j] =
4740 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4741 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4742 initialPosition[2][j] = WhiteCannon;
4743 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4747 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4749 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4752 initialPosition[1][j] = WhiteBishop;
4753 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4755 initialPosition[1][j] = WhiteRook;
4756 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4759 if( nrCastlingRights == -1) {
4760 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4761 /* This sets default castling rights from none to normal corners */
4762 /* Variants with other castling rights must set them themselves above */
4763 nrCastlingRights = 6;
4765 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4766 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4767 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4768 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4769 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4770 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4773 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4774 if(gameInfo.variant == VariantGreat) { // promotion commoners
4775 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4776 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4777 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4778 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4780 if (appData.debugMode) {
4781 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4783 if(shuffleOpenings) {
4784 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4785 startedFromSetupPosition = TRUE;
4787 if(startedFromPositionFile) {
4788 /* [HGM] loadPos: use PositionFile for every new game */
4789 CopyBoard(initialPosition, filePosition);
4790 for(i=0; i<nrCastlingRights; i++)
4791 castlingRights[0][i] = initialRights[i] = fileRights[i];
4792 startedFromSetupPosition = TRUE;
4795 CopyBoard(boards[0], initialPosition);
4797 if(oldx != gameInfo.boardWidth ||
4798 oldy != gameInfo.boardHeight ||
4799 oldh != gameInfo.holdingsWidth
4801 || oldv == VariantGothic || // For licensing popups
4802 gameInfo.variant == VariantGothic
4805 || oldv == VariantFalcon ||
4806 gameInfo.variant == VariantFalcon
4809 InitDrawingSizes(-2 ,0);
4812 DrawPosition(TRUE, boards[currentMove]);
4816 SendBoard(cps, moveNum)
4817 ChessProgramState *cps;
4820 char message[MSG_SIZ];
4822 if (cps->useSetboard) {
4823 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4824 sprintf(message, "setboard %s\n", fen);
4825 SendToProgram(message, cps);
4831 /* Kludge to set black to move, avoiding the troublesome and now
4832 * deprecated "black" command.
4834 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4836 SendToProgram("edit\n", cps);
4837 SendToProgram("#\n", cps);
4838 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4839 bp = &boards[moveNum][i][BOARD_LEFT];
4840 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4841 if ((int) *bp < (int) BlackPawn) {
4842 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4844 if(message[0] == '+' || message[0] == '~') {
4845 sprintf(message, "%c%c%c+\n",
4846 PieceToChar((ChessSquare)(DEMOTED *bp)),
4849 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4850 message[1] = BOARD_RGHT - 1 - j + '1';
4851 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4853 SendToProgram(message, cps);
4858 SendToProgram("c\n", cps);
4859 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4860 bp = &boards[moveNum][i][BOARD_LEFT];
4861 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4862 if (((int) *bp != (int) EmptySquare)
4863 && ((int) *bp >= (int) BlackPawn)) {
4864 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4866 if(message[0] == '+' || message[0] == '~') {
4867 sprintf(message, "%c%c%c+\n",
4868 PieceToChar((ChessSquare)(DEMOTED *bp)),
4871 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4872 message[1] = BOARD_RGHT - 1 - j + '1';
4873 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4875 SendToProgram(message, cps);
4880 SendToProgram(".\n", cps);
4882 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4886 IsPromotion(fromX, fromY, toX, toY)
4887 int fromX, fromY, toX, toY;
4889 /* [HGM] add Shogi promotions */
4890 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4893 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4894 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4895 /* [HGM] Note to self: line above also weeds out drops */
4896 piece = boards[currentMove][fromY][fromX];
4897 if(gameInfo.variant == VariantShogi) {
4898 promotionZoneSize = 3;
4899 highestPromotingPiece = (int)WhiteKing;
4900 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4901 and if in normal chess we then allow promotion to King, why not
4902 allow promotion of other piece in Shogi? */
4904 if((int)piece >= BlackPawn) {
4905 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4907 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4909 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4910 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4912 return ( (int)piece <= highestPromotingPiece );
4916 InPalace(row, column)
4918 { /* [HGM] for Xiangqi */
4919 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4920 column < (BOARD_WIDTH + 4)/2 &&
4921 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4926 PieceForSquare (x, y)
4930 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4933 return boards[currentMove][y][x];
4937 OKToStartUserMove(x, y)
4940 ChessSquare from_piece;
4943 if (matchMode) return FALSE;
4944 if (gameMode == EditPosition) return TRUE;
4946 if (x >= 0 && y >= 0)
4947 from_piece = boards[currentMove][y][x];
4949 from_piece = EmptySquare;
4951 if (from_piece == EmptySquare) return FALSE;
4953 white_piece = (int)from_piece >= (int)WhitePawn &&
4954 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4957 case PlayFromGameFile:
4959 case TwoMachinesPlay:
4967 case MachinePlaysWhite:
4968 case IcsPlayingBlack:
4969 if (appData.zippyPlay) return FALSE;
4971 DisplayMoveError(_("You are playing Black"));
4976 case MachinePlaysBlack:
4977 case IcsPlayingWhite:
4978 if (appData.zippyPlay) return FALSE;
4980 DisplayMoveError(_("You are playing White"));
4986 if (!white_piece && WhiteOnMove(currentMove)) {
4987 DisplayMoveError(_("It is White's turn"));
4990 if (white_piece && !WhiteOnMove(currentMove)) {
4991 DisplayMoveError(_("It is Black's turn"));
4994 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4995 /* Editing correspondence game history */
4996 /* Could disallow this or prompt for confirmation */
4999 if (currentMove < forwardMostMove) {
5000 /* Discarding moves */
5001 /* Could prompt for confirmation here,
5002 but I don't think that's such a good idea */
5003 forwardMostMove = currentMove;
5007 case BeginningOfGame:
5008 if (appData.icsActive) return FALSE;
5009 if (!appData.noChessProgram) {
5011 DisplayMoveError(_("You are playing White"));
5018 if (!white_piece && WhiteOnMove(currentMove)) {
5019 DisplayMoveError(_("It is White's turn"));
5022 if (white_piece && !WhiteOnMove(currentMove)) {
5023 DisplayMoveError(_("It is Black's turn"));
5032 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5033 && gameMode != AnalyzeFile && gameMode != Training) {
5034 DisplayMoveError(_("Displayed position is not current"));
5040 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5041 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5042 int lastLoadGameUseList = FALSE;
5043 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5044 ChessMove lastLoadGameStart = (ChessMove) 0;
5047 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5048 int fromX, fromY, toX, toY;
5053 ChessSquare pdown, pup;
5055 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5057 /* [HGM] suppress all moves into holdings area and guard band */
5058 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5059 return ImpossibleMove;
5061 /* [HGM] <sameColor> moved to here from winboard.c */
5062 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5063 pdown = boards[currentMove][fromY][fromX];
5064 pup = boards[currentMove][toY][toX];
5065 if ( gameMode != EditPosition && !captureOwn &&
5066 (WhitePawn <= pdown && pdown < BlackPawn &&
5067 WhitePawn <= pup && pup < BlackPawn ||
5068 BlackPawn <= pdown && pdown < EmptySquare &&
5069 BlackPawn <= pup && pup < EmptySquare
5070 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5071 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5072 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5073 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5074 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5078 /* Check if the user is playing in turn. This is complicated because we
5079 let the user "pick up" a piece before it is his turn. So the piece he
5080 tried to pick up may have been captured by the time he puts it down!
5081 Therefore we use the color the user is supposed to be playing in this
5082 test, not the color of the piece that is currently on the starting
5083 square---except in EditGame mode, where the user is playing both
5084 sides; fortunately there the capture race can't happen. (It can
5085 now happen in IcsExamining mode, but that's just too bad. The user
5086 will get a somewhat confusing message in that case.)
5090 case PlayFromGameFile:
5092 case TwoMachinesPlay:
5096 /* We switched into a game mode where moves are not accepted,
5097 perhaps while the mouse button was down. */
5098 return ImpossibleMove;
5100 case MachinePlaysWhite:
5101 /* User is moving for Black */
5102 if (WhiteOnMove(currentMove)) {
5103 DisplayMoveError(_("It is White's turn"));
5104 return ImpossibleMove;
5108 case MachinePlaysBlack:
5109 /* User is moving for White */
5110 if (!WhiteOnMove(currentMove)) {
5111 DisplayMoveError(_("It is Black's turn"));
5112 return ImpossibleMove;
5118 case BeginningOfGame:
5121 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5122 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5123 /* User is moving for Black */
5124 if (WhiteOnMove(currentMove)) {
5125 DisplayMoveError(_("It is White's turn"));
5126 return ImpossibleMove;
5129 /* User is moving for White */
5130 if (!WhiteOnMove(currentMove)) {
5131 DisplayMoveError(_("It is Black's turn"));
5132 return ImpossibleMove;
5137 case IcsPlayingBlack:
5138 /* User is moving for Black */
5139 if (WhiteOnMove(currentMove)) {
5140 if (!appData.premove) {
5141 DisplayMoveError(_("It is White's turn"));
5142 } else if (toX >= 0 && toY >= 0) {
5145 premoveFromX = fromX;
5146 premoveFromY = fromY;
5147 premovePromoChar = promoChar;
5149 if (appData.debugMode)
5150 fprintf(debugFP, "Got premove: fromX %d,"
5151 "fromY %d, toX %d, toY %d\n",
5152 fromX, fromY, toX, toY);
5154 return ImpossibleMove;
5158 case IcsPlayingWhite:
5159 /* User is moving for White */
5160 if (!WhiteOnMove(currentMove)) {
5161 if (!appData.premove) {
5162 DisplayMoveError(_("It is Black's turn"));
5163 } else if (toX >= 0 && toY >= 0) {
5166 premoveFromX = fromX;
5167 premoveFromY = fromY;
5168 premovePromoChar = promoChar;
5170 if (appData.debugMode)
5171 fprintf(debugFP, "Got premove: fromX %d,"
5172 "fromY %d, toX %d, toY %d\n",
5173 fromX, fromY, toX, toY);
5175 return ImpossibleMove;
5183 /* EditPosition, empty square, or different color piece;
5184 click-click move is possible */
5185 if (toX == -2 || toY == -2) {
5186 boards[0][fromY][fromX] = EmptySquare;
5187 return AmbiguousMove;
5188 } else if (toX >= 0 && toY >= 0) {
5189 boards[0][toY][toX] = boards[0][fromY][fromX];
5190 boards[0][fromY][fromX] = EmptySquare;
5191 return AmbiguousMove;
5193 return ImpossibleMove;
5196 /* [HGM] If move started in holdings, it means a drop */
5197 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5198 if( pup != EmptySquare ) return ImpossibleMove;
5199 if(appData.testLegality) {
5200 /* it would be more logical if LegalityTest() also figured out
5201 * which drops are legal. For now we forbid pawns on back rank.
5202 * Shogi is on its own here...
5204 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5205 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5206 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5208 return WhiteDrop; /* Not needed to specify white or black yet */
5211 userOfferedDraw = FALSE;
5213 /* [HGM] always test for legality, to get promotion info */
5214 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5215 epStatus[currentMove], castlingRights[currentMove],
5216 fromY, fromX, toY, toX, promoChar);
5217 /* [HGM] but possibly ignore an IllegalMove result */
5218 if (appData.testLegality) {
5219 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5220 DisplayMoveError(_("Illegal move"));
5221 return ImpossibleMove;
5224 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5226 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5227 function is made into one that returns an OK move type if FinishMove
5228 should be called. This to give the calling driver routine the
5229 opportunity to finish the userMove input with a promotion popup,
5230 without bothering the user with this for invalid or illegal moves */
5232 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5235 /* Common tail of UserMoveEvent and DropMenuEvent */
5237 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5239 int fromX, fromY, toX, toY;
5240 /*char*/int promoChar;
5243 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5244 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5245 // [HGM] superchess: suppress promotions to non-available piece
5246 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5247 if(WhiteOnMove(currentMove)) {
5248 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5250 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5254 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5255 move type in caller when we know the move is a legal promotion */
5256 if(moveType == NormalMove && promoChar)
5257 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5258 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5259 /* [HGM] convert drag-and-drop piece drops to standard form */
5260 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5261 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5262 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5263 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5264 // fromX = boards[currentMove][fromY][fromX];
5265 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5266 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5267 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5268 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5272 /* [HGM] <popupFix> The following if has been moved here from
5273 UserMoveEvent(). Because it seemed to belon here (why not allow
5274 piece drops in training games?), and because it can only be
5275 performed after it is known to what we promote. */
5276 if (gameMode == Training) {
5277 /* compare the move played on the board to the next move in the
5278 * game. If they match, display the move and the opponent's response.
5279 * If they don't match, display an error message.
5282 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5283 CopyBoard(testBoard, boards[currentMove]);
5284 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5286 if (CompareBoards(testBoard, boards[currentMove+1])) {
5287 ForwardInner(currentMove+1);
5289 /* Autoplay the opponent's response.
5290 * if appData.animate was TRUE when Training mode was entered,
5291 * the response will be animated.
5293 saveAnimate = appData.animate;
5294 appData.animate = animateTraining;
5295 ForwardInner(currentMove+1);
5296 appData.animate = saveAnimate;
5298 /* check for the end of the game */
5299 if (currentMove >= forwardMostMove) {
5300 gameMode = PlayFromGameFile;
5302 SetTrainingModeOff();
5303 DisplayInformation(_("End of game"));
5306 DisplayError(_("Incorrect move"), 0);
5311 /* Ok, now we know that the move is good, so we can kill
5312 the previous line in Analysis Mode */
5313 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5314 forwardMostMove = currentMove;
5317 /* If we need the chess program but it's dead, restart it */
5318 ResurrectChessProgram();
5320 /* A user move restarts a paused game*/
5324 thinkOutput[0] = NULLCHAR;
5326 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5328 if (gameMode == BeginningOfGame) {
5329 if (appData.noChessProgram) {
5330 gameMode = EditGame;
5334 gameMode = MachinePlaysBlack;
5337 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5339 if (first.sendName) {
5340 sprintf(buf, "name %s\n", gameInfo.white);
5341 SendToProgram(buf, &first);
5347 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5348 /* Relay move to ICS or chess engine */
5349 if (appData.icsActive) {
5350 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5351 gameMode == IcsExamining) {
5352 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5356 if (first.sendTime && (gameMode == BeginningOfGame ||
5357 gameMode == MachinePlaysWhite ||
5358 gameMode == MachinePlaysBlack)) {
5359 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5361 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5362 // [HGM] book: if program might be playing, let it use book
5363 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5364 first.maybeThinking = TRUE;
5365 } else SendMoveToProgram(forwardMostMove-1, &first);
5366 if (currentMove == cmailOldMove + 1) {
5367 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5371 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5375 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5376 EP_UNKNOWN, castlingRights[currentMove]) ) {
5382 if (WhiteOnMove(currentMove)) {
5383 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5385 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5389 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5394 case MachinePlaysBlack:
5395 case MachinePlaysWhite:
5396 /* disable certain menu options while machine is thinking */
5397 SetMachineThinkingEnables();
5404 if(bookHit) { // [HGM] book: simulate book reply
5405 static char bookMove[MSG_SIZ]; // a bit generous?
5407 programStats.nodes = programStats.depth = programStats.time =
5408 programStats.score = programStats.got_only_move = 0;
5409 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5411 strcpy(bookMove, "move ");
5412 strcat(bookMove, bookHit);
5413 HandleMachineMove(bookMove, &first);
5419 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5420 int fromX, fromY, toX, toY;
5423 /* [HGM] This routine was added to allow calling of its two logical
5424 parts from other modules in the old way. Before, UserMoveEvent()
5425 automatically called FinishMove() if the move was OK, and returned
5426 otherwise. I separated the two, in order to make it possible to
5427 slip a promotion popup in between. But that it always needs two
5428 calls, to the first part, (now called UserMoveTest() ), and to
5429 FinishMove if the first part succeeded. Calls that do not need
5430 to do anything in between, can call this routine the old way.
5432 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5433 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5434 if(moveType == AmbiguousMove)
5435 DrawPosition(FALSE, boards[currentMove]);
5436 else if(moveType != ImpossibleMove && moveType != Comment)
5437 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5440 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5442 // char * hint = lastHint;
5443 FrontEndProgramStats stats;
5445 stats.which = cps == &first ? 0 : 1;
5446 stats.depth = cpstats->depth;
5447 stats.nodes = cpstats->nodes;
5448 stats.score = cpstats->score;
5449 stats.time = cpstats->time;
5450 stats.pv = cpstats->movelist;
5451 stats.hint = lastHint;
5452 stats.an_move_index = 0;
5453 stats.an_move_count = 0;
5455 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5456 stats.hint = cpstats->move_name;
5457 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5458 stats.an_move_count = cpstats->nr_moves;
5461 SetProgramStats( &stats );
5464 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5465 { // [HGM] book: this routine intercepts moves to simulate book replies
5466 char *bookHit = NULL;
5468 //first determine if the incoming move brings opponent into his book
5469 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5470 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5471 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5472 if(bookHit != NULL && !cps->bookSuspend) {
5473 // make sure opponent is not going to reply after receiving move to book position
5474 SendToProgram("force\n", cps);
5475 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5477 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5478 // now arrange restart after book miss
5480 // after a book hit we never send 'go', and the code after the call to this routine
5481 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5483 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5484 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5485 SendToProgram(buf, cps);
5486 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5487 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5488 SendToProgram("go\n", cps);
5489 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5490 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5491 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5492 SendToProgram("go\n", cps);
5493 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5495 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5499 ChessProgramState *savedState;
5500 void DeferredBookMove(void)
5502 if(savedState->lastPing != savedState->lastPong)
5503 ScheduleDelayedEvent(DeferredBookMove, 10);
5505 HandleMachineMove(savedMessage, savedState);
5509 HandleMachineMove(message, cps)
5511 ChessProgramState *cps;
5513 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5514 char realname[MSG_SIZ];
5515 int fromX, fromY, toX, toY;
5522 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5524 * Kludge to ignore BEL characters
5526 while (*message == '\007') message++;
5529 * [HGM] engine debug message: ignore lines starting with '#' character
5531 if(cps->debug && *message == '#') return;
5534 * Look for book output
5536 if (cps == &first && bookRequested) {
5537 if (message[0] == '\t' || message[0] == ' ') {
5538 /* Part of the book output is here; append it */
5539 strcat(bookOutput, message);
5540 strcat(bookOutput, " \n");
5542 } else if (bookOutput[0] != NULLCHAR) {
5543 /* All of book output has arrived; display it */
5544 char *p = bookOutput;
5545 while (*p != NULLCHAR) {
5546 if (*p == '\t') *p = ' ';
5549 DisplayInformation(bookOutput);
5550 bookRequested = FALSE;
5551 /* Fall through to parse the current output */
5556 * Look for machine move.
5558 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5559 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5561 /* This method is only useful on engines that support ping */
5562 if (cps->lastPing != cps->lastPong) {
5563 if (gameMode == BeginningOfGame) {
5564 /* Extra move from before last new; ignore */
5565 if (appData.debugMode) {
5566 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5569 if (appData.debugMode) {
5570 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5571 cps->which, gameMode);
5574 SendToProgram("undo\n", cps);
5580 case BeginningOfGame:
5581 /* Extra move from before last reset; ignore */
5582 if (appData.debugMode) {
5583 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5590 /* Extra move after we tried to stop. The mode test is
5591 not a reliable way of detecting this problem, but it's
5592 the best we can do on engines that don't support ping.
5594 if (appData.debugMode) {
5595 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5596 cps->which, gameMode);
5598 SendToProgram("undo\n", cps);
5601 case MachinePlaysWhite:
5602 case IcsPlayingWhite:
5603 machineWhite = TRUE;
5606 case MachinePlaysBlack:
5607 case IcsPlayingBlack:
5608 machineWhite = FALSE;
5611 case TwoMachinesPlay:
5612 machineWhite = (cps->twoMachinesColor[0] == 'w');
5615 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5616 if (appData.debugMode) {
5618 "Ignoring move out of turn by %s, gameMode %d"
5619 ", forwardMost %d\n",
5620 cps->which, gameMode, forwardMostMove);
5625 if (appData.debugMode) { int f = forwardMostMove;
5626 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5627 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5629 if(cps->alphaRank) AlphaRank(machineMove, 4);
5630 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5631 &fromX, &fromY, &toX, &toY, &promoChar)) {
5632 /* Machine move could not be parsed; ignore it. */
5633 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5634 machineMove, cps->which);
5635 DisplayError(buf1, 0);
5636 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5637 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5638 if (gameMode == TwoMachinesPlay) {
5639 GameEnds(machineWhite ? BlackWins : WhiteWins,
5645 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5646 /* So we have to redo legality test with true e.p. status here, */
5647 /* to make sure an illegal e.p. capture does not slip through, */
5648 /* to cause a forfeit on a justified illegal-move complaint */
5649 /* of the opponent. */
5650 if( gameMode==TwoMachinesPlay && appData.testLegality
5651 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5654 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5655 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5656 fromY, fromX, toY, toX, promoChar);
5657 if (appData.debugMode) {
5659 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5660 castlingRights[forwardMostMove][i], castlingRank[i]);
5661 fprintf(debugFP, "castling rights\n");
5663 if(moveType == IllegalMove) {
5664 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5665 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5666 GameEnds(machineWhite ? BlackWins : WhiteWins,
5669 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5670 /* [HGM] Kludge to handle engines that send FRC-style castling
5671 when they shouldn't (like TSCP-Gothic) */
5673 case WhiteASideCastleFR:
5674 case BlackASideCastleFR:
5676 currentMoveString[2]++;
5678 case WhiteHSideCastleFR:
5679 case BlackHSideCastleFR:
5681 currentMoveString[2]--;
5683 default: ; // nothing to do, but suppresses warning of pedantic compilers
5686 hintRequested = FALSE;
5687 lastHint[0] = NULLCHAR;
5688 bookRequested = FALSE;
5689 /* Program may be pondering now */
5690 cps->maybeThinking = TRUE;
5691 if (cps->sendTime == 2) cps->sendTime = 1;
5692 if (cps->offeredDraw) cps->offeredDraw--;
5695 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5697 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5699 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5700 char buf[3*MSG_SIZ];
5702 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5703 programStats.score / 100.,
5705 programStats.time / 100.,
5706 (unsigned int)programStats.nodes,
5707 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5708 programStats.movelist);
5710 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5714 /* currentMoveString is set as a side-effect of ParseOneMove */
5715 strcpy(machineMove, currentMoveString);
5716 strcat(machineMove, "\n");
5717 strcpy(moveList[forwardMostMove], machineMove);
5719 /* [AS] Save move info and clear stats for next move */
5720 pvInfoList[ forwardMostMove ].score = programStats.score;
5721 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5722 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5723 ClearProgramStats();
5724 thinkOutput[0] = NULLCHAR;
5725 hiddenThinkOutputState = 0;
5727 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5729 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5730 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5733 while( count < adjudicateLossPlies ) {
5734 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5737 score = -score; /* Flip score for winning side */
5740 if( score > adjudicateLossThreshold ) {
5747 if( count >= adjudicateLossPlies ) {
5748 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5750 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5751 "Xboard adjudication",
5758 if( gameMode == TwoMachinesPlay ) {
5759 // [HGM] some adjudications useful with buggy engines
5760 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5761 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5764 if( appData.testLegality )
5765 { /* [HGM] Some more adjudications for obstinate engines */
5766 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5767 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5768 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5769 static int moveCount = 6;
5771 char *reason = NULL;
5773 /* Count what is on board. */
5774 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5775 { ChessSquare p = boards[forwardMostMove][i][j];
5779 { /* count B,N,R and other of each side */
5782 NrK++; break; // [HGM] atomic: count Kings
5786 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5787 bishopsColor |= 1 << ((i^j)&1);
5792 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5793 bishopsColor |= 1 << ((i^j)&1);
5808 PawnAdvance += m; NrPawns++;
5810 NrPieces += (p != EmptySquare);
5811 NrW += ((int)p < (int)BlackPawn);
5812 if(gameInfo.variant == VariantXiangqi &&
5813 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5814 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5815 NrW -= ((int)p < (int)BlackPawn);
5819 /* Some material-based adjudications that have to be made before stalemate test */
5820 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5821 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5822 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5823 if(appData.checkMates) {
5824 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5825 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5826 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5827 "Xboard adjudication: King destroyed", GE_XBOARD );
5832 /* Bare King in Shatranj (loses) or Losers (wins) */
5833 if( NrW == 1 || NrPieces - NrW == 1) {
5834 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5835 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5836 if(appData.checkMates) {
5837 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5838 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5839 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5840 "Xboard adjudication: Bare king", GE_XBOARD );
5844 if( gameInfo.variant == VariantShatranj && --bare < 0)
5846 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5847 if(appData.checkMates) {
5848 /* but only adjudicate if adjudication enabled */
5849 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5850 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5851 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5852 "Xboard adjudication: Bare king", GE_XBOARD );
5859 // don't wait for engine to announce game end if we can judge ourselves
5860 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5861 castlingRights[forwardMostMove]) ) {
5863 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5864 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5865 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5866 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5869 reason = "Xboard adjudication: 3rd check";
5870 epStatus[forwardMostMove] = EP_CHECKMATE;
5880 reason = "Xboard adjudication: Stalemate";
5881 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5882 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5883 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5884 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5885 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5886 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5887 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5888 EP_CHECKMATE : EP_WINS);
5889 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5890 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5894 reason = "Xboard adjudication: Checkmate";
5895 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5899 switch(i = epStatus[forwardMostMove]) {
5901 result = GameIsDrawn; break;
5903 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5905 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5907 result = (ChessMove) 0;
5909 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5910 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5911 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5912 GameEnds( result, reason, GE_XBOARD );
5916 /* Next absolutely insufficient mating material. */
5917 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5918 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5919 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5920 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5921 { /* KBK, KNK, KK of KBKB with like Bishops */
5923 /* always flag draws, for judging claims */
5924 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5926 if(appData.materialDraws) {
5927 /* but only adjudicate them if adjudication enabled */
5928 SendToProgram("force\n", cps->other); // suppress reply
5929 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5930 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5931 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5936 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5938 ( NrWR == 1 && NrBR == 1 /* KRKR */
5939 || NrWQ==1 && NrBQ==1 /* KQKQ */
5940 || NrWN==2 || NrBN==2 /* KNNK */
5941 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5943 if(--moveCount < 0 && appData.trivialDraws)
5944 { /* if the first 3 moves do not show a tactical win, declare draw */
5945 SendToProgram("force\n", cps->other); // suppress reply
5946 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5947 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5948 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5951 } else moveCount = 6;
5955 if (appData.debugMode) { int i;
5956 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5957 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5958 appData.drawRepeats);
5959 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5960 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5964 /* Check for rep-draws */
5966 for(k = forwardMostMove-2;
5967 k>=backwardMostMove && k>=forwardMostMove-100 &&
5968 epStatus[k] < EP_UNKNOWN &&
5969 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5972 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5973 /* compare castling rights */
5974 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5975 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5976 rights++; /* King lost rights, while rook still had them */
5977 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5978 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5979 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5980 rights++; /* but at least one rook lost them */
5982 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5983 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5985 if( castlingRights[forwardMostMove][5] >= 0 ) {
5986 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5987 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5990 if( rights == 0 && ++count > appData.drawRepeats-2
5991 && appData.drawRepeats > 1) {
5992 /* adjudicate after user-specified nr of repeats */
5993 SendToProgram("force\n", cps->other); // suppress reply
5994 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5995 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5996 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5997 // [HGM] xiangqi: check for forbidden perpetuals
5998 int m, ourPerpetual = 1, hisPerpetual = 1;
5999 for(m=forwardMostMove; m>k; m-=2) {
6000 if(MateTest(boards[m], PosFlags(m),
6001 EP_NONE, castlingRights[m]) != MT_CHECK)
6002 ourPerpetual = 0; // the current mover did not always check
6003 if(MateTest(boards[m-1], PosFlags(m-1),
6004 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6005 hisPerpetual = 0; // the opponent did not always check
6007 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6008 ourPerpetual, hisPerpetual);
6009 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6010 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6011 "Xboard adjudication: perpetual checking", GE_XBOARD );
6014 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6015 break; // (or we would have caught him before). Abort repetition-checking loop.
6016 // Now check for perpetual chases
6017 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6018 hisPerpetual = PerpetualChase(k, forwardMostMove);
6019 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6020 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6021 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6022 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6025 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6026 break; // Abort repetition-checking loop.
6028 // if neither of us is checking or chasing all the time, or both are, it is draw
6030 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6033 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6034 epStatus[forwardMostMove] = EP_REP_DRAW;
6038 /* Now we test for 50-move draws. Determine ply count */
6039 count = forwardMostMove;
6040 /* look for last irreversble move */
6041 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6043 /* if we hit starting position, add initial plies */
6044 if( count == backwardMostMove )
6045 count -= initialRulePlies;
6046 count = forwardMostMove - count;
6048 epStatus[forwardMostMove] = EP_RULE_DRAW;
6049 /* this is used to judge if draw claims are legal */
6050 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6051 SendToProgram("force\n", cps->other); // suppress reply
6052 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6053 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6054 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6058 /* if draw offer is pending, treat it as a draw claim
6059 * when draw condition present, to allow engines a way to
6060 * claim draws before making their move to avoid a race
6061 * condition occurring after their move
6063 if( cps->other->offeredDraw || cps->offeredDraw ) {
6065 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6066 p = "Draw claim: 50-move rule";
6067 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6068 p = "Draw claim: 3-fold repetition";
6069 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6070 p = "Draw claim: insufficient mating material";
6072 SendToProgram("force\n", cps->other); // suppress reply
6073 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6074 GameEnds( GameIsDrawn, p, GE_XBOARD );
6075 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6081 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6082 SendToProgram("force\n", cps->other); // suppress reply
6083 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6084 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6086 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6093 if (gameMode == TwoMachinesPlay) {
6094 /* [HGM] relaying draw offers moved to after reception of move */
6095 /* and interpreting offer as claim if it brings draw condition */
6096 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6097 SendToProgram("draw\n", cps->other);
6099 if (cps->other->sendTime) {
6100 SendTimeRemaining(cps->other,
6101 cps->other->twoMachinesColor[0] == 'w');
6103 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6104 if (firstMove && !bookHit) {
6106 if (cps->other->useColors) {
6107 SendToProgram(cps->other->twoMachinesColor, cps->other);
6109 SendToProgram("go\n", cps->other);
6111 cps->other->maybeThinking = TRUE;
6114 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116 if (!pausing && appData.ringBellAfterMoves) {
6121 * Reenable menu items that were disabled while
6122 * machine was thinking
6124 if (gameMode != TwoMachinesPlay)
6125 SetUserThinkingEnables();
6127 // [HGM] book: after book hit opponent has received move and is now in force mode
6128 // force the book reply into it, and then fake that it outputted this move by jumping
6129 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6131 static char bookMove[MSG_SIZ]; // a bit generous?
6133 strcpy(bookMove, "move ");
6134 strcat(bookMove, bookHit);
6137 programStats.nodes = programStats.depth = programStats.time =
6138 programStats.score = programStats.got_only_move = 0;
6139 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6141 if(cps->lastPing != cps->lastPong) {
6142 savedMessage = message; // args for deferred call
6144 ScheduleDelayedEvent(DeferredBookMove, 10);
6153 /* Set special modes for chess engines. Later something general
6154 * could be added here; for now there is just one kludge feature,
6155 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6156 * when "xboard" is given as an interactive command.
6158 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6159 cps->useSigint = FALSE;
6160 cps->useSigterm = FALSE;
6162 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6163 ParseFeatures(message+8, cps);
6164 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6167 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6168 * want this, I was asked to put it in, and obliged.
6170 if (!strncmp(message, "setboard ", 9)) {
6171 Board initial_position; int i;
6173 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6175 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6176 DisplayError(_("Bad FEN received from engine"), 0);
6179 Reset(FALSE, FALSE);
6180 CopyBoard(boards[0], initial_position);
6181 initialRulePlies = FENrulePlies;
6182 epStatus[0] = FENepStatus;
6183 for( i=0; i<nrCastlingRights; i++ )
6184 castlingRights[0][i] = FENcastlingRights[i];
6185 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6186 else gameMode = MachinePlaysBlack;
6187 DrawPosition(FALSE, boards[currentMove]);
6193 * Look for communication commands
6195 if (!strncmp(message, "telluser ", 9)) {
6196 DisplayNote(message + 9);
6199 if (!strncmp(message, "tellusererror ", 14)) {
6200 DisplayError(message + 14, 0);
6203 if (!strncmp(message, "tellopponent ", 13)) {
6204 if (appData.icsActive) {
6206 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6210 DisplayNote(message + 13);
6214 if (!strncmp(message, "tellothers ", 11)) {
6215 if (appData.icsActive) {
6217 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6223 if (!strncmp(message, "tellall ", 8)) {
6224 if (appData.icsActive) {
6226 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6230 DisplayNote(message + 8);
6234 if (strncmp(message, "warning", 7) == 0) {
6235 /* Undocumented feature, use tellusererror in new code */
6236 DisplayError(message, 0);
6239 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6240 strcpy(realname, cps->tidy);
6241 strcat(realname, " query");
6242 AskQuestion(realname, buf2, buf1, cps->pr);
6245 /* Commands from the engine directly to ICS. We don't allow these to be
6246 * sent until we are logged on. Crafty kibitzes have been known to
6247 * interfere with the login process.
6250 if (!strncmp(message, "tellics ", 8)) {
6251 SendToICS(message + 8);
6255 if (!strncmp(message, "tellicsnoalias ", 15)) {
6256 SendToICS(ics_prefix);
6257 SendToICS(message + 15);
6261 /* The following are for backward compatibility only */
6262 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6263 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6264 SendToICS(ics_prefix);
6270 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6274 * If the move is illegal, cancel it and redraw the board.
6275 * Also deal with other error cases. Matching is rather loose
6276 * here to accommodate engines written before the spec.
6278 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6279 strncmp(message, "Error", 5) == 0) {
6280 if (StrStr(message, "name") ||
6281 StrStr(message, "rating") || StrStr(message, "?") ||
6282 StrStr(message, "result") || StrStr(message, "board") ||
6283 StrStr(message, "bk") || StrStr(message, "computer") ||
6284 StrStr(message, "variant") || StrStr(message, "hint") ||
6285 StrStr(message, "random") || StrStr(message, "depth") ||
6286 StrStr(message, "accepted")) {
6289 if (StrStr(message, "protover")) {
6290 /* Program is responding to input, so it's apparently done
6291 initializing, and this error message indicates it is
6292 protocol version 1. So we don't need to wait any longer
6293 for it to initialize and send feature commands. */
6294 FeatureDone(cps, 1);
6295 cps->protocolVersion = 1;
6298 cps->maybeThinking = FALSE;
6300 if (StrStr(message, "draw")) {
6301 /* Program doesn't have "draw" command */
6302 cps->sendDrawOffers = 0;
6305 if (cps->sendTime != 1 &&
6306 (StrStr(message, "time") || StrStr(message, "otim"))) {
6307 /* Program apparently doesn't have "time" or "otim" command */
6311 if (StrStr(message, "analyze")) {
6312 cps->analysisSupport = FALSE;
6313 cps->analyzing = FALSE;
6315 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6316 DisplayError(buf2, 0);
6319 if (StrStr(message, "(no matching move)st")) {
6320 /* Special kludge for GNU Chess 4 only */
6321 cps->stKludge = TRUE;
6322 SendTimeControl(cps, movesPerSession, timeControl,
6323 timeIncrement, appData.searchDepth,
6327 if (StrStr(message, "(no matching move)sd")) {
6328 /* Special kludge for GNU Chess 4 only */
6329 cps->sdKludge = TRUE;
6330 SendTimeControl(cps, movesPerSession, timeControl,
6331 timeIncrement, appData.searchDepth,
6335 if (!StrStr(message, "llegal")) {
6338 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6339 gameMode == IcsIdle) return;
6340 if (forwardMostMove <= backwardMostMove) return;
6341 if (pausing) PauseEvent();
6342 if(appData.forceIllegal) {
6343 // [HGM] illegal: machine refused move; force position after move into it
6344 SendToProgram("force\n", cps);
6345 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6346 // we have a real problem now, as SendBoard will use the a2a3 kludge
6347 // when black is to move, while there might be nothing on a2 or black
6348 // might already have the move. So send the board as if white has the move.
6349 // But first we must change the stm of the engine, as it refused the last move
6350 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6351 if(WhiteOnMove(forwardMostMove)) {
6352 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6353 SendBoard(cps, forwardMostMove); // kludgeless board
6355 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6356 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6357 SendBoard(cps, forwardMostMove+1); // kludgeless board
6359 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6360 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6361 gameMode == TwoMachinesPlay)
6362 SendToProgram("go\n", cps);
6365 if (gameMode == PlayFromGameFile) {
6366 /* Stop reading this game file */
6367 gameMode = EditGame;
6370 currentMove = --forwardMostMove;
6371 DisplayMove(currentMove-1); /* before DisplayMoveError */
6373 DisplayBothClocks();
6374 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6375 parseList[currentMove], cps->which);
6376 DisplayMoveError(buf1);
6377 DrawPosition(FALSE, boards[currentMove]);
6379 /* [HGM] illegal-move claim should forfeit game when Xboard */
6380 /* only passes fully legal moves */
6381 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6382 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6383 "False illegal-move claim", GE_XBOARD );
6387 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6388 /* Program has a broken "time" command that
6389 outputs a string not ending in newline.
6395 * If chess program startup fails, exit with an error message.
6396 * Attempts to recover here are futile.
6398 if ((StrStr(message, "unknown host") != NULL)
6399 || (StrStr(message, "No remote directory") != NULL)
6400 || (StrStr(message, "not found") != NULL)
6401 || (StrStr(message, "No such file") != NULL)
6402 || (StrStr(message, "can't alloc") != NULL)
6403 || (StrStr(message, "Permission denied") != NULL)) {
6405 cps->maybeThinking = FALSE;
6406 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6407 cps->which, cps->program, cps->host, message);
6408 RemoveInputSource(cps->isr);
6409 DisplayFatalError(buf1, 0, 1);
6414 * Look for hint output
6416 if (sscanf(message, "Hint: %s", buf1) == 1) {
6417 if (cps == &first && hintRequested) {
6418 hintRequested = FALSE;
6419 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6420 &fromX, &fromY, &toX, &toY, &promoChar)) {
6421 (void) CoordsToAlgebraic(boards[forwardMostMove],
6422 PosFlags(forwardMostMove), EP_UNKNOWN,
6423 fromY, fromX, toY, toX, promoChar, buf1);
6424 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6425 DisplayInformation(buf2);
6427 /* Hint move could not be parsed!? */
6428 snprintf(buf2, sizeof(buf2),
6429 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6431 DisplayError(buf2, 0);
6434 strcpy(lastHint, buf1);
6440 * Ignore other messages if game is not in progress
6442 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6443 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6446 * look for win, lose, draw, or draw offer
6448 if (strncmp(message, "1-0", 3) == 0) {
6449 char *p, *q, *r = "";
6450 p = strchr(message, '{');
6458 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6460 } else if (strncmp(message, "0-1", 3) == 0) {
6461 char *p, *q, *r = "";
6462 p = strchr(message, '{');
6470 /* Kludge for Arasan 4.1 bug */
6471 if (strcmp(r, "Black resigns") == 0) {
6472 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6475 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6477 } else if (strncmp(message, "1/2", 3) == 0) {
6478 char *p, *q, *r = "";
6479 p = strchr(message, '{');
6488 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6491 } else if (strncmp(message, "White resign", 12) == 0) {
6492 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6494 } else if (strncmp(message, "Black resign", 12) == 0) {
6495 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6497 } else if (strncmp(message, "White matches", 13) == 0 ||
6498 strncmp(message, "Black matches", 13) == 0 ) {
6499 /* [HGM] ignore GNUShogi noises */
6501 } else if (strncmp(message, "White", 5) == 0 &&
6502 message[5] != '(' &&
6503 StrStr(message, "Black") == NULL) {
6504 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6506 } else if (strncmp(message, "Black", 5) == 0 &&
6507 message[5] != '(') {
6508 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6510 } else if (strcmp(message, "resign") == 0 ||
6511 strcmp(message, "computer resigns") == 0) {
6513 case MachinePlaysBlack:
6514 case IcsPlayingBlack:
6515 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6517 case MachinePlaysWhite:
6518 case IcsPlayingWhite:
6519 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6521 case TwoMachinesPlay:
6522 if (cps->twoMachinesColor[0] == 'w')
6523 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6525 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6532 } else if (strncmp(message, "opponent mates", 14) == 0) {
6534 case MachinePlaysBlack:
6535 case IcsPlayingBlack:
6536 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6538 case MachinePlaysWhite:
6539 case IcsPlayingWhite:
6540 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6542 case TwoMachinesPlay:
6543 if (cps->twoMachinesColor[0] == 'w')
6544 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6546 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6553 } else if (strncmp(message, "computer mates", 14) == 0) {
6555 case MachinePlaysBlack:
6556 case IcsPlayingBlack:
6557 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6559 case MachinePlaysWhite:
6560 case IcsPlayingWhite:
6561 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6563 case TwoMachinesPlay:
6564 if (cps->twoMachinesColor[0] == 'w')
6565 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6567 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6574 } else if (strncmp(message, "checkmate", 9) == 0) {
6575 if (WhiteOnMove(forwardMostMove)) {
6576 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6578 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6581 } else if (strstr(message, "Draw") != NULL ||
6582 strstr(message, "game is a draw") != NULL) {
6583 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6585 } else if (strstr(message, "offer") != NULL &&
6586 strstr(message, "draw") != NULL) {
6588 if (appData.zippyPlay && first.initDone) {
6589 /* Relay offer to ICS */
6590 SendToICS(ics_prefix);
6591 SendToICS("draw\n");
6594 cps->offeredDraw = 2; /* valid until this engine moves twice */
6595 if (gameMode == TwoMachinesPlay) {
6596 if (cps->other->offeredDraw) {
6597 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6598 /* [HGM] in two-machine mode we delay relaying draw offer */
6599 /* until after we also have move, to see if it is really claim */
6601 } else if (gameMode == MachinePlaysWhite ||
6602 gameMode == MachinePlaysBlack) {
6603 if (userOfferedDraw) {
6604 DisplayInformation(_("Machine accepts your draw offer"));
6605 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6607 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6614 * Look for thinking output
6616 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6617 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6619 int plylev, mvleft, mvtot, curscore, time;
6620 char mvname[MOVE_LEN];
6624 int prefixHint = FALSE;
6625 mvname[0] = NULLCHAR;
6628 case MachinePlaysBlack:
6629 case IcsPlayingBlack:
6630 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6632 case MachinePlaysWhite:
6633 case IcsPlayingWhite:
6634 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6639 case IcsObserving: /* [DM] icsEngineAnalyze */
6640 if (!appData.icsEngineAnalyze) ignore = TRUE;
6642 case TwoMachinesPlay:
6643 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6654 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6655 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6657 if (plyext != ' ' && plyext != '\t') {
6661 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6662 if( cps->scoreIsAbsolute &&
6663 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6665 curscore = -curscore;
6669 programStats.depth = plylev;
6670 programStats.nodes = nodes;
6671 programStats.time = time;
6672 programStats.score = curscore;
6673 programStats.got_only_move = 0;
6675 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6678 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6679 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6680 if(WhiteOnMove(forwardMostMove))
6681 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6682 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6685 /* Buffer overflow protection */
6686 if (buf1[0] != NULLCHAR) {
6687 if (strlen(buf1) >= sizeof(programStats.movelist)
6688 && appData.debugMode) {
6690 "PV is too long; using the first %d bytes.\n",
6691 sizeof(programStats.movelist) - 1);
6694 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6696 sprintf(programStats.movelist, " no PV\n");
6699 if (programStats.seen_stat) {
6700 programStats.ok_to_send = 1;
6703 if (strchr(programStats.movelist, '(') != NULL) {
6704 programStats.line_is_book = 1;
6705 programStats.nr_moves = 0;
6706 programStats.moves_left = 0;
6708 programStats.line_is_book = 0;
6711 SendProgramStatsToFrontend( cps, &programStats );
6714 [AS] Protect the thinkOutput buffer from overflow... this
6715 is only useful if buf1 hasn't overflowed first!
6717 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6719 (gameMode == TwoMachinesPlay ?
6720 ToUpper(cps->twoMachinesColor[0]) : ' '),
6721 ((double) curscore) / 100.0,
6722 prefixHint ? lastHint : "",
6723 prefixHint ? " " : "" );
6725 if( buf1[0] != NULLCHAR ) {
6726 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6728 if( strlen(buf1) > max_len ) {
6729 if( appData.debugMode) {
6730 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6732 buf1[max_len+1] = '\0';
6735 strcat( thinkOutput, buf1 );
6738 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6739 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6740 DisplayMove(currentMove - 1);
6744 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6745 /* crafty (9.25+) says "(only move) <move>"
6746 * if there is only 1 legal move
6748 sscanf(p, "(only move) %s", buf1);
6749 sprintf(thinkOutput, "%s (only move)", buf1);
6750 sprintf(programStats.movelist, "%s (only move)", buf1);
6751 programStats.depth = 1;
6752 programStats.nr_moves = 1;
6753 programStats.moves_left = 1;
6754 programStats.nodes = 1;
6755 programStats.time = 1;
6756 programStats.got_only_move = 1;
6758 /* Not really, but we also use this member to
6759 mean "line isn't going to change" (Crafty
6760 isn't searching, so stats won't change) */
6761 programStats.line_is_book = 1;
6763 SendProgramStatsToFrontend( cps, &programStats );
6765 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6766 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6767 DisplayMove(currentMove - 1);
6770 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6771 &time, &nodes, &plylev, &mvleft,
6772 &mvtot, mvname) >= 5) {
6773 /* The stat01: line is from Crafty (9.29+) in response
6774 to the "." command */
6775 programStats.seen_stat = 1;
6776 cps->maybeThinking = TRUE;
6778 if (programStats.got_only_move || !appData.periodicUpdates)
6781 programStats.depth = plylev;
6782 programStats.time = time;
6783 programStats.nodes = nodes;
6784 programStats.moves_left = mvleft;
6785 programStats.nr_moves = mvtot;
6786 strcpy(programStats.move_name, mvname);
6787 programStats.ok_to_send = 1;
6788 programStats.movelist[0] = '\0';
6790 SendProgramStatsToFrontend( cps, &programStats );
6794 } else if (strncmp(message,"++",2) == 0) {
6795 /* Crafty 9.29+ outputs this */
6796 programStats.got_fail = 2;
6799 } else if (strncmp(message,"--",2) == 0) {
6800 /* Crafty 9.29+ outputs this */
6801 programStats.got_fail = 1;
6804 } else if (thinkOutput[0] != NULLCHAR &&
6805 strncmp(message, " ", 4) == 0) {
6806 unsigned message_len;
6809 while (*p && *p == ' ') p++;
6811 message_len = strlen( p );
6813 /* [AS] Avoid buffer overflow */
6814 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6815 strcat(thinkOutput, " ");
6816 strcat(thinkOutput, p);
6819 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6820 strcat(programStats.movelist, " ");
6821 strcat(programStats.movelist, p);
6824 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6825 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6826 DisplayMove(currentMove - 1);
6834 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6835 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6837 ChessProgramStats cpstats;
6839 if (plyext != ' ' && plyext != '\t') {
6843 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6844 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6845 curscore = -curscore;
6848 cpstats.depth = plylev;
6849 cpstats.nodes = nodes;
6850 cpstats.time = time;
6851 cpstats.score = curscore;
6852 cpstats.got_only_move = 0;
6853 cpstats.movelist[0] = '\0';
6855 if (buf1[0] != NULLCHAR) {
6856 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6859 cpstats.ok_to_send = 0;
6860 cpstats.line_is_book = 0;
6861 cpstats.nr_moves = 0;
6862 cpstats.moves_left = 0;
6864 SendProgramStatsToFrontend( cps, &cpstats );
6871 /* Parse a game score from the character string "game", and
6872 record it as the history of the current game. The game
6873 score is NOT assumed to start from the standard position.
6874 The display is not updated in any way.
6877 ParseGameHistory(game)
6881 int fromX, fromY, toX, toY, boardIndex;
6886 if (appData.debugMode)
6887 fprintf(debugFP, "Parsing game history: %s\n", game);
6889 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6890 gameInfo.site = StrSave(appData.icsHost);
6891 gameInfo.date = PGNDate();
6892 gameInfo.round = StrSave("-");
6894 /* Parse out names of players */
6895 while (*game == ' ') game++;
6897 while (*game != ' ') *p++ = *game++;
6899 gameInfo.white = StrSave(buf);
6900 while (*game == ' ') game++;
6902 while (*game != ' ' && *game != '\n') *p++ = *game++;
6904 gameInfo.black = StrSave(buf);
6907 boardIndex = blackPlaysFirst ? 1 : 0;
6910 yyboardindex = boardIndex;
6911 moveType = (ChessMove) yylex();
6913 case IllegalMove: /* maybe suicide chess, etc. */
6914 if (appData.debugMode) {
6915 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6916 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6917 setbuf(debugFP, NULL);
6919 case WhitePromotionChancellor:
6920 case BlackPromotionChancellor:
6921 case WhitePromotionArchbishop:
6922 case BlackPromotionArchbishop:
6923 case WhitePromotionQueen:
6924 case BlackPromotionQueen:
6925 case WhitePromotionRook:
6926 case BlackPromotionRook:
6927 case WhitePromotionBishop:
6928 case BlackPromotionBishop:
6929 case WhitePromotionKnight:
6930 case BlackPromotionKnight:
6931 case WhitePromotionKing:
6932 case BlackPromotionKing:
6934 case WhiteCapturesEnPassant:
6935 case BlackCapturesEnPassant:
6936 case WhiteKingSideCastle:
6937 case WhiteQueenSideCastle:
6938 case BlackKingSideCastle:
6939 case BlackQueenSideCastle:
6940 case WhiteKingSideCastleWild:
6941 case WhiteQueenSideCastleWild:
6942 case BlackKingSideCastleWild:
6943 case BlackQueenSideCastleWild:
6945 case WhiteHSideCastleFR:
6946 case WhiteASideCastleFR:
6947 case BlackHSideCastleFR:
6948 case BlackASideCastleFR:
6950 fromX = currentMoveString[0] - AAA;
6951 fromY = currentMoveString[1] - ONE;
6952 toX = currentMoveString[2] - AAA;
6953 toY = currentMoveString[3] - ONE;
6954 promoChar = currentMoveString[4];
6958 fromX = moveType == WhiteDrop ?
6959 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6960 (int) CharToPiece(ToLower(currentMoveString[0]));
6962 toX = currentMoveString[2] - AAA;
6963 toY = currentMoveString[3] - ONE;
6964 promoChar = NULLCHAR;
6968 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6969 if (appData.debugMode) {
6970 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6971 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6972 setbuf(debugFP, NULL);
6974 DisplayError(buf, 0);
6976 case ImpossibleMove:
6978 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6979 if (appData.debugMode) {
6980 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6981 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6982 setbuf(debugFP, NULL);
6984 DisplayError(buf, 0);
6986 case (ChessMove) 0: /* end of file */
6987 if (boardIndex < backwardMostMove) {
6988 /* Oops, gap. How did that happen? */
6989 DisplayError(_("Gap in move list"), 0);
6992 backwardMostMove = blackPlaysFirst ? 1 : 0;
6993 if (boardIndex > forwardMostMove) {
6994 forwardMostMove = boardIndex;
6998 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6999 strcat(parseList[boardIndex-1], " ");
7000 strcat(parseList[boardIndex-1], yy_text);
7012 case GameUnfinished:
7013 if (gameMode == IcsExamining) {
7014 if (boardIndex < backwardMostMove) {
7015 /* Oops, gap. How did that happen? */
7018 backwardMostMove = blackPlaysFirst ? 1 : 0;
7021 gameInfo.result = moveType;
7022 p = strchr(yy_text, '{');
7023 if (p == NULL) p = strchr(yy_text, '(');
7026 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7028 q = strchr(p, *p == '{' ? '}' : ')');
7029 if (q != NULL) *q = NULLCHAR;
7032 gameInfo.resultDetails = StrSave(p);
7035 if (boardIndex >= forwardMostMove &&
7036 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7037 backwardMostMove = blackPlaysFirst ? 1 : 0;
7040 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7041 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7042 parseList[boardIndex]);
7043 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7044 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7045 /* currentMoveString is set as a side-effect of yylex */
7046 strcpy(moveList[boardIndex], currentMoveString);
7047 strcat(moveList[boardIndex], "\n");
7049 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7050 castlingRights[boardIndex], &epStatus[boardIndex]);
7051 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7052 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7058 if(gameInfo.variant != VariantShogi)
7059 strcat(parseList[boardIndex - 1], "+");
7063 strcat(parseList[boardIndex - 1], "#");
7070 /* Apply a move to the given board */
7072 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7073 int fromX, fromY, toX, toY;
7079 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7081 /* [HGM] compute & store e.p. status and castling rights for new position */
7082 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7085 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7089 if( board[toY][toX] != EmptySquare )
7092 if( board[fromY][fromX] == WhitePawn ) {
7093 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7096 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7097 gameInfo.variant != VariantBerolina || toX < fromX)
7098 *ep = toX | berolina;
7099 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7100 gameInfo.variant != VariantBerolina || toX > fromX)
7104 if( board[fromY][fromX] == BlackPawn ) {
7105 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7107 if( toY-fromY== -2) {
7108 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7109 gameInfo.variant != VariantBerolina || toX < fromX)
7110 *ep = toX | berolina;
7111 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7112 gameInfo.variant != VariantBerolina || toX > fromX)
7117 for(i=0; i<nrCastlingRights; i++) {
7118 if(castling[i] == fromX && castlingRank[i] == fromY ||
7119 castling[i] == toX && castlingRank[i] == toY
7120 ) castling[i] = -1; // revoke for moved or captured piece
7125 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7126 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7127 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7129 if (fromX == toX && fromY == toY) return;
7131 if (fromY == DROP_RANK) {
7133 piece = board[toY][toX] = (ChessSquare) fromX;
7135 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7136 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7137 if(gameInfo.variant == VariantKnightmate)
7138 king += (int) WhiteUnicorn - (int) WhiteKing;
7140 /* Code added by Tord: */
7141 /* FRC castling assumed when king captures friendly rook. */
7142 if (board[fromY][fromX] == WhiteKing &&
7143 board[toY][toX] == WhiteRook) {
7144 board[fromY][fromX] = EmptySquare;
7145 board[toY][toX] = EmptySquare;
7147 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7149 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7151 } else if (board[fromY][fromX] == BlackKing &&
7152 board[toY][toX] == BlackRook) {
7153 board[fromY][fromX] = EmptySquare;
7154 board[toY][toX] = EmptySquare;
7156 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7158 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7160 /* End of code added by Tord */
7162 } else if (board[fromY][fromX] == king
7163 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7164 && toY == fromY && toX > fromX+1) {
7165 board[fromY][fromX] = EmptySquare;
7166 board[toY][toX] = king;
7167 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7168 board[fromY][BOARD_RGHT-1] = EmptySquare;
7169 } else if (board[fromY][fromX] == king
7170 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7171 && toY == fromY && toX < fromX-1) {
7172 board[fromY][fromX] = EmptySquare;
7173 board[toY][toX] = king;
7174 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7175 board[fromY][BOARD_LEFT] = EmptySquare;
7176 } else if (board[fromY][fromX] == WhitePawn
7177 && toY == BOARD_HEIGHT-1
7178 && gameInfo.variant != VariantXiangqi
7180 /* white pawn promotion */
7181 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7182 if (board[toY][toX] == EmptySquare) {
7183 board[toY][toX] = WhiteQueen;
7185 if(gameInfo.variant==VariantBughouse ||
7186 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7187 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7188 board[fromY][fromX] = EmptySquare;
7189 } else if ((fromY == BOARD_HEIGHT-4)
7191 && gameInfo.variant != VariantXiangqi
7192 && gameInfo.variant != VariantBerolina
7193 && (board[fromY][fromX] == WhitePawn)
7194 && (board[toY][toX] == EmptySquare)) {
7195 board[fromY][fromX] = EmptySquare;
7196 board[toY][toX] = WhitePawn;
7197 captured = board[toY - 1][toX];
7198 board[toY - 1][toX] = EmptySquare;
7199 } else if ((fromY == BOARD_HEIGHT-4)
7201 && gameInfo.variant == VariantBerolina
7202 && (board[fromY][fromX] == WhitePawn)
7203 && (board[toY][toX] == EmptySquare)) {
7204 board[fromY][fromX] = EmptySquare;
7205 board[toY][toX] = WhitePawn;
7206 if(oldEP & EP_BEROLIN_A) {
7207 captured = board[fromY][fromX-1];
7208 board[fromY][fromX-1] = EmptySquare;
7209 }else{ captured = board[fromY][fromX+1];
7210 board[fromY][fromX+1] = EmptySquare;
7212 } else if (board[fromY][fromX] == king
7213 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7214 && toY == fromY && toX > fromX+1) {
7215 board[fromY][fromX] = EmptySquare;
7216 board[toY][toX] = king;
7217 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7218 board[fromY][BOARD_RGHT-1] = EmptySquare;
7219 } else if (board[fromY][fromX] == king
7220 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7221 && toY == fromY && toX < fromX-1) {
7222 board[fromY][fromX] = EmptySquare;
7223 board[toY][toX] = king;
7224 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7225 board[fromY][BOARD_LEFT] = EmptySquare;
7226 } else if (fromY == 7 && fromX == 3
7227 && board[fromY][fromX] == BlackKing
7228 && toY == 7 && toX == 5) {
7229 board[fromY][fromX] = EmptySquare;
7230 board[toY][toX] = BlackKing;
7231 board[fromY][7] = EmptySquare;
7232 board[toY][4] = BlackRook;
7233 } else if (fromY == 7 && fromX == 3
7234 && board[fromY][fromX] == BlackKing
7235 && toY == 7 && toX == 1) {
7236 board[fromY][fromX] = EmptySquare;
7237 board[toY][toX] = BlackKing;
7238 board[fromY][0] = EmptySquare;
7239 board[toY][2] = BlackRook;
7240 } else if (board[fromY][fromX] == BlackPawn
7242 && gameInfo.variant != VariantXiangqi
7244 /* black pawn promotion */
7245 board[0][toX] = CharToPiece(ToLower(promoChar));
7246 if (board[0][toX] == EmptySquare) {
7247 board[0][toX] = BlackQueen;
7249 if(gameInfo.variant==VariantBughouse ||
7250 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7251 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7252 board[fromY][fromX] = EmptySquare;
7253 } else if ((fromY == 3)
7255 && gameInfo.variant != VariantXiangqi
7256 && gameInfo.variant != VariantBerolina
7257 && (board[fromY][fromX] == BlackPawn)
7258 && (board[toY][toX] == EmptySquare)) {
7259 board[fromY][fromX] = EmptySquare;
7260 board[toY][toX] = BlackPawn;
7261 captured = board[toY + 1][toX];
7262 board[toY + 1][toX] = EmptySquare;
7263 } else if ((fromY == 3)
7265 && gameInfo.variant == VariantBerolina
7266 && (board[fromY][fromX] == BlackPawn)
7267 && (board[toY][toX] == EmptySquare)) {
7268 board[fromY][fromX] = EmptySquare;
7269 board[toY][toX] = BlackPawn;
7270 if(oldEP & EP_BEROLIN_A) {
7271 captured = board[fromY][fromX-1];
7272 board[fromY][fromX-1] = EmptySquare;
7273 }else{ captured = board[fromY][fromX+1];
7274 board[fromY][fromX+1] = EmptySquare;
7277 board[toY][toX] = board[fromY][fromX];
7278 board[fromY][fromX] = EmptySquare;
7281 /* [HGM] now we promote for Shogi, if needed */
7282 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7283 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7286 if (gameInfo.holdingsWidth != 0) {
7288 /* !!A lot more code needs to be written to support holdings */
7289 /* [HGM] OK, so I have written it. Holdings are stored in the */
7290 /* penultimate board files, so they are automaticlly stored */
7291 /* in the game history. */
7292 if (fromY == DROP_RANK) {
7293 /* Delete from holdings, by decreasing count */
7294 /* and erasing image if necessary */
7296 if(p < (int) BlackPawn) { /* white drop */
7297 p -= (int)WhitePawn;
7298 if(p >= gameInfo.holdingsSize) p = 0;
7299 if(--board[p][BOARD_WIDTH-2] == 0)
7300 board[p][BOARD_WIDTH-1] = EmptySquare;
7301 } else { /* black drop */
7302 p -= (int)BlackPawn;
7303 if(p >= gameInfo.holdingsSize) p = 0;
7304 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7305 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7308 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7309 && gameInfo.variant != VariantBughouse ) {
7310 /* [HGM] holdings: Add to holdings, if holdings exist */
7311 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7312 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7313 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7316 if (p >= (int) BlackPawn) {
7317 p -= (int)BlackPawn;
7318 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7319 /* in Shogi restore piece to its original first */
7320 captured = (ChessSquare) (DEMOTED captured);
7323 p = PieceToNumber((ChessSquare)p);
7324 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7325 board[p][BOARD_WIDTH-2]++;
7326 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7328 p -= (int)WhitePawn;
7329 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7330 captured = (ChessSquare) (DEMOTED captured);
7333 p = PieceToNumber((ChessSquare)p);
7334 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7335 board[BOARD_HEIGHT-1-p][1]++;
7336 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7340 } else if (gameInfo.variant == VariantAtomic) {
7341 if (captured != EmptySquare) {
7343 for (y = toY-1; y <= toY+1; y++) {
7344 for (x = toX-1; x <= toX+1; x++) {
7345 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7346 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7347 board[y][x] = EmptySquare;
7351 board[toY][toX] = EmptySquare;
7354 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7355 /* [HGM] Shogi promotions */
7356 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7359 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7360 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7361 // [HGM] superchess: take promotion piece out of holdings
7362 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7363 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7364 if(!--board[k][BOARD_WIDTH-2])
7365 board[k][BOARD_WIDTH-1] = EmptySquare;
7367 if(!--board[BOARD_HEIGHT-1-k][1])
7368 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7374 /* Updates forwardMostMove */
7376 MakeMove(fromX, fromY, toX, toY, promoChar)
7377 int fromX, fromY, toX, toY;
7380 // forwardMostMove++; // [HGM] bare: moved downstream
7382 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7383 int timeLeft; static int lastLoadFlag=0; int king, piece;
7384 piece = boards[forwardMostMove][fromY][fromX];
7385 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7386 if(gameInfo.variant == VariantKnightmate)
7387 king += (int) WhiteUnicorn - (int) WhiteKing;
7388 if(forwardMostMove == 0) {
7390 fprintf(serverMoves, "%s;", second.tidy);
7391 fprintf(serverMoves, "%s;", first.tidy);
7392 if(!blackPlaysFirst)
7393 fprintf(serverMoves, "%s;", second.tidy);
7394 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7395 lastLoadFlag = loadFlag;
7397 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7398 // print castling suffix
7399 if( toY == fromY && piece == king ) {
7401 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7403 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7406 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7407 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7408 boards[forwardMostMove][toY][toX] == EmptySquare
7410 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7412 if(promoChar != NULLCHAR)
7413 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7415 fprintf(serverMoves, "/%d/%d",
7416 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7417 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7418 else timeLeft = blackTimeRemaining/1000;
7419 fprintf(serverMoves, "/%d", timeLeft);
7421 fflush(serverMoves);
7424 if (forwardMostMove+1 >= MAX_MOVES) {
7425 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7429 if (commentList[forwardMostMove+1] != NULL) {
7430 free(commentList[forwardMostMove+1]);
7431 commentList[forwardMostMove+1] = NULL;
7433 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7434 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7435 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7436 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7437 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7438 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7439 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7440 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7441 gameInfo.result = GameUnfinished;
7442 if (gameInfo.resultDetails != NULL) {
7443 free(gameInfo.resultDetails);
7444 gameInfo.resultDetails = NULL;
7446 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7447 moveList[forwardMostMove - 1]);
7448 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7449 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7450 fromY, fromX, toY, toX, promoChar,
7451 parseList[forwardMostMove - 1]);
7452 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7453 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7454 castlingRights[forwardMostMove]) ) {
7460 if(gameInfo.variant != VariantShogi)
7461 strcat(parseList[forwardMostMove - 1], "+");
7465 strcat(parseList[forwardMostMove - 1], "#");
7468 if (appData.debugMode) {
7469 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7474 /* Updates currentMove if not pausing */
7476 ShowMove(fromX, fromY, toX, toY)
7478 int instant = (gameMode == PlayFromGameFile) ?
7479 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7480 if(appData.noGUI) return;
7481 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7483 if (forwardMostMove == currentMove + 1) {
7484 AnimateMove(boards[forwardMostMove - 1],
7485 fromX, fromY, toX, toY);
7487 if (appData.highlightLastMove) {
7488 SetHighlights(fromX, fromY, toX, toY);
7491 currentMove = forwardMostMove;
7494 if (instant) return;
7496 DisplayMove(currentMove - 1);
7497 DrawPosition(FALSE, boards[currentMove]);
7498 DisplayBothClocks();
7499 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7502 void SendEgtPath(ChessProgramState *cps)
7503 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7504 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7506 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7509 char c, *q = name+1, *r, *s;
7511 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7512 while(*p && *p != ',') *q++ = *p++;
7514 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7515 strcmp(name, ",nalimov:") == 0 ) {
7516 // take nalimov path from the menu-changeable option first, if it is defined
7517 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7518 SendToProgram(buf,cps); // send egtbpath command for nalimov
7520 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7521 (s = StrStr(appData.egtFormats, name)) != NULL) {
7522 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7523 s = r = StrStr(s, ":") + 1; // beginning of path info
7524 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7525 c = *r; *r = 0; // temporarily null-terminate path info
7526 *--q = 0; // strip of trailig ':' from name
7527 sprintf(buf, "egtpath %s %s\n", name+1, s);
7529 SendToProgram(buf,cps); // send egtbpath command for this format
7531 if(*p == ',') p++; // read away comma to position for next format name
7536 InitChessProgram(cps, setup)
7537 ChessProgramState *cps;
7538 int setup; /* [HGM] needed to setup FRC opening position */
7540 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7541 if (appData.noChessProgram) return;
7542 hintRequested = FALSE;
7543 bookRequested = FALSE;
7545 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7546 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7547 if(cps->memSize) { /* [HGM] memory */
7548 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7549 SendToProgram(buf, cps);
7551 SendEgtPath(cps); /* [HGM] EGT */
7552 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7553 sprintf(buf, "cores %d\n", appData.smpCores);
7554 SendToProgram(buf, cps);
7557 SendToProgram(cps->initString, cps);
7558 if (gameInfo.variant != VariantNormal &&
7559 gameInfo.variant != VariantLoadable
7560 /* [HGM] also send variant if board size non-standard */
7561 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7563 char *v = VariantName(gameInfo.variant);
7564 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7565 /* [HGM] in protocol 1 we have to assume all variants valid */
7566 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7567 DisplayFatalError(buf, 0, 1);
7571 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7572 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7573 if( gameInfo.variant == VariantXiangqi )
7574 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7575 if( gameInfo.variant == VariantShogi )
7576 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7577 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7578 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7579 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7580 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7581 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7582 if( gameInfo.variant == VariantCourier )
7583 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7584 if( gameInfo.variant == VariantSuper )
7585 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7586 if( gameInfo.variant == VariantGreat )
7587 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7590 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7591 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7592 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7593 if(StrStr(cps->variants, b) == NULL) {
7594 // specific sized variant not known, check if general sizing allowed
7595 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7596 if(StrStr(cps->variants, "boardsize") == NULL) {
7597 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7598 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7599 DisplayFatalError(buf, 0, 1);
7602 /* [HGM] here we really should compare with the maximum supported board size */
7605 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7606 sprintf(buf, "variant %s\n", b);
7607 SendToProgram(buf, cps);
7609 currentlyInitializedVariant = gameInfo.variant;
7611 /* [HGM] send opening position in FRC to first engine */
7613 SendToProgram("force\n", cps);
7615 /* engine is now in force mode! Set flag to wake it up after first move. */
7616 setboardSpoiledMachineBlack = 1;
7620 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7621 SendToProgram(buf, cps);
7623 cps->maybeThinking = FALSE;
7624 cps->offeredDraw = 0;
7625 if (!appData.icsActive) {
7626 SendTimeControl(cps, movesPerSession, timeControl,
7627 timeIncrement, appData.searchDepth,
7630 if (appData.showThinking
7631 // [HGM] thinking: four options require thinking output to be sent
7632 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7634 SendToProgram("post\n", cps);
7636 SendToProgram("hard\n", cps);
7637 if (!appData.ponderNextMove) {
7638 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7639 it without being sure what state we are in first. "hard"
7640 is not a toggle, so that one is OK.
7642 SendToProgram("easy\n", cps);
7645 sprintf(buf, "ping %d\n", ++cps->lastPing);
7646 SendToProgram(buf, cps);
7648 cps->initDone = TRUE;
7653 StartChessProgram(cps)
7654 ChessProgramState *cps;
7659 if (appData.noChessProgram) return;
7660 cps->initDone = FALSE;
7662 if (strcmp(cps->host, "localhost") == 0) {
7663 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7664 } else if (*appData.remoteShell == NULLCHAR) {
7665 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7667 if (*appData.remoteUser == NULLCHAR) {
7668 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7671 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7672 cps->host, appData.remoteUser, cps->program);
7674 err = StartChildProcess(buf, "", &cps->pr);
7678 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7679 DisplayFatalError(buf, err, 1);
7685 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7686 if (cps->protocolVersion > 1) {
7687 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7688 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7689 cps->comboCnt = 0; // and values of combo boxes
7690 SendToProgram(buf, cps);
7692 SendToProgram("xboard\n", cps);
7698 TwoMachinesEventIfReady P((void))
7700 if (first.lastPing != first.lastPong) {
7701 DisplayMessage("", _("Waiting for first chess program"));
7702 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7705 if (second.lastPing != second.lastPong) {
7706 DisplayMessage("", _("Waiting for second chess program"));
7707 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7715 NextMatchGame P((void))
7717 int index; /* [HGM] autoinc: step lod index during match */
7719 if (*appData.loadGameFile != NULLCHAR) {
7720 index = appData.loadGameIndex;
7721 if(index < 0) { // [HGM] autoinc
7722 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7723 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7725 LoadGameFromFile(appData.loadGameFile,
7727 appData.loadGameFile, FALSE);
7728 } else if (*appData.loadPositionFile != NULLCHAR) {
7729 index = appData.loadPositionIndex;
7730 if(index < 0) { // [HGM] autoinc
7731 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7732 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7734 LoadPositionFromFile(appData.loadPositionFile,
7736 appData.loadPositionFile);
7738 TwoMachinesEventIfReady();
7741 void UserAdjudicationEvent( int result )
7743 ChessMove gameResult = GameIsDrawn;
7746 gameResult = WhiteWins;
7748 else if( result < 0 ) {
7749 gameResult = BlackWins;
7752 if( gameMode == TwoMachinesPlay ) {
7753 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7758 // [HGM] save: calculate checksum of game to make games easily identifiable
7759 int StringCheckSum(char *s)
7762 if(s==NULL) return 0;
7763 while(*s) i = i*259 + *s++;
7770 for(i=backwardMostMove; i<forwardMostMove; i++) {
7771 sum += pvInfoList[i].depth;
7772 sum += StringCheckSum(parseList[i]);
7773 sum += StringCheckSum(commentList[i]);
7776 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7777 return sum + StringCheckSum(commentList[i]);
7778 } // end of save patch
7781 GameEnds(result, resultDetails, whosays)
7783 char *resultDetails;
7786 GameMode nextGameMode;
7790 if(endingGame) return; /* [HGM] crash: forbid recursion */
7793 if (appData.debugMode) {
7794 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7795 result, resultDetails ? resultDetails : "(null)", whosays);
7798 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7799 /* If we are playing on ICS, the server decides when the
7800 game is over, but the engine can offer to draw, claim
7804 if (appData.zippyPlay && first.initDone) {
7805 if (result == GameIsDrawn) {
7806 /* In case draw still needs to be claimed */
7807 SendToICS(ics_prefix);
7808 SendToICS("draw\n");
7809 } else if (StrCaseStr(resultDetails, "resign")) {
7810 SendToICS(ics_prefix);
7811 SendToICS("resign\n");
7815 endingGame = 0; /* [HGM] crash */
7819 /* If we're loading the game from a file, stop */
7820 if (whosays == GE_FILE) {
7821 (void) StopLoadGameTimer();
7825 /* Cancel draw offers */
7826 first.offeredDraw = second.offeredDraw = 0;
7828 /* If this is an ICS game, only ICS can really say it's done;
7829 if not, anyone can. */
7830 isIcsGame = (gameMode == IcsPlayingWhite ||
7831 gameMode == IcsPlayingBlack ||
7832 gameMode == IcsObserving ||
7833 gameMode == IcsExamining);
7835 if (!isIcsGame || whosays == GE_ICS) {
7836 /* OK -- not an ICS game, or ICS said it was done */
7838 if (!isIcsGame && !appData.noChessProgram)
7839 SetUserThinkingEnables();
7841 /* [HGM] if a machine claims the game end we verify this claim */
7842 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7843 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7845 ChessMove trueResult = (ChessMove) -1;
7847 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7848 first.twoMachinesColor[0] :
7849 second.twoMachinesColor[0] ;
7851 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7852 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7853 /* [HGM] verify: engine mate claims accepted if they were flagged */
7854 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7856 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7857 /* [HGM] verify: engine mate claims accepted if they were flagged */
7858 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7860 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7861 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7864 // now verify win claims, but not in drop games, as we don't understand those yet
7865 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7866 || gameInfo.variant == VariantGreat) &&
7867 (result == WhiteWins && claimer == 'w' ||
7868 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7869 if (appData.debugMode) {
7870 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7871 result, epStatus[forwardMostMove], forwardMostMove);
7873 if(result != trueResult) {
7874 sprintf(buf, "False win claim: '%s'", resultDetails);
7875 result = claimer == 'w' ? BlackWins : WhiteWins;
7876 resultDetails = buf;
7879 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7880 && (forwardMostMove <= backwardMostMove ||
7881 epStatus[forwardMostMove-1] > EP_DRAWS ||
7882 (claimer=='b')==(forwardMostMove&1))
7884 /* [HGM] verify: draws that were not flagged are false claims */
7885 sprintf(buf, "False draw claim: '%s'", resultDetails);
7886 result = claimer == 'w' ? BlackWins : WhiteWins;
7887 resultDetails = buf;
7889 /* (Claiming a loss is accepted no questions asked!) */
7891 /* [HGM] bare: don't allow bare King to win */
7892 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7893 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7894 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7895 && result != GameIsDrawn)
7896 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7897 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7898 int p = (int)boards[forwardMostMove][i][j] - color;
7899 if(p >= 0 && p <= (int)WhiteKing) k++;
7901 if (appData.debugMode) {
7902 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7903 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7906 result = GameIsDrawn;
7907 sprintf(buf, "%s but bare king", resultDetails);
7908 resultDetails = buf;
7914 if(serverMoves != NULL && !loadFlag) { char c = '=';
7915 if(result==WhiteWins) c = '+';
7916 if(result==BlackWins) c = '-';
7917 if(resultDetails != NULL)
7918 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7920 if (resultDetails != NULL) {
7921 gameInfo.result = result;
7922 gameInfo.resultDetails = StrSave(resultDetails);
7924 /* display last move only if game was not loaded from file */
7925 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7926 DisplayMove(currentMove - 1);
7928 if (forwardMostMove != 0) {
7929 if (gameMode != PlayFromGameFile && gameMode != EditGame
7930 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7932 if (*appData.saveGameFile != NULLCHAR) {
7933 SaveGameToFile(appData.saveGameFile, TRUE);
7934 } else if (appData.autoSaveGames) {
7937 if (*appData.savePositionFile != NULLCHAR) {
7938 SavePositionToFile(appData.savePositionFile);
7943 /* Tell program how game ended in case it is learning */
7944 /* [HGM] Moved this to after saving the PGN, just in case */
7945 /* engine died and we got here through time loss. In that */
7946 /* case we will get a fatal error writing the pipe, which */
7947 /* would otherwise lose us the PGN. */
7948 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7949 /* output during GameEnds should never be fatal anymore */
7950 if (gameMode == MachinePlaysWhite ||
7951 gameMode == MachinePlaysBlack ||
7952 gameMode == TwoMachinesPlay ||
7953 gameMode == IcsPlayingWhite ||
7954 gameMode == IcsPlayingBlack ||
7955 gameMode == BeginningOfGame) {
7957 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7959 if (first.pr != NoProc) {
7960 SendToProgram(buf, &first);
7962 if (second.pr != NoProc &&
7963 gameMode == TwoMachinesPlay) {
7964 SendToProgram(buf, &second);
7969 if (appData.icsActive) {
7970 if (appData.quietPlay &&
7971 (gameMode == IcsPlayingWhite ||
7972 gameMode == IcsPlayingBlack)) {
7973 SendToICS(ics_prefix);
7974 SendToICS("set shout 1\n");
7976 nextGameMode = IcsIdle;
7977 ics_user_moved = FALSE;
7978 /* clean up premove. It's ugly when the game has ended and the
7979 * premove highlights are still on the board.
7983 ClearPremoveHighlights();
7984 DrawPosition(FALSE, boards[currentMove]);
7986 if (whosays == GE_ICS) {
7989 if (gameMode == IcsPlayingWhite)
7991 else if(gameMode == IcsPlayingBlack)
7995 if (gameMode == IcsPlayingBlack)
7997 else if(gameMode == IcsPlayingWhite)
8004 PlayIcsUnfinishedSound();
8007 } else if (gameMode == EditGame ||
8008 gameMode == PlayFromGameFile ||
8009 gameMode == AnalyzeMode ||
8010 gameMode == AnalyzeFile) {
8011 nextGameMode = gameMode;
8013 nextGameMode = EndOfGame;
8018 nextGameMode = gameMode;
8021 if (appData.noChessProgram) {
8022 gameMode = nextGameMode;
8024 endingGame = 0; /* [HGM] crash */
8029 /* Put first chess program into idle state */
8030 if (first.pr != NoProc &&
8031 (gameMode == MachinePlaysWhite ||
8032 gameMode == MachinePlaysBlack ||
8033 gameMode == TwoMachinesPlay ||
8034 gameMode == IcsPlayingWhite ||
8035 gameMode == IcsPlayingBlack ||
8036 gameMode == BeginningOfGame)) {
8037 SendToProgram("force\n", &first);
8038 if (first.usePing) {
8040 sprintf(buf, "ping %d\n", ++first.lastPing);
8041 SendToProgram(buf, &first);
8044 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8045 /* Kill off first chess program */
8046 if (first.isr != NULL)
8047 RemoveInputSource(first.isr);
8050 if (first.pr != NoProc) {
8052 DoSleep( appData.delayBeforeQuit );
8053 SendToProgram("quit\n", &first);
8054 DoSleep( appData.delayAfterQuit );
8055 DestroyChildProcess(first.pr, first.useSigterm);
8060 /* Put second chess program into idle state */
8061 if (second.pr != NoProc &&
8062 gameMode == TwoMachinesPlay) {
8063 SendToProgram("force\n", &second);
8064 if (second.usePing) {
8066 sprintf(buf, "ping %d\n", ++second.lastPing);
8067 SendToProgram(buf, &second);
8070 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8071 /* Kill off second chess program */
8072 if (second.isr != NULL)
8073 RemoveInputSource(second.isr);
8076 if (second.pr != NoProc) {
8077 DoSleep( appData.delayBeforeQuit );
8078 SendToProgram("quit\n", &second);
8079 DoSleep( appData.delayAfterQuit );
8080 DestroyChildProcess(second.pr, second.useSigterm);
8085 if (matchMode && gameMode == TwoMachinesPlay) {
8088 if (first.twoMachinesColor[0] == 'w') {
8095 if (first.twoMachinesColor[0] == 'b') {
8104 if (matchGame < appData.matchGames) {
8106 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8107 tmp = first.twoMachinesColor;
8108 first.twoMachinesColor = second.twoMachinesColor;
8109 second.twoMachinesColor = tmp;
8111 gameMode = nextGameMode;
8113 if(appData.matchPause>10000 || appData.matchPause<10)
8114 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8115 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8116 endingGame = 0; /* [HGM] crash */
8120 gameMode = nextGameMode;
8121 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8122 first.tidy, second.tidy,
8123 first.matchWins, second.matchWins,
8124 appData.matchGames - (first.matchWins + second.matchWins));
8125 DisplayFatalError(buf, 0, 0);
8128 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8129 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8131 gameMode = nextGameMode;
8133 endingGame = 0; /* [HGM] crash */
8136 /* Assumes program was just initialized (initString sent).
8137 Leaves program in force mode. */
8139 FeedMovesToProgram(cps, upto)
8140 ChessProgramState *cps;
8145 if (appData.debugMode)
8146 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8147 startedFromSetupPosition ? "position and " : "",
8148 backwardMostMove, upto, cps->which);
8149 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8150 // [HGM] variantswitch: make engine aware of new variant
8151 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8152 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8153 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8154 SendToProgram(buf, cps);
8155 currentlyInitializedVariant = gameInfo.variant;
8157 SendToProgram("force\n", cps);
8158 if (startedFromSetupPosition) {
8159 SendBoard(cps, backwardMostMove);
8160 if (appData.debugMode) {
8161 fprintf(debugFP, "feedMoves\n");
8164 for (i = backwardMostMove; i < upto; i++) {
8165 SendMoveToProgram(i, cps);
8171 ResurrectChessProgram()
8173 /* The chess program may have exited.
8174 If so, restart it and feed it all the moves made so far. */
8176 if (appData.noChessProgram || first.pr != NoProc) return;
8178 StartChessProgram(&first);
8179 InitChessProgram(&first, FALSE);
8180 FeedMovesToProgram(&first, currentMove);
8182 if (!first.sendTime) {
8183 /* can't tell gnuchess what its clock should read,
8184 so we bow to its notion. */
8186 timeRemaining[0][currentMove] = whiteTimeRemaining;
8187 timeRemaining[1][currentMove] = blackTimeRemaining;
8190 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8191 appData.icsEngineAnalyze) && first.analysisSupport) {
8192 SendToProgram("analyze\n", &first);
8193 first.analyzing = TRUE;
8206 if (appData.debugMode) {
8207 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8208 redraw, init, gameMode);
8210 pausing = pauseExamInvalid = FALSE;
8211 startedFromSetupPosition = blackPlaysFirst = FALSE;
8213 whiteFlag = blackFlag = FALSE;
8214 userOfferedDraw = FALSE;
8215 hintRequested = bookRequested = FALSE;
8216 first.maybeThinking = FALSE;
8217 second.maybeThinking = FALSE;
8218 first.bookSuspend = FALSE; // [HGM] book
8219 second.bookSuspend = FALSE;
8220 thinkOutput[0] = NULLCHAR;
8221 lastHint[0] = NULLCHAR;
8222 ClearGameInfo(&gameInfo);
8223 gameInfo.variant = StringToVariant(appData.variant);
8224 ics_user_moved = ics_clock_paused = FALSE;
8225 ics_getting_history = H_FALSE;
8227 white_holding[0] = black_holding[0] = NULLCHAR;
8228 ClearProgramStats();
8229 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8233 flipView = appData.flipView;
8234 ClearPremoveHighlights();
8236 alarmSounded = FALSE;
8238 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8239 if(appData.serverMovesName != NULL) {
8240 /* [HGM] prepare to make moves file for broadcasting */
8241 clock_t t = clock();
8242 if(serverMoves != NULL) fclose(serverMoves);
8243 serverMoves = fopen(appData.serverMovesName, "r");
8244 if(serverMoves != NULL) {
8245 fclose(serverMoves);
8246 /* delay 15 sec before overwriting, so all clients can see end */
8247 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8249 serverMoves = fopen(appData.serverMovesName, "w");
8253 gameMode = BeginningOfGame;
8255 if(appData.icsActive) gameInfo.variant = VariantNormal;
8256 currentMove = forwardMostMove = backwardMostMove = 0;
8257 InitPosition(redraw);
8258 for (i = 0; i < MAX_MOVES; i++) {
8259 if (commentList[i] != NULL) {
8260 free(commentList[i]);
8261 commentList[i] = NULL;
8265 timeRemaining[0][0] = whiteTimeRemaining;
8266 timeRemaining[1][0] = blackTimeRemaining;
8267 if (first.pr == NULL) {
8268 StartChessProgram(&first);
8271 InitChessProgram(&first, startedFromSetupPosition);
8274 DisplayMessage("", "");
8275 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8276 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8283 if (!AutoPlayOneMove())
8285 if (matchMode || appData.timeDelay == 0)
8287 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8289 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8298 int fromX, fromY, toX, toY;
8300 if (appData.debugMode) {
8301 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8304 if (gameMode != PlayFromGameFile)
8307 if (currentMove >= forwardMostMove) {
8308 gameMode = EditGame;
8311 /* [AS] Clear current move marker at the end of a game */
8312 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8317 toX = moveList[currentMove][2] - AAA;
8318 toY = moveList[currentMove][3] - ONE;
8320 if (moveList[currentMove][1] == '@') {
8321 if (appData.highlightLastMove) {
8322 SetHighlights(-1, -1, toX, toY);
8325 fromX = moveList[currentMove][0] - AAA;
8326 fromY = moveList[currentMove][1] - ONE;
8328 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8330 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8332 if (appData.highlightLastMove) {
8333 SetHighlights(fromX, fromY, toX, toY);
8336 DisplayMove(currentMove);
8337 SendMoveToProgram(currentMove++, &first);
8338 DisplayBothClocks();
8339 DrawPosition(FALSE, boards[currentMove]);
8340 // [HGM] PV info: always display, routine tests if empty
8341 DisplayComment(currentMove - 1, commentList[currentMove]);
8347 LoadGameOneMove(readAhead)
8348 ChessMove readAhead;
8350 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8351 char promoChar = NULLCHAR;
8356 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8357 gameMode != AnalyzeMode && gameMode != Training) {
8362 yyboardindex = forwardMostMove;
8363 if (readAhead != (ChessMove)0) {
8364 moveType = readAhead;
8366 if (gameFileFP == NULL)
8368 moveType = (ChessMove) yylex();
8374 if (appData.debugMode)
8375 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8377 if (*p == '{' || *p == '[' || *p == '(') {
8378 p[strlen(p) - 1] = NULLCHAR;
8382 /* append the comment but don't display it */
8383 while (*p == '\n') p++;
8384 AppendComment(currentMove, p);
8387 case WhiteCapturesEnPassant:
8388 case BlackCapturesEnPassant:
8389 case WhitePromotionChancellor:
8390 case BlackPromotionChancellor:
8391 case WhitePromotionArchbishop:
8392 case BlackPromotionArchbishop:
8393 case WhitePromotionCentaur:
8394 case BlackPromotionCentaur:
8395 case WhitePromotionQueen:
8396 case BlackPromotionQueen:
8397 case WhitePromotionRook:
8398 case BlackPromotionRook:
8399 case WhitePromotionBishop:
8400 case BlackPromotionBishop:
8401 case WhitePromotionKnight:
8402 case BlackPromotionKnight:
8403 case WhitePromotionKing:
8404 case BlackPromotionKing:
8406 case WhiteKingSideCastle:
8407 case WhiteQueenSideCastle:
8408 case BlackKingSideCastle:
8409 case BlackQueenSideCastle:
8410 case WhiteKingSideCastleWild:
8411 case WhiteQueenSideCastleWild:
8412 case BlackKingSideCastleWild:
8413 case BlackQueenSideCastleWild:
8415 case WhiteHSideCastleFR:
8416 case WhiteASideCastleFR:
8417 case BlackHSideCastleFR:
8418 case BlackASideCastleFR:
8420 if (appData.debugMode)
8421 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8422 fromX = currentMoveString[0] - AAA;
8423 fromY = currentMoveString[1] - ONE;
8424 toX = currentMoveString[2] - AAA;
8425 toY = currentMoveString[3] - ONE;
8426 promoChar = currentMoveString[4];
8431 if (appData.debugMode)
8432 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8433 fromX = moveType == WhiteDrop ?
8434 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8435 (int) CharToPiece(ToLower(currentMoveString[0]));
8437 toX = currentMoveString[2] - AAA;
8438 toY = currentMoveString[3] - ONE;
8444 case GameUnfinished:
8445 if (appData.debugMode)
8446 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8447 p = strchr(yy_text, '{');
8448 if (p == NULL) p = strchr(yy_text, '(');
8451 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8453 q = strchr(p, *p == '{' ? '}' : ')');
8454 if (q != NULL) *q = NULLCHAR;
8457 GameEnds(moveType, p, GE_FILE);
8459 if (cmailMsgLoaded) {
8461 flipView = WhiteOnMove(currentMove);
8462 if (moveType == GameUnfinished) flipView = !flipView;
8463 if (appData.debugMode)
8464 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8468 case (ChessMove) 0: /* end of file */
8469 if (appData.debugMode)
8470 fprintf(debugFP, "Parser hit end of file\n");
8471 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8472 EP_UNKNOWN, castlingRights[currentMove]) ) {
8478 if (WhiteOnMove(currentMove)) {
8479 GameEnds(BlackWins, "Black mates", GE_FILE);
8481 GameEnds(WhiteWins, "White mates", GE_FILE);
8485 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8492 if (lastLoadGameStart == GNUChessGame) {
8493 /* GNUChessGames have numbers, but they aren't move numbers */
8494 if (appData.debugMode)
8495 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8496 yy_text, (int) moveType);
8497 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8499 /* else fall thru */
8504 /* Reached start of next game in file */
8505 if (appData.debugMode)
8506 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8507 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8508 EP_UNKNOWN, castlingRights[currentMove]) ) {
8514 if (WhiteOnMove(currentMove)) {
8515 GameEnds(BlackWins, "Black mates", GE_FILE);
8517 GameEnds(WhiteWins, "White mates", GE_FILE);
8521 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8527 case PositionDiagram: /* should not happen; ignore */
8528 case ElapsedTime: /* ignore */
8529 case NAG: /* ignore */
8530 if (appData.debugMode)
8531 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8532 yy_text, (int) moveType);
8533 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8536 if (appData.testLegality) {
8537 if (appData.debugMode)
8538 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8539 sprintf(move, _("Illegal move: %d.%s%s"),
8540 (forwardMostMove / 2) + 1,
8541 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8542 DisplayError(move, 0);
8545 if (appData.debugMode)
8546 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8547 yy_text, currentMoveString);
8548 fromX = currentMoveString[0] - AAA;
8549 fromY = currentMoveString[1] - ONE;
8550 toX = currentMoveString[2] - AAA;
8551 toY = currentMoveString[3] - ONE;
8552 promoChar = currentMoveString[4];
8557 if (appData.debugMode)
8558 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8559 sprintf(move, _("Ambiguous move: %d.%s%s"),
8560 (forwardMostMove / 2) + 1,
8561 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8562 DisplayError(move, 0);
8567 case ImpossibleMove:
8568 if (appData.debugMode)
8569 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8570 sprintf(move, _("Illegal move: %d.%s%s"),
8571 (forwardMostMove / 2) + 1,
8572 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8573 DisplayError(move, 0);
8579 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8580 DrawPosition(FALSE, boards[currentMove]);
8581 DisplayBothClocks();
8582 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8583 DisplayComment(currentMove - 1, commentList[currentMove]);
8585 (void) StopLoadGameTimer();
8587 cmailOldMove = forwardMostMove;
8590 /* currentMoveString is set as a side-effect of yylex */
8591 strcat(currentMoveString, "\n");
8592 strcpy(moveList[forwardMostMove], currentMoveString);
8594 thinkOutput[0] = NULLCHAR;
8595 MakeMove(fromX, fromY, toX, toY, promoChar);
8596 currentMove = forwardMostMove;
8601 /* Load the nth game from the given file */
8603 LoadGameFromFile(filename, n, title, useList)
8607 /*Boolean*/ int useList;
8612 if (strcmp(filename, "-") == 0) {
8616 f = fopen(filename, "rb");
8618 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8619 DisplayError(buf, errno);
8623 if (fseek(f, 0, 0) == -1) {
8624 /* f is not seekable; probably a pipe */
8627 if (useList && n == 0) {
8628 int error = GameListBuild(f);
8630 DisplayError(_("Cannot build game list"), error);
8631 } else if (!ListEmpty(&gameList) &&
8632 ((ListGame *) gameList.tailPred)->number > 1) {
8633 GameListPopUp(f, title);
8640 return LoadGame(f, n, title, FALSE);
8645 MakeRegisteredMove()
8647 int fromX, fromY, toX, toY;
8649 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8650 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8653 if (appData.debugMode)
8654 fprintf(debugFP, "Restoring %s for game %d\n",
8655 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8657 thinkOutput[0] = NULLCHAR;
8658 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8659 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8660 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8661 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8662 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8663 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8664 MakeMove(fromX, fromY, toX, toY, promoChar);
8665 ShowMove(fromX, fromY, toX, toY);
8667 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8668 EP_UNKNOWN, castlingRights[currentMove]) ) {
8675 if (WhiteOnMove(currentMove)) {
8676 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8678 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8683 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8690 if (WhiteOnMove(currentMove)) {
8691 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8693 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8698 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8709 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8711 CmailLoadGame(f, gameNumber, title, useList)
8719 if (gameNumber > nCmailGames) {
8720 DisplayError(_("No more games in this message"), 0);
8723 if (f == lastLoadGameFP) {
8724 int offset = gameNumber - lastLoadGameNumber;
8726 cmailMsg[0] = NULLCHAR;
8727 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8728 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8729 nCmailMovesRegistered--;
8731 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8732 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8733 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8736 if (! RegisterMove()) return FALSE;
8740 retVal = LoadGame(f, gameNumber, title, useList);
8742 /* Make move registered during previous look at this game, if any */
8743 MakeRegisteredMove();
8745 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8746 commentList[currentMove]
8747 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8748 DisplayComment(currentMove - 1, commentList[currentMove]);
8754 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8759 int gameNumber = lastLoadGameNumber + offset;
8760 if (lastLoadGameFP == NULL) {
8761 DisplayError(_("No game has been loaded yet"), 0);
8764 if (gameNumber <= 0) {
8765 DisplayError(_("Can't back up any further"), 0);
8768 if (cmailMsgLoaded) {
8769 return CmailLoadGame(lastLoadGameFP, gameNumber,
8770 lastLoadGameTitle, lastLoadGameUseList);
8772 return LoadGame(lastLoadGameFP, gameNumber,
8773 lastLoadGameTitle, lastLoadGameUseList);
8779 /* Load the nth game from open file f */
8781 LoadGame(f, gameNumber, title, useList)
8789 int gn = gameNumber;
8790 ListGame *lg = NULL;
8793 GameMode oldGameMode;
8794 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8796 if (appData.debugMode)
8797 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8799 if (gameMode == Training )
8800 SetTrainingModeOff();
8802 oldGameMode = gameMode;
8803 if (gameMode != BeginningOfGame) {
8808 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8809 fclose(lastLoadGameFP);
8813 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8816 fseek(f, lg->offset, 0);
8817 GameListHighlight(gameNumber);
8821 DisplayError(_("Game number out of range"), 0);
8826 if (fseek(f, 0, 0) == -1) {
8827 if (f == lastLoadGameFP ?
8828 gameNumber == lastLoadGameNumber + 1 :
8832 DisplayError(_("Can't seek on game file"), 0);
8838 lastLoadGameNumber = gameNumber;
8839 strcpy(lastLoadGameTitle, title);
8840 lastLoadGameUseList = useList;
8844 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8845 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8846 lg->gameInfo.black);
8848 } else if (*title != NULLCHAR) {
8849 if (gameNumber > 1) {
8850 sprintf(buf, "%s %d", title, gameNumber);
8853 DisplayTitle(title);
8857 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8858 gameMode = PlayFromGameFile;
8862 currentMove = forwardMostMove = backwardMostMove = 0;
8863 CopyBoard(boards[0], initialPosition);
8867 * Skip the first gn-1 games in the file.
8868 * Also skip over anything that precedes an identifiable
8869 * start of game marker, to avoid being confused by
8870 * garbage at the start of the file. Currently
8871 * recognized start of game markers are the move number "1",
8872 * the pattern "gnuchess .* game", the pattern
8873 * "^[#;%] [^ ]* game file", and a PGN tag block.
8874 * A game that starts with one of the latter two patterns
8875 * will also have a move number 1, possibly
8876 * following a position diagram.
8877 * 5-4-02: Let's try being more lenient and allowing a game to
8878 * start with an unnumbered move. Does that break anything?
8880 cm = lastLoadGameStart = (ChessMove) 0;
8882 yyboardindex = forwardMostMove;
8883 cm = (ChessMove) yylex();
8886 if (cmailMsgLoaded) {
8887 nCmailGames = CMAIL_MAX_GAMES - gn;
8890 DisplayError(_("Game not found in file"), 0);
8897 lastLoadGameStart = cm;
8901 switch (lastLoadGameStart) {
8908 gn--; /* count this game */
8909 lastLoadGameStart = cm;
8918 switch (lastLoadGameStart) {
8923 gn--; /* count this game */
8924 lastLoadGameStart = cm;
8927 lastLoadGameStart = cm; /* game counted already */
8935 yyboardindex = forwardMostMove;
8936 cm = (ChessMove) yylex();
8937 } while (cm == PGNTag || cm == Comment);
8944 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8945 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8946 != CMAIL_OLD_RESULT) {
8948 cmailResult[ CMAIL_MAX_GAMES
8949 - gn - 1] = CMAIL_OLD_RESULT;
8955 /* Only a NormalMove can be at the start of a game
8956 * without a position diagram. */
8957 if (lastLoadGameStart == (ChessMove) 0) {
8959 lastLoadGameStart = MoveNumberOne;
8968 if (appData.debugMode)
8969 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8971 if (cm == XBoardGame) {
8972 /* Skip any header junk before position diagram and/or move 1 */
8974 yyboardindex = forwardMostMove;
8975 cm = (ChessMove) yylex();
8977 if (cm == (ChessMove) 0 ||
8978 cm == GNUChessGame || cm == XBoardGame) {
8979 /* Empty game; pretend end-of-file and handle later */
8984 if (cm == MoveNumberOne || cm == PositionDiagram ||
8985 cm == PGNTag || cm == Comment)
8988 } else if (cm == GNUChessGame) {
8989 if (gameInfo.event != NULL) {
8990 free(gameInfo.event);
8992 gameInfo.event = StrSave(yy_text);
8995 startedFromSetupPosition = FALSE;
8996 while (cm == PGNTag) {
8997 if (appData.debugMode)
8998 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8999 err = ParsePGNTag(yy_text, &gameInfo);
9000 if (!err) numPGNTags++;
9002 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9003 if(gameInfo.variant != oldVariant) {
9004 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9006 oldVariant = gameInfo.variant;
9007 if (appData.debugMode)
9008 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9012 if (gameInfo.fen != NULL) {
9013 Board initial_position;
9014 startedFromSetupPosition = TRUE;
9015 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9017 DisplayError(_("Bad FEN position in file"), 0);
9020 CopyBoard(boards[0], initial_position);
9021 if (blackPlaysFirst) {
9022 currentMove = forwardMostMove = backwardMostMove = 1;
9023 CopyBoard(boards[1], initial_position);
9024 strcpy(moveList[0], "");
9025 strcpy(parseList[0], "");
9026 timeRemaining[0][1] = whiteTimeRemaining;
9027 timeRemaining[1][1] = blackTimeRemaining;
9028 if (commentList[0] != NULL) {
9029 commentList[1] = commentList[0];
9030 commentList[0] = NULL;
9033 currentMove = forwardMostMove = backwardMostMove = 0;
9035 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9037 initialRulePlies = FENrulePlies;
9038 epStatus[forwardMostMove] = FENepStatus;
9039 for( i=0; i< nrCastlingRights; i++ )
9040 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9042 yyboardindex = forwardMostMove;
9044 gameInfo.fen = NULL;
9047 yyboardindex = forwardMostMove;
9048 cm = (ChessMove) yylex();
9050 /* Handle comments interspersed among the tags */
9051 while (cm == Comment) {
9053 if (appData.debugMode)
9054 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9056 if (*p == '{' || *p == '[' || *p == '(') {
9057 p[strlen(p) - 1] = NULLCHAR;
9060 while (*p == '\n') p++;
9061 AppendComment(currentMove, p);
9062 yyboardindex = forwardMostMove;
9063 cm = (ChessMove) yylex();
9067 /* don't rely on existence of Event tag since if game was
9068 * pasted from clipboard the Event tag may not exist
9070 if (numPGNTags > 0){
9072 if (gameInfo.variant == VariantNormal) {
9073 gameInfo.variant = StringToVariant(gameInfo.event);
9076 if( appData.autoDisplayTags ) {
9077 tags = PGNTags(&gameInfo);
9078 TagsPopUp(tags, CmailMsg());
9083 /* Make something up, but don't display it now */
9088 if (cm == PositionDiagram) {
9091 Board initial_position;
9093 if (appData.debugMode)
9094 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9096 if (!startedFromSetupPosition) {
9098 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9099 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9109 initial_position[i][j++] = CharToPiece(*p);
9112 while (*p == ' ' || *p == '\t' ||
9113 *p == '\n' || *p == '\r') p++;
9115 if (strncmp(p, "black", strlen("black"))==0)
9116 blackPlaysFirst = TRUE;
9118 blackPlaysFirst = FALSE;
9119 startedFromSetupPosition = TRUE;
9121 CopyBoard(boards[0], initial_position);
9122 if (blackPlaysFirst) {
9123 currentMove = forwardMostMove = backwardMostMove = 1;
9124 CopyBoard(boards[1], initial_position);
9125 strcpy(moveList[0], "");
9126 strcpy(parseList[0], "");
9127 timeRemaining[0][1] = whiteTimeRemaining;
9128 timeRemaining[1][1] = blackTimeRemaining;
9129 if (commentList[0] != NULL) {
9130 commentList[1] = commentList[0];
9131 commentList[0] = NULL;
9134 currentMove = forwardMostMove = backwardMostMove = 0;
9137 yyboardindex = forwardMostMove;
9138 cm = (ChessMove) yylex();
9141 if (first.pr == NoProc) {
9142 StartChessProgram(&first);
9144 InitChessProgram(&first, FALSE);
9145 SendToProgram("force\n", &first);
9146 if (startedFromSetupPosition) {
9147 SendBoard(&first, forwardMostMove);
9148 if (appData.debugMode) {
9149 fprintf(debugFP, "Load Game\n");
9151 DisplayBothClocks();
9154 /* [HGM] server: flag to write setup moves in broadcast file as one */
9155 loadFlag = appData.suppressLoadMoves;
9157 while (cm == Comment) {
9159 if (appData.debugMode)
9160 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9162 if (*p == '{' || *p == '[' || *p == '(') {
9163 p[strlen(p) - 1] = NULLCHAR;
9166 while (*p == '\n') p++;
9167 AppendComment(currentMove, p);
9168 yyboardindex = forwardMostMove;
9169 cm = (ChessMove) yylex();
9172 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9173 cm == WhiteWins || cm == BlackWins ||
9174 cm == GameIsDrawn || cm == GameUnfinished) {
9175 DisplayMessage("", _("No moves in game"));
9176 if (cmailMsgLoaded) {
9177 if (appData.debugMode)
9178 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9182 DrawPosition(FALSE, boards[currentMove]);
9183 DisplayBothClocks();
9184 gameMode = EditGame;
9191 // [HGM] PV info: routine tests if comment empty
9192 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9193 DisplayComment(currentMove - 1, commentList[currentMove]);
9195 if (!matchMode && appData.timeDelay != 0)
9196 DrawPosition(FALSE, boards[currentMove]);
9198 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9199 programStats.ok_to_send = 1;
9202 /* if the first token after the PGN tags is a move
9203 * and not move number 1, retrieve it from the parser
9205 if (cm != MoveNumberOne)
9206 LoadGameOneMove(cm);
9208 /* load the remaining moves from the file */
9209 while (LoadGameOneMove((ChessMove)0)) {
9210 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9211 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9214 /* rewind to the start of the game */
9215 currentMove = backwardMostMove;
9217 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9219 if (oldGameMode == AnalyzeFile ||
9220 oldGameMode == AnalyzeMode) {
9224 if (matchMode || appData.timeDelay == 0) {
9226 gameMode = EditGame;
9228 } else if (appData.timeDelay > 0) {
9232 if (appData.debugMode)
9233 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9235 loadFlag = 0; /* [HGM] true game starts */
9239 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9241 ReloadPosition(offset)
9244 int positionNumber = lastLoadPositionNumber + offset;
9245 if (lastLoadPositionFP == NULL) {
9246 DisplayError(_("No position has been loaded yet"), 0);
9249 if (positionNumber <= 0) {
9250 DisplayError(_("Can't back up any further"), 0);
9253 return LoadPosition(lastLoadPositionFP, positionNumber,
9254 lastLoadPositionTitle);
9257 /* Load the nth position from the given file */
9259 LoadPositionFromFile(filename, n, title)
9267 if (strcmp(filename, "-") == 0) {
9268 return LoadPosition(stdin, n, "stdin");
9270 f = fopen(filename, "rb");
9272 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9273 DisplayError(buf, errno);
9276 return LoadPosition(f, n, title);
9281 /* Load the nth position from the given open file, and close it */
9283 LoadPosition(f, positionNumber, title)
9288 char *p, line[MSG_SIZ];
9289 Board initial_position;
9290 int i, j, fenMode, pn;
9292 if (gameMode == Training )
9293 SetTrainingModeOff();
9295 if (gameMode != BeginningOfGame) {
9298 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9299 fclose(lastLoadPositionFP);
9301 if (positionNumber == 0) positionNumber = 1;
9302 lastLoadPositionFP = f;
9303 lastLoadPositionNumber = positionNumber;
9304 strcpy(lastLoadPositionTitle, title);
9305 if (first.pr == NoProc) {
9306 StartChessProgram(&first);
9307 InitChessProgram(&first, FALSE);
9309 pn = positionNumber;
9310 if (positionNumber < 0) {
9311 /* Negative position number means to seek to that byte offset */
9312 if (fseek(f, -positionNumber, 0) == -1) {
9313 DisplayError(_("Can't seek on position file"), 0);
9318 if (fseek(f, 0, 0) == -1) {
9319 if (f == lastLoadPositionFP ?
9320 positionNumber == lastLoadPositionNumber + 1 :
9321 positionNumber == 1) {
9324 DisplayError(_("Can't seek on position file"), 0);
9329 /* See if this file is FEN or old-style xboard */
9330 if (fgets(line, MSG_SIZ, f) == NULL) {
9331 DisplayError(_("Position not found in file"), 0);
9334 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9335 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9338 if (fenMode || line[0] == '#') pn--;
9340 /* skip positions before number pn */
9341 if (fgets(line, MSG_SIZ, f) == NULL) {
9343 DisplayError(_("Position not found in file"), 0);
9346 if (fenMode || line[0] == '#') pn--;
9351 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9352 DisplayError(_("Bad FEN position in file"), 0);
9356 (void) fgets(line, MSG_SIZ, f);
9357 (void) fgets(line, MSG_SIZ, f);
9359 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9360 (void) fgets(line, MSG_SIZ, f);
9361 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9364 initial_position[i][j++] = CharToPiece(*p);
9368 blackPlaysFirst = FALSE;
9370 (void) fgets(line, MSG_SIZ, f);
9371 if (strncmp(line, "black", strlen("black"))==0)
9372 blackPlaysFirst = TRUE;
9375 startedFromSetupPosition = TRUE;
9377 SendToProgram("force\n", &first);
9378 CopyBoard(boards[0], initial_position);
9379 if (blackPlaysFirst) {
9380 currentMove = forwardMostMove = backwardMostMove = 1;
9381 strcpy(moveList[0], "");
9382 strcpy(parseList[0], "");
9383 CopyBoard(boards[1], initial_position);
9384 DisplayMessage("", _("Black to play"));
9386 currentMove = forwardMostMove = backwardMostMove = 0;
9387 DisplayMessage("", _("White to play"));
9389 /* [HGM] copy FEN attributes as well */
9391 initialRulePlies = FENrulePlies;
9392 epStatus[forwardMostMove] = FENepStatus;
9393 for( i=0; i< nrCastlingRights; i++ )
9394 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9396 SendBoard(&first, forwardMostMove);
9397 if (appData.debugMode) {
9399 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9400 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9401 fprintf(debugFP, "Load Position\n");
9404 if (positionNumber > 1) {
9405 sprintf(line, "%s %d", title, positionNumber);
9408 DisplayTitle(title);
9410 gameMode = EditGame;
9413 timeRemaining[0][1] = whiteTimeRemaining;
9414 timeRemaining[1][1] = blackTimeRemaining;
9415 DrawPosition(FALSE, boards[currentMove]);
9422 CopyPlayerNameIntoFileName(dest, src)
9425 while (*src != NULLCHAR && *src != ',') {
9430 *(*dest)++ = *src++;
9435 char *DefaultFileName(ext)
9438 static char def[MSG_SIZ];
9441 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9443 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9445 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9454 /* Save the current game to the given file */
9456 SaveGameToFile(filename, append)
9463 if (strcmp(filename, "-") == 0) {
9464 return SaveGame(stdout, 0, NULL);
9466 f = fopen(filename, append ? "a" : "w");
9468 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9469 DisplayError(buf, errno);
9472 return SaveGame(f, 0, NULL);
9481 static char buf[MSG_SIZ];
9484 p = strchr(str, ' ');
9485 if (p == NULL) return str;
9486 strncpy(buf, str, p - str);
9487 buf[p - str] = NULLCHAR;
9491 #define PGN_MAX_LINE 75
9493 #define PGN_SIDE_WHITE 0
9494 #define PGN_SIDE_BLACK 1
9497 static int FindFirstMoveOutOfBook( int side )
9501 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9502 int index = backwardMostMove;
9503 int has_book_hit = 0;
9505 if( (index % 2) != side ) {
9509 while( index < forwardMostMove ) {
9510 /* Check to see if engine is in book */
9511 int depth = pvInfoList[index].depth;
9512 int score = pvInfoList[index].score;
9518 else if( score == 0 && depth == 63 ) {
9519 in_book = 1; /* Zappa */
9521 else if( score == 2 && depth == 99 ) {
9522 in_book = 1; /* Abrok */
9525 has_book_hit += in_book;
9541 void GetOutOfBookInfo( char * buf )
9545 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9547 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9548 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9552 if( oob[0] >= 0 || oob[1] >= 0 ) {
9553 for( i=0; i<2; i++ ) {
9557 if( i > 0 && oob[0] >= 0 ) {
9561 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9562 sprintf( buf+strlen(buf), "%s%.2f",
9563 pvInfoList[idx].score >= 0 ? "+" : "",
9564 pvInfoList[idx].score / 100.0 );
9570 /* Save game in PGN style and close the file */
9575 int i, offset, linelen, newblock;
9579 int movelen, numlen, blank;
9580 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9582 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9584 tm = time((time_t *) NULL);
9586 PrintPGNTags(f, &gameInfo);
9588 if (backwardMostMove > 0 || startedFromSetupPosition) {
9589 char *fen = PositionToFEN(backwardMostMove, NULL);
9590 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9591 fprintf(f, "\n{--------------\n");
9592 PrintPosition(f, backwardMostMove);
9593 fprintf(f, "--------------}\n");
9597 /* [AS] Out of book annotation */
9598 if( appData.saveOutOfBookInfo ) {
9601 GetOutOfBookInfo( buf );
9603 if( buf[0] != '\0' ) {
9604 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9611 i = backwardMostMove;
9615 while (i < forwardMostMove) {
9616 /* Print comments preceding this move */
9617 if (commentList[i] != NULL) {
9618 if (linelen > 0) fprintf(f, "\n");
9619 fprintf(f, "{\n%s}\n", commentList[i]);
9624 /* Format move number */
9626 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9629 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9631 numtext[0] = NULLCHAR;
9634 numlen = strlen(numtext);
9637 /* Print move number */
9638 blank = linelen > 0 && numlen > 0;
9639 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9648 fprintf(f, "%s", numtext);
9652 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9653 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9656 blank = linelen > 0 && movelen > 0;
9657 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9666 fprintf(f, "%s", move_buffer);
9669 /* [AS] Add PV info if present */
9670 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9671 /* [HGM] add time */
9672 char buf[MSG_SIZ]; int seconds = 0;
9674 if(i >= backwardMostMove) {
9676 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9677 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9679 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9680 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9682 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9684 if( seconds <= 0) buf[0] = 0; else
9685 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9686 seconds = (seconds + 4)/10; // round to full seconds
9687 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9688 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9691 sprintf( move_buffer, "{%s%.2f/%d%s}",
9692 pvInfoList[i].score >= 0 ? "+" : "",
9693 pvInfoList[i].score / 100.0,
9694 pvInfoList[i].depth,
9697 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9699 /* Print score/depth */
9700 blank = linelen > 0 && movelen > 0;
9701 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9710 fprintf(f, "%s", move_buffer);
9717 /* Start a new line */
9718 if (linelen > 0) fprintf(f, "\n");
9720 /* Print comments after last move */
9721 if (commentList[i] != NULL) {
9722 fprintf(f, "{\n%s}\n", commentList[i]);
9726 if (gameInfo.resultDetails != NULL &&
9727 gameInfo.resultDetails[0] != NULLCHAR) {
9728 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9729 PGNResult(gameInfo.result));
9731 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9735 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9739 /* Save game in old style and close the file */
9747 tm = time((time_t *) NULL);
9749 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9752 if (backwardMostMove > 0 || startedFromSetupPosition) {
9753 fprintf(f, "\n[--------------\n");
9754 PrintPosition(f, backwardMostMove);
9755 fprintf(f, "--------------]\n");
9760 i = backwardMostMove;
9761 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9763 while (i < forwardMostMove) {
9764 if (commentList[i] != NULL) {
9765 fprintf(f, "[%s]\n", commentList[i]);
9769 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9772 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9774 if (commentList[i] != NULL) {
9778 if (i >= forwardMostMove) {
9782 fprintf(f, "%s\n", parseList[i]);
9787 if (commentList[i] != NULL) {
9788 fprintf(f, "[%s]\n", commentList[i]);
9791 /* This isn't really the old style, but it's close enough */
9792 if (gameInfo.resultDetails != NULL &&
9793 gameInfo.resultDetails[0] != NULLCHAR) {
9794 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9795 gameInfo.resultDetails);
9797 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9804 /* Save the current game to open file f and close the file */
9806 SaveGame(f, dummy, dummy2)
9811 if (gameMode == EditPosition) EditPositionDone();
9812 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9813 if (appData.oldSaveStyle)
9814 return SaveGameOldStyle(f);
9816 return SaveGamePGN(f);
9819 /* Save the current position to the given file */
9821 SavePositionToFile(filename)
9827 if (strcmp(filename, "-") == 0) {
9828 return SavePosition(stdout, 0, NULL);
9830 f = fopen(filename, "a");
9832 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9833 DisplayError(buf, errno);
9836 SavePosition(f, 0, NULL);
9842 /* Save the current position to the given open file and close the file */
9844 SavePosition(f, dummy, dummy2)
9852 if (appData.oldSaveStyle) {
9853 tm = time((time_t *) NULL);
9855 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9857 fprintf(f, "[--------------\n");
9858 PrintPosition(f, currentMove);
9859 fprintf(f, "--------------]\n");
9861 fen = PositionToFEN(currentMove, NULL);
9862 fprintf(f, "%s\n", fen);
9870 ReloadCmailMsgEvent(unregister)
9874 static char *inFilename = NULL;
9875 static char *outFilename;
9877 struct stat inbuf, outbuf;
9880 /* Any registered moves are unregistered if unregister is set, */
9881 /* i.e. invoked by the signal handler */
9883 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9884 cmailMoveRegistered[i] = FALSE;
9885 if (cmailCommentList[i] != NULL) {
9886 free(cmailCommentList[i]);
9887 cmailCommentList[i] = NULL;
9890 nCmailMovesRegistered = 0;
9893 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9894 cmailResult[i] = CMAIL_NOT_RESULT;
9898 if (inFilename == NULL) {
9899 /* Because the filenames are static they only get malloced once */
9900 /* and they never get freed */
9901 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9902 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9904 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9905 sprintf(outFilename, "%s.out", appData.cmailGameName);
9908 status = stat(outFilename, &outbuf);
9910 cmailMailedMove = FALSE;
9912 status = stat(inFilename, &inbuf);
9913 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9916 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9917 counts the games, notes how each one terminated, etc.
9919 It would be nice to remove this kludge and instead gather all
9920 the information while building the game list. (And to keep it
9921 in the game list nodes instead of having a bunch of fixed-size
9922 parallel arrays.) Note this will require getting each game's
9923 termination from the PGN tags, as the game list builder does
9924 not process the game moves. --mann
9926 cmailMsgLoaded = TRUE;
9927 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9929 /* Load first game in the file or popup game menu */
9930 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9940 char string[MSG_SIZ];
9942 if ( cmailMailedMove
9943 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9944 return TRUE; /* Allow free viewing */
9947 /* Unregister move to ensure that we don't leave RegisterMove */
9948 /* with the move registered when the conditions for registering no */
9950 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9951 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9952 nCmailMovesRegistered --;
9954 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9956 free(cmailCommentList[lastLoadGameNumber - 1]);
9957 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9961 if (cmailOldMove == -1) {
9962 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9966 if (currentMove > cmailOldMove + 1) {
9967 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9971 if (currentMove < cmailOldMove) {
9972 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9976 if (forwardMostMove > currentMove) {
9977 /* Silently truncate extra moves */
9981 if ( (currentMove == cmailOldMove + 1)
9982 || ( (currentMove == cmailOldMove)
9983 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9984 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9985 if (gameInfo.result != GameUnfinished) {
9986 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9989 if (commentList[currentMove] != NULL) {
9990 cmailCommentList[lastLoadGameNumber - 1]
9991 = StrSave(commentList[currentMove]);
9993 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9995 if (appData.debugMode)
9996 fprintf(debugFP, "Saving %s for game %d\n",
9997 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10000 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10002 f = fopen(string, "w");
10003 if (appData.oldSaveStyle) {
10004 SaveGameOldStyle(f); /* also closes the file */
10006 sprintf(string, "%s.pos.out", appData.cmailGameName);
10007 f = fopen(string, "w");
10008 SavePosition(f, 0, NULL); /* also closes the file */
10010 fprintf(f, "{--------------\n");
10011 PrintPosition(f, currentMove);
10012 fprintf(f, "--------------}\n\n");
10014 SaveGame(f, 0, NULL); /* also closes the file*/
10017 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10018 nCmailMovesRegistered ++;
10019 } else if (nCmailGames == 1) {
10020 DisplayError(_("You have not made a move yet"), 0);
10031 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10032 FILE *commandOutput;
10033 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10034 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10040 if (! cmailMsgLoaded) {
10041 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10045 if (nCmailGames == nCmailResults) {
10046 DisplayError(_("No unfinished games"), 0);
10050 #if CMAIL_PROHIBIT_REMAIL
10051 if (cmailMailedMove) {
10052 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);
10053 DisplayError(msg, 0);
10058 if (! (cmailMailedMove || RegisterMove())) return;
10060 if ( cmailMailedMove
10061 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10062 sprintf(string, partCommandString,
10063 appData.debugMode ? " -v" : "", appData.cmailGameName);
10064 commandOutput = popen(string, "r");
10066 if (commandOutput == NULL) {
10067 DisplayError(_("Failed to invoke cmail"), 0);
10069 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10070 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10072 if (nBuffers > 1) {
10073 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10074 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10075 nBytes = MSG_SIZ - 1;
10077 (void) memcpy(msg, buffer, nBytes);
10079 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10081 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10082 cmailMailedMove = TRUE; /* Prevent >1 moves */
10085 for (i = 0; i < nCmailGames; i ++) {
10086 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10091 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10093 sprintf(buffer, "%s/%s.%s.archive",
10095 appData.cmailGameName,
10097 LoadGameFromFile(buffer, 1, buffer, FALSE);
10098 cmailMsgLoaded = FALSE;
10102 DisplayInformation(msg);
10103 pclose(commandOutput);
10106 if ((*cmailMsg) != '\0') {
10107 DisplayInformation(cmailMsg);
10112 #endif /* !WIN32 */
10121 int prependComma = 0;
10123 char string[MSG_SIZ]; /* Space for game-list */
10126 if (!cmailMsgLoaded) return "";
10128 if (cmailMailedMove) {
10129 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10131 /* Create a list of games left */
10132 sprintf(string, "[");
10133 for (i = 0; i < nCmailGames; i ++) {
10134 if (! ( cmailMoveRegistered[i]
10135 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10136 if (prependComma) {
10137 sprintf(number, ",%d", i + 1);
10139 sprintf(number, "%d", i + 1);
10143 strcat(string, number);
10146 strcat(string, "]");
10148 if (nCmailMovesRegistered + nCmailResults == 0) {
10149 switch (nCmailGames) {
10152 _("Still need to make move for game\n"));
10157 _("Still need to make moves for both games\n"));
10162 _("Still need to make moves for all %d games\n"),
10167 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10170 _("Still need to make a move for game %s\n"),
10175 if (nCmailResults == nCmailGames) {
10176 sprintf(cmailMsg, _("No unfinished games\n"));
10178 sprintf(cmailMsg, _("Ready to send mail\n"));
10184 _("Still need to make moves for games %s\n"),
10196 if (gameMode == Training)
10197 SetTrainingModeOff();
10200 cmailMsgLoaded = FALSE;
10201 if (appData.icsActive) {
10202 SendToICS(ics_prefix);
10203 SendToICS("refresh\n");
10213 /* Give up on clean exit */
10217 /* Keep trying for clean exit */
10221 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10223 if (telnetISR != NULL) {
10224 RemoveInputSource(telnetISR);
10226 if (icsPR != NoProc) {
10227 DestroyChildProcess(icsPR, TRUE);
10230 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10231 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10233 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10234 /* make sure this other one finishes before killing it! */
10235 if(endingGame) { int count = 0;
10236 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10237 while(endingGame && count++ < 10) DoSleep(1);
10238 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10241 /* Kill off chess programs */
10242 if (first.pr != NoProc) {
10245 DoSleep( appData.delayBeforeQuit );
10246 SendToProgram("quit\n", &first);
10247 DoSleep( appData.delayAfterQuit );
10248 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10250 if (second.pr != NoProc) {
10251 DoSleep( appData.delayBeforeQuit );
10252 SendToProgram("quit\n", &second);
10253 DoSleep( appData.delayAfterQuit );
10254 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10256 if (first.isr != NULL) {
10257 RemoveInputSource(first.isr);
10259 if (second.isr != NULL) {
10260 RemoveInputSource(second.isr);
10263 ShutDownFrontEnd();
10270 if (appData.debugMode)
10271 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10275 if (gameMode == MachinePlaysWhite ||
10276 gameMode == MachinePlaysBlack) {
10279 DisplayBothClocks();
10281 if (gameMode == PlayFromGameFile) {
10282 if (appData.timeDelay >= 0)
10283 AutoPlayGameLoop();
10284 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10285 Reset(FALSE, TRUE);
10286 SendToICS(ics_prefix);
10287 SendToICS("refresh\n");
10288 } else if (currentMove < forwardMostMove) {
10289 ForwardInner(forwardMostMove);
10291 pauseExamInvalid = FALSE;
10293 switch (gameMode) {
10297 pauseExamForwardMostMove = forwardMostMove;
10298 pauseExamInvalid = FALSE;
10301 case IcsPlayingWhite:
10302 case IcsPlayingBlack:
10306 case PlayFromGameFile:
10307 (void) StopLoadGameTimer();
10311 case BeginningOfGame:
10312 if (appData.icsActive) return;
10313 /* else fall through */
10314 case MachinePlaysWhite:
10315 case MachinePlaysBlack:
10316 case TwoMachinesPlay:
10317 if (forwardMostMove == 0)
10318 return; /* don't pause if no one has moved */
10319 if ((gameMode == MachinePlaysWhite &&
10320 !WhiteOnMove(forwardMostMove)) ||
10321 (gameMode == MachinePlaysBlack &&
10322 WhiteOnMove(forwardMostMove))) {
10335 char title[MSG_SIZ];
10337 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10338 strcpy(title, _("Edit comment"));
10340 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10341 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10342 parseList[currentMove - 1]);
10345 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10352 char *tags = PGNTags(&gameInfo);
10353 EditTagsPopUp(tags);
10360 if (appData.noChessProgram || gameMode == AnalyzeMode)
10363 if (gameMode != AnalyzeFile) {
10364 if (!appData.icsEngineAnalyze) {
10366 if (gameMode != EditGame) return;
10368 ResurrectChessProgram();
10369 SendToProgram("analyze\n", &first);
10370 first.analyzing = TRUE;
10371 /*first.maybeThinking = TRUE;*/
10372 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10373 EngineOutputPopUp();
10375 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10380 StartAnalysisClock();
10381 GetTimeMark(&lastNodeCountTime);
10388 if (appData.noChessProgram || gameMode == AnalyzeFile)
10391 if (gameMode != AnalyzeMode) {
10393 if (gameMode != EditGame) return;
10394 ResurrectChessProgram();
10395 SendToProgram("analyze\n", &first);
10396 first.analyzing = TRUE;
10397 /*first.maybeThinking = TRUE;*/
10398 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10399 EngineOutputPopUp();
10401 gameMode = AnalyzeFile;
10406 StartAnalysisClock();
10407 GetTimeMark(&lastNodeCountTime);
10412 MachineWhiteEvent()
10415 char *bookHit = NULL;
10417 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10421 if (gameMode == PlayFromGameFile ||
10422 gameMode == TwoMachinesPlay ||
10423 gameMode == Training ||
10424 gameMode == AnalyzeMode ||
10425 gameMode == EndOfGame)
10428 if (gameMode == EditPosition)
10429 EditPositionDone();
10431 if (!WhiteOnMove(currentMove)) {
10432 DisplayError(_("It is not White's turn"), 0);
10436 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10439 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10440 gameMode == AnalyzeFile)
10443 ResurrectChessProgram(); /* in case it isn't running */
10444 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10445 gameMode = MachinePlaysWhite;
10448 gameMode = MachinePlaysWhite;
10452 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10454 if (first.sendName) {
10455 sprintf(buf, "name %s\n", gameInfo.black);
10456 SendToProgram(buf, &first);
10458 if (first.sendTime) {
10459 if (first.useColors) {
10460 SendToProgram("black\n", &first); /*gnu kludge*/
10462 SendTimeRemaining(&first, TRUE);
10464 if (first.useColors) {
10465 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10467 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10468 SetMachineThinkingEnables();
10469 first.maybeThinking = TRUE;
10473 if (appData.autoFlipView && !flipView) {
10474 flipView = !flipView;
10475 DrawPosition(FALSE, NULL);
10476 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10479 if(bookHit) { // [HGM] book: simulate book reply
10480 static char bookMove[MSG_SIZ]; // a bit generous?
10482 programStats.nodes = programStats.depth = programStats.time =
10483 programStats.score = programStats.got_only_move = 0;
10484 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10486 strcpy(bookMove, "move ");
10487 strcat(bookMove, bookHit);
10488 HandleMachineMove(bookMove, &first);
10493 MachineBlackEvent()
10496 char *bookHit = NULL;
10498 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10502 if (gameMode == PlayFromGameFile ||
10503 gameMode == TwoMachinesPlay ||
10504 gameMode == Training ||
10505 gameMode == AnalyzeMode ||
10506 gameMode == EndOfGame)
10509 if (gameMode == EditPosition)
10510 EditPositionDone();
10512 if (WhiteOnMove(currentMove)) {
10513 DisplayError(_("It is not Black's turn"), 0);
10517 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10520 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10521 gameMode == AnalyzeFile)
10524 ResurrectChessProgram(); /* in case it isn't running */
10525 gameMode = MachinePlaysBlack;
10529 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10531 if (first.sendName) {
10532 sprintf(buf, "name %s\n", gameInfo.white);
10533 SendToProgram(buf, &first);
10535 if (first.sendTime) {
10536 if (first.useColors) {
10537 SendToProgram("white\n", &first); /*gnu kludge*/
10539 SendTimeRemaining(&first, FALSE);
10541 if (first.useColors) {
10542 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10544 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10545 SetMachineThinkingEnables();
10546 first.maybeThinking = TRUE;
10549 if (appData.autoFlipView && flipView) {
10550 flipView = !flipView;
10551 DrawPosition(FALSE, NULL);
10552 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10554 if(bookHit) { // [HGM] book: simulate book reply
10555 static char bookMove[MSG_SIZ]; // a bit generous?
10557 programStats.nodes = programStats.depth = programStats.time =
10558 programStats.score = programStats.got_only_move = 0;
10559 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10561 strcpy(bookMove, "move ");
10562 strcat(bookMove, bookHit);
10563 HandleMachineMove(bookMove, &first);
10569 DisplayTwoMachinesTitle()
10572 if (appData.matchGames > 0) {
10573 if (first.twoMachinesColor[0] == 'w') {
10574 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10575 gameInfo.white, gameInfo.black,
10576 first.matchWins, second.matchWins,
10577 matchGame - 1 - (first.matchWins + second.matchWins));
10579 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10580 gameInfo.white, gameInfo.black,
10581 second.matchWins, first.matchWins,
10582 matchGame - 1 - (first.matchWins + second.matchWins));
10585 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10591 TwoMachinesEvent P((void))
10595 ChessProgramState *onmove;
10596 char *bookHit = NULL;
10598 if (appData.noChessProgram) return;
10600 switch (gameMode) {
10601 case TwoMachinesPlay:
10603 case MachinePlaysWhite:
10604 case MachinePlaysBlack:
10605 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10606 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10610 case BeginningOfGame:
10611 case PlayFromGameFile:
10614 if (gameMode != EditGame) return;
10617 EditPositionDone();
10628 forwardMostMove = currentMove;
10629 ResurrectChessProgram(); /* in case first program isn't running */
10631 if (second.pr == NULL) {
10632 StartChessProgram(&second);
10633 if (second.protocolVersion == 1) {
10634 TwoMachinesEventIfReady();
10636 /* kludge: allow timeout for initial "feature" command */
10638 DisplayMessage("", _("Starting second chess program"));
10639 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10643 DisplayMessage("", "");
10644 InitChessProgram(&second, FALSE);
10645 SendToProgram("force\n", &second);
10646 if (startedFromSetupPosition) {
10647 SendBoard(&second, backwardMostMove);
10648 if (appData.debugMode) {
10649 fprintf(debugFP, "Two Machines\n");
10652 for (i = backwardMostMove; i < forwardMostMove; i++) {
10653 SendMoveToProgram(i, &second);
10656 gameMode = TwoMachinesPlay;
10660 DisplayTwoMachinesTitle();
10662 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10668 SendToProgram(first.computerString, &first);
10669 if (first.sendName) {
10670 sprintf(buf, "name %s\n", second.tidy);
10671 SendToProgram(buf, &first);
10673 SendToProgram(second.computerString, &second);
10674 if (second.sendName) {
10675 sprintf(buf, "name %s\n", first.tidy);
10676 SendToProgram(buf, &second);
10680 if (!first.sendTime || !second.sendTime) {
10681 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10682 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10684 if (onmove->sendTime) {
10685 if (onmove->useColors) {
10686 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10688 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10690 if (onmove->useColors) {
10691 SendToProgram(onmove->twoMachinesColor, onmove);
10693 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10694 // SendToProgram("go\n", onmove);
10695 onmove->maybeThinking = TRUE;
10696 SetMachineThinkingEnables();
10700 if(bookHit) { // [HGM] book: simulate book reply
10701 static char bookMove[MSG_SIZ]; // a bit generous?
10703 programStats.nodes = programStats.depth = programStats.time =
10704 programStats.score = programStats.got_only_move = 0;
10705 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10707 strcpy(bookMove, "move ");
10708 strcat(bookMove, bookHit);
10709 HandleMachineMove(bookMove, &first);
10716 if (gameMode == Training) {
10717 SetTrainingModeOff();
10718 gameMode = PlayFromGameFile;
10719 DisplayMessage("", _("Training mode off"));
10721 gameMode = Training;
10722 animateTraining = appData.animate;
10724 /* make sure we are not already at the end of the game */
10725 if (currentMove < forwardMostMove) {
10726 SetTrainingModeOn();
10727 DisplayMessage("", _("Training mode on"));
10729 gameMode = PlayFromGameFile;
10730 DisplayError(_("Already at end of game"), 0);
10739 if (!appData.icsActive) return;
10740 switch (gameMode) {
10741 case IcsPlayingWhite:
10742 case IcsPlayingBlack:
10745 case BeginningOfGame:
10753 EditPositionDone();
10766 gameMode = IcsIdle;
10777 switch (gameMode) {
10779 SetTrainingModeOff();
10781 case MachinePlaysWhite:
10782 case MachinePlaysBlack:
10783 case BeginningOfGame:
10784 SendToProgram("force\n", &first);
10785 SetUserThinkingEnables();
10787 case PlayFromGameFile:
10788 (void) StopLoadGameTimer();
10789 if (gameFileFP != NULL) {
10794 EditPositionDone();
10799 SendToProgram("force\n", &first);
10801 case TwoMachinesPlay:
10802 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10803 ResurrectChessProgram();
10804 SetUserThinkingEnables();
10807 ResurrectChessProgram();
10809 case IcsPlayingBlack:
10810 case IcsPlayingWhite:
10811 DisplayError(_("Warning: You are still playing a game"), 0);
10814 DisplayError(_("Warning: You are still observing a game"), 0);
10817 DisplayError(_("Warning: You are still examining a game"), 0);
10828 first.offeredDraw = second.offeredDraw = 0;
10830 if (gameMode == PlayFromGameFile) {
10831 whiteTimeRemaining = timeRemaining[0][currentMove];
10832 blackTimeRemaining = timeRemaining[1][currentMove];
10836 if (gameMode == MachinePlaysWhite ||
10837 gameMode == MachinePlaysBlack ||
10838 gameMode == TwoMachinesPlay ||
10839 gameMode == EndOfGame) {
10840 i = forwardMostMove;
10841 while (i > currentMove) {
10842 SendToProgram("undo\n", &first);
10845 whiteTimeRemaining = timeRemaining[0][currentMove];
10846 blackTimeRemaining = timeRemaining[1][currentMove];
10847 DisplayBothClocks();
10848 if (whiteFlag || blackFlag) {
10849 whiteFlag = blackFlag = 0;
10854 gameMode = EditGame;
10861 EditPositionEvent()
10863 if (gameMode == EditPosition) {
10869 if (gameMode != EditGame) return;
10871 gameMode = EditPosition;
10874 if (currentMove > 0)
10875 CopyBoard(boards[0], boards[currentMove]);
10877 blackPlaysFirst = !WhiteOnMove(currentMove);
10879 currentMove = forwardMostMove = backwardMostMove = 0;
10880 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10887 /* [DM] icsEngineAnalyze - possible call from other functions */
10888 if (appData.icsEngineAnalyze) {
10889 appData.icsEngineAnalyze = FALSE;
10891 DisplayMessage("",_("Close ICS engine analyze..."));
10893 if (first.analysisSupport && first.analyzing) {
10894 SendToProgram("exit\n", &first);
10895 first.analyzing = FALSE;
10897 thinkOutput[0] = NULLCHAR;
10903 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10905 startedFromSetupPosition = TRUE;
10906 InitChessProgram(&first, FALSE);
10907 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10908 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10909 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10910 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10911 } else castlingRights[0][2] = -1;
10912 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10913 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10914 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10915 } else castlingRights[0][5] = -1;
10916 SendToProgram("force\n", &first);
10917 if (blackPlaysFirst) {
10918 strcpy(moveList[0], "");
10919 strcpy(parseList[0], "");
10920 currentMove = forwardMostMove = backwardMostMove = 1;
10921 CopyBoard(boards[1], boards[0]);
10922 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10924 epStatus[1] = epStatus[0];
10925 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10928 currentMove = forwardMostMove = backwardMostMove = 0;
10930 SendBoard(&first, forwardMostMove);
10931 if (appData.debugMode) {
10932 fprintf(debugFP, "EditPosDone\n");
10935 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10936 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10937 gameMode = EditGame;
10939 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10940 ClearHighlights(); /* [AS] */
10943 /* Pause for `ms' milliseconds */
10944 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10954 } while (SubtractTimeMarks(&m2, &m1) < ms);
10957 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10959 SendMultiLineToICS(buf)
10962 char temp[MSG_SIZ+1], *p;
10969 strncpy(temp, buf, len);
10974 if (*p == '\n' || *p == '\r')
10979 strcat(temp, "\n");
10981 SendToPlayer(temp, strlen(temp));
10985 SetWhiteToPlayEvent()
10987 if (gameMode == EditPosition) {
10988 blackPlaysFirst = FALSE;
10989 DisplayBothClocks(); /* works because currentMove is 0 */
10990 } else if (gameMode == IcsExamining) {
10991 SendToICS(ics_prefix);
10992 SendToICS("tomove white\n");
10997 SetBlackToPlayEvent()
10999 if (gameMode == EditPosition) {
11000 blackPlaysFirst = TRUE;
11001 currentMove = 1; /* kludge */
11002 DisplayBothClocks();
11004 } else if (gameMode == IcsExamining) {
11005 SendToICS(ics_prefix);
11006 SendToICS("tomove black\n");
11011 EditPositionMenuEvent(selection, x, y)
11012 ChessSquare selection;
11016 ChessSquare piece = boards[0][y][x];
11018 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11020 switch (selection) {
11022 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11023 SendToICS(ics_prefix);
11024 SendToICS("bsetup clear\n");
11025 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11026 SendToICS(ics_prefix);
11027 SendToICS("clearboard\n");
11029 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11030 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11031 for (y = 0; y < BOARD_HEIGHT; y++) {
11032 if (gameMode == IcsExamining) {
11033 if (boards[currentMove][y][x] != EmptySquare) {
11034 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11039 boards[0][y][x] = p;
11044 if (gameMode == EditPosition) {
11045 DrawPosition(FALSE, boards[0]);
11050 SetWhiteToPlayEvent();
11054 SetBlackToPlayEvent();
11058 if (gameMode == IcsExamining) {
11059 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11062 boards[0][y][x] = EmptySquare;
11063 DrawPosition(FALSE, boards[0]);
11068 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11069 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11070 selection = (ChessSquare) (PROMOTED piece);
11071 } else if(piece == EmptySquare) selection = WhiteSilver;
11072 else selection = (ChessSquare)((int)piece - 1);
11076 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11077 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11078 selection = (ChessSquare) (DEMOTED piece);
11079 } else if(piece == EmptySquare) selection = BlackSilver;
11080 else selection = (ChessSquare)((int)piece + 1);
11085 if(gameInfo.variant == VariantShatranj ||
11086 gameInfo.variant == VariantXiangqi ||
11087 gameInfo.variant == VariantCourier )
11088 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11093 if(gameInfo.variant == VariantXiangqi)
11094 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11095 if(gameInfo.variant == VariantKnightmate)
11096 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11099 if (gameMode == IcsExamining) {
11100 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11101 PieceToChar(selection), AAA + x, ONE + y);
11104 boards[0][y][x] = selection;
11105 DrawPosition(FALSE, boards[0]);
11113 DropMenuEvent(selection, x, y)
11114 ChessSquare selection;
11117 ChessMove moveType;
11119 switch (gameMode) {
11120 case IcsPlayingWhite:
11121 case MachinePlaysBlack:
11122 if (!WhiteOnMove(currentMove)) {
11123 DisplayMoveError(_("It is Black's turn"));
11126 moveType = WhiteDrop;
11128 case IcsPlayingBlack:
11129 case MachinePlaysWhite:
11130 if (WhiteOnMove(currentMove)) {
11131 DisplayMoveError(_("It is White's turn"));
11134 moveType = BlackDrop;
11137 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11143 if (moveType == BlackDrop && selection < BlackPawn) {
11144 selection = (ChessSquare) ((int) selection
11145 + (int) BlackPawn - (int) WhitePawn);
11147 if (boards[currentMove][y][x] != EmptySquare) {
11148 DisplayMoveError(_("That square is occupied"));
11152 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11158 /* Accept a pending offer of any kind from opponent */
11160 if (appData.icsActive) {
11161 SendToICS(ics_prefix);
11162 SendToICS("accept\n");
11163 } else if (cmailMsgLoaded) {
11164 if (currentMove == cmailOldMove &&
11165 commentList[cmailOldMove] != NULL &&
11166 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11167 "Black offers a draw" : "White offers a draw")) {
11169 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11170 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11172 DisplayError(_("There is no pending offer on this move"), 0);
11173 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11176 /* Not used for offers from chess program */
11183 /* Decline a pending offer of any kind from opponent */
11185 if (appData.icsActive) {
11186 SendToICS(ics_prefix);
11187 SendToICS("decline\n");
11188 } else if (cmailMsgLoaded) {
11189 if (currentMove == cmailOldMove &&
11190 commentList[cmailOldMove] != NULL &&
11191 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11192 "Black offers a draw" : "White offers a draw")) {
11194 AppendComment(cmailOldMove, "Draw declined");
11195 DisplayComment(cmailOldMove - 1, "Draw declined");
11198 DisplayError(_("There is no pending offer on this move"), 0);
11201 /* Not used for offers from chess program */
11208 /* Issue ICS rematch command */
11209 if (appData.icsActive) {
11210 SendToICS(ics_prefix);
11211 SendToICS("rematch\n");
11218 /* Call your opponent's flag (claim a win on time) */
11219 if (appData.icsActive) {
11220 SendToICS(ics_prefix);
11221 SendToICS("flag\n");
11223 switch (gameMode) {
11226 case MachinePlaysWhite:
11229 GameEnds(GameIsDrawn, "Both players ran out of time",
11232 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11234 DisplayError(_("Your opponent is not out of time"), 0);
11237 case MachinePlaysBlack:
11240 GameEnds(GameIsDrawn, "Both players ran out of time",
11243 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11245 DisplayError(_("Your opponent is not out of time"), 0);
11255 /* Offer draw or accept pending draw offer from opponent */
11257 if (appData.icsActive) {
11258 /* Note: tournament rules require draw offers to be
11259 made after you make your move but before you punch
11260 your clock. Currently ICS doesn't let you do that;
11261 instead, you immediately punch your clock after making
11262 a move, but you can offer a draw at any time. */
11264 SendToICS(ics_prefix);
11265 SendToICS("draw\n");
11266 } else if (cmailMsgLoaded) {
11267 if (currentMove == cmailOldMove &&
11268 commentList[cmailOldMove] != NULL &&
11269 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11270 "Black offers a draw" : "White offers a draw")) {
11271 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11272 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11273 } else if (currentMove == cmailOldMove + 1) {
11274 char *offer = WhiteOnMove(cmailOldMove) ?
11275 "White offers a draw" : "Black offers a draw";
11276 AppendComment(currentMove, offer);
11277 DisplayComment(currentMove - 1, offer);
11278 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11280 DisplayError(_("You must make your move before offering a draw"), 0);
11281 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11283 } else if (first.offeredDraw) {
11284 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11286 if (first.sendDrawOffers) {
11287 SendToProgram("draw\n", &first);
11288 userOfferedDraw = TRUE;
11296 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11298 if (appData.icsActive) {
11299 SendToICS(ics_prefix);
11300 SendToICS("adjourn\n");
11302 /* Currently GNU Chess doesn't offer or accept Adjourns */
11310 /* Offer Abort or accept pending Abort offer from opponent */
11312 if (appData.icsActive) {
11313 SendToICS(ics_prefix);
11314 SendToICS("abort\n");
11316 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11323 /* Resign. You can do this even if it's not your turn. */
11325 if (appData.icsActive) {
11326 SendToICS(ics_prefix);
11327 SendToICS("resign\n");
11329 switch (gameMode) {
11330 case MachinePlaysWhite:
11331 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11333 case MachinePlaysBlack:
11334 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11337 if (cmailMsgLoaded) {
11339 if (WhiteOnMove(cmailOldMove)) {
11340 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11342 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11344 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11355 StopObservingEvent()
11357 /* Stop observing current games */
11358 SendToICS(ics_prefix);
11359 SendToICS("unobserve\n");
11363 StopExaminingEvent()
11365 /* Stop observing current game */
11366 SendToICS(ics_prefix);
11367 SendToICS("unexamine\n");
11371 ForwardInner(target)
11376 if (appData.debugMode)
11377 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11378 target, currentMove, forwardMostMove);
11380 if (gameMode == EditPosition)
11383 if (gameMode == PlayFromGameFile && !pausing)
11386 if (gameMode == IcsExamining && pausing)
11387 limit = pauseExamForwardMostMove;
11389 limit = forwardMostMove;
11391 if (target > limit) target = limit;
11393 if (target > 0 && moveList[target - 1][0]) {
11394 int fromX, fromY, toX, toY;
11395 toX = moveList[target - 1][2] - AAA;
11396 toY = moveList[target - 1][3] - ONE;
11397 if (moveList[target - 1][1] == '@') {
11398 if (appData.highlightLastMove) {
11399 SetHighlights(-1, -1, toX, toY);
11402 fromX = moveList[target - 1][0] - AAA;
11403 fromY = moveList[target - 1][1] - ONE;
11404 if (target == currentMove + 1) {
11405 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11407 if (appData.highlightLastMove) {
11408 SetHighlights(fromX, fromY, toX, toY);
11412 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11413 gameMode == Training || gameMode == PlayFromGameFile ||
11414 gameMode == AnalyzeFile) {
11415 while (currentMove < target) {
11416 SendMoveToProgram(currentMove++, &first);
11419 currentMove = target;
11422 if (gameMode == EditGame || gameMode == EndOfGame) {
11423 whiteTimeRemaining = timeRemaining[0][currentMove];
11424 blackTimeRemaining = timeRemaining[1][currentMove];
11426 DisplayBothClocks();
11427 DisplayMove(currentMove - 1);
11428 DrawPosition(FALSE, boards[currentMove]);
11429 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11430 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11431 DisplayComment(currentMove - 1, commentList[currentMove]);
11439 if (gameMode == IcsExamining && !pausing) {
11440 SendToICS(ics_prefix);
11441 SendToICS("forward\n");
11443 ForwardInner(currentMove + 1);
11450 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11451 /* to optimze, we temporarily turn off analysis mode while we feed
11452 * the remaining moves to the engine. Otherwise we get analysis output
11455 if (first.analysisSupport) {
11456 SendToProgram("exit\nforce\n", &first);
11457 first.analyzing = FALSE;
11461 if (gameMode == IcsExamining && !pausing) {
11462 SendToICS(ics_prefix);
11463 SendToICS("forward 999999\n");
11465 ForwardInner(forwardMostMove);
11468 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11469 /* we have fed all the moves, so reactivate analysis mode */
11470 SendToProgram("analyze\n", &first);
11471 first.analyzing = TRUE;
11472 /*first.maybeThinking = TRUE;*/
11473 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11478 BackwardInner(target)
11481 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11483 if (appData.debugMode)
11484 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11485 target, currentMove, forwardMostMove);
11487 if (gameMode == EditPosition) return;
11488 if (currentMove <= backwardMostMove) {
11490 DrawPosition(full_redraw, boards[currentMove]);
11493 if (gameMode == PlayFromGameFile && !pausing)
11496 if (moveList[target][0]) {
11497 int fromX, fromY, toX, toY;
11498 toX = moveList[target][2] - AAA;
11499 toY = moveList[target][3] - ONE;
11500 if (moveList[target][1] == '@') {
11501 if (appData.highlightLastMove) {
11502 SetHighlights(-1, -1, toX, toY);
11505 fromX = moveList[target][0] - AAA;
11506 fromY = moveList[target][1] - ONE;
11507 if (target == currentMove - 1) {
11508 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11510 if (appData.highlightLastMove) {
11511 SetHighlights(fromX, fromY, toX, toY);
11515 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11516 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11517 while (currentMove > target) {
11518 SendToProgram("undo\n", &first);
11522 currentMove = target;
11525 if (gameMode == EditGame || gameMode == EndOfGame) {
11526 whiteTimeRemaining = timeRemaining[0][currentMove];
11527 blackTimeRemaining = timeRemaining[1][currentMove];
11529 DisplayBothClocks();
11530 DisplayMove(currentMove - 1);
11531 DrawPosition(full_redraw, boards[currentMove]);
11532 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11533 // [HGM] PV info: routine tests if comment empty
11534 DisplayComment(currentMove - 1, commentList[currentMove]);
11540 if (gameMode == IcsExamining && !pausing) {
11541 SendToICS(ics_prefix);
11542 SendToICS("backward\n");
11544 BackwardInner(currentMove - 1);
11551 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11552 /* to optimze, we temporarily turn off analysis mode while we undo
11553 * all the moves. Otherwise we get analysis output after each undo.
11555 if (first.analysisSupport) {
11556 SendToProgram("exit\nforce\n", &first);
11557 first.analyzing = FALSE;
11561 if (gameMode == IcsExamining && !pausing) {
11562 SendToICS(ics_prefix);
11563 SendToICS("backward 999999\n");
11565 BackwardInner(backwardMostMove);
11568 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11569 /* we have fed all the moves, so reactivate analysis mode */
11570 SendToProgram("analyze\n", &first);
11571 first.analyzing = TRUE;
11572 /*first.maybeThinking = TRUE;*/
11573 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11580 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11581 if (to >= forwardMostMove) to = forwardMostMove;
11582 if (to <= backwardMostMove) to = backwardMostMove;
11583 if (to < currentMove) {
11593 if (gameMode != IcsExamining) {
11594 DisplayError(_("You are not examining a game"), 0);
11598 DisplayError(_("You can't revert while pausing"), 0);
11601 SendToICS(ics_prefix);
11602 SendToICS("revert\n");
11608 switch (gameMode) {
11609 case MachinePlaysWhite:
11610 case MachinePlaysBlack:
11611 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11612 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11615 if (forwardMostMove < 2) return;
11616 currentMove = forwardMostMove = forwardMostMove - 2;
11617 whiteTimeRemaining = timeRemaining[0][currentMove];
11618 blackTimeRemaining = timeRemaining[1][currentMove];
11619 DisplayBothClocks();
11620 DisplayMove(currentMove - 1);
11621 ClearHighlights();/*!! could figure this out*/
11622 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11623 SendToProgram("remove\n", &first);
11624 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11627 case BeginningOfGame:
11631 case IcsPlayingWhite:
11632 case IcsPlayingBlack:
11633 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11634 SendToICS(ics_prefix);
11635 SendToICS("takeback 2\n");
11637 SendToICS(ics_prefix);
11638 SendToICS("takeback 1\n");
11647 ChessProgramState *cps;
11649 switch (gameMode) {
11650 case MachinePlaysWhite:
11651 if (!WhiteOnMove(forwardMostMove)) {
11652 DisplayError(_("It is your turn"), 0);
11657 case MachinePlaysBlack:
11658 if (WhiteOnMove(forwardMostMove)) {
11659 DisplayError(_("It is your turn"), 0);
11664 case TwoMachinesPlay:
11665 if (WhiteOnMove(forwardMostMove) ==
11666 (first.twoMachinesColor[0] == 'w')) {
11672 case BeginningOfGame:
11676 SendToProgram("?\n", cps);
11680 TruncateGameEvent()
11683 if (gameMode != EditGame) return;
11690 if (forwardMostMove > currentMove) {
11691 if (gameInfo.resultDetails != NULL) {
11692 free(gameInfo.resultDetails);
11693 gameInfo.resultDetails = NULL;
11694 gameInfo.result = GameUnfinished;
11696 forwardMostMove = currentMove;
11697 HistorySet(parseList, backwardMostMove, forwardMostMove,
11705 if (appData.noChessProgram) return;
11706 switch (gameMode) {
11707 case MachinePlaysWhite:
11708 if (WhiteOnMove(forwardMostMove)) {
11709 DisplayError(_("Wait until your turn"), 0);
11713 case BeginningOfGame:
11714 case MachinePlaysBlack:
11715 if (!WhiteOnMove(forwardMostMove)) {
11716 DisplayError(_("Wait until your turn"), 0);
11721 DisplayError(_("No hint available"), 0);
11724 SendToProgram("hint\n", &first);
11725 hintRequested = TRUE;
11731 if (appData.noChessProgram) return;
11732 switch (gameMode) {
11733 case MachinePlaysWhite:
11734 if (WhiteOnMove(forwardMostMove)) {
11735 DisplayError(_("Wait until your turn"), 0);
11739 case BeginningOfGame:
11740 case MachinePlaysBlack:
11741 if (!WhiteOnMove(forwardMostMove)) {
11742 DisplayError(_("Wait until your turn"), 0);
11747 EditPositionDone();
11749 case TwoMachinesPlay:
11754 SendToProgram("bk\n", &first);
11755 bookOutput[0] = NULLCHAR;
11756 bookRequested = TRUE;
11762 char *tags = PGNTags(&gameInfo);
11763 TagsPopUp(tags, CmailMsg());
11767 /* end button procedures */
11770 PrintPosition(fp, move)
11776 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11777 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11778 char c = PieceToChar(boards[move][i][j]);
11779 fputc(c == 'x' ? '.' : c, fp);
11780 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11783 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11784 fprintf(fp, "white to play\n");
11786 fprintf(fp, "black to play\n");
11793 if (gameInfo.white != NULL) {
11794 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11800 /* Find last component of program's own name, using some heuristics */
11802 TidyProgramName(prog, host, buf)
11803 char *prog, *host, buf[MSG_SIZ];
11806 int local = (strcmp(host, "localhost") == 0);
11807 while (!local && (p = strchr(prog, ';')) != NULL) {
11809 while (*p == ' ') p++;
11812 if (*prog == '"' || *prog == '\'') {
11813 q = strchr(prog + 1, *prog);
11815 q = strchr(prog, ' ');
11817 if (q == NULL) q = prog + strlen(prog);
11819 while (p >= prog && *p != '/' && *p != '\\') p--;
11821 if(p == prog && *p == '"') p++;
11822 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11823 memcpy(buf, p, q - p);
11824 buf[q - p] = NULLCHAR;
11832 TimeControlTagValue()
11835 if (!appData.clockMode) {
11837 } else if (movesPerSession > 0) {
11838 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11839 } else if (timeIncrement == 0) {
11840 sprintf(buf, "%ld", timeControl/1000);
11842 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11844 return StrSave(buf);
11850 /* This routine is used only for certain modes */
11851 VariantClass v = gameInfo.variant;
11852 ClearGameInfo(&gameInfo);
11853 gameInfo.variant = v;
11855 switch (gameMode) {
11856 case MachinePlaysWhite:
11857 gameInfo.event = StrSave( appData.pgnEventHeader );
11858 gameInfo.site = StrSave(HostName());
11859 gameInfo.date = PGNDate();
11860 gameInfo.round = StrSave("-");
11861 gameInfo.white = StrSave(first.tidy);
11862 gameInfo.black = StrSave(UserName());
11863 gameInfo.timeControl = TimeControlTagValue();
11866 case MachinePlaysBlack:
11867 gameInfo.event = StrSave( appData.pgnEventHeader );
11868 gameInfo.site = StrSave(HostName());
11869 gameInfo.date = PGNDate();
11870 gameInfo.round = StrSave("-");
11871 gameInfo.white = StrSave(UserName());
11872 gameInfo.black = StrSave(first.tidy);
11873 gameInfo.timeControl = TimeControlTagValue();
11876 case TwoMachinesPlay:
11877 gameInfo.event = StrSave( appData.pgnEventHeader );
11878 gameInfo.site = StrSave(HostName());
11879 gameInfo.date = PGNDate();
11880 if (matchGame > 0) {
11882 sprintf(buf, "%d", matchGame);
11883 gameInfo.round = StrSave(buf);
11885 gameInfo.round = StrSave("-");
11887 if (first.twoMachinesColor[0] == 'w') {
11888 gameInfo.white = StrSave(first.tidy);
11889 gameInfo.black = StrSave(second.tidy);
11891 gameInfo.white = StrSave(second.tidy);
11892 gameInfo.black = StrSave(first.tidy);
11894 gameInfo.timeControl = TimeControlTagValue();
11898 gameInfo.event = StrSave("Edited game");
11899 gameInfo.site = StrSave(HostName());
11900 gameInfo.date = PGNDate();
11901 gameInfo.round = StrSave("-");
11902 gameInfo.white = StrSave("-");
11903 gameInfo.black = StrSave("-");
11907 gameInfo.event = StrSave("Edited position");
11908 gameInfo.site = StrSave(HostName());
11909 gameInfo.date = PGNDate();
11910 gameInfo.round = StrSave("-");
11911 gameInfo.white = StrSave("-");
11912 gameInfo.black = StrSave("-");
11915 case IcsPlayingWhite:
11916 case IcsPlayingBlack:
11921 case PlayFromGameFile:
11922 gameInfo.event = StrSave("Game from non-PGN file");
11923 gameInfo.site = StrSave(HostName());
11924 gameInfo.date = PGNDate();
11925 gameInfo.round = StrSave("-");
11926 gameInfo.white = StrSave("?");
11927 gameInfo.black = StrSave("?");
11936 ReplaceComment(index, text)
11942 while (*text == '\n') text++;
11943 len = strlen(text);
11944 while (len > 0 && text[len - 1] == '\n') len--;
11946 if (commentList[index] != NULL)
11947 free(commentList[index]);
11950 commentList[index] = NULL;
11953 commentList[index] = (char *) malloc(len + 2);
11954 strncpy(commentList[index], text, len);
11955 commentList[index][len] = '\n';
11956 commentList[index][len + 1] = NULLCHAR;
11969 if (ch == '\r') continue;
11971 } while (ch != '\0');
11975 AppendComment(index, text)
11982 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11985 while (*text == '\n') text++;
11986 len = strlen(text);
11987 while (len > 0 && text[len - 1] == '\n') len--;
11989 if (len == 0) return;
11991 if (commentList[index] != NULL) {
11992 old = commentList[index];
11993 oldlen = strlen(old);
11994 commentList[index] = (char *) malloc(oldlen + len + 2);
11995 strcpy(commentList[index], old);
11997 strncpy(&commentList[index][oldlen], text, len);
11998 commentList[index][oldlen + len] = '\n';
11999 commentList[index][oldlen + len + 1] = NULLCHAR;
12001 commentList[index] = (char *) malloc(len + 2);
12002 strncpy(commentList[index], text, len);
12003 commentList[index][len] = '\n';
12004 commentList[index][len + 1] = NULLCHAR;
12008 static char * FindStr( char * text, char * sub_text )
12010 char * result = strstr( text, sub_text );
12012 if( result != NULL ) {
12013 result += strlen( sub_text );
12019 /* [AS] Try to extract PV info from PGN comment */
12020 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12021 char *GetInfoFromComment( int index, char * text )
12025 if( text != NULL && index > 0 ) {
12028 int time = -1, sec = 0, deci;
12029 char * s_eval = FindStr( text, "[%eval " );
12030 char * s_emt = FindStr( text, "[%emt " );
12032 if( s_eval != NULL || s_emt != NULL ) {
12036 if( s_eval != NULL ) {
12037 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12041 if( delim != ']' ) {
12046 if( s_emt != NULL ) {
12050 /* We expect something like: [+|-]nnn.nn/dd */
12053 sep = strchr( text, '/' );
12054 if( sep == NULL || sep < (text+4) ) {
12058 time = -1; sec = -1; deci = -1;
12059 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12060 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12061 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12062 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12066 if( score_lo < 0 || score_lo >= 100 ) {
12070 if(sec >= 0) time = 600*time + 10*sec; else
12071 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12073 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12075 /* [HGM] PV time: now locate end of PV info */
12076 while( *++sep >= '0' && *sep <= '9'); // strip depth
12078 while( *++sep >= '0' && *sep <= '9'); // strip time
12080 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12082 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12083 while(*sep == ' ') sep++;
12094 pvInfoList[index-1].depth = depth;
12095 pvInfoList[index-1].score = score;
12096 pvInfoList[index-1].time = 10*time; // centi-sec
12102 SendToProgram(message, cps)
12104 ChessProgramState *cps;
12106 int count, outCount, error;
12109 if (cps->pr == NULL) return;
12112 if (appData.debugMode) {
12115 fprintf(debugFP, "%ld >%-6s: %s",
12116 SubtractTimeMarks(&now, &programStartTime),
12117 cps->which, message);
12120 count = strlen(message);
12121 outCount = OutputToProcess(cps->pr, message, count, &error);
12122 if (outCount < count && !exiting
12123 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12124 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12125 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12126 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12127 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12128 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12130 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12132 gameInfo.resultDetails = buf;
12134 DisplayFatalError(buf, error, 1);
12139 ReceiveFromProgram(isr, closure, message, count, error)
12140 InputSourceRef isr;
12148 ChessProgramState *cps = (ChessProgramState *)closure;
12150 if (isr != cps->isr) return; /* Killed intentionally */
12154 _("Error: %s chess program (%s) exited unexpectedly"),
12155 cps->which, cps->program);
12156 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12157 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12158 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12159 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12161 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12163 gameInfo.resultDetails = buf;
12165 RemoveInputSource(cps->isr);
12166 DisplayFatalError(buf, 0, 1);
12169 _("Error reading from %s chess program (%s)"),
12170 cps->which, cps->program);
12171 RemoveInputSource(cps->isr);
12173 /* [AS] Program is misbehaving badly... kill it */
12174 if( count == -2 ) {
12175 DestroyChildProcess( cps->pr, 9 );
12179 DisplayFatalError(buf, error, 1);
12184 if ((end_str = strchr(message, '\r')) != NULL)
12185 *end_str = NULLCHAR;
12186 if ((end_str = strchr(message, '\n')) != NULL)
12187 *end_str = NULLCHAR;
12189 if (appData.debugMode) {
12190 TimeMark now; int print = 1;
12191 char *quote = ""; char c; int i;
12193 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12194 char start = message[0];
12195 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12196 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12197 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12198 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12199 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12200 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12201 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12202 sscanf(message, "pong %c", &c)!=1 && start != '#')
12203 { quote = "# "; print = (appData.engineComments == 2); }
12204 message[0] = start; // restore original message
12208 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12209 SubtractTimeMarks(&now, &programStartTime), cps->which,
12215 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12216 if (appData.icsEngineAnalyze) {
12217 if (strstr(message, "whisper") != NULL ||
12218 strstr(message, "kibitz") != NULL ||
12219 strstr(message, "tellics") != NULL) return;
12222 HandleMachineMove(message, cps);
12227 SendTimeControl(cps, mps, tc, inc, sd, st)
12228 ChessProgramState *cps;
12229 int mps, inc, sd, st;
12235 if( timeControl_2 > 0 ) {
12236 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12237 tc = timeControl_2;
12240 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12241 inc /= cps->timeOdds;
12242 st /= cps->timeOdds;
12244 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12247 /* Set exact time per move, normally using st command */
12248 if (cps->stKludge) {
12249 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12251 if (seconds == 0) {
12252 sprintf(buf, "level 1 %d\n", st/60);
12254 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12257 sprintf(buf, "st %d\n", st);
12260 /* Set conventional or incremental time control, using level command */
12261 if (seconds == 0) {
12262 /* Note old gnuchess bug -- minutes:seconds used to not work.
12263 Fixed in later versions, but still avoid :seconds
12264 when seconds is 0. */
12265 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12267 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12268 seconds, inc/1000);
12271 SendToProgram(buf, cps);
12273 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12274 /* Orthogonally, limit search to given depth */
12276 if (cps->sdKludge) {
12277 sprintf(buf, "depth\n%d\n", sd);
12279 sprintf(buf, "sd %d\n", sd);
12281 SendToProgram(buf, cps);
12284 if(cps->nps > 0) { /* [HGM] nps */
12285 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12287 sprintf(buf, "nps %d\n", cps->nps);
12288 SendToProgram(buf, cps);
12293 ChessProgramState *WhitePlayer()
12294 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12296 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12297 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12303 SendTimeRemaining(cps, machineWhite)
12304 ChessProgramState *cps;
12305 int /*boolean*/ machineWhite;
12307 char message[MSG_SIZ];
12310 /* Note: this routine must be called when the clocks are stopped
12311 or when they have *just* been set or switched; otherwise
12312 it will be off by the time since the current tick started.
12314 if (machineWhite) {
12315 time = whiteTimeRemaining / 10;
12316 otime = blackTimeRemaining / 10;
12318 time = blackTimeRemaining / 10;
12319 otime = whiteTimeRemaining / 10;
12321 /* [HGM] translate opponent's time by time-odds factor */
12322 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12323 if (appData.debugMode) {
12324 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12327 if (time <= 0) time = 1;
12328 if (otime <= 0) otime = 1;
12330 sprintf(message, "time %ld\n", time);
12331 SendToProgram(message, cps);
12333 sprintf(message, "otim %ld\n", otime);
12334 SendToProgram(message, cps);
12338 BoolFeature(p, name, loc, cps)
12342 ChessProgramState *cps;
12345 int len = strlen(name);
12347 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12349 sscanf(*p, "%d", &val);
12351 while (**p && **p != ' ') (*p)++;
12352 sprintf(buf, "accepted %s\n", name);
12353 SendToProgram(buf, cps);
12360 IntFeature(p, name, loc, cps)
12364 ChessProgramState *cps;
12367 int len = strlen(name);
12368 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12370 sscanf(*p, "%d", loc);
12371 while (**p && **p != ' ') (*p)++;
12372 sprintf(buf, "accepted %s\n", name);
12373 SendToProgram(buf, cps);
12380 StringFeature(p, name, loc, cps)
12384 ChessProgramState *cps;
12387 int len = strlen(name);
12388 if (strncmp((*p), name, len) == 0
12389 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12391 sscanf(*p, "%[^\"]", loc);
12392 while (**p && **p != '\"') (*p)++;
12393 if (**p == '\"') (*p)++;
12394 sprintf(buf, "accepted %s\n", name);
12395 SendToProgram(buf, cps);
12402 ParseOption(Option *opt, ChessProgramState *cps)
12403 // [HGM] options: process the string that defines an engine option, and determine
12404 // name, type, default value, and allowed value range
12406 char *p, *q, buf[MSG_SIZ];
12407 int n, min = (-1)<<31, max = 1<<31, def;
12409 if(p = strstr(opt->name, " -spin ")) {
12410 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12411 if(max < min) max = min; // enforce consistency
12412 if(def < min) def = min;
12413 if(def > max) def = max;
12418 } else if((p = strstr(opt->name, " -slider "))) {
12419 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12420 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12421 if(max < min) max = min; // enforce consistency
12422 if(def < min) def = min;
12423 if(def > max) def = max;
12427 opt->type = Spin; // Slider;
12428 } else if((p = strstr(opt->name, " -string "))) {
12429 opt->textValue = p+9;
12430 opt->type = TextBox;
12431 } else if((p = strstr(opt->name, " -file "))) {
12432 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12433 opt->textValue = p+7;
12434 opt->type = TextBox; // FileName;
12435 } else if((p = strstr(opt->name, " -path "))) {
12436 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12437 opt->textValue = p+7;
12438 opt->type = TextBox; // PathName;
12439 } else if(p = strstr(opt->name, " -check ")) {
12440 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12441 opt->value = (def != 0);
12442 opt->type = CheckBox;
12443 } else if(p = strstr(opt->name, " -combo ")) {
12444 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12445 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12446 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12447 opt->value = n = 0;
12448 while(q = StrStr(q, " /// ")) {
12449 n++; *q = 0; // count choices, and null-terminate each of them
12451 if(*q == '*') { // remember default, which is marked with * prefix
12455 cps->comboList[cps->comboCnt++] = q;
12457 cps->comboList[cps->comboCnt++] = NULL;
12459 opt->type = ComboBox;
12460 } else if(p = strstr(opt->name, " -button")) {
12461 opt->type = Button;
12462 } else if(p = strstr(opt->name, " -save")) {
12463 opt->type = SaveButton;
12464 } else return FALSE;
12465 *p = 0; // terminate option name
12466 // now look if the command-line options define a setting for this engine option.
12467 if(cps->optionSettings && cps->optionSettings[0])
12468 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12469 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12470 sprintf(buf, "option %s", p);
12471 if(p = strstr(buf, ",")) *p = 0;
12473 SendToProgram(buf, cps);
12479 FeatureDone(cps, val)
12480 ChessProgramState* cps;
12483 DelayedEventCallback cb = GetDelayedEvent();
12484 if ((cb == InitBackEnd3 && cps == &first) ||
12485 (cb == TwoMachinesEventIfReady && cps == &second)) {
12486 CancelDelayedEvent();
12487 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12489 cps->initDone = val;
12492 /* Parse feature command from engine */
12494 ParseFeatures(args, cps)
12496 ChessProgramState *cps;
12504 while (*p == ' ') p++;
12505 if (*p == NULLCHAR) return;
12507 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12508 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12509 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12510 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12511 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12512 if (BoolFeature(&p, "reuse", &val, cps)) {
12513 /* Engine can disable reuse, but can't enable it if user said no */
12514 if (!val) cps->reuse = FALSE;
12517 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12518 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12519 if (gameMode == TwoMachinesPlay) {
12520 DisplayTwoMachinesTitle();
12526 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12527 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12528 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12529 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12530 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12531 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12532 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12533 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12534 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12535 if (IntFeature(&p, "done", &val, cps)) {
12536 FeatureDone(cps, val);
12539 /* Added by Tord: */
12540 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12541 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12542 /* End of additions by Tord */
12544 /* [HGM] added features: */
12545 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12546 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12547 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12548 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12549 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12550 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12551 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12552 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12553 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12554 SendToProgram(buf, cps);
12557 if(cps->nrOptions >= MAX_OPTIONS) {
12559 sprintf(buf, "%s engine has too many options\n", cps->which);
12560 DisplayError(buf, 0);
12564 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12565 /* End of additions by HGM */
12567 /* unknown feature: complain and skip */
12569 while (*q && *q != '=') q++;
12570 sprintf(buf, "rejected %.*s\n", q-p, p);
12571 SendToProgram(buf, cps);
12577 while (*p && *p != '\"') p++;
12578 if (*p == '\"') p++;
12580 while (*p && *p != ' ') p++;
12588 PeriodicUpdatesEvent(newState)
12591 if (newState == appData.periodicUpdates)
12594 appData.periodicUpdates=newState;
12596 /* Display type changes, so update it now */
12597 // DisplayAnalysis();
12599 /* Get the ball rolling again... */
12601 AnalysisPeriodicEvent(1);
12602 StartAnalysisClock();
12607 PonderNextMoveEvent(newState)
12610 if (newState == appData.ponderNextMove) return;
12611 if (gameMode == EditPosition) EditPositionDone();
12613 SendToProgram("hard\n", &first);
12614 if (gameMode == TwoMachinesPlay) {
12615 SendToProgram("hard\n", &second);
12618 SendToProgram("easy\n", &first);
12619 thinkOutput[0] = NULLCHAR;
12620 if (gameMode == TwoMachinesPlay) {
12621 SendToProgram("easy\n", &second);
12624 appData.ponderNextMove = newState;
12628 NewSettingEvent(option, command, value)
12634 if (gameMode == EditPosition) EditPositionDone();
12635 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12636 SendToProgram(buf, &first);
12637 if (gameMode == TwoMachinesPlay) {
12638 SendToProgram(buf, &second);
12643 ShowThinkingEvent()
12644 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12646 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12647 int newState = appData.showThinking
12648 // [HGM] thinking: other features now need thinking output as well
12649 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12651 if (oldState == newState) return;
12652 oldState = newState;
12653 if (gameMode == EditPosition) EditPositionDone();
12655 SendToProgram("post\n", &first);
12656 if (gameMode == TwoMachinesPlay) {
12657 SendToProgram("post\n", &second);
12660 SendToProgram("nopost\n", &first);
12661 thinkOutput[0] = NULLCHAR;
12662 if (gameMode == TwoMachinesPlay) {
12663 SendToProgram("nopost\n", &second);
12666 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12670 AskQuestionEvent(title, question, replyPrefix, which)
12671 char *title; char *question; char *replyPrefix; char *which;
12673 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12674 if (pr == NoProc) return;
12675 AskQuestion(title, question, replyPrefix, pr);
12679 DisplayMove(moveNumber)
12682 char message[MSG_SIZ];
12684 char cpThinkOutput[MSG_SIZ];
12686 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12688 if (moveNumber == forwardMostMove - 1 ||
12689 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12691 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12693 if (strchr(cpThinkOutput, '\n')) {
12694 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12697 *cpThinkOutput = NULLCHAR;
12700 /* [AS] Hide thinking from human user */
12701 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12702 *cpThinkOutput = NULLCHAR;
12703 if( thinkOutput[0] != NULLCHAR ) {
12706 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12707 cpThinkOutput[i] = '.';
12709 cpThinkOutput[i] = NULLCHAR;
12710 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12714 if (moveNumber == forwardMostMove - 1 &&
12715 gameInfo.resultDetails != NULL) {
12716 if (gameInfo.resultDetails[0] == NULLCHAR) {
12717 sprintf(res, " %s", PGNResult(gameInfo.result));
12719 sprintf(res, " {%s} %s",
12720 gameInfo.resultDetails, PGNResult(gameInfo.result));
12726 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12727 DisplayMessage(res, cpThinkOutput);
12729 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12730 WhiteOnMove(moveNumber) ? " " : ".. ",
12731 parseList[moveNumber], res);
12732 DisplayMessage(message, cpThinkOutput);
12737 DisplayComment(moveNumber, text)
12741 char title[MSG_SIZ];
12742 char buf[8000]; // comment can be long!
12745 if( appData.autoDisplayComment ) {
12746 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12747 strcpy(title, "Comment");
12749 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12750 WhiteOnMove(moveNumber) ? " " : ".. ",
12751 parseList[moveNumber]);
12753 // [HGM] PV info: display PV info together with (or as) comment
12754 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12755 if(text == NULL) text = "";
12756 score = pvInfoList[moveNumber].score;
12757 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12758 depth, (pvInfoList[moveNumber].time+50)/100, text);
12761 } else title[0] = 0;
12764 CommentPopUp(title, text);
12767 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12768 * might be busy thinking or pondering. It can be omitted if your
12769 * gnuchess is configured to stop thinking immediately on any user
12770 * input. However, that gnuchess feature depends on the FIONREAD
12771 * ioctl, which does not work properly on some flavors of Unix.
12775 ChessProgramState *cps;
12778 if (!cps->useSigint) return;
12779 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12780 switch (gameMode) {
12781 case MachinePlaysWhite:
12782 case MachinePlaysBlack:
12783 case TwoMachinesPlay:
12784 case IcsPlayingWhite:
12785 case IcsPlayingBlack:
12788 /* Skip if we know it isn't thinking */
12789 if (!cps->maybeThinking) return;
12790 if (appData.debugMode)
12791 fprintf(debugFP, "Interrupting %s\n", cps->which);
12792 InterruptChildProcess(cps->pr);
12793 cps->maybeThinking = FALSE;
12798 #endif /*ATTENTION*/
12804 if (whiteTimeRemaining <= 0) {
12807 if (appData.icsActive) {
12808 if (appData.autoCallFlag &&
12809 gameMode == IcsPlayingBlack && !blackFlag) {
12810 SendToICS(ics_prefix);
12811 SendToICS("flag\n");
12815 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12817 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12818 if (appData.autoCallFlag) {
12819 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12826 if (blackTimeRemaining <= 0) {
12829 if (appData.icsActive) {
12830 if (appData.autoCallFlag &&
12831 gameMode == IcsPlayingWhite && !whiteFlag) {
12832 SendToICS(ics_prefix);
12833 SendToICS("flag\n");
12837 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12839 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12840 if (appData.autoCallFlag) {
12841 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12854 if (!appData.clockMode || appData.icsActive ||
12855 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12858 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12860 if ( !WhiteOnMove(forwardMostMove) )
12861 /* White made time control */
12862 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12863 /* [HGM] time odds: correct new time quota for time odds! */
12864 / WhitePlayer()->timeOdds;
12866 /* Black made time control */
12867 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12868 / WhitePlayer()->other->timeOdds;
12872 DisplayBothClocks()
12874 int wom = gameMode == EditPosition ?
12875 !blackPlaysFirst : WhiteOnMove(currentMove);
12876 DisplayWhiteClock(whiteTimeRemaining, wom);
12877 DisplayBlackClock(blackTimeRemaining, !wom);
12881 /* Timekeeping seems to be a portability nightmare. I think everyone
12882 has ftime(), but I'm really not sure, so I'm including some ifdefs
12883 to use other calls if you don't. Clocks will be less accurate if
12884 you have neither ftime nor gettimeofday.
12887 /* VS 2008 requires the #include outside of the function */
12888 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12889 #include <sys/timeb.h>
12892 /* Get the current time as a TimeMark */
12897 #if HAVE_GETTIMEOFDAY
12899 struct timeval timeVal;
12900 struct timezone timeZone;
12902 gettimeofday(&timeVal, &timeZone);
12903 tm->sec = (long) timeVal.tv_sec;
12904 tm->ms = (int) (timeVal.tv_usec / 1000L);
12906 #else /*!HAVE_GETTIMEOFDAY*/
12909 // include <sys/timeb.h> / moved to just above start of function
12910 struct timeb timeB;
12913 tm->sec = (long) timeB.time;
12914 tm->ms = (int) timeB.millitm;
12916 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12917 tm->sec = (long) time(NULL);
12923 /* Return the difference in milliseconds between two
12924 time marks. We assume the difference will fit in a long!
12927 SubtractTimeMarks(tm2, tm1)
12928 TimeMark *tm2, *tm1;
12930 return 1000L*(tm2->sec - tm1->sec) +
12931 (long) (tm2->ms - tm1->ms);
12936 * Code to manage the game clocks.
12938 * In tournament play, black starts the clock and then white makes a move.
12939 * We give the human user a slight advantage if he is playing white---the
12940 * clocks don't run until he makes his first move, so it takes zero time.
12941 * Also, we don't account for network lag, so we could get out of sync
12942 * with GNU Chess's clock -- but then, referees are always right.
12945 static TimeMark tickStartTM;
12946 static long intendedTickLength;
12949 NextTickLength(timeRemaining)
12950 long timeRemaining;
12952 long nominalTickLength, nextTickLength;
12954 if (timeRemaining > 0L && timeRemaining <= 10000L)
12955 nominalTickLength = 100L;
12957 nominalTickLength = 1000L;
12958 nextTickLength = timeRemaining % nominalTickLength;
12959 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12961 return nextTickLength;
12964 /* Adjust clock one minute up or down */
12966 AdjustClock(Boolean which, int dir)
12968 if(which) blackTimeRemaining += 60000*dir;
12969 else whiteTimeRemaining += 60000*dir;
12970 DisplayBothClocks();
12973 /* Stop clocks and reset to a fresh time control */
12977 (void) StopClockTimer();
12978 if (appData.icsActive) {
12979 whiteTimeRemaining = blackTimeRemaining = 0;
12980 } else { /* [HGM] correct new time quote for time odds */
12981 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
12982 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
12984 if (whiteFlag || blackFlag) {
12986 whiteFlag = blackFlag = FALSE;
12988 DisplayBothClocks();
12991 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
12993 /* Decrement running clock by amount of time that has passed */
12997 long timeRemaining;
12998 long lastTickLength, fudge;
13001 if (!appData.clockMode) return;
13002 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13006 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13008 /* Fudge if we woke up a little too soon */
13009 fudge = intendedTickLength - lastTickLength;
13010 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13012 if (WhiteOnMove(forwardMostMove)) {
13013 if(whiteNPS >= 0) lastTickLength = 0;
13014 timeRemaining = whiteTimeRemaining -= lastTickLength;
13015 DisplayWhiteClock(whiteTimeRemaining - fudge,
13016 WhiteOnMove(currentMove));
13018 if(blackNPS >= 0) lastTickLength = 0;
13019 timeRemaining = blackTimeRemaining -= lastTickLength;
13020 DisplayBlackClock(blackTimeRemaining - fudge,
13021 !WhiteOnMove(currentMove));
13024 if (CheckFlags()) return;
13027 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13028 StartClockTimer(intendedTickLength);
13030 /* if the time remaining has fallen below the alarm threshold, sound the
13031 * alarm. if the alarm has sounded and (due to a takeback or time control
13032 * with increment) the time remaining has increased to a level above the
13033 * threshold, reset the alarm so it can sound again.
13036 if (appData.icsActive && appData.icsAlarm) {
13038 /* make sure we are dealing with the user's clock */
13039 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13040 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13043 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13044 alarmSounded = FALSE;
13045 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13047 alarmSounded = TRUE;
13053 /* A player has just moved, so stop the previously running
13054 clock and (if in clock mode) start the other one.
13055 We redisplay both clocks in case we're in ICS mode, because
13056 ICS gives us an update to both clocks after every move.
13057 Note that this routine is called *after* forwardMostMove
13058 is updated, so the last fractional tick must be subtracted
13059 from the color that is *not* on move now.
13064 long lastTickLength;
13066 int flagged = FALSE;
13070 if (StopClockTimer() && appData.clockMode) {
13071 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13072 if (WhiteOnMove(forwardMostMove)) {
13073 if(blackNPS >= 0) lastTickLength = 0;
13074 blackTimeRemaining -= lastTickLength;
13075 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13076 // if(pvInfoList[forwardMostMove-1].time == -1)
13077 pvInfoList[forwardMostMove-1].time = // use GUI time
13078 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13080 if(whiteNPS >= 0) lastTickLength = 0;
13081 whiteTimeRemaining -= lastTickLength;
13082 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13083 // if(pvInfoList[forwardMostMove-1].time == -1)
13084 pvInfoList[forwardMostMove-1].time =
13085 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13087 flagged = CheckFlags();
13089 CheckTimeControl();
13091 if (flagged || !appData.clockMode) return;
13093 switch (gameMode) {
13094 case MachinePlaysBlack:
13095 case MachinePlaysWhite:
13096 case BeginningOfGame:
13097 if (pausing) return;
13101 case PlayFromGameFile:
13110 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13111 whiteTimeRemaining : blackTimeRemaining);
13112 StartClockTimer(intendedTickLength);
13116 /* Stop both clocks */
13120 long lastTickLength;
13123 if (!StopClockTimer()) return;
13124 if (!appData.clockMode) return;
13128 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13129 if (WhiteOnMove(forwardMostMove)) {
13130 if(whiteNPS >= 0) lastTickLength = 0;
13131 whiteTimeRemaining -= lastTickLength;
13132 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13134 if(blackNPS >= 0) lastTickLength = 0;
13135 blackTimeRemaining -= lastTickLength;
13136 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13141 /* Start clock of player on move. Time may have been reset, so
13142 if clock is already running, stop and restart it. */
13146 (void) StopClockTimer(); /* in case it was running already */
13147 DisplayBothClocks();
13148 if (CheckFlags()) return;
13150 if (!appData.clockMode) return;
13151 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13153 GetTimeMark(&tickStartTM);
13154 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13155 whiteTimeRemaining : blackTimeRemaining);
13157 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13158 whiteNPS = blackNPS = -1;
13159 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13160 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13161 whiteNPS = first.nps;
13162 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13163 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13164 blackNPS = first.nps;
13165 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13166 whiteNPS = second.nps;
13167 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13168 blackNPS = second.nps;
13169 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13171 StartClockTimer(intendedTickLength);
13178 long second, minute, hour, day;
13180 static char buf[32];
13182 if (ms > 0 && ms <= 9900) {
13183 /* convert milliseconds to tenths, rounding up */
13184 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13186 sprintf(buf, " %03.1f ", tenths/10.0);
13190 /* convert milliseconds to seconds, rounding up */
13191 /* use floating point to avoid strangeness of integer division
13192 with negative dividends on many machines */
13193 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13200 day = second / (60 * 60 * 24);
13201 second = second % (60 * 60 * 24);
13202 hour = second / (60 * 60);
13203 second = second % (60 * 60);
13204 minute = second / 60;
13205 second = second % 60;
13208 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13209 sign, day, hour, minute, second);
13211 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13213 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13220 * This is necessary because some C libraries aren't ANSI C compliant yet.
13223 StrStr(string, match)
13224 char *string, *match;
13228 length = strlen(match);
13230 for (i = strlen(string) - length; i >= 0; i--, string++)
13231 if (!strncmp(match, string, length))
13238 StrCaseStr(string, match)
13239 char *string, *match;
13243 length = strlen(match);
13245 for (i = strlen(string) - length; i >= 0; i--, string++) {
13246 for (j = 0; j < length; j++) {
13247 if (ToLower(match[j]) != ToLower(string[j]))
13250 if (j == length) return string;
13264 c1 = ToLower(*s1++);
13265 c2 = ToLower(*s2++);
13266 if (c1 > c2) return 1;
13267 if (c1 < c2) return -1;
13268 if (c1 == NULLCHAR) return 0;
13277 return isupper(c) ? tolower(c) : c;
13285 return islower(c) ? toupper(c) : c;
13287 #endif /* !_amigados */
13295 if ((ret = (char *) malloc(strlen(s) + 1))) {
13302 StrSavePtr(s, savePtr)
13303 char *s, **savePtr;
13308 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13309 strcpy(*savePtr, s);
13321 clock = time((time_t *)NULL);
13322 tm = localtime(&clock);
13323 sprintf(buf, "%04d.%02d.%02d",
13324 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13325 return StrSave(buf);
13330 PositionToFEN(move, overrideCastling)
13332 char *overrideCastling;
13334 int i, j, fromX, fromY, toX, toY;
13341 whiteToPlay = (gameMode == EditPosition) ?
13342 !blackPlaysFirst : (move % 2 == 0);
13345 /* Piece placement data */
13346 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13348 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13349 if (boards[move][i][j] == EmptySquare) {
13351 } else { ChessSquare piece = boards[move][i][j];
13352 if (emptycount > 0) {
13353 if(emptycount<10) /* [HGM] can be >= 10 */
13354 *p++ = '0' + emptycount;
13355 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13358 if(PieceToChar(piece) == '+') {
13359 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13361 piece = (ChessSquare)(DEMOTED piece);
13363 *p++ = PieceToChar(piece);
13365 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13366 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13371 if (emptycount > 0) {
13372 if(emptycount<10) /* [HGM] can be >= 10 */
13373 *p++ = '0' + emptycount;
13374 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13381 /* [HGM] print Crazyhouse or Shogi holdings */
13382 if( gameInfo.holdingsWidth ) {
13383 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13385 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13386 piece = boards[move][i][BOARD_WIDTH-1];
13387 if( piece != EmptySquare )
13388 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13389 *p++ = PieceToChar(piece);
13391 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13392 piece = boards[move][BOARD_HEIGHT-i-1][0];
13393 if( piece != EmptySquare )
13394 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13395 *p++ = PieceToChar(piece);
13398 if( q == p ) *p++ = '-';
13404 *p++ = whiteToPlay ? 'w' : 'b';
13407 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13408 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13410 if(nrCastlingRights) {
13412 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13413 /* [HGM] write directly from rights */
13414 if(castlingRights[move][2] >= 0 &&
13415 castlingRights[move][0] >= 0 )
13416 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13417 if(castlingRights[move][2] >= 0 &&
13418 castlingRights[move][1] >= 0 )
13419 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13420 if(castlingRights[move][5] >= 0 &&
13421 castlingRights[move][3] >= 0 )
13422 *p++ = castlingRights[move][3] + AAA;
13423 if(castlingRights[move][5] >= 0 &&
13424 castlingRights[move][4] >= 0 )
13425 *p++ = castlingRights[move][4] + AAA;
13428 /* [HGM] write true castling rights */
13429 if( nrCastlingRights == 6 ) {
13430 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13431 castlingRights[move][2] >= 0 ) *p++ = 'K';
13432 if(castlingRights[move][1] == BOARD_LEFT &&
13433 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13434 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13435 castlingRights[move][5] >= 0 ) *p++ = 'k';
13436 if(castlingRights[move][4] == BOARD_LEFT &&
13437 castlingRights[move][5] >= 0 ) *p++ = 'q';
13440 if (q == p) *p++ = '-'; /* No castling rights */
13444 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13445 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13446 /* En passant target square */
13447 if (move > backwardMostMove) {
13448 fromX = moveList[move - 1][0] - AAA;
13449 fromY = moveList[move - 1][1] - ONE;
13450 toX = moveList[move - 1][2] - AAA;
13451 toY = moveList[move - 1][3] - ONE;
13452 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13453 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13454 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13456 /* 2-square pawn move just happened */
13458 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13462 } else if(move == backwardMostMove) {
13463 // [HGM] perhaps we should always do it like this, and forget the above?
13464 if(epStatus[move] >= 0) {
13465 *p++ = epStatus[move] + AAA;
13466 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13477 /* [HGM] find reversible plies */
13478 { int i = 0, j=move;
13480 if (appData.debugMode) { int k;
13481 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13482 for(k=backwardMostMove; k<=forwardMostMove; k++)
13483 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13487 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13488 if( j == backwardMostMove ) i += initialRulePlies;
13489 sprintf(p, "%d ", i);
13490 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13492 /* Fullmove number */
13493 sprintf(p, "%d", (move / 2) + 1);
13495 return StrSave(buf);
13499 ParseFEN(board, blackPlaysFirst, fen)
13501 int *blackPlaysFirst;
13511 /* [HGM] by default clear Crazyhouse holdings, if present */
13512 if(gameInfo.holdingsWidth) {
13513 for(i=0; i<BOARD_HEIGHT; i++) {
13514 board[i][0] = EmptySquare; /* black holdings */
13515 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13516 board[i][1] = (ChessSquare) 0; /* black counts */
13517 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13521 /* Piece placement data */
13522 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13525 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13526 if (*p == '/') p++;
13527 emptycount = gameInfo.boardWidth - j;
13528 while (emptycount--)
13529 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13531 #if(BOARD_SIZE >= 10)
13532 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13533 p++; emptycount=10;
13534 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13535 while (emptycount--)
13536 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13538 } else if (isdigit(*p)) {
13539 emptycount = *p++ - '0';
13540 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13541 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13542 while (emptycount--)
13543 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13544 } else if (*p == '+' || isalpha(*p)) {
13545 if (j >= gameInfo.boardWidth) return FALSE;
13547 piece = CharToPiece(*++p);
13548 if(piece == EmptySquare) return FALSE; /* unknown piece */
13549 piece = (ChessSquare) (PROMOTED piece ); p++;
13550 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13551 } else piece = CharToPiece(*p++);
13553 if(piece==EmptySquare) return FALSE; /* unknown piece */
13554 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13555 piece = (ChessSquare) (PROMOTED piece);
13556 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13559 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13565 while (*p == '/' || *p == ' ') p++;
13567 /* [HGM] look for Crazyhouse holdings here */
13568 while(*p==' ') p++;
13569 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13571 if(*p == '-' ) *p++; /* empty holdings */ else {
13572 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13573 /* if we would allow FEN reading to set board size, we would */
13574 /* have to add holdings and shift the board read so far here */
13575 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13577 if((int) piece >= (int) BlackPawn ) {
13578 i = (int)piece - (int)BlackPawn;
13579 i = PieceToNumber((ChessSquare)i);
13580 if( i >= gameInfo.holdingsSize ) return FALSE;
13581 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13582 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13584 i = (int)piece - (int)WhitePawn;
13585 i = PieceToNumber((ChessSquare)i);
13586 if( i >= gameInfo.holdingsSize ) return FALSE;
13587 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13588 board[i][BOARD_WIDTH-2]++; /* black holdings */
13592 if(*p == ']') *p++;
13595 while(*p == ' ') p++;
13600 *blackPlaysFirst = FALSE;
13603 *blackPlaysFirst = TRUE;
13609 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13610 /* return the extra info in global variiables */
13612 /* set defaults in case FEN is incomplete */
13613 FENepStatus = EP_UNKNOWN;
13614 for(i=0; i<nrCastlingRights; i++ ) {
13615 FENcastlingRights[i] =
13616 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13617 } /* assume possible unless obviously impossible */
13618 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13619 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13620 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13621 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13622 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13623 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13626 while(*p==' ') p++;
13627 if(nrCastlingRights) {
13628 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13629 /* castling indicator present, so default becomes no castlings */
13630 for(i=0; i<nrCastlingRights; i++ ) {
13631 FENcastlingRights[i] = -1;
13634 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13635 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13636 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13637 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13638 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13640 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13641 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13642 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13646 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13647 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13648 FENcastlingRights[2] = whiteKingFile;
13651 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13652 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13653 FENcastlingRights[2] = whiteKingFile;
13656 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13657 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13658 FENcastlingRights[5] = blackKingFile;
13661 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13662 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13663 FENcastlingRights[5] = blackKingFile;
13666 default: /* FRC castlings */
13667 if(c >= 'a') { /* black rights */
13668 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13669 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13670 if(i == BOARD_RGHT) break;
13671 FENcastlingRights[5] = i;
13673 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13674 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13676 FENcastlingRights[3] = c;
13678 FENcastlingRights[4] = c;
13679 } else { /* white rights */
13680 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13681 if(board[0][i] == WhiteKing) break;
13682 if(i == BOARD_RGHT) break;
13683 FENcastlingRights[2] = i;
13684 c -= AAA - 'a' + 'A';
13685 if(board[0][c] >= WhiteKing) break;
13687 FENcastlingRights[0] = c;
13689 FENcastlingRights[1] = c;
13693 if (appData.debugMode) {
13694 fprintf(debugFP, "FEN castling rights:");
13695 for(i=0; i<nrCastlingRights; i++)
13696 fprintf(debugFP, " %d", FENcastlingRights[i]);
13697 fprintf(debugFP, "\n");
13700 while(*p==' ') p++;
13703 /* read e.p. field in games that know e.p. capture */
13704 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13705 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13707 p++; FENepStatus = EP_NONE;
13709 char c = *p++ - AAA;
13711 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13712 if(*p >= '0' && *p <='9') *p++;
13718 if(sscanf(p, "%d", &i) == 1) {
13719 FENrulePlies = i; /* 50-move ply counter */
13720 /* (The move number is still ignored) */
13727 EditPositionPasteFEN(char *fen)
13730 Board initial_position;
13732 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13733 DisplayError(_("Bad FEN position in clipboard"), 0);
13736 int savedBlackPlaysFirst = blackPlaysFirst;
13737 EditPositionEvent();
13738 blackPlaysFirst = savedBlackPlaysFirst;
13739 CopyBoard(boards[0], initial_position);
13740 /* [HGM] copy FEN attributes as well */
13742 initialRulePlies = FENrulePlies;
13743 epStatus[0] = FENepStatus;
13744 for( i=0; i<nrCastlingRights; i++ )
13745 castlingRights[0][i] = FENcastlingRights[i];
13747 EditPositionDone();
13748 DisplayBothClocks();
13749 DrawPosition(FALSE, boards[currentMove]);